diff --git a/.github/ISSUE_TEMPLATE/bug-template.yaml b/.github/ISSUE_TEMPLATE/bug-template.yaml index 84072af6b..70c7a6807 100644 --- a/.github/ISSUE_TEMPLATE/bug-template.yaml +++ b/.github/ISSUE_TEMPLATE/bug-template.yaml @@ -7,36 +7,55 @@ body: - type: markdown attributes: value: | - ## Quick Bug Form - Thank you for taking the time to file a bug report! Please fill out this form as completely as possible. + Thank you for taking the time to file a bug report! Please provide as much information as possible. + + - type: checkboxes + id: verified + attributes: + label: Report hasn't been filed before. + options: + - label: I have verified that the bug I'm about to report hasn't been filed before. + required: true - type: input attributes: label: What version of `drizzle-orm` are you using? + description: You can check the version by opening the `package.json` file in your project. placeholder: 0.0.0 validations: required: true + - type: input attributes: label: What version of `drizzle-kit` are you using? + description: You can check the version by opening the `package.json` file in your project. placeholder: 0.0.0 - validations: - required: false - - type: textarea - attributes: - label: Describe the Bug - description: Steps to reproduce validations: required: true - - type: textarea + + - type: input attributes: - label: Expected behavior - description: What you expect to happen + label: Other packages + description: If this bug is related to one of the other first-party packages we maintain, please list them here alongside their version. + placeholder: drizzle-zod@0.0.0, drizzle-valibot@0.0.0 validations: required: false + - type: textarea attributes: - label: Environment & setup - description: In which environment does the problem occur? + label: Describe the Bug + description: | + To fill this field, please answer the following: + - What is the undesired behavior? + - What are the steps to reproduce it? + - What is the desired result? + + If the issue is more specific, consider answering the following questions if you think they may be relevant: + - What database engine are you using? Are you using a specific cloud provider? Which one? + - Do you think this bug pertains to a specific database driver? Which one? + - Are you working in a monorepo? + - If this is a bug related to types: What Typescript version are you using? What's the content of your tsconfig.json file? + - If you're using a runtime that isn't Node.js: Which one? What version? Have you verified that this isn't an issue with the runtime itself? validations: - required: false + required: true + diff --git a/.github/ISSUE_TEMPLATE/docs-template.yaml b/.github/ISSUE_TEMPLATE/docs-template.yaml new file mode 100644 index 000000000..26abc9257 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs-template.yaml @@ -0,0 +1,20 @@ +name: "Documentation Enhancement" +description: Suggest documentation improvements +title: "[DOCS]:" +labels: ["docs"] + +body: + - type: checkboxes + id: verified + attributes: + label: Enhancement hasn't been filed before. + options: + - label: I have verified this enhancement I'm about to request hasn't been suggested before. + required: true + + - type: textarea + attributes: + label: Describe the enhancement you want to request + description: What do you want to change or add to the documentation? + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature-template.yaml b/.github/ISSUE_TEMPLATE/feature-template.yaml index 163b179a9..dcb10969f 100644 --- a/.github/ISSUE_TEMPLATE/feature-template.yaml +++ b/.github/ISSUE_TEMPLATE/feature-template.yaml @@ -1,12 +1,20 @@ name: "Feature Request" -description: Suggest new feature for Drizzle +description: Suggest new feature title: "[FEATURE]:" labels: ["enhancement"] body: + - type: checkboxes + id: verified + attributes: + label: Feature hasn't been suggested before. + options: + - label: I have verified this feature I'm about to request hasn't been suggested before. + required: true + - type: textarea attributes: - label: Describe what you want - description: What is the problem? What do you want to change or add? + label: Describe the enhancement you want to request + description: What do you want to change or add? What are the benefits of implementing this? validations: required: true diff --git a/.github/workflows/release-feature-branch.yaml b/.github/workflows/release-feature-branch.yaml index d0e0240c0..3868db792 100644 --- a/.github/workflows/release-feature-branch.yaml +++ b/.github/workflows/release-feature-branch.yaml @@ -16,6 +16,7 @@ jobs: - drizzle-orm - drizzle-kit - drizzle-zod + - drizzle-seed - drizzle-typebox - drizzle-valibot - eslint-plugin-drizzle @@ -136,12 +137,6 @@ jobs: cd drizzle-orm pnpm prisma generate --schema src/prisma/schema.prisma ) - ( - cd integration-tests - pnpm prisma generate --schema tests/prisma/pg/schema.prisma - pnpm prisma generate --schema tests/prisma/mysql/schema.prisma - pnpm prisma generate --schema tests/prisma/sqlite/schema.prisma - ) pnpm build - name: Run tests @@ -157,6 +152,8 @@ jobs: XATA_API_KEY: ${{ secrets.XATA_API_KEY }} XATA_BRANCH: ${{ secrets.XATA_BRANCH }} LIBSQL_URL: file:local.db + LIBSQL_REMOTE_URL: ${{ secrets.LIBSQL_REMOTE_URL }} + LIBSQL_REMOTE_TOKEN: ${{ secrets.LIBSQL_REMOTE_TOKEN }} run: | if [[ ${{ github.event_name }} != "push" && "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then export SKIP_EXTERNAL_DB_TESTS=1 diff --git a/.github/workflows/release-latest.yaml b/.github/workflows/release-latest.yaml index ce194530f..fdab8f90e 100644 --- a/.github/workflows/release-latest.yaml +++ b/.github/workflows/release-latest.yaml @@ -12,6 +12,7 @@ jobs: - drizzle-orm - drizzle-kit - drizzle-zod + - drizzle-seed - drizzle-typebox - drizzle-valibot - eslint-plugin-drizzle @@ -139,12 +140,6 @@ jobs: cd drizzle-orm pnpm prisma generate --schema src/prisma/schema.prisma ) - ( - cd integration-tests - pnpm prisma generate --schema tests/prisma/pg/schema.prisma - pnpm prisma generate --schema tests/prisma/mysql/schema.prisma - pnpm prisma generate --schema tests/prisma/sqlite/schema.prisma - ) pnpm build - name: Run tests @@ -160,6 +155,8 @@ jobs: XATA_API_KEY: ${{ secrets.XATA_API_KEY }} XATA_BRANCH: ${{ secrets.XATA_BRANCH }} LIBSQL_URL: file:local.db + LIBSQL_REMOTE_URL: ${{ secrets.LIBSQL_REMOTE_URL }} + LIBSQL_REMOTE_TOKEN: ${{ secrets.LIBSQL_REMOTE_TOKEN }} run: | if [[ "${{ matrix.package }}" == "drizzle-orm" ]]; then pnpm test --filter ${{ matrix.package }} --filter integration-tests diff --git a/.gitignore b/.gitignore index 45788cac5..97b7170df 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ dist-dts rollup.config-*.mjs *.log .DS_Store +drizzle-seed/src/dev \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5f91755e..38b9f4642 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,169 +2,137 @@ Welcome! We're glad you're interested in Drizzle ORM and want to help us make it better. -Drizzle ORM is owned by [Drizzle Team](https://drizzle.team) and maintained by community members, mainly by our core contributors [@AndriiSherman](https://github.com/AndriiSherman) [@AlexBlokh](https://github.com/AlexBlokh) [@dankochetov](https://github.com/dankochetov). Everything that is going to be merged should be approved by all core contributors members +Drizzle ORM is owned by [Drizzle Team](https://drizzle.team) and maintained by community members, mainly by our core contributors ([@AndriiSherman](https://github.com/AndriiSherman), [@AlexBlokh](https://github.com/AlexBlokh), [@dankochetov](https://github.com/dankochetov)). Everything that is going to be merged should be approved by all core contributors members. --- -There are many ways you can contribute to the Drizzle ORM project +There are many ways you can contribute to the Drizzle ORM project: -- [Submitting bug reports](#bugreport) -- [Submitting feature request](#featurerequest) +- [Submitting bug reports](#bug-report) +- [Submitting feature request](#feature-request) - [Providing feedback](#feedback) -- [Contribution guidelines](#contributing) +- [Contribution guidelines](#contribution-guidelines) -## Submitting bug report +## Submitting bug report ---- - -To submit a bug or issue, please use our [issue form](https://github.com/drizzle-team/drizzle-orm/issues/new/choose) and choose Bug Report +To report a bug or issue, please use our [issue form](https://github.com/drizzle-team/drizzle-orm/issues/new/choose) and choose Bug Report. -## Submitting feature request +## Submitting feature request ---- - -To submit a bug or issue, please use our [issue form](https://github.com/drizzle-team/drizzle-orm/issues/new/choose) and choose Feature Request +To request a feature, please use our [issue form](https://github.com/drizzle-team/drizzle-orm/issues/new/choose) and choose Feature Request. ## Providing feedback ---- +There are several ways you can provide feedback: -There are several ways how you can provide a feedback +- You can join our [Discord server](https://discord.gg/yfjTbVXMW4) and provide feedback there. +- You can add new ticket in [Discussions](https://github.com/drizzle-team/drizzle-orm/discussions). +- Mention our [Twitter account](https://twitter.com/DrizzleOrm). -- You can join our [Discord](https://discord.gg/yfjTbVXMW4) channel and provide feedback there -- You can add new ticket in [Discussions](https://github.com/drizzle-team/drizzle-orm/discussions) -- Mention our [Twitter account](https://twitter.com/DrizzleOrm) +## Contribution guidelines -## Contribution guidelines - ---- - -- [Pre-Contribution setup](#pre-contribution) - - [Installing node](#-installing-node) - - [Install pnpm](#-install-pnpm) - - [Install docker](#-install-docker) - - [Clone project](#-clone-project) - - [Repository Structure](#repo-structure) - - [Build project](#-build-project) +- [Pre-contribution setup](#pre-contribution) + - [Installing Node](#installing-node) + - [Installing pnpm](#installing-pnpm) + - [Installing Docker](#installing-docker) + - [Cloning the repository](#cloning-the-repository) + - [Repository structure](#repository-structure) + - [Building the project](#building-the-project) +- [Commit message guidelines](#commit-message-guidelines) - [Contributing to `drizzle-orm`](#contributing-orm) - [Project structure](#project-structure-orm) - - [Run tests](#run-tests-orm) - - [Commits and PRs](#commits-and-prs-orm) - - [Commit guideline](#commit-guideline-orm) - - [PR guideline](#pr-guideline-orm) + - [Running tests](#running-tests-orm) + - [PR guidelines](#pr-guidelines-orm) - [Contributing to `drizzle-kit`](#contributing-kit) - [Project structure](#project-structure-kit) - - [Run tests](#run-tests-kit) - - [Commits and PRs](#commits-and-prs-kit) - - [Commit guideline](#commit-guideline-kit) - - [PR guideline](#-pr-guideline) - -## Pre-Contribution setup + - [Running tests](#running-tests-kit) + - [PR guidelines](#pr-guidelines-kit) -### Installing node +## Pre-contribution setup ---- +### Installing Node ```bash # https://github.com/nvm-sh/nvm#install--update-script -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash nvm install 18.13.0 nvm use 18.13.0 ``` -### Install pnpm - ---- +### Installing pnpm ```bash # https://pnpm.io/installation npm install -g pnpm ``` -### Install docker - ---- +### Installing Docker ```bash # https://docs.docker.com/get-docker/ -Use docker guide to install docker on your OS +# Use Docker's guide to install Docker for your OS. ``` -## Local project setup - -### Clone project - ---- +### Cloning the repository ```bash git clone https://github.com/drizzle-team/drizzle-orm.git cd drizzle-orm ``` -### Repository Structure - -``` -📂 drizzle-orm/ - orm core package with all main logic for each dialect - -📂 drizzle-kit/ - kit core package with all main logic and tests for each dialect +### Repository structure -📂 drizzle-typebox/ - all the code related to drizzle+typebox extension +- 📂 `drizzle-orm/` -📂 drizzle-valibot/ - all the code related to drizzle+valibot extension + orm core package with all main logic for each dialect -📂 drizzle-zod/ - all the code related to drizzle+zod extension +- 📂 `drizzle-kit/` -📂 eslint-plugin-drizzle/ - all the code related to drizzle eslint plugin + kit core package with all main logic and tests for each dialect -📂 changelogs/ - all changelogs for drizzle-orm, drizzle-kit, drizzle-typebox, drizzle-zod, drizzle-valibot modules +- 📂 `drizzle-typebox/` -📂 examples/ - package with Drizzle ORM usage examples + all the code related to drizzle+typebox extension -📂 integration-tests/ - package with all type of tests for each supported database -``` +- 📂 `drizzle-valibot/` -### Build project + all the code related to drizzle+valibot extension ---- +- 📂 `drizzle-zod/` -- `"pnpm i && pnpm build"` -> if you run this script from root folder - it will build whole monorepo. Running this script from specific package folder will only build current package + all the code related to drizzle+zod extension -## Contributing to `drizzle-orm` - -### Project structure - -``` -Project structure +- 📂 `eslint-plugin-drizzle/` -📂 pg-core, mysql-core, sqlite-core - core packages for each dialect with all the main logic for relation and query builder + all the code related to drizzle eslint plugin -📂 sql/ - package containing all expressions and SQL template implementation +- 📂 `changelogs/` -All other folders are for specific drivers that Drizzle ORM supports. -``` + all changelogs for drizzle-orm, drizzle-kit, drizzle-typebox, drizzle-zod, drizzle-valibot modules -### Run tests +- 📂 `examples/` ---- -All tests for Drizzle ORM are integration tests, that are simulating real database and different queries and responses from database. Each file in `integration-tests` has a list of different scenarios for different dialect+driver. Each file is creating a docker instance with needed database and running test cases there. Right after all tests were run - docker container with database will be deleted + package with Drizzle ORM usage examples -If you have added additional logic to core package - make sure that all tests were executed without any errors +- 📂 `integration-tests/` -> If you have added data types or feature for query building, you need to create additional test cases with this data type/syntax changes, syntax addition + package with all type of tests for each supported database -- `"cd integration-tests && pnpm test"` -> will run all tests in integration test folder +### Building the project -## Commits and PRs +Run the following script from the root folder to build the whole monorepo. Running it from a specific package folder will only build that package. -### Commit guideline +```bash +pnpm install && pnpm build +``` ---- +## Commit message guidelines We have specific rules on how commit messages should be structured. -It's important to make sure your commit messages are clear, concise, and informative to make it easier for others to understand the changes you are making +It's important to make sure your commit messages are clear, concise, and informative to make it easier for others to understand the changes you are making. -Commit message pattern +All commit messages should follow the pattern below: ``` @@ -172,7 +140,7 @@ Commit message pattern ``` -Example +Example: ``` Add groupBy error message @@ -181,117 +149,131 @@ In specific case, groupBy was responding with unreadable error ... ``` -> **Warning**: -> All commits should be signed, before submitting PR. Please check detailed info on [how to sign commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) +> [!WARNING] +> All commits should be signed before submitting a PR. Please check the documentation on [how to sign commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification). -### PR guideline +## Contributing to `drizzle-orm` ---- +### Project structure -1. PR should be created with specific name pattern +- 📂 `pg-core/`, `mysql-core/`, `sqlite-core/` + + core packages for each dialect with all the main logic for relation and query builder -``` -[Dialect name]: -``` +- 📂 `sql/` -Example + package containing all expressions and SQL template implementation -``` -[Pg] Add PostGIS extension support +- All other folders are for each specific driver that Drizzle ORM supports. + +### Running tests + +All tests for Drizzle ORM are integration tests that simulate real databases with different queries and responses from each database. Each file in `integration-tests` has a list of different scenarios for different dialects and drivers. Each file creates a Docker container with the needed database and runs the test cases there. After every test is run, the Docker container will be deleted. + +If you have added additional logic to a core package, make sure that all tests completed without any failures. + +> [!NOTE] +> If you have added data types or a feature for query building, you need to create additional test cases using the new API to ensure it works properly. + +If you are in the root of the repository, run all integration tests with the following script: + +```bash +cd integration-tests && pnpm test ``` -2. PR should contain detailed description with everything, that was changed +### PR guidelines -3. Each PR should contain - - Tests on feature, that was created; - - Tests on bugs, that was fixed; +1. PR titles should follow the pattern below: -To understand how test should be created and run - please check [Run tests](#-run-tests) section + ``` + []: + ``` + Example: + + ``` + [Pg] Add PostGIS extension support + ``` + +2. PRs should contain a detailed description of everything that was changed. + +3. Commit messages should follow the [message style guidelines](#commit-message-guidelines). + +4. PRs should implement: + - Tests for features that were added. + - Tests for bugs that were fixed. + +> [!NOTE] +> To understand how tests should be created and run, please check the [Running tests](#running-tests-orm) section. ## Contributing to `drizzle-kit` ### Project structure -``` -📂 cli/ - | - | -> 📄 schema.ts - all the commands defined using brocli - | - | -> 📂 commands - all the business logic for drizzle-kit commands - -📂 extensions/ - all the extension helpers for databases +- 📂 `cli/` + - 📄 `schema.ts` -📂 serialaizer/ - all the necessary logic to read from the Drizzle ORM schema and convert it to a common JSON format, as well as the logic to introspect all tables, types, and other database elements and convert them to a common JSON format + all the commands defined using brocli -📄 introspect-pg.ts, introspect-mysql.ts, introspect-sqlite.ts - these files are responsible for mapping JSON snapshots to TypeScript files during introspect commands + - 📂 `commands/` -📄 snapshotsDiffer.ts - this file handles the mapping from JSON snapshot format to JSON statement objects. + all the business logic for drizzle-kit commands -📄 jsonStatements.ts - this file defines JSON statement types, interfaces, and helper functions. +- 📂 `extensions/` -📄 sqlgenerator.ts - this file converts JSON statements to SQL strings. -``` + all the extension helpers for databases -### Run tests +- 📂 `serializer/` ---- -All tests for Drizzle Kit are integration tests, that are simulating real database and different queries and responses from database. Each file in `drizzle-kit/tests` has a list of different scenarios for different commands. Each MySQL file is creating a docker instance with needed database and running test cases there. Right after all tests were run - docker container with database will be deleted. For PostgreSQL we are using PgLite and for SQLite we are using SQLite files. + all the necessary logic to read from the Drizzle ORM schema and convert it to a common JSON format, as well as the logic to introspect all tables, types, and other database elements and convert them to a common JSON format -If you are in the root of repo: +- 📄 `introspect-pg.ts`, `introspect-mysql.ts`, `introspect-sqlite.ts` -- `"cd drizzle-kit && pnpm test"` -> will run all tests + these files are responsible for mapping JSON snapshots to TypeScript files during introspect commands -## Commits and PRs +- 📄 `snapshotsDiffer.ts` -### Commit guideline + this file handles the mapping from JSON snapshot format to JSON statement objects. ---- +- 📄 `jsonStatements.ts` -We have specific rules on how commit messages should be structured. + this file defines JSON statement types, interfaces, and helper functions. -It's important to make sure your commit messages are clear, concise, and informative to make it easier for others to understand the changes you are making +- 📄 `sqlgenerator.ts` -Commit message pattern + this file converts JSON statements to SQL strings. -``` - - - -``` +### Running tests -Example +All tests for Drizzle Kit are integration tests that simulate real databases with different queries and responses from each database. Each file in `drizzle-kit/tests` has a list of different scenarios for different commands. Each file creates a Docker container with the needed database and runs the test cases there. After every test is run, the Docker container will be deleted. We test MySQL, PostgreSQL (using PGlite), and SQLite. -``` -Add groupBy error message +If you are in the root of the repository, run all Drizzle Kit tests with the following script: -In specific case, groupBy was responding with unreadable error -... +```bash +cd drizzle-kit && pnpm test ``` -> **Warning**: -> All commits should be signed, before submitting PR. Please check detailed info on [how to sign commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) +### PR guidelines -### PR guideline +1. PR titles should follow the pattern below: ---- + ``` + [-kit]: + ``` -1. PR should be created with specific name pattern + Example: -``` -[-kit]: -``` - -Example + ``` + [Pg-kit] Add PostGIS extension support + ``` -``` -[Pg-kit] Add PostGIS extension support -``` +2. PRs should contain a detailed description of everything that was changed. -2. PR should contain detailed description with everything, that was changed +3. Commit messages should follow the [message style guidelines](#commit-message-guidelines). -3. Each PR should contain - - Tests on feature, that was created; - - Tests on bugs, that was fixed; +4. PRs should implement: + - Tests for features that were added. + - Tests for bugs that were fixed. -To understand how test should be created and run - please check [Run tests](#run-tests) section +> [!NOTE] +> To understand how tests should be created and run, please check the [Running tests](#running-tests-kit) section. diff --git a/README.md b/README.md index ac509ab0d..4342d8afe 100644 --- a/README.md +++ b/README.md @@ -17,24 +17,24 @@ ### What's Drizzle? Drizzle is a modern TypeScript ORM developers [wanna use in their next project](https://stateofdb.com/tools/drizzle). -It is [lightweight](https://bundlephobia.com/package/drizzle-orm) at only ~7.4kb minified+gzipped, it's tree shakeable with exactly 0 dependencies. +It is [lightweight](https://bundlephobia.com/package/drizzle-orm) at only ~7.4kb minified+gzipped, and it's tree shakeable with exactly 0 dependencies. -**Drizzle supports every PostgreSQL, MySQL and SQLite databases**, including serverless ones like [Turso](https://orm.drizzle.team/docs/get-started-sqlite#turso), [Neon](https://orm.drizzle.team/docs/get-started-postgresql#neon), [Xata](xata.io), [PlanetScale](https://orm.drizzle.team/docs/get-started-mysql#planetscale), [Cloudflare D1](https://orm.drizzle.team/docs/get-started-sqlite#cloudflare-d1), [FlyIO LiteFS](https://fly.io/docs/litefs/), [Vercel Postgres](https://orm.drizzle.team/docs/get-started-postgresql#vercel-postgres), [Supabase](https://orm.drizzle.team/docs/get-started-postgresql#supabase) and [AWS Data API](https://orm.drizzle.team/docs/get-started-postgresql#aws-data-api). No bells and whistles, no rust binaries, no serverless adapters, everything just works out of the box. +**Drizzle supports every PostgreSQL, MySQL and SQLite database**, including serverless ones like [Turso](https://orm.drizzle.team/docs/get-started-sqlite#turso), [Neon](https://orm.drizzle.team/docs/get-started-postgresql#neon), [Xata](xata.io), [PlanetScale](https://orm.drizzle.team/docs/get-started-mysql#planetscale), [Cloudflare D1](https://orm.drizzle.team/docs/get-started-sqlite#cloudflare-d1), [FlyIO LiteFS](https://fly.io/docs/litefs/), [Vercel Postgres](https://orm.drizzle.team/docs/get-started-postgresql#vercel-postgres), [Supabase](https://orm.drizzle.team/docs/get-started-postgresql#supabase) and [AWS Data API](https://orm.drizzle.team/docs/get-started-postgresql#aws-data-api). No bells and whistles, no Rust binaries, no serverless adapters, everything just works out of the box. -**Drizzle is serverless-ready by design**, it works in every major JavaScript runtime like NodeJS, Bun, Deno, Cloudflare Workers, Supabase functions, any Edge runtime and even in Browsers. -With Drizzle you can be [**fast out of the box**](https://orm.drizzle.team/benchmarks), save time and costs while never introducing any data proxies into your infrastructure. +**Drizzle is serverless-ready by design**. It works in every major JavaScript runtime like NodeJS, Bun, Deno, Cloudflare Workers, Supabase functions, any Edge runtime, and even in browsers. +With Drizzle you can be [**fast out of the box**](https://orm.drizzle.team/benchmarks) and save time and costs while never introducing any data proxies into your infrastructure. -While you can use Drizzle as a JavaScript library, it shines in the TypeScript. It lets you [**declare SQL schema**](https://orm.drizzle.team/docs/sql-schema-declaration) and build both [**relational**](https://orm.drizzle.team/docs/rqb) and [**SQL-like queries**](https://orm.drizzle.team/docs/select), while keeping the balance between type-safety and extensibility for toolmakers to build on top. +While you can use Drizzle as a JavaScript library, it shines with TypeScript. It lets you [**declare SQL schemas**](https://orm.drizzle.team/docs/sql-schema-declaration) and build both [**relational**](https://orm.drizzle.team/docs/rqb) and [**SQL-like queries**](https://orm.drizzle.team/docs/select), while keeping the balance between type-safety and extensibility for toolmakers to build on top. ### Ecosystem While Drizzle ORM remains a thin typed layer on top of SQL, we made a set of tools for people to have best possible developer experience. -Drizzle comes with a powerful [**Drizzle Kit**](https://orm.drizzle.team/kit-docs/overview) CLI companion for you to have hasstle-free migrations. It can generate SQL migration files for you or apply schema changes directly to the database. +Drizzle comes with a powerful [**Drizzle Kit**](https://orm.drizzle.team/kit-docs/overview) CLI companion for you to have hassle-free migrations. It can generate SQL migration files for you or apply schema changes directly to the database. -And we have a [**Drizzle Studio**](https://orm.drizzle.team/drizzle-studio/overview) for you to effortlessly browse and manipulate data in your database of choice. +We also have [**Drizzle Studio**](https://orm.drizzle.team/drizzle-studio/overview) for you to effortlessly browse and manipulate data in your database of choice. ### Documentation -Check out the full documentation on [the website](https://orm.drizzle.team/docs/overview) +Check out the full documentation on [the website](https://orm.drizzle.team/docs/overview). ### Our sponsors ❤️

diff --git a/changelogs/drizzle-kit/0.24.0.md b/changelogs/drizzle-kit/0.24.0.md new file mode 100644 index 000000000..b766e018b --- /dev/null +++ b/changelogs/drizzle-kit/0.24.0.md @@ -0,0 +1,24 @@ +## Breaking changes (for SQLite users) + +#### Fixed [Composite primary key order is not consistent](https://github.com/drizzle-team/drizzle-kit-mirror/issues/342) by removing `sort` in SQLite and to be consistant with the same logic in PostgreSQL and MySQL + +The issue that may arise for SQLite users with any driver using composite primary keys is that the order in the database may differ from the Drizzle schema. + +- If you are using `push`, you **MAY** be prompted to update your table with a new order of columns in the composite primary key. You will need to either change it manually in the database or push the changes, but this may lead to data loss, etc. + +- If you are using `generate`, you **MAY** also be prompted to update your table with a new order of columns in the composite primary key. You can either keep that migration or skip it by emptying the SQL migration file. + +If nothing works for you and you are blocked, please reach out to me @AndriiSherman. I will try to help you! + + +## Bug fixes + +- [[BUG] When using double type columns, import is not inserted](https://github.com/drizzle-team/drizzle-kit-mirror/issues/403) - thanks @Karibash +- [[BUG] A number value is specified as the default for a column of type char](https://github.com/drizzle-team/drizzle-kit-mirror/issues/404) - thanks @Karibash +- [[BUG]: Array default in migrations are wrong](https://github.com/drizzle-team/drizzle-orm/issues/2621) - thanks @L-Mario564 +- [[FEATURE]: Simpler default array fields](https://github.com/drizzle-team/drizzle-orm/issues/2709) - thanks @L-Mario564 +- [[BUG]: drizzle-kit generate succeeds but generates invalid SQL for default([]) - Postgres](https://github.com/drizzle-team/drizzle-orm/issues/2432) - thanks @L-Mario564 +- [[BUG]: Incorrect type for array column default value](https://github.com/drizzle-team/drizzle-orm/issues/2334) - thanks @L-Mario564 +- [[BUG]: error: column is of type integer[] but default expression is of type integer](https://github.com/drizzle-team/drizzle-orm/issues/2224) - thanks @L-Mario564 +- [[BUG]: Default value in array generating wrong migration file](https://github.com/drizzle-team/drizzle-orm/issues/1003) - thanks @L-Mario564 +- [[BUG]: enum as array, not possible?](https://github.com/drizzle-team/drizzle-orm/issues/1564) - thanks @L-Mario564 \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.24.1.md b/changelogs/drizzle-kit/0.24.1.md new file mode 100644 index 000000000..d70f6ebbe --- /dev/null +++ b/changelogs/drizzle-kit/0.24.1.md @@ -0,0 +1,32 @@ +## Bug fixes + +> Big thanks to @L-Mario564 for his [PR](https://github.com/drizzle-team/drizzle-orm/pull/2804). It conflicted in most cases with a PR that was merged, but we incorporated some of his logic. Merging it would have caused more problems and taken more time to resolve, so we just took a few things from his PR, like removing "::" mappings in introspect and some array type default handlers + +### What was fixed + +1. The Drizzle Kit CLI was not working properly for the `introspect` command. +2. Added the ability to use column names with special characters for all dialects. +3. Included PostgreSQL sequences in the introspection process. +4. Reworked array type introspection and added all test cases. +5. Fixed all (we hope) default issues in PostgreSQL, where `::` was included in the introspected output. +6. `preserve` casing option was broken + +### Tickets that were closed + +- [[BUG]: invalid schema generation with drizzle-kit introspect:pg](https://github.com/drizzle-team/drizzle-orm/issues/1210) +- [[BUG][mysql introspection]: TS error when introspect column including colon](https://github.com/drizzle-team/drizzle-orm/issues/1928) +- [[BUG]: Unhandled defaults when introspecting postgres db](https://github.com/drizzle-team/drizzle-orm/issues/1625) +- [[BUG]: PostgreSQL Enum Naming and Schema Typing Issue](https://github.com/drizzle-team/drizzle-orm/issues/2315) +- [[BUG]: drizzle-kit instrospect command generates syntax error on varchar column types](https://github.com/drizzle-team/drizzle-orm/issues/2714) +- [[BUG]: Introspecting varchar[] type produces syntactically invalid schema.ts](https://github.com/drizzle-team/drizzle-orm/issues/1633) +- [[BUG]: introspect:pg column not using generated enum name](https://github.com/drizzle-team/drizzle-orm/issues/1648) +- [[BUG]: drizzle-kit introspect casing "preserve" config not working](https://github.com/drizzle-team/drizzle-orm/issues/2773) +- [[BUG]: drizzle-kit introspect fails on required param that is defined](https://github.com/drizzle-team/drizzle-orm/issues/2719) +- [[BUG]: Error when running npx drizzle-kit introspect: "Expected object, received string"](https://github.com/drizzle-team/drizzle-orm/issues/2657) +- [[BUG]: Missing index names when running introspect command [MYSQL]](https://github.com/drizzle-team/drizzle-orm/issues/2525) +- [[BUG]: drizzle-kit introspect TypeError: Cannot read properties of undefined (reading 'toLowerCase')](https://github.com/drizzle-team/drizzle-orm/issues/2338) +- [[BUG]: Wrong column name when using PgEnum.array()](https://github.com/drizzle-team/drizzle-orm/issues/2100) +- [[BUG]: Incorrect Schema Generated when introspecting extisting pg database](https://github.com/drizzle-team/drizzle-orm/issues/1985) +- [[⚠️🐞BUG]: index() missing argument after introspection, causes tsc error that fails the build](https://github.com/drizzle-team/drizzle-orm/issues/1870) +- [[BUG]: drizzle-kit introspect small errors](https://github.com/drizzle-team/drizzle-orm/issues/1738) +- [[BUG]: Missing bigint import in drizzle-kit introspect](https://github.com/drizzle-team/drizzle-orm/issues/1020) \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.24.2.md b/changelogs/drizzle-kit/0.24.2.md new file mode 100644 index 000000000..962a29acc --- /dev/null +++ b/changelogs/drizzle-kit/0.24.2.md @@ -0,0 +1,24 @@ +## New Features + +### 🎉 Support for `pglite` driver + +You can now use pglite with all drizzle-kit commands, including Drizzle Studio! + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "postgresql", + driver: "pglite", + schema: "./schema.ts", + dbCredentials: { + url: "local-pg.db", + }, + verbose: true, + strict: true, +}); +``` + +## Bug fixes + +- mysql-kit: fix GENERATED ALWAYS AS ... NOT NULL - [#2824](https://github.com/drizzle-team/drizzle-orm/pull/2824) \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.25.0.md b/changelogs/drizzle-kit/0.25.0.md new file mode 100644 index 000000000..e33879d97 --- /dev/null +++ b/changelogs/drizzle-kit/0.25.0.md @@ -0,0 +1,180 @@ +## Breaking changes and migrate guide for Turso users + +If you are using Turso and libsql, you will need to upgrade your `drizzle.config` and `@libsql/client` package. + +1. This version of drizzle-orm will only work with `@libsql/client@0.10.0` or higher if you are using the `migrate` function. For other use cases, you can continue using previous versions(But the suggestion is to upgrade) +To install the latest version, use the command: + +```bash +npm i @libsql/client@latest +``` + +2. Previously, we had a common `drizzle.config` for SQLite and Turso users, which allowed a shared strategy for both dialects. Starting with this release, we are introducing the turso dialect in drizzle-kit. We will evolve and improve Turso as a separate dialect with its own migration strategies. + +**Before** + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "sqlite", + schema: "./schema.ts", + out: "./drizzle", + dbCredentials: { + url: "database.db", + }, + breakpoints: true, + verbose: true, + strict: true, +}); +``` + +**After** + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "turso", + schema: "./schema.ts", + out: "./drizzle", + dbCredentials: { + url: "database.db", + }, + breakpoints: true, + verbose: true, + strict: true, +}); +``` + +If you are using only SQLite, you can use `dialect: "sqlite"` + +## LibSQL/Turso and Sqlite migration updates + +### SQLite "generate" and "push" statements updates + +Starting from this release, we will no longer generate comments like this: + +```sql + '/*\n SQLite does not support "Changing existing column type" out of the box, we do not generate automatic migration for that, so it has to be done manually' + + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' + + '\n https://www.sqlite.org/lang_altertable.html' + + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' + + "\n\n Due to that we don't generate migration automatically and it has to be done manually" + + '\n*/' +``` + +We will generate a set of statements, and you can decide if it's appropriate to create data-moving statements instead. Here is an example of the SQL file you'll receive now: + +```sql +PRAGMA foreign_keys=OFF; +--> statement-breakpoint +CREATE TABLE `__new_worker` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `salary` text NOT NULL, + `job_id` integer, + FOREIGN KEY (`job_id`) REFERENCES `job`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_worker`("id", "name", "salary", "job_id") SELECT "id", "name", "salary", "job_id" FROM `worker`; +--> statement-breakpoint +DROP TABLE `worker`; +--> statement-breakpoint +ALTER TABLE `__new_worker` RENAME TO `worker`; +--> statement-breakpoint +PRAGMA foreign_keys=ON; +``` + +### LibSQL/Turso "generate" and "push" statements updates + +Since LibSQL supports more ALTER statements than SQLite, we can generate more statements without recreating your schema and moving all the data, which can be potentially dangerous for production environments. + +LibSQL and Turso will now have a separate dialect in the Drizzle config file, meaning that we will evolve Turso and LibSQL independently from SQLite and will aim to support as many features as Turso/LibSQL offer. + +With the updated LibSQL migration strategy, you will have the ability to: + +- **Change Data Type**: Set a new data type for existing columns. +- **Set and Drop Default Values**: Add or remove default values for existing columns. +- **Set and Drop NOT NULL**: Add or remove the NOT NULL constraint on existing columns. +- **Add References to Existing Columns**: Add foreign key references to existing columns + +You can find more information in the [LibSQL documentation](https://github.com/tursodatabase/libsql/blob/main/libsql-sqlite3/doc/libsql_extensions.md#altering-columns) + +### LIMITATIONS + +- Dropping or altering an index will cause table recreation. + +This is because LibSQL/Turso does not support dropping this type of index. + +```sql +CREATE TABLE `users` ( + `id` integer NOT NULL, + `name` integer, + `age` integer PRIMARY KEY NOT NULL + FOREIGN KEY (`name`) REFERENCES `users1`("id") ON UPDATE no action ON DELETE no action +); +``` + +- If the table has indexes, altering columns will cause table recreation. +- Drizzle-Kit will drop the indexes, modify the columns, and then recreate the indexes. +- Adding or dropping composite foreign keys is not supported and will cause table recreation + +### NOTES + +- You can create a reference on any column type, but if you want to insert values, the referenced column must have a unique index or primary key. + +```sql +CREATE TABLE parent(a PRIMARY KEY, b UNIQUE, c, d, e, f); +CREATE UNIQUE INDEX i1 ON parent(c, d); +CREATE INDEX i2 ON parent(e); +CREATE UNIQUE INDEX i3 ON parent(f COLLATE nocase); + +CREATE TABLE child1(f, g REFERENCES parent(a)); -- Ok +CREATE TABLE child2(h, i REFERENCES parent(b)); -- Ok +CREATE TABLE child3(j, k, FOREIGN KEY(j, k) REFERENCES parent(c, d)); -- Ok +CREATE TABLE child4(l, m REFERENCES parent(e)); -- Error! +CREATE TABLE child5(n, o REFERENCES parent(f)); -- Error! +CREATE TABLE child6(p, q, FOREIGN KEY(p, q) REFERENCES parent(b, c)); -- Error! +CREATE TABLE child7(r REFERENCES parent(c)); -- Error! +``` + +> **NOTE**: The foreign key for the table child5 is an error because, although the parent key column has a unique index, the index uses a different collating sequence. + +See more: https://www.sqlite.org/foreignkeys.html + +## New `casing` param in `drizzle-orm` and `drizzle-kit` + +There are more improvements you can make to your schema definition. The most common way to name your variables in a database and in TypeScript code is usually `snake_case` in the database and `camelCase` in the code. For this case, in Drizzle, you can now define a naming strategy in your database to help Drizzle map column keys automatically. Let's take a table from the previous example and make it work with the new casing API in Drizzle + +Table can now become: +```ts +import { pgTable } from "drizzle-orm/pg-core"; + +export const ingredients = pgTable("ingredients", (t) => ({ + id: t.uuid().defaultRandom().primaryKey(), + name: t.text().notNull(), + description: t.text(), + inStock: t.boolean().default(true), +})); +``` +As you can see, `inStock` doesn't have a database name alias, but by defining the casing configuration at the connection level, all queries will automatically map it to `snake_case` + +```ts +const db = await drizzle('node-postgres', { connection: '', casing: 'snake_case' }) +``` + +For `drizzle-kit` migrations generation you should also specify `casing` param in drizzle config, so you can be sure you casing strategy will be applied to drizzle-kit as well + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "postgresql", + schema: "./schema.ts", + dbCredentials: { + url: "postgresql://postgres:password@localhost:5432/db", + }, + casing: "snake_case", +}); +``` \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.26.0.md b/changelogs/drizzle-kit/0.26.0.md new file mode 100644 index 000000000..8e054812b --- /dev/null +++ b/changelogs/drizzle-kit/0.26.0.md @@ -0,0 +1,122 @@ +# New Features + +## Checks support in `drizzle-kit` + +You can use drizzle-kit to manage your `check` constraint defined in drizzle-orm schema definition + +For example current drizzle table: + +```ts +import { sql } from "drizzle-orm"; +import { check, pgTable } from "drizzle-orm/pg-core"; + +export const users = pgTable( + "users", + (c) => ({ + id: c.uuid().defaultRandom().primaryKey(), + username: c.text().notNull(), + age: c.integer(), + }), + (table) => ({ + checkConstraint: check("age_check", sql`${table.age} > 21`), + }) +); +``` + +will be generated into + +```sql +CREATE TABLE IF NOT EXISTS "users" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "username" text NOT NULL, + "age" integer, + CONSTRAINT "age_check" CHECK ("users"."age" > 21) +); +``` + +The same is supported in all dialects + +### Limitations + +- `generate` will work as expected for all check constraint changes. +- `push` will detect only check renames and will recreate the constraint. All other changes to SQL won't be detected and will be ignored. + +So, if you want to change the constraint's SQL definition using only `push`, you would need to manually comment out the constraint, `push`, then put it back with the new SQL definition and `push` one more time. + +## Views support in `drizzle-kit` + +You can use drizzle-kit to manage your `views` defined in drizzle-orm schema definition. It will work with all existing dialects and view options + +### PostgreSQL + +For example current drizzle table: + +```ts +import { sql } from "drizzle-orm"; +import { + check, + pgMaterializedView, + pgTable, + pgView, +} from "drizzle-orm/pg-core"; + +export const users = pgTable( + "users", + (c) => ({ + id: c.uuid().defaultRandom().primaryKey(), + username: c.text().notNull(), + age: c.integer(), + }), + (table) => ({ + checkConstraint: check("age_check", sql`${table.age} > 21`), + }) +); + +export const simpleView = pgView("simple_users_view").as((qb) => + qb.select().from(users) +); + +export const materializedView = pgMaterializedView( + "materialized_users_view" +).as((qb) => qb.select().from(users)); +``` + +will be generated into + +```sql +CREATE TABLE IF NOT EXISTS "users" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "username" text NOT NULL, + "age" integer, + CONSTRAINT "age_check" CHECK ("users"."age" > 21) +); + +CREATE VIEW "public"."simple_users_view" AS (select "id", "username", "age" from "users"); + +CREATE MATERIALIZED VIEW "public"."materialized_users_view" AS (select "id", "username", "age" from "users"); +``` + +Views supported in all dialects, but materialized views are supported only in PostgreSQL + +#### Limitations + +- `generate` will work as expected for all view changes +- `push` limitations: + +1. If you want to change the view's SQL definition using only `push`, you would need to manually comment out the view, `push`, then put it back with the new SQL definition and `push` one more time. + +## Updates for PostgreSQL enums behavior + +We've updated enum behavior in Drizzle with PostgreSQL: + +- Add value after or before in enum: With this change, Drizzle will now respect the order of values in the enum and allow adding new values after or before a specific one. + +- Support for dropping a value from an enum: In this case, Drizzle will attempt to alter all columns using the enum to text, then drop the existing enum and create a new one with the updated set of values. After that, all columns previously using the enum will be altered back to the new enum. + +> If the deleted enum value was used by a column, this process will result in a database error. + +- Support for dropping an enum + +- Support for moving enums between schemas + +- Support for renaming enums diff --git a/changelogs/drizzle-kit/0.26.1.md b/changelogs/drizzle-kit/0.26.1.md new file mode 100644 index 000000000..da488ede8 --- /dev/null +++ b/changelogs/drizzle-kit/0.26.1.md @@ -0,0 +1 @@ +- Fix `data is malformed` for views \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.26.2.md b/changelogs/drizzle-kit/0.26.2.md new file mode 100644 index 000000000..59e29de14 --- /dev/null +++ b/changelogs/drizzle-kit/0.26.2.md @@ -0,0 +1 @@ +- Updated internal versions for the drizzle-kit and drizzle-orm packages. Changes were introduced in the last minor release, and you are required to upgrade both packages to ensure they work as expected \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.27.0.md b/changelogs/drizzle-kit/0.27.0.md new file mode 100644 index 000000000..e4f51fdb3 --- /dev/null +++ b/changelogs/drizzle-kit/0.27.0.md @@ -0,0 +1,421 @@ +> This version of `drizzle-jit` requires `drizzle-orm@0.36.0` to enable all new features + +# New Features + +## Row-Level Security (RLS) + +With Drizzle, you can enable Row-Level Security (RLS) for any Postgres table, create policies with various options, and define and manage the roles those policies apply to. + +Drizzle supports a raw representation of Postgres policies and roles that can be used in any way you want. This works with popular Postgres database providers such as `Neon` and `Supabase`. + +In Drizzle, we have specific predefined RLS roles and functions for RLS with both database providers, but you can also define your own logic. + +### Enable RLS + +If you just want to enable RLS on a table without adding policies, you can use `.enableRLS()` + +As mentioned in the PostgreSQL documentation: + +> If no policy exists for the table, a default-deny policy is used, meaning that no rows are visible or can be modified. +Operations that apply to the whole table, such as TRUNCATE and REFERENCES, are not subject to row security. + +```ts +import { integer, pgTable } from 'drizzle-orm/pg-core'; + +export const users = pgTable('users', { + id: integer(), +}).enableRLS(); +``` + +> If you add a policy to a table, RLS will be enabled automatically. So, there’s no need to explicitly enable RLS when adding policies to a table. + +### Roles + +Currently, Drizzle supports defining roles with a few different options, as shown below. Support for more options will be added in a future release. + +```ts +import { pgRole } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin', { createRole: true, createDb: true, inherit: true }); +``` + +If a role already exists in your database, and you don’t want drizzle-kit to ‘see’ it or include it in migrations, you can mark the role as existing. + +```ts +import { pgRole } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin').existing(); +``` + +### Policies + +To fully leverage RLS, you can define policies within a Drizzle table. + +> In PostgreSQL, policies should be linked to an existing table. Since policies are always associated with a specific table, we decided that policy definitions should be defined as a parameter of `pgTable` + +**Example of pgPolicy with all available properties** +```ts +import { sql } from 'drizzle-orm'; +import { integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + pgPolicy('policy', { + as: 'permissive', + to: admin, + for: 'delete', + using: sql``, + withCheck: sql``, + }), +]); +``` + +**Link Policy to an existing table** + +There are situations where you need to link a policy to an existing table in your database. +The most common use case is with database providers like `Neon` or `Supabase`, where you need to add a policy +to their existing tables. In this case, you can use the `.link()` API + +```ts +import { sql } from "drizzle-orm"; +import { pgPolicy } from "drizzle-orm/pg-core"; +import { authenticatedRole, realtimeMessages } from "drizzle-orm/supabase"; + +export const policy = pgPolicy("authenticated role insert policy", { + for: "insert", + to: authenticatedRole, + using: sql``, +}).link(realtimeMessages); +``` + +### Migrations + +If you are using drizzle-kit to manage your schema and roles, there may be situations where you want to refer to roles that are not defined in your Drizzle schema. In such cases, you may want drizzle-kit to skip managing these roles without having to define each role in your drizzle schema and marking it with `.existing()`. + +In these cases, you can use `entities.roles` in `drizzle.config.ts`. For a complete reference, refer to the the [`drizzle.config.ts`](https://orm.drizzle.team/docs/drizzle-config-file) documentation. + +By default, `drizzle-kit` does not manage roles for you, so you will need to enable this feature in `drizzle.config.ts`. + +```ts {12-14} +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: 'postgresql', + schema: "./drizzle/schema.ts", + dbCredentials: { + url: process.env.DATABASE_URL! + }, + verbose: true, + strict: true, + entities: { + roles: true + } +}); +``` + +In case you need additional configuration options, let's take a look at a few more examples. + +**You have an `admin` role and want to exclude it from the list of manageable roles** + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + exclude: ['admin'] + } + } +}); +``` + +**You have an `admin` role and want to include it in the list of manageable roles** + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + include: ['admin'] + } + } +}); +``` + +**If you are using `Neon` and want to exclude Neon-defined roles, you can use the provider option** + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + provider: 'neon' + } + } +}); +``` + +**If you are using `Supabase` and want to exclude Supabase-defined roles, you can use the provider option** + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + provider: 'supabase' + } + } +}); +``` + +> You may encounter situations where Drizzle is slightly outdated compared to new roles specified by your database provider. +In such cases, you can use the `provider` option and `exclude` additional roles: + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + provider: 'supabase', + exclude: ['new_supabase_role'] + } + } +}); +``` + +### RLS on views + +With Drizzle, you can also specify RLS policies on views. For this, you need to use `security_invoker` in the view's WITH options. Here is a small example: + +```ts {5} +... + +export const roomsUsersProfiles = pgView("rooms_users_profiles") + .with({ + securityInvoker: true, + }) + .as((qb) => + qb + .select({ + ...getTableColumns(roomsUsers), + email: profiles.email, + }) + .from(roomsUsers) + .innerJoin(profiles, eq(roomsUsers.userId, profiles.id)) + ); +``` + +### Using with Neon + +The Neon Team helped us implement their vision of a wrapper on top of our raw policies API. We defined a specific +`/neon` import with the `crudPolicy` function that includes predefined functions and Neon's default roles. + +Here's an example of how to use the `crudPolicy` function: + +```ts +import { crudPolicy } from 'drizzle-orm/neon'; +import { integer, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + crudPolicy({ role: admin, read: true, modify: false }), +]); +``` + +This policy is equivalent to: + +```ts +import { sql } from 'drizzle-orm'; +import { integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + pgPolicy(`crud-${admin.name}-policy-insert`, { + for: 'insert', + to: admin, + withCheck: sql`false`, + }), + pgPolicy(`crud-${admin.name}-policy-update`, { + for: 'update', + to: admin, + using: sql`false`, + withCheck: sql`false`, + }), + pgPolicy(`crud-${admin.name}-policy-delete`, { + for: 'delete', + to: admin, + using: sql`false`, + }), + pgPolicy(`crud-${admin.name}-policy-select`, { + for: 'select', + to: admin, + using: sql`true`, + }), +]); +``` + +`Neon` exposes predefined `authenticated` and `anaonymous` roles and related functions. If you are using `Neon` for RLS, you can use these roles, which are marked as existing, and the related functions in your RLS queries. + +```ts +// drizzle-orm/neon +export const authenticatedRole = pgRole('authenticated').existing(); +export const anonymousRole = pgRole('anonymous').existing(); + +export const authUid = (userIdColumn: AnyPgColumn) => sql`(select auth.user_id() = ${userIdColumn})`; +``` + +For example, you can use the `Neon` predefined roles and functions like this: + + +```ts +import { sql } from 'drizzle-orm'; +import { authenticatedRole } from 'drizzle-orm/neon'; +import { integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + pgPolicy(`policy-insert`, { + for: 'insert', + to: authenticatedRole, + withCheck: sql`false`, + }), +]); +``` + +### Using with Supabase + +We also have a `/supabase` import with a set of predefined roles marked as existing, which you can use in your schema. +This import will be extended in a future release with more functions and helpers to make using RLS and `Supabase` simpler. + +```ts +// drizzle-orm/supabase +export const anonRole = pgRole('anon').existing(); +export const authenticatedRole = pgRole('authenticated').existing(); +export const serviceRole = pgRole('service_role').existing(); +export const postgresRole = pgRole('postgres_role').existing(); +export const supabaseAuthAdminRole = pgRole('supabase_auth_admin').existing(); +``` + +For example, you can use the `Supabase` predefined roles like this: + +```ts +import { sql } from 'drizzle-orm'; +import { serviceRole } from 'drizzle-orm/supabase'; +import { integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + pgPolicy(`policy-insert`, { + for: 'insert', + to: serviceRole, + withCheck: sql`false`, + }), +]); +``` + +The `/supabase` import also includes predefined tables and functions that you can use in your application + +```ts +// drizzle-orm/supabase + +const auth = pgSchema('auth'); +export const authUsers = auth.table('users', { + id: uuid().primaryKey().notNull(), +}); + +const realtime = pgSchema('realtime'); +export const realtimeMessages = realtime.table( + 'messages', + { + id: bigserial({ mode: 'bigint' }).primaryKey(), + topic: text().notNull(), + extension: text({ + enum: ['presence', 'broadcast', 'postgres_changes'], + }).notNull(), + }, +); + +export const authUid = sql`(select auth.uid())`; +export const realtimeTopic = sql`realtime.topic()`; +``` + +This allows you to use it in your code, and Drizzle Kit will treat them as existing databases, +using them only as information to connect to other entities + +```ts +import { foreignKey, pgPolicy, pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm/sql"; +import { authenticatedRole, authUsers } from "drizzle-orm/supabase"; + +export const profiles = pgTable( + "profiles", + { + id: uuid().primaryKey().notNull(), + email: text().notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.id], + // reference to the auth table from Supabase + foreignColumns: [authUsers.id], + name: "profiles_id_fk", + }).onDelete("cascade"), + pgPolicy("authenticated can view all profiles", { + for: "select", + // using predefined role from Supabase + to: authenticatedRole, + using: sql`true`, + }), + ] +); +``` + +Let's check an example of adding a policy to a table that exists in `Supabase` + +```ts +import { sql } from "drizzle-orm"; +import { pgPolicy } from "drizzle-orm/pg-core"; +import { authenticatedRole, realtimeMessages } from "drizzle-orm/supabase"; + +export const policy = pgPolicy("authenticated role insert policy", { + for: "insert", + to: authenticatedRole, + using: sql``, +}).link(realtimeMessages); +``` + +# Bug fixes + +- [[BUG]: Studio + mysql default mode, wrong format related timezone](https://github.com/drizzle-team/drizzle-orm/issues/2747) +- [[BUG]: Drizzle Studio CORS error](https://github.com/drizzle-team/drizzle-orm/issues/1857) +- [[BUG]: TIMESTAMPS showing up incorrectly on drizzle studio](https://github.com/drizzle-team/drizzle-orm/issues/2549) \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.27.1.md b/changelogs/drizzle-kit/0.27.1.md new file mode 100644 index 000000000..d7a63a723 --- /dev/null +++ b/changelogs/drizzle-kit/0.27.1.md @@ -0,0 +1 @@ +- Fix: [[BUG]: When using RLS policies and Views, the view is the last clause generated](https://github.com/drizzle-team/drizzle-orm/issues/3378) \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.27.2.md b/changelogs/drizzle-kit/0.27.2.md new file mode 100644 index 000000000..bafd17222 --- /dev/null +++ b/changelogs/drizzle-kit/0.27.2.md @@ -0,0 +1,3 @@ +- Fix [[BUG]: Undefined properties when using drizzle-kit push](https://github.com/drizzle-team/drizzle-orm/issues/3391) +- Fix TypeError: Cannot read properties of undefined (reading 'isRLSEnabled') +- Fix push bugs, when pushing a schema with linked policy to a table from `drizzle-orm/supabase` diff --git a/changelogs/drizzle-kit/0.28.0.md b/changelogs/drizzle-kit/0.28.0.md new file mode 100644 index 000000000..6881c677e --- /dev/null +++ b/changelogs/drizzle-kit/0.28.0.md @@ -0,0 +1,28 @@ +# Improvements + +- Added an OHM static imports checker to identify unexpected imports within a chain of imports in the drizzle-kit repo. For example, it checks if drizzle-orm is imported before drizzle-kit and verifies if the drizzle-orm import is available in your project. +- [Adding more columns to Supabase auth.users table schema](https://github.com/drizzle-team/drizzle-orm/issues/3327) - thanks @nicholasdly + +# Bug Fixes + +- [[BUG]: [drizzle-kit]: Fix breakpoints option cannot be disabled](https://github.com/drizzle-team/drizzle-orm/issues/2828) - thanks @klotztech +- [[BUG]: drizzle-kit introspect: SMALLINT import missing and incorrect DECIMAL UNSIGNED handling](https://github.com/drizzle-team/drizzle-orm/issues/2950) - thanks @L-Mario564 +- [Unsigned tinyints preventing migrations](https://github.com/drizzle-team/drizzle-orm/issues/1571) - thanks @L-Mario564 +- [[BUG]: Can't parse float(8,2) from database (precision and scale and/or unsigned breaks float types)](https://github.com/drizzle-team/drizzle-orm/issues/3285) - thanks @L-Mario564 +- [[BUG]: PgEnum generated migration doesn't escape single quotes](https://github.com/drizzle-team/drizzle-orm/issues/1272) - thanks @L-Mario564 +- [[BUG]: single quote not escaped correctly in migration file](https://github.com/drizzle-team/drizzle-orm/issues/2184) - thanks @L-Mario564 +- [[BUG]: Migrations does not escape single quotes](https://github.com/drizzle-team/drizzle-orm/issues/1765) - thanks @L-Mario564 +- [[BUG]: Issue with quoted default string values](https://github.com/drizzle-team/drizzle-orm/issues/2122) - thanks @L-Mario564 +- [[BUG]: SQl commands in wrong roder](https://github.com/drizzle-team/drizzle-orm/issues/2390) - thanks @L-Mario564 +- [[BUG]: Time with precision in drizzle-orm/pg-core adds double-quotes around type](https://github.com/drizzle-team/drizzle-orm/issues/1804) - thanks @L-Mario564 +- [[BUG]: Postgres push fails due to lack of quotes](https://github.com/drizzle-team/drizzle-orm/issues/2396) - thanks @L-Mario564 +- [[BUG]: TypeError: Cannot read properties of undefined (reading 'compositePrimaryKeys')](https://github.com/drizzle-team/drizzle-orm/issues/2344) - thanks @L-Mario564 +- [[BUG]: drizzle-kit introspect generates CURRENT_TIMESTAMP without sql operator on date column](https://github.com/drizzle-team/drizzle-orm/issues/2899) - thanks @L-Mario564 +- [[BUG]: Drizzle-kit introspect doesn't pull correct defautl statement](https://github.com/drizzle-team/drizzle-orm/issues/2905) - thanks @L-Mario564 +- [[BUG]: Problem on MacBook - This statement does not return data. Use run() instead](https://github.com/drizzle-team/drizzle-orm/issues/2623) - thanks @L-Mario564 +- [[BUG]: Enum column names that are used as arrays are not quoted](https://github.com/drizzle-team/drizzle-orm/issues/2598) - thanks @L-Mario564 +- [[BUG]: drizzle-kit generate ignores index operators](https://github.com/drizzle-team/drizzle-orm/issues/2935) - thanks @L-Mario564 +- [dialect param config error message is wrong](https://github.com/drizzle-team/drizzle-orm/issues/3427) - thanks @L-Mario564 +- [[BUG]: Error setting default enum field values](https://github.com/drizzle-team/drizzle-orm/issues/2299) - thanks @L-Mario564 +- [[BUG]: drizzle-kit does not respect the order of columns configured in primaryKey()](https://github.com/drizzle-team/drizzle-orm/issues/2326) - thanks @L-Mario564 +- [[BUG]: Cannot drop Unique Constraint MySQL](https://github.com/drizzle-team/drizzle-orm/issues/998) - thanks @L-Mario564 diff --git a/changelogs/drizzle-kit/0.28.1.md b/changelogs/drizzle-kit/0.28.1.md new file mode 100644 index 000000000..194b4bd57 --- /dev/null +++ b/changelogs/drizzle-kit/0.28.1.md @@ -0,0 +1,4 @@ +# Bug fixes + +- Fixed typos in repository: thanks @armandsalle, @masto, @wackbyte, @Asher-JH, @MaxLeiter +- [fix: wrong dialect set in mysql/sqlite introspect](https://github.com/drizzle-team/drizzle-orm/pull/2865) \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.29.0.md b/changelogs/drizzle-kit/0.29.0.md new file mode 100644 index 000000000..b68b33085 --- /dev/null +++ b/changelogs/drizzle-kit/0.29.0.md @@ -0,0 +1,40 @@ +# New Dialects + +### 🎉 `SingleStore` dialect is now available in Drizzle + +Thanks to the SingleStore team for creating a PR with all the necessary changes to support the MySQL-compatible part of SingleStore. You can already start using it with Drizzle. The SingleStore team will also help us iterate through updates and make more SingleStore-specific features available in Drizzle + +```ts +import 'dotenv/config'; +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + dialect: 'singlestore', + out: './drizzle', + schema: './src/db/schema.ts', + dbCredentials: { + url: process.env.DATABASE_URL!, + }, +}); +``` + +You can check out our [Getting started guides](https://orm.drizzle.team/docs/get-started/singlestore-new) to try SingleStore! + +# New Drivers + +### 🎉 `SQLite Durable Objects` driver is now available in Drizzle + +You can now query SQLite Durable Objects in Drizzle! + +For the full example, please check our [Get Started](https://orm.drizzle.team/docs/get-started/do-new) Section + +```ts +import 'dotenv/config'; +import { defineConfig } from 'drizzle-kit'; +export default defineConfig({ + out: './drizzle', + schema: './src/db/schema.ts', + dialect: 'sqlite', + driver: 'durable-sqlite', +}); +``` \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.29.1.md b/changelogs/drizzle-kit/0.29.1.md new file mode 100644 index 000000000..8e8c6d3d3 --- /dev/null +++ b/changelogs/drizzle-kit/0.29.1.md @@ -0,0 +1 @@ +- Fix SingleStore generate migrations command \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.30.0.md b/changelogs/drizzle-kit/0.30.0.md new file mode 100644 index 000000000..7accf9c9c --- /dev/null +++ b/changelogs/drizzle-kit/0.30.0.md @@ -0,0 +1,7 @@ +Starting from this update, the PostgreSQL dialect will align with the behavior of all other dialects. It will no longer include `IF NOT EXISTS`, `$DO`, or similar statements, which could cause incorrect DDL statements to not fail when an object already exists in the database and should actually fail. + +This change marks our first step toward several major upgrades we are preparing: + +- An updated and improved migration workflow featuring commutative migrations, a revised folder structure, and enhanced collaboration capabilities for migrations. +- Better support for Xata migrations. +- Compatibility with CockroachDB (achieving full compatibility will only require removing serial fields from the migration folder). \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.30.1.md b/changelogs/drizzle-kit/0.30.1.md new file mode 100644 index 000000000..1b671312d --- /dev/null +++ b/changelogs/drizzle-kit/0.30.1.md @@ -0,0 +1,35 @@ +# New Features + +### `drizzle-kit export` + +To make drizzle-kit integration with other migration tools, like Atlas much easier, we've prepared a new command called `export`. It will translate your drizzle schema in SQL representation(DDL) statements and outputs to the console + +```ts +// schema.ts +import { pgTable, serial, text } from 'drizzle-orm/pg-core' + +export const users = pgTable('users', { + id: serial('id').primaryKey(), + email: text('email').notNull(), + name: text('name') +}); +``` +Running +```bash +npx drizzle-kit export +``` + +will output this string to console +```bash +CREATE TABLE "users" ( + "id" serial PRIMARY KEY NOT NULL, + "email" text NOT NULL, + "name" text +); +``` + +By default, the only option for now is `--sql`, so the output format will be SQL DDL statements. In the future, we will support additional output formats to accommodate more migration tools + +```bash +npx drizzle-kit export --sql +``` \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.30.2.md b/changelogs/drizzle-kit/0.30.2.md new file mode 100644 index 000000000..8db1c961a --- /dev/null +++ b/changelogs/drizzle-kit/0.30.2.md @@ -0,0 +1 @@ +- Fix certificates generation utility for Drizzle Studio; [[BUG]: [drizzle-kit]: drizzle-kit dependency on drizzle-studio perms error](https://github.com/drizzle-team/drizzle-orm/issues/3729) \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.29.5.md b/changelogs/drizzle-orm/0.29.5.md index 9ef656b57..74748caad 100644 --- a/changelogs/drizzle-orm/0.29.5.md +++ b/changelogs/drizzle-orm/0.29.5.md @@ -67,7 +67,7 @@ await migrate(db, { }); ``` -### 🎉 SQLite Proxy bacth and Relational Queries support +### 🎉 SQLite Proxy batch and Relational Queries support - You can now use `.query.findFirst` and `.query.findMany` syntax with sqlite proxy driver diff --git a/changelogs/drizzle-orm/0.32.2.md b/changelogs/drizzle-orm/0.32.2.md new file mode 100644 index 000000000..9ce68473c --- /dev/null +++ b/changelogs/drizzle-orm/0.32.2.md @@ -0,0 +1,4 @@ +- Fix AWS Data API type hints bugs in RQB +- Fix set transactions in MySQL bug - thanks @roguesherlock +- Add forwaring dependencies within useLiveQuery, fixes [#2651](https://github.com/drizzle-team/drizzle-orm/issues/2651) - thanks @anstapol +- Export additional types from SQLite package, like `AnySQLiteUpdate` - thanks @veloii \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.33.0.md b/changelogs/drizzle-orm/0.33.0.md new file mode 100644 index 000000000..0093c9d05 --- /dev/null +++ b/changelogs/drizzle-orm/0.33.0.md @@ -0,0 +1,64 @@ +## Breaking changes (for some of postgres.js users) + +#### Bugs fixed for this breaking change + +- [Open +[BUG]: jsonb always inserted as a json string when using postgres-js](https://github.com/drizzle-team/drizzle-orm/issues/724) +- [[BUG]: jsonb type on postgres implement incorrectly](https://github.com/drizzle-team/drizzle-orm/issues/1511) + +> As we are doing with other drivers, we've changed the behavior of PostgreSQL-JS to pass raw JSON values, the same as you see them in the database. So if you are using the PostgreSQL-JS driver and passing data to Drizzle elsewhere, please check the new behavior of the client after it is passed to Drizzle. + +> We will update it to ensure it does not override driver behaviors, but this will be done as a complex task for everything in Drizzle in other releases + +If you were using `postgres-js` with `jsonb` fields, you might have seen stringified objects in your database, while drizzle insert and select operations were working as expected. + +You need to convert those fields from strings to actual JSON objects. To do this, you can use the following query to update your database: + +**if you are using jsonb:** +```sql +update table_name +set jsonb_column = (jsonb_column #>> '{}')::jsonb; +``` + +**if you are using json:** +```sql +update table_name +set json_column = (json_column #>> '{}')::json; +``` + +We've tested it in several cases, and it worked well, but only if all stringified objects are arrays or objects. If you have primitives like strings, numbers, booleans, etc., you can use this query to update all the fields + +**if you are using jsonb:** +```sql +UPDATE table_name +SET jsonb_column = CASE + -- Convert to JSONB if it is a valid JSON object or array + WHEN jsonb_column #>> '{}' LIKE '{%' OR jsonb_column #>> '{}' LIKE '[%' THEN + (jsonb_column #>> '{}')::jsonb + ELSE + jsonb_column +END +WHERE + jsonb_column IS NOT NULL; +``` + +**if you are using json:** +```sql +UPDATE table_name +SET json_column = CASE + -- Convert to JSON if it is a valid JSON object or array + WHEN json_column #>> '{}' LIKE '{%' OR json_column #>> '{}' LIKE '[%' THEN + (json_column #>> '{}')::json + ELSE + json_column +END +WHERE json_column IS NOT NULL; +``` + +If nothing works for you and you are blocked, please reach out to me @AndriiSherman. I will try to help you! + +## Bug Fixes + +- [[BUG]: boolean mode not working with prepared statements (bettersqlite)](https://github.com/drizzle-team/drizzle-orm/issues/2568) - thanks @veloii +- [[BUG]: isTable helper function is not working](https://github.com/drizzle-team/drizzle-orm/issues/2672) - thanks @hajek-raven +- [[BUG]: Documentation is outdated on inArray and notInArray Methods](https://github.com/drizzle-team/drizzle-orm/issues/2690) - thanks @RemiPeruto \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.34.0.md b/changelogs/drizzle-orm/0.34.0.md new file mode 100644 index 000000000..cfc3e4e38 --- /dev/null +++ b/changelogs/drizzle-orm/0.34.0.md @@ -0,0 +1,345 @@ +## Breaking changes and migrate guide for Turso users + +If you are using Turso and libsql, you will need to upgrade your `drizzle.config` and `@libsql/client` package. + +1. This version of drizzle-orm will only work with `@libsql/client@0.10.0` or higher if you are using the `migrate` function. For other use cases, you can continue using previous versions(But the suggestion is to upgrade) +To install the latest version, use the command: + +```bash +npm i @libsql/client@latest +``` + +2. Previously, we had a common `drizzle.config` for SQLite and Turso users, which allowed a shared strategy for both dialects. Starting with this release, we are introducing the turso dialect in drizzle-kit. We will evolve and improve Turso as a separate dialect with its own migration strategies. + +**Before** + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "sqlite", + schema: "./schema.ts", + out: "./drizzle", + dbCredentials: { + url: "database.db", + }, + breakpoints: true, + verbose: true, + strict: true, +}); +``` + +**After** + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "turso", + schema: "./schema.ts", + out: "./drizzle", + dbCredentials: { + url: "database.db", + }, + breakpoints: true, + verbose: true, + strict: true, +}); +``` + +If you are using only SQLite, you can use `dialect: "sqlite"` + +## LibSQL/Turso and Sqlite migration updates + +### SQLite "generate" and "push" statements updates + +Starting from this release, we will no longer generate comments like this: + +```sql + '/*\n SQLite does not support "Changing existing column type" out of the box, we do not generate automatic migration for that, so it has to be done manually' + + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' + + '\n https://www.sqlite.org/lang_altertable.html' + + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' + + "\n\n Due to that we don't generate migration automatically and it has to be done manually" + + '\n*/' +``` + +We will generate a set of statements, and you can decide if it's appropriate to create data-moving statements instead. Here is an example of the SQL file you'll receive now: + +```sql +PRAGMA foreign_keys=OFF; +--> statement-breakpoint +CREATE TABLE `__new_worker` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `salary` text NOT NULL, + `job_id` integer, + FOREIGN KEY (`job_id`) REFERENCES `job`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_worker`("id", "name", "salary", "job_id") SELECT "id", "name", "salary", "job_id" FROM `worker`; +--> statement-breakpoint +DROP TABLE `worker`; +--> statement-breakpoint +ALTER TABLE `__new_worker` RENAME TO `worker`; +--> statement-breakpoint +PRAGMA foreign_keys=ON; +``` + +### LibSQL/Turso "generate" and "push" statements updates + +Since LibSQL supports more ALTER statements than SQLite, we can generate more statements without recreating your schema and moving all the data, which can be potentially dangerous for production environments. + +LibSQL and Turso will now have a separate dialect in the Drizzle config file, meaning that we will evolve Turso and LibSQL independently from SQLite and will aim to support as many features as Turso/LibSQL offer. + +With the updated LibSQL migration strategy, you will have the ability to: + +- **Change Data Type**: Set a new data type for existing columns. +- **Set and Drop Default Values**: Add or remove default values for existing columns. +- **Set and Drop NOT NULL**: Add or remove the NOT NULL constraint on existing columns. +- **Add References to Existing Columns**: Add foreign key references to existing columns + +You can find more information in the [LibSQL documentation](https://github.com/tursodatabase/libsql/blob/main/libsql-sqlite3/doc/libsql_extensions.md#altering-columns) + +### LIMITATIONS + +- Dropping or altering an index will cause table recreation. + +This is because LibSQL/Turso does not support dropping this type of index. + +```sql +CREATE TABLE `users` ( + `id` integer NOT NULL, + `name` integer, + `age` integer PRIMARY KEY NOT NULL + FOREIGN KEY (`name`) REFERENCES `users1`("id") ON UPDATE no action ON DELETE no action +); +``` + +- If the table has indexes, altering columns will cause table recreation. +- Drizzle-Kit will drop the indexes, modify the columns, and then recreate the indexes. +- Adding or dropping composite foreign keys is not supported and will cause table recreation + +### NOTES + +- You can create a reference on any column type, but if you want to insert values, the referenced column must have a unique index or primary key. + +```sql +CREATE TABLE parent(a PRIMARY KEY, b UNIQUE, c, d, e, f); +CREATE UNIQUE INDEX i1 ON parent(c, d); +CREATE INDEX i2 ON parent(e); +CREATE UNIQUE INDEX i3 ON parent(f COLLATE nocase); + +CREATE TABLE child1(f, g REFERENCES parent(a)); -- Ok +CREATE TABLE child2(h, i REFERENCES parent(b)); -- Ok +CREATE TABLE child3(j, k, FOREIGN KEY(j, k) REFERENCES parent(c, d)); -- Ok +CREATE TABLE child4(l, m REFERENCES parent(e)); -- Error! +CREATE TABLE child5(n, o REFERENCES parent(f)); -- Error! +CREATE TABLE child6(p, q, FOREIGN KEY(p, q) REFERENCES parent(b, c)); -- Error! +CREATE TABLE child7(r REFERENCES parent(c)); -- Error! +``` + +> **NOTE**: The foreign key for the table child5 is an error because, although the parent key column has a unique index, the index uses a different collating sequence. + +See more: https://www.sqlite.org/foreignkeys.html + +## A new and easy way to start using drizzle + +Current and the only way to do, is to define client yourself and pass it to drizzle + +```ts +const client = new Pool({ url: '' }); +drizzle(client, { logger: true }); +``` + +But we want to introduce you to a new API, which is a simplified method in addition to the existing one. + +Most clients will have a few options to connect, starting with the easiest and most common one, and allowing you to control your client connection as needed. + +Let's use `node-postgres` as an example, but the same pattern can be applied to all other clients + +```ts +// Finally, one import for all available clients and dialects! +import { drizzle } from 'drizzle-orm' + +// Choose a client and use a connection URL — nothing else is needed! +const db1 = await drizzle("node-postgres", process.env.POSTGRES_URL); + +// If you need to pass a logger, schema, or other configurations, you can use an object and specify the client-specific URL in the connection +const db2 = await drizzle("node-postgres", { + connection: process.env.POSTGRES_URL, + logger: true +}); + +// And finally, if you need to use full client/driver-specific types in connections, you can use a URL or host/port/etc. as an object inferred from the underlying client connection types +const db3 = await drizzle("node-postgres", { + connection: { + connectionString: process.env.POSTGRES_URL, + }, +}); + +const db4 = await drizzle("node-postgres", { + connection: { + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + database: process.env.DB_NAME, + ssl: true, + }, +}); +``` + +A few clients will have a slightly different API due to their specific behavior. Let's take a look at them: + +For `aws-data-api-pg`, Drizzle will require `resourceArn`, `database`, and `secretArn`, along with any other AWS Data API client types for the connection, such as credentials, region, etc. + +```ts +drizzle("aws-data-api-pg", { + connection: { + resourceArn: "", + database: "", + secretArn: "", + }, +}); +``` + +For `d1`, the CloudFlare Worker types as described in the [documentation](https://developers.cloudflare.com/d1/get-started/) here will be required. + +```ts +drizzle("d1", { + connection: env.DB // CloudFlare Worker Types +}) +``` + +For `vercel-postgres`, nothing is needed since Vercel automatically retrieves the `POSTGRES_URL` from the `.env` file. You can check this [documentation](https://vercel.com/docs/storage/vercel-postgres/quickstart) for more info + +```ts +drizzle("vercel-postgres") +``` + +> Note that the first example with the client is still available and not deprecated. You can use it if you don't want to await the drizzle object. The new way of defining drizzle is designed to make it easier to import from one place and get autocomplete for all the available clients + +## Optional names for columns and callback in drizzle table + +We believe that schema definition in Drizzle is extremely powerful and aims to be as close to SQL as possible while adding more helper functions for JS runtime values. + +However, there are a few areas that could be improved, which we addressed in this release. These include: + +- Unnecessary database column names when TypeScript keys are essentially just copies of them +- A callback that provides all column types available for a specific table. + +Let's look at an example with PostgreSQL (this applies to all the dialects supported by Drizzle) + +**Previously** +```ts +import { boolean, pgTable, text, uuid } from "drizzle-orm/pg-core"; + +export const ingredients = pgTable("ingredients", { + id: uuid("id").defaultRandom().primaryKey(), + name: text("name").notNull(), + description: text("description"), + inStock: boolean("in_stock").default(true), +}); +``` + +The previous table definition will still be valid in the new release, but it can be replaced with this instead + +```ts +import { pgTable } from "drizzle-orm/pg-core"; + +export const ingredients = pgTable("ingredients", (t) => ({ + id: t.uuid().defaultRandom().primaryKey(), + name: t.text().notNull(), + description: t.text(), + inStock: t.boolean("in_stock").default(true), +})); +``` + +## New `casing` param in `drizzle-orm` and `drizzle-kit` + +There are more improvements you can make to your schema definition. The most common way to name your variables in a database and in TypeScript code is usually `snake_case` in the database and `camelCase` in the code. For this case, in Drizzle, you can now define a naming strategy in your database to help Drizzle map column keys automatically. Let's take a table from the previous example and make it work with the new casing API in Drizzle + +Table can now become: +```ts +import { pgTable } from "drizzle-orm/pg-core"; + +export const ingredients = pgTable("ingredients", (t) => ({ + id: t.uuid().defaultRandom().primaryKey(), + name: t.text().notNull(), + description: t.text(), + inStock: t.boolean().default(true), +})); +``` +As you can see, `inStock` doesn't have a database name alias, but by defining the casing configuration at the connection level, all queries will automatically map it to `snake_case` + +```ts +const db = await drizzle('node-postgres', { connection: '', casing: 'snake_case' }) +``` + +For `drizzle-kit` migrations generation you should also specify `casing` param in drizzle config, so you can be sure you casing strategy will be applied to drizzle-kit as well + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "postgresql", + schema: "./schema.ts", + dbCredentials: { + url: "postgresql://postgres:password@localhost:5432/db", + }, + casing: "snake_case", +}); +``` + +## New "count" API + +Before this release to count entities in a table, you would need to do this: + +```ts +const res = await db.select({ count: sql`count(*)` }).from(users); +const count = res[0].count; +``` + +The new API will look like this: + +```ts +// how many users are in the database +const count: number = await db.$count(users); + +// how many users with the name "Dan" are in the database +const count: number = await db.$count(users, eq(name, "Dan")); +``` + +This can also work as a subquery and within relational queries + +```ts +const users = await db.select({ + ...users, + postsCount: db.$count(posts, eq(posts.authorId, users.id)) +}); + +const users = await db.query.users.findMany({ + extras: { + postsCount: db.$count(posts, eq(posts.authorId, users.id)) + } +}) +``` + +## Ability to execute raw strings instead of using SQL templates for raw queries + +Previously, you would have needed to do this to execute a raw query with Drizzle + +```ts +import { sql } from 'drizzle-orm' + +db.execute(sql`select * from ${users}`); +// or +db.execute(sql.raw(`select * from ${users}`)); +``` + +You can now do this as well + +```ts +db.execute('select * from users') +``` diff --git a/changelogs/drizzle-orm/0.34.1.md b/changelogs/drizzle-orm/0.34.1.md new file mode 100644 index 000000000..e314b5fd2 --- /dev/null +++ b/changelogs/drizzle-orm/0.34.1.md @@ -0,0 +1 @@ +- Fixed dynamic imports for CJS and MJS in the `/connect` module \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.35.0.md b/changelogs/drizzle-orm/0.35.0.md new file mode 100644 index 000000000..92bafdff1 --- /dev/null +++ b/changelogs/drizzle-orm/0.35.0.md @@ -0,0 +1,85 @@ +# Important change after 0.34.0 release + +## Updated the init Drizzle database API + +The API from version 0.34.0 turned out to be unusable and needs to be changed. You can read more about our decisions in [this discussion](https://github.com/drizzle-team/drizzle-orm/discussions/3097) + +If you still want to use the new API introduced in 0.34.0, which can create driver clients for you under the hood, you can now do so +```ts +import { drizzle } from "drizzle-orm/node-postgres"; + +const db = drizzle(process.env.DATABASE_URL); +// or +const db = drizzle({ + connection: process.env.DATABASE_URL +}); +const db = drizzle({ + connection: { + user: "...", + password: "...", + host: "...", + port: 4321, + db: "...", + }, +}); + +// if you need to pass logger or schema +const db = drizzle({ + connection: process.env.DATABASE_URL, + logger: true, + schema: schema, +}); +``` + +in order to not introduce breaking change - we will still leave support for deprecated API until V1 release. +It will degrade autocomplete performance in connection params due to `DatabaseDriver` | `ConnectionParams` types collision, +but that's a decent compromise against breaking changes + +```ts +import { drizzle } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; + +const client = new Pool({ connectionString: process.env.DATABASE_URL }); +const db = drizzle(client); // deprecated but available + +// new version +const db = drizzle({ + client: client, +}); +``` + +# New Features + +## New .orderBy() and .limit() functions in update and delete statements SQLite and MySQL + +You now have more options for the `update` and `delete` query builders in MySQL and SQLite + +**Example** + +```ts +await db.update(usersTable).set({ verified: true }).limit(2).orderBy(asc(usersTable.name)); + +await db.delete(usersTable).where(eq(usersTable.verified, false)).limit(1).orderBy(asc(usersTable.name)); +``` + +## New `drizzle.mock()` function + +There were cases where you didn't need to provide a driver to the Drizzle object, and this served as a workaround +```ts +const db = drizzle({} as any) +``` + +Now you can do this using a mock function +```ts +const db = drizzle.mock() +``` + +There is no valid production use case for this, but we used it in situations where we needed to check types, etc., without making actual database calls or dealing with driver creation. If anyone was using it, please switch to using mocks now + +# Internal updates + +- Upgraded TS in codebase to the version 5.6.3 + +# Bug fixes + +- [[BUG]: New $count API error with @neondatabase/serverless](https://github.com/drizzle-team/drizzle-orm/issues/3081) \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.35.1.md b/changelogs/drizzle-orm/0.35.1.md new file mode 100644 index 000000000..59e29de14 --- /dev/null +++ b/changelogs/drizzle-orm/0.35.1.md @@ -0,0 +1 @@ +- Updated internal versions for the drizzle-kit and drizzle-orm packages. Changes were introduced in the last minor release, and you are required to upgrade both packages to ensure they work as expected \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.35.2.md b/changelogs/drizzle-orm/0.35.2.md new file mode 100644 index 000000000..1df586618 --- /dev/null +++ b/changelogs/drizzle-orm/0.35.2.md @@ -0,0 +1,6 @@ +- Fix issues with importing in several environments after updating the Drizzle driver implementation + +We've added approximately 240 tests to check the ESM and CJS builds for all the drivers we have. You can check them [here](https://github.com/drizzle-team/drizzle-orm/tree/main/integration-tests/js-tests/driver-init) + +- Fixed [[BUG]: Type Error in PgTransaction Missing $client Property After Upgrading to drizzle-orm@0.35.1](https://github.com/drizzle-team/drizzle-orm/issues/3140) +- Fixed [[BUG]: New critical Build error drizzle 0.35.0 deploying on Cloudflare ](https://github.com/drizzle-team/drizzle-orm/issues/3137) \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.35.3.md b/changelogs/drizzle-orm/0.35.3.md new file mode 100644 index 000000000..b25658e93 --- /dev/null +++ b/changelogs/drizzle-orm/0.35.3.md @@ -0,0 +1,80 @@ +# New LibSQL driver modules + +Drizzle now has native support for all `@libsql/client` driver variations: + +1. `@libsql/client` - defaults to node import, automatically changes to web if target or platform is set for bundler, e.g. `esbuild --platform=browser` + +```ts +import { drizzle } from 'drizzle-orm/libsql'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +2. `@libsql/client/node` node compatible module, supports :memory:, file, wss, http and turso connection protocols + +```ts +import { drizzle } from 'drizzle-orm/libsql/node'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +3. `@libsql/client/web` module for fullstack web frameworks like next, nuxt, astro, etc. + +```ts +import { drizzle } from 'drizzle-orm/libsql/web'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +4. `@libsql/client/http` module for http and https connection protocols + +```ts +import { drizzle } from 'drizzle-orm/libsql/http'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +5. `@libsql/client/ws` module for ws and wss connection protocols + +```ts +import { drizzle } from 'drizzle-orm/libsql/ws'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +6. `@libsql/client/sqlite3` module for :memory: and file connection protocols + +```ts +import { drizzle } from 'drizzle-orm/libsql/wasm'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +7. `@libsql/client-wasm` Separate experimental package for WASM + +```ts +import { drizzle } from 'drizzle-orm/libsql'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` diff --git a/changelogs/drizzle-orm/0.36.0.md b/changelogs/drizzle-orm/0.36.0.md new file mode 100644 index 000000000..0f01b6e91 --- /dev/null +++ b/changelogs/drizzle-orm/0.36.0.md @@ -0,0 +1,419 @@ +> This version of `drizzle-orm` requires `drizzle-kit@0.27.0` to enable all new features + +# New Features + +## Row-Level Security (RLS) + +With Drizzle, you can enable Row-Level Security (RLS) for any Postgres table, create policies with various options, and define and manage the roles those policies apply to. + +Drizzle supports a raw representation of Postgres policies and roles that can be used in any way you want. This works with popular Postgres database providers such as `Neon` and `Supabase`. + +In Drizzle, we have specific predefined RLS roles and functions for RLS with both database providers, but you can also define your own logic. + +### Enable RLS + +If you just want to enable RLS on a table without adding policies, you can use `.enableRLS()` + +As mentioned in the PostgreSQL documentation: + +> If no policy exists for the table, a default-deny policy is used, meaning that no rows are visible or can be modified. +Operations that apply to the whole table, such as TRUNCATE and REFERENCES, are not subject to row security. + +```ts +import { integer, pgTable } from 'drizzle-orm/pg-core'; + +export const users = pgTable('users', { + id: integer(), +}).enableRLS(); +``` + +> If you add a policy to a table, RLS will be enabled automatically. So, there’s no need to explicitly enable RLS when adding policies to a table. + +### Roles + +Currently, Drizzle supports defining roles with a few different options, as shown below. Support for more options will be added in a future release. + +```ts +import { pgRole } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin', { createRole: true, createDb: true, inherit: true }); +``` + +If a role already exists in your database, and you don’t want drizzle-kit to ‘see’ it or include it in migrations, you can mark the role as existing. + +```ts +import { pgRole } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin').existing(); +``` + +### Policies + +To fully leverage RLS, you can define policies within a Drizzle table. + +> In PostgreSQL, policies should be linked to an existing table. Since policies are always associated with a specific table, we decided that policy definitions should be defined as a parameter of `pgTable` + +**Example of pgPolicy with all available properties** +```ts +import { sql } from 'drizzle-orm'; +import { integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + pgPolicy('policy', { + as: 'permissive', + to: admin, + for: 'delete', + using: sql``, + withCheck: sql``, + }), +]); +``` + +**Link Policy to an existing table** + +There are situations where you need to link a policy to an existing table in your database. +The most common use case is with database providers like `Neon` or `Supabase`, where you need to add a policy +to their existing tables. In this case, you can use the `.link()` API + +```ts +import { sql } from "drizzle-orm"; +import { pgPolicy } from "drizzle-orm/pg-core"; +import { authenticatedRole, realtimeMessages } from "drizzle-orm/supabase"; + +export const policy = pgPolicy("authenticated role insert policy", { + for: "insert", + to: authenticatedRole, + using: sql``, +}).link(realtimeMessages); +``` + +### Migrations + +If you are using drizzle-kit to manage your schema and roles, there may be situations where you want to refer to roles that are not defined in your Drizzle schema. In such cases, you may want drizzle-kit to skip managing these roles without having to define each role in your drizzle schema and marking it with `.existing()`. + +In these cases, you can use `entities.roles` in `drizzle.config.ts`. For a complete reference, refer to the the [`drizzle.config.ts`](https://orm.drizzle.team/docs/drizzle-config-file) documentation. + +By default, `drizzle-kit` does not manage roles for you, so you will need to enable this feature in `drizzle.config.ts`. + +```ts {12-14} +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: 'postgresql', + schema: "./drizzle/schema.ts", + dbCredentials: { + url: process.env.DATABASE_URL! + }, + verbose: true, + strict: true, + entities: { + roles: true + } +}); +``` + +In case you need additional configuration options, let's take a look at a few more examples. + +**You have an `admin` role and want to exclude it from the list of manageable roles** + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + exclude: ['admin'] + } + } +}); +``` + +**You have an `admin` role and want to include it in the list of manageable roles** + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + include: ['admin'] + } + } +}); +``` + +**If you are using `Neon` and want to exclude Neon-defined roles, you can use the provider option** + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + provider: 'neon' + } + } +}); +``` + +**If you are using `Supabase` and want to exclude Supabase-defined roles, you can use the provider option** + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + provider: 'supabase' + } + } +}); +``` + +> You may encounter situations where Drizzle is slightly outdated compared to new roles specified by your database provider. +In such cases, you can use the `provider` option and `exclude` additional roles: + +```ts +// drizzle.config.ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + ... + entities: { + roles: { + provider: 'supabase', + exclude: ['new_supabase_role'] + } + } +}); +``` + +### RLS on views + +With Drizzle, you can also specify RLS policies on views. For this, you need to use `security_invoker` in the view's WITH options. Here is a small example: + +```ts {5} +... + +export const roomsUsersProfiles = pgView("rooms_users_profiles") + .with({ + securityInvoker: true, + }) + .as((qb) => + qb + .select({ + ...getTableColumns(roomsUsers), + email: profiles.email, + }) + .from(roomsUsers) + .innerJoin(profiles, eq(roomsUsers.userId, profiles.id)) + ); +``` + +### Using with Neon + +The Neon Team helped us implement their vision of a wrapper on top of our raw policies API. We defined a specific +`/neon` import with the `crudPolicy` function that includes predefined functions and Neon's default roles. + +Here's an example of how to use the `crudPolicy` function: + +```ts +import { crudPolicy } from 'drizzle-orm/neon'; +import { integer, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + crudPolicy({ role: admin, read: true, modify: false }), +]); +``` + +This policy is equivalent to: + +```ts +import { sql } from 'drizzle-orm'; +import { integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + pgPolicy(`crud-${admin.name}-policy-insert`, { + for: 'insert', + to: admin, + withCheck: sql`false`, + }), + pgPolicy(`crud-${admin.name}-policy-update`, { + for: 'update', + to: admin, + using: sql`false`, + withCheck: sql`false`, + }), + pgPolicy(`crud-${admin.name}-policy-delete`, { + for: 'delete', + to: admin, + using: sql`false`, + }), + pgPolicy(`crud-${admin.name}-policy-select`, { + for: 'select', + to: admin, + using: sql`true`, + }), +]); +``` + +`Neon` exposes predefined `authenticated` and `anaonymous` roles and related functions. If you are using `Neon` for RLS, you can use these roles, which are marked as existing, and the related functions in your RLS queries. + +```ts +// drizzle-orm/neon +export const authenticatedRole = pgRole('authenticated').existing(); +export const anonymousRole = pgRole('anonymous').existing(); + +export const authUid = (userIdColumn: AnyPgColumn) => sql`(select auth.user_id() = ${userIdColumn})`; +``` + +For example, you can use the `Neon` predefined roles and functions like this: + + +```ts +import { sql } from 'drizzle-orm'; +import { authenticatedRole } from 'drizzle-orm/neon'; +import { integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + pgPolicy(`policy-insert`, { + for: 'insert', + to: authenticatedRole, + withCheck: sql`false`, + }), +]); +``` + +### Using with Supabase + +We also have a `/supabase` import with a set of predefined roles marked as existing, which you can use in your schema. +This import will be extended in a future release with more functions and helpers to make using RLS and `Supabase` simpler. + +```ts +// drizzle-orm/supabase +export const anonRole = pgRole('anon').existing(); +export const authenticatedRole = pgRole('authenticated').existing(); +export const serviceRole = pgRole('service_role').existing(); +export const postgresRole = pgRole('postgres_role').existing(); +export const supabaseAuthAdminRole = pgRole('supabase_auth_admin').existing(); +``` + +For example, you can use the `Supabase` predefined roles like this: + +```ts +import { sql } from 'drizzle-orm'; +import { serviceRole } from 'drizzle-orm/supabase'; +import { integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; + +export const admin = pgRole('admin'); + +export const users = pgTable('users', { + id: integer(), +}, (t) => [ + pgPolicy(`policy-insert`, { + for: 'insert', + to: serviceRole, + withCheck: sql`false`, + }), +]); +``` + +The `/supabase` import also includes predefined tables and functions that you can use in your application + +```ts +// drizzle-orm/supabase + +const auth = pgSchema('auth'); +export const authUsers = auth.table('users', { + id: uuid().primaryKey().notNull(), +}); + +const realtime = pgSchema('realtime'); +export const realtimeMessages = realtime.table( + 'messages', + { + id: bigserial({ mode: 'bigint' }).primaryKey(), + topic: text().notNull(), + extension: text({ + enum: ['presence', 'broadcast', 'postgres_changes'], + }).notNull(), + }, +); + +export const authUid = sql`(select auth.uid())`; +export const realtimeTopic = sql`realtime.topic()`; +``` + +This allows you to use it in your code, and Drizzle Kit will treat them as existing databases, +using them only as information to connect to other entities + +```ts +import { foreignKey, pgPolicy, pgTable, text, uuid } from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm/sql"; +import { authenticatedRole, authUsers } from "drizzle-orm/supabase"; + +export const profiles = pgTable( + "profiles", + { + id: uuid().primaryKey().notNull(), + email: text().notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.id], + // reference to the auth table from Supabase + foreignColumns: [authUsers.id], + name: "profiles_id_fk", + }).onDelete("cascade"), + pgPolicy("authenticated can view all profiles", { + for: "select", + // using predefined role from Supabase + to: authenticatedRole, + using: sql`true`, + }), + ] +); +``` + +Let's check an example of adding a policy to a table that exists in `Supabase` + +```ts +import { sql } from "drizzle-orm"; +import { pgPolicy } from "drizzle-orm/pg-core"; +import { authenticatedRole, realtimeMessages } from "drizzle-orm/supabase"; + +export const policy = pgPolicy("authenticated role insert policy", { + for: "insert", + to: authenticatedRole, + using: sql``, +}).link(realtimeMessages); +``` + +# Bug fixes + +- [[BUG]: postgres-js driver throws error when using new { client } constructor arguments ](https://github.com/drizzle-team/drizzle-orm/issues/3176) \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.36.1.md b/changelogs/drizzle-orm/0.36.1.md new file mode 100644 index 000000000..74d256a13 --- /dev/null +++ b/changelogs/drizzle-orm/0.36.1.md @@ -0,0 +1,6 @@ +# Bug Fixes + +- [[BUG]: Using sql.placeholder with limit and/or offset for a prepared statement produces TS error](https://github.com/drizzle-team/drizzle-orm/issues/2146) - thanks @L-Mario564 +- [[BUG] If a query I am trying to modify with a dynamic query (....$dynamic()) contains any placeholders, I'm getting an error that says No value for placeholder.... provided](https://github.com/drizzle-team/drizzle-orm/issues/2272) - thanks @L-Mario564 +- [[BUG]: Error thrown when trying to insert an array of new rows using generatedAlwaysAsIdentity() for the id column](https://github.com/drizzle-team/drizzle-orm/issues/2849) - thanks @L-Mario564 +- [[BUG]: Unable to Use BigInt Types with Bun and Drizzle](https://github.com/drizzle-team/drizzle-orm/issues/2603) - thanks @L-Mario564 diff --git a/changelogs/drizzle-orm/0.36.2.md b/changelogs/drizzle-orm/0.36.2.md new file mode 100644 index 000000000..3790e0944 --- /dev/null +++ b/changelogs/drizzle-orm/0.36.2.md @@ -0,0 +1,15 @@ +# New Features + +- [Support more types in like, notLike, ilike and notIlike expressions](https://github.com/drizzle-team/drizzle-orm/pull/2805) + + +# Bug and typo fixes + +- Fixed typos in repository: thanks @armandsalle, @masto, @wackbyte, @Asher-JH, @MaxLeiter + +- [Fixed .generated behavior with non-strict tsconfig](https://github.com/drizzle-team/drizzle-orm/pull/3542) +- [Fix Drizzle ORM for expo-sqlite](https://github.com/drizzle-team/drizzle-orm/pull/3197) +- [Fixed lack of schema name on columns in sql](https://github.com/drizzle-team/drizzle-orm/pull/3531) +- [fix: Adjust neon http driver entity kind](https://github.com/drizzle-team/drizzle-orm/pull/3424) +- [Export PgIntegerBuilderInitial type](https://github.com/drizzle-team/drizzle-orm/pull/2846) +- [[MySQL] Correct $returningId() implementation to correctly store selected fields](https://github.com/drizzle-team/drizzle-orm/pull/2975) \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.36.3.md b/changelogs/drizzle-orm/0.36.3.md new file mode 100644 index 000000000..4602f348e --- /dev/null +++ b/changelogs/drizzle-orm/0.36.3.md @@ -0,0 +1,154 @@ +# New Features + +## Support for `UPDATE ... FROM` in PostgreSQL and SQLite + +As the SQLite documentation mentions: + +> [!NOTE] +> The UPDATE-FROM idea is an extension to SQL that allows an UPDATE statement to be driven by other tables in the database. +The "target" table is the specific table that is being updated. With UPDATE-FROM you can join the target table +against other tables in the database in order to help compute which rows need updating and what +the new values should be on those rows + +Similarly, the PostgreSQL documentation states: + +> [!NOTE] +> A table expression allowing columns from other tables to appear in the WHERE condition and update expressions + +Drizzle also supports this feature starting from this version + +For example, current query: + +```ts +await db + .update(users) + .set({ cityId: cities.id }) + .from(cities) + .where(and(eq(cities.name, 'Seattle'), eq(users.name, 'John'))) +``` + +Will generate this sql + +```sql +update "users" set "city_id" = "cities"."id" +from "cities" +where ("cities"."name" = $1 and "users"."name" = $2) + +-- params: [ 'Seattle', 'John' ] +``` + +You can also alias tables that are joined (in PG, you can also alias the updating table too). + +```ts +const c = alias(cities, 'c'); +await db + .update(users) + .set({ cityId: c.id }) + .from(c); +``` + +Will generate this sql + +```sql +update "users" set "city_id" = "c"."id" +from "cities" "c" +``` + +In PostgreSQL, you can also return columns from the joined tables. + +```ts +const updatedUsers = await db + .update(users) + .set({ cityId: cities.id }) + .from(cities) + .returning({ id: users.id, cityName: cities.name }); +``` + +Will generate this sql + +```sql +update "users" set "city_id" = "cities"."id" +from "cities" +returning "users"."id", "cities"."name" +``` + +## Support for `INSERT INTO ... SELECT` in all dialects + +As the SQLite documentation mentions: + +> [!NOTE] +> The second form of the INSERT statement contains a SELECT statement instead of a VALUES clause. +A new entry is inserted into the table for each row of data returned by executing the SELECT statement. +If a column-list is specified, the number of columns in the result of the SELECT must be the same as +the number of items in the column-list. Otherwise, if no column-list is specified, the number of +columns in the result of the SELECT must be the same as the number of columns in the table. +Any SELECT statement, including compound SELECTs and SELECT statements with ORDER BY and/or LIMIT clauses, +may be used in an INSERT statement of this form. + +> [!CAUTION] +> To avoid a parsing ambiguity, the SELECT statement should always contain a WHERE clause, even if that clause is simply "WHERE true", if the upsert-clause is present. Without the WHERE clause, the parser does not know if the token "ON" is part of a join constraint on the SELECT, or the beginning of the upsert-clause. + +As the PostgreSQL documentation mentions: +> [!NOTE] +> A query (SELECT statement) that supplies the rows to be inserted + +And as the MySQL documentation mentions: + +> [!NOTE] +> With INSERT ... SELECT, you can quickly insert many rows into a table from the result of a SELECT statement, which can select from one or many tables + +Drizzle supports the current syntax for all dialects, and all of them share the same syntax. Let's review some common scenarios and API usage. +There are several ways to use select inside insert statements, allowing you to choose your preferred approach: + +- You can pass a query builder inside the select function. +- You can use a query builder inside a callback. +- You can pass an SQL template tag with any custom select query you want to use + +**Query Builder** + +```ts +const insertedEmployees = await db + .insert(employees) + .select( + db.select({ name: users.name }).from(users).where(eq(users.role, 'employee')) + ) + .returning({ + id: employees.id, + name: employees.name + }); +``` + +```ts +const qb = new QueryBuilder(); +await db.insert(employees).select( + qb.select({ name: users.name }).from(users).where(eq(users.role, 'employee')) +); +``` + +**Callback** + +```ts +await db.insert(employees).select( + () => db.select({ name: users.name }).from(users).where(eq(users.role, 'employee')) +); +``` + +```ts +await db.insert(employees).select( + (qb) => qb.select({ name: users.name }).from(users).where(eq(users.role, 'employee')) +); +``` + +**SQL template tag** + +```ts +await db.insert(employees).select( + sql`select "users"."name" as "name" from "users" where "users"."role" = 'employee'` +); +``` + +```ts +await db.insert(employees).select( + () => sql`select "users"."name" as "name" from "users" where "users"."role" = 'employee'` +); +``` diff --git a/changelogs/drizzle-orm/0.36.4.md b/changelogs/drizzle-orm/0.36.4.md new file mode 100644 index 000000000..056c5a01e --- /dev/null +++ b/changelogs/drizzle-orm/0.36.4.md @@ -0,0 +1,84 @@ +# New Package: `drizzle-seed` + +> [!NOTE] +> `drizzle-seed` can only be used with `drizzle-orm@0.36.4` or higher. Versions lower than this may work at runtime but could have type issues and identity column issues, as this patch was introduced in `drizzle-orm@0.36.4` + +## Full Reference + +The full API reference and package overview can be found in our [official documentation](https://orm.drizzle.team/docs/seed-overview) + +## Basic Usage + +In this example we will create 10 users with random names and ids + +```ts {12} +import { pgTable, integer, text } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { seed } from "drizzle-seed"; + +const users = pgTable("users", { + id: integer().primaryKey(), + name: text().notNull(), +}); + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { users }); +} + +main(); +``` + +## Options + +**`count`** + +By default, the `seed` function will create 10 entities. +However, if you need more for your tests, you can specify this in the seed options object + +```ts +await seed(db, schema, { count: 1000 }); +``` + +**`seed`** + +If you need a seed to generate a different set of values for all subsequent runs, you can define a different number +in the `seed` option. Any new number will generate a unique set of values + +```ts +await seed(db, schema, { seed: 12345 }); +``` + +The full API reference and package overview can be found in our [official documentation](https://orm.drizzle.team/docs/seed-overview) + +# Features + +## Added `OVERRIDING SYSTEM VALUE` api to db.insert() + +If you want to force you own values for `GENERATED ALWAYS AS IDENTITY` columns, you can use `OVERRIDING SYSTEM VALUE` + +As PostgreSQL docs mentions +> In an INSERT command, if ALWAYS is selected, a user-specified value is only accepted if the INSERT statement specifies OVERRIDING SYSTEM VALUE. If BY DEFAULT is selected, then the user-specified value takes precedence + +```ts +await db.insert(identityColumnsTable).overridingSystemValue().values([ + { alwaysAsIdentity: 2 }, +]); +``` + +## Added `.$withAuth()` API for Neon HTTP driver + +Using this API, Drizzle will send you an auth token to authorize your query. It can be used with any query available in Drizzle by simply adding `.$withAuth()` before it. This token will be used for a specific query + +Examples + +```ts +const token = 'HdncFj1Nm' + +await db.$withAuth(token).select().from(usersTable); +await db.$withAuth(token).update(usersTable).set({ name: 'CHANGED' }).where(eq(usersTable.name, 'TARGET')) +``` + +# Bug Fixes + +- [[BUG]: TypeScript error Please install '@neondatabase/serverless' to allow Drizzle ORM to connect to the database](https://github.com/drizzle-team/drizzle-orm/issues/3521) diff --git a/changelogs/drizzle-orm/0.37.0.md b/changelogs/drizzle-orm/0.37.0.md new file mode 100644 index 000000000..f54fa4a27 --- /dev/null +++ b/changelogs/drizzle-orm/0.37.0.md @@ -0,0 +1,98 @@ +# New Dialects + +### 🎉 `SingleStore` dialect is now available in Drizzle + +Thanks to the SingleStore team for creating a PR with all the necessary changes to support the MySQL-compatible part of SingleStore. You can already start using it with Drizzle. The SingleStore team will also help us iterate through updates and make more SingleStore-specific features available in Drizzle + +```ts +import { int, singlestoreTable, varchar } from 'drizzle-orm/singlestore-core'; +import { drizzle } from 'drizzle-orm/singlestore'; + +export const usersTable = singlestoreTable('users_table', { + id: int().primaryKey(), + name: varchar({ length: 255 }).notNull(), + age: int().notNull(), + email: varchar({ length: 255 }).notNull().unique(), +}); + +... + +const db = drizzle(process.env.DATABASE_URL!); + +db.select()... +``` + +You can check out our [Getting started guides](https://orm.drizzle.team/docs/get-started/singlestore-new) to try SingleStore! + +# New Drivers + +### 🎉 `SQLite Durable Objects` driver is now available in Drizzle + +You can now query SQLite Durable Objects in Drizzle! + +For the full example, please check our [Get Started](https://orm.drizzle.team/docs/get-started/do-new) Section + +```ts +/// +import { drizzle, DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite'; +import { DurableObject } from 'cloudflare:workers' +import { migrate } from 'drizzle-orm/durable-sqlite/migrator'; +import migrations from '../drizzle/migrations'; +import { usersTable } from './db/schema'; + +export class MyDurableObject1 extends DurableObject { + storage: DurableObjectStorage; + db: DrizzleSqliteDODatabase; + + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + this.storage = ctx.storage; + this.db = drizzle(this.storage, { logger: false }); + } + + async migrate() { + migrate(this.db, migrations); + } + + async insert(user: typeof usersTable.$inferInsert) { + await this.db.insert(usersTable).values(user); + } + + async select() { + return this.db.select().from(usersTable); + } +} + +export default { + /** + * This is the standard fetch handler for a Cloudflare Worker + * + * @param request - The request submitted to the Worker from the client + * @param env - The interface to reference bindings declared in wrangler.toml + * @param ctx - The execution context of the Worker + * @returns The response to be sent back to the client + */ + async fetch(request: Request, env: Env): Promise { + const id: DurableObjectId = env.MY_DURABLE_OBJECT1.idFromName('durable-object'); + const stub = env.MY_DURABLE_OBJECT1.get(id); + await stub.migrate(); + + await stub.insert({ + name: 'John', + age: 30, + email: 'john@example.com', + }) + console.log('New user created!') + + const users = await stub.select(); + console.log('Getting all users from the database: ', users) + + return new Response(); + } +} +``` + +# Bug fixes + +- [[BUG]: $with is undefined on withReplicas](https://github.com/drizzle-team/drizzle-orm/issues/1834) +- [[BUG]: Neon serverless driver accepts authToken as a promise, but the $withAuth does not](https://github.com/drizzle-team/drizzle-orm/issues/3597) \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.38.0.md b/changelogs/drizzle-orm/0.38.0.md new file mode 100644 index 000000000..863fe5182 --- /dev/null +++ b/changelogs/drizzle-orm/0.38.0.md @@ -0,0 +1,75 @@ +# Types breaking changes + +A few internal types were changed and extra generic types for length of column types were added in this release. It won't affect anyone, unless you are using those internal types for some custom wrappers, logic, etc. Here is a list of all types that were changed, so if you are relying on those, please review them before upgrading + +- `MySqlCharBuilderInitial` +- `MySqlVarCharBuilderInitial` +- `PgCharBuilderInitial` +- `PgArrayBuilder` +- `PgArray` +- `PgVarcharBuilderInitial` +- `PgBinaryVectorBuilderInitial` +- `PgBinaryVectorBuilder` +- `PgBinaryVector` +- `PgHalfVectorBuilderInitial` +- `PgHalfVectorBuilder` +- `PgHalfVector` +- `PgVectorBuilderInitial` +- `PgVectorBuilder` +- `PgVector` +- `SQLiteTextBuilderInitial` + +# New Features + +- Added new function `getViewSelectedFields` +- Added `$inferSelect` function to views +- Added `InferSelectViewModel` type for views +- Added `isView` function + +# Validator packages updates + +- `drizzle-zod` has been completely rewritten. You can find detailed information about it [here](https://github.com/drizzle-team/drizzle-orm/blob/main/changelogs/drizzle-zod/0.6.0.md) +- `drizzle-valibot` has been completely rewritten. You can find detailed information about it [here](https://github.com/drizzle-team/drizzle-orm/blob/main/changelogs/drizzle-valibot/0.3.0.md) +- `drizzle-typebox` has been completely rewritten. You can find detailed information about it [here](https://github.com/drizzle-team/drizzle-orm/blob/main/changelogs/drizzle-typebox/0.2.0.md) + +Thanks to @L-Mario564 for making more updates than we expected to be shipped in this release. We'll copy his message from a PR regarding improvements made in this release: + +- Output for all packages are now unminified, makes exploring the compiled code easier when published to npm. +- Smaller footprint. Previously, we imported the column types at runtime for each dialect, meaning that for example, if you're just using Postgres then you'd likely only have drizzle-orm and drizzle-orm/pg-core in the build output of your app; however, these packages imported all dialects which could lead to mysql-core and sqlite-core being bundled as well even if they're unused in your app. This is now fixed. +- Slight performance gain. To determine the column data type we used the is function which performs a few checks to ensure the column data type matches. This was slow, as these checks would pile up every quickly when comparing all data types for many fields in a table/view. The easier and faster alternative is to simply go off of the column's columnType property. +- Some changes had to be made at the type level in the ORM package for better compatibility with drizzle-valibot. + +And a set of new features + +- `createSelectSchema` function now also accepts views and enums. +- New function: `createUpdateSchema`, for use in updating queries. +- New function: `createSchemaFactory`, to provide more advanced options and to avoid bloating the parameters of the other schema functions + +# Bug fixes + +- [[FEATURE]: publish packages un-minified](https://github.com/drizzle-team/drizzle-orm/issues/2247) +- [Don't allow unknown keys in drizzle-zod refinement](https://github.com/drizzle-team/drizzle-orm/issues/573) +- [[BUG]:drizzle-zod not working with pgSchema](https://github.com/drizzle-team/drizzle-orm/issues/1458) +- [Add createUpdateSchema to drizzle-zod](https://github.com/drizzle-team/drizzle-orm/issues/503) +- [[BUG]:drizzle-zod produces wrong type](https://github.com/drizzle-team/drizzle-orm/issues/1110) +- [[BUG]:Drizzle-zod:Boolean and Serial types from Schema are defined as enum when using CreateInsertSchema and CreateSelectSchema](https://github.com/drizzle-team/drizzle-orm/issues/1327) +- [[BUG]: Drizzle typebox enum array wrong schema and type](https://github.com/drizzle-team/drizzle-orm/issues/1345) +- [[BUG]:drizzle-zod not working with pgSchema](https://github.com/drizzle-team/drizzle-orm/issues/1458) +- [[BUG]: drizzle-zod not parsing arrays correctly](https://github.com/drizzle-team/drizzle-orm/issues/1609) +- [[BUG]: Drizzle typebox not supporting array](https://github.com/drizzle-team/drizzle-orm/issues/1810) +- [[FEATURE]: Export factory functions from drizzle-zod to allow usage with extended Zod classes](https://github.com/drizzle-team/drizzle-orm/issues/2245) +- [[FEATURE]: Add support for new pipe syntax for drizzle-valibot](https://github.com/drizzle-team/drizzle-orm/issues/2358) +- [[BUG]: drizzle-zod's createInsertSchema() can't handle column of type vector](https://github.com/drizzle-team/drizzle-orm/issues/2424) +- [[BUG]: drizzle-typebox fails to map geometry column to type-box schema](https://github.com/drizzle-team/drizzle-orm/issues/2516) +- [[BUG]: drizzle-valibot does not provide types for returned schemas](https://github.com/drizzle-team/drizzle-orm/issues/2521) +- [[BUG]: Drizzle-typebox types SQLite real field to string](https://github.com/drizzle-team/drizzle-orm/issues/2524) +- [[BUG]: drizzle-zod: documented usage generates type error with exactOptionalPropertyTypes](https://github.com/drizzle-team/drizzle-orm/issues/2550) +- [[BUG]: drizzle-zod does not respect/count db type range](https://github.com/drizzle-team/drizzle-orm/issues/2737) +- [[BUG]: drizzle-zod not overriding optional](https://github.com/drizzle-team/drizzle-orm/issues/2755) +- [[BUG]:drizzle-zod doesn't accept custom id value](https://github.com/drizzle-team/drizzle-orm/issues/2957) +- [[FEATURE]: Support for Database Views in Drizzle Zod](https://github.com/drizzle-team/drizzle-orm/issues/3398) +- [[BUG]: drizzle-valibot return type any](https://github.com/drizzle-team/drizzle-orm/issues/3621) +- [[BUG]: drizzle-zod Type generation results in undefined types](https://github.com/drizzle-team/drizzle-orm/issues/3645) +- [[BUG]: GeneratedAlwaysAs](https://github.com/drizzle-team/drizzle-orm/issues/3511) +- [[FEATURE]: $inferSelect on a view](https://github.com/drizzle-team/drizzle-orm/issues/2610) +- [[BUG]:Can't infer props from view in schema](https://github.com/drizzle-team/drizzle-orm/issues/3392) diff --git a/changelogs/drizzle-orm/0.38.1.md b/changelogs/drizzle-orm/0.38.1.md new file mode 100644 index 000000000..1ee83b233 --- /dev/null +++ b/changelogs/drizzle-orm/0.38.1.md @@ -0,0 +1 @@ +- Closed [[FEATURE]: Add more flexible typing for usage with exactOptionalPropertyTypes](https://github.com/drizzle-team/drizzle-orm/issues/2742) \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.38.2.md b/changelogs/drizzle-orm/0.38.2.md new file mode 100644 index 000000000..50fb31429 --- /dev/null +++ b/changelogs/drizzle-orm/0.38.2.md @@ -0,0 +1,58 @@ +# New features + +## `USE INDEX`, `FORCE INDEX` and `IGNORE INDEX` for MySQL + +In MySQL, the statements USE INDEX, FORCE INDEX, and IGNORE INDEX are hints used in SQL queries to influence how the query optimizer selects indexes. These hints provide fine-grained control over index usage, helping optimize performance when the default behavior of the optimizer is not ideal. + +### Use Index + +The `USE INDEX` hint suggests to the optimizer which indexes to consider when processing the query. The optimizer is not forced to use these indexes but will prioritize them if they are suitable. + +```ts +export const users = mysqlTable('users', { + id: int('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), +}, () => [usersTableNameIndex]); + +const usersTableNameIndex = index('users_name_index').on(users.name); + +await db.select() + .from(users, { useIndex: usersTableNameIndex }) + .where(eq(users.name, 'David')); +``` + +### Ignore Index + +The `IGNORE INDEX` hint tells the optimizer to avoid using specific indexes for the query. MySQL will consider all other indexes (if any) or perform a full table scan if necessary. + +```ts +export const users = mysqlTable('users', { + id: int('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), +}, () => [usersTableNameIndex]); + +const usersTableNameIndex = index('users_name_index').on(users.name); + +await db.select() + .from(users, { ignoreIndex: usersTableNameIndex }) + .where(eq(users.name, 'David')); +``` + +### Force Index + +The `FORCE INDEX` hint forces the optimizer to use the specified index(es) for the query. If the specified index cannot be used, MySQL will not fall back to other indexes; it might resort to a full table scan instead. + +```ts copy +export const users = mysqlTable('users', { + id: int('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), +}, () => [usersTableNameIndex]); + +const usersTableNameIndex = index('users_name_index').on(users.name); + +await db.select() + .from(users, { forceIndex: usersTableNameIndex }) + .where(eq(users.name, 'David')); +``` + +You can also combine those hints and use multiple indexes in a query if you need \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.38.3.md b/changelogs/drizzle-orm/0.38.3.md new file mode 100644 index 000000000..3f94386b6 --- /dev/null +++ b/changelogs/drizzle-orm/0.38.3.md @@ -0,0 +1 @@ +- Fix incorrect deprecation detection for table declarations \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.38.4.md b/changelogs/drizzle-orm/0.38.4.md new file mode 100644 index 000000000..037d7809d --- /dev/null +++ b/changelogs/drizzle-orm/0.38.4.md @@ -0,0 +1,3 @@ +- New SingleStore type `vector` - thanks @mitchwadair +- Fix wrong DROP INDEX statement generation, [#3866](https://github.com/drizzle-team/drizzle-orm/pull/3866) - thanks @WaciX +- Typo fixes - thanks @stephan281094 \ No newline at end of file diff --git a/changelogs/drizzle-seed/0.1.1.md b/changelogs/drizzle-seed/0.1.1.md new file mode 100644 index 000000000..34dca67b6 --- /dev/null +++ b/changelogs/drizzle-seed/0.1.1.md @@ -0,0 +1,52 @@ +# Initial Release + +> [!NOTE] +> `drizzle-seed` can only be used with `drizzle-orm@0.36.4` or higher. Versions lower than this may work at runtime but could have type issues and identity column issues, as this patch was introduced in `drizzle-orm@0.36.4` + +## Full Reference + +The full API reference and package overview can be found in our [official documentation](https://orm.drizzle.team/docs/seed-overview) + +## Basic Usage + +In this example we will create 10 users with random names and ids + +```ts {12} +import { pgTable, integer, text } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { seed } from "drizzle-seed"; + +const users = pgTable("users", { + id: integer().primaryKey(), + name: text().notNull(), +}); + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { users }); +} + +main(); +``` + +## Options + +**`count`** + +By default, the `seed` function will create 10 entities. +However, if you need more for your tests, you can specify this in the seed options object + +```ts +await seed(db, schema, { count: 1000 }); +``` + +**`seed`** + +If you need a seed to generate a different set of values for all subsequent runs, you can define a different number +in the `seed` option. Any new number will generate a unique set of values + +```ts +await seed(db, schema, { seed: 12345 }); +``` + +The full API reference and package overview can be found in our [official documentation](https://orm.drizzle.team/docs/seed-overview) \ No newline at end of file diff --git a/changelogs/drizzle-seed/0.1.2.md b/changelogs/drizzle-seed/0.1.2.md new file mode 100644 index 000000000..4df3c89f4 --- /dev/null +++ b/changelogs/drizzle-seed/0.1.2.md @@ -0,0 +1,2 @@ +- Fixed: [[BUG]: drizzle-seed reset fails without @electric-sql/pglite installed](https://github.com/drizzle-team/drizzle-orm/issues/3603) +- Fixed: [[BUG]: TypeScript type error in drizzle-seed with schema passed to drizzle in IDE](https://github.com/drizzle-team/drizzle-orm/issues/3599) \ No newline at end of file diff --git a/changelogs/drizzle-seed/0.1.3.md b/changelogs/drizzle-seed/0.1.3.md new file mode 100644 index 000000000..d7bb7ac72 --- /dev/null +++ b/changelogs/drizzle-seed/0.1.3.md @@ -0,0 +1,131 @@ +## Bug fixes + +- https://github.com/drizzle-team/drizzle-orm/issues/3644 +- seeding a table with columns that have .default(sql``) will result in an error + +## Features + +- added support for postgres uuid columns + +Example + +```ts +import { pgTable, uuid } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { seed } from "drizzle-seed"; + +const users = pgTable("users", { + uuid: uuid("uuid"), +}); + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + // You can let it seed automatically + // await seed(db, { users }); + + // Alternatively, you can manually specify the generator in refine. + await seed(db, { users }, { count: 1000 }).refine((funcs) => ({ + users: { + columns: { + uuid: funcs.uuid(), + }, + }, + })); +} + +main(); +``` + +## + +- added support for postgres array columns + +Example + +```ts +import { pgTable, integer, text, varchar } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { seed } from "drizzle-seed"; + +const users = pgTable("users", { + id: integer().primaryKey(), + name: text().notNull(), + phone_numbers: varchar({ length: 256 }).array(), +}); +``` + +You can specify the `arraySize` parameter in generator options, like `funcs.phoneNumber({ arraySize: 3 })`, to generate 1D arrays. + +```ts +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { users }, { count: 1000 }).refine((funcs) => ({ + users: { + columns: { + phone_numbers: funcs.phoneNumber({ arraySize: 3 }), + }, + }, + })); +} + +main(); +``` + +Alternatively, you can let it seed automatically, and it will handle arrays of any dimension. + +```ts +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { users }); +} + +main(); +``` + +## + +- added support for cyclic tables + +You can now seed tables with cyclic relations. + +```ts +import type { AnyPgColumn } from "drizzle-orm/pg-core"; +import { + foreignKey, + integer, + pgTable, + serial, + varchar, +} from "drizzle-orm/pg-core"; + +export const modelTable = pgTable( + "model", + { + id: serial().primaryKey(), + name: varchar().notNull(), + defaultImageId: integer(), + }, + (t) => [ + foreignKey({ + columns: [t.defaultImageId], + foreignColumns: [modelImageTable.id], + }), + ] +); + +export const modelImageTable = pgTable("model_image", { + id: serial().primaryKey(), + url: varchar().notNull(), + caption: varchar(), + modelId: integer() + .notNull() + .references((): AnyPgColumn => modelTable.id), +}); + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { modelTable, modelImageTable }); +} + +main(); +``` diff --git a/changelogs/drizzle-seed/0.2.1.md b/changelogs/drizzle-seed/0.2.1.md new file mode 100644 index 000000000..0978c7a98 --- /dev/null +++ b/changelogs/drizzle-seed/0.2.1.md @@ -0,0 +1,165 @@ +## API updates + +We are introducing a new parameter, `version`, to the `seed` function options. This parameter, which controls generator versioning, has been added to make it easier to update deterministic generators in the future. Since values should remain consistent after each regeneration, it is crucial to provide a well-designed API for gradual updates + +```ts +await seed(db, schema, { version: '2' }); +``` + +#### Example: + +> This is not an actual API change; it is just an example of how we will proceed with `drizzle-seed` versioning + +For example, `lastName` generator was changed, and new version, `V2`, of this generator became available. + +Later, `firstName` generator was changed, making `V3` version of this generator available. + +| | `V1` | `V2` | `V3(latest)` | +| :--------------: | :--------------: | :-------------: | :--------------: | +| **LastNameGen** | `LastNameGenV1` | `LastNameGenV2` | | +| **FirstNameGen** | `FirstNameGenV1` | | `FirstNameGenV3` | + + +##### Use the `firstName` generator of version 3 and the `lastName` generator of version 2 +```ts +await seed(db, schema); +``` + +If you are not ready to use latest generator version right away, you can specify max version to use + +##### Use the `firstName` generator of version 1 and the `lastName` generator of version 2 +```ts +await seed(db, schema, { version: '2' }); +``` + +##### Use the `firstName` generator of version 1 and the `lastName` generator of version 1. +```ts +await seed(db, schema, { version: '1' }); +``` + +Each update with breaking changes for generators will be documented on our docs and in release notes, explaining which version you should use, if you are not ready to upgrade the way generators works + +## Breaking changes + +### `interval` unique generator was changed and upgraded to v2 + +```ts +await seed(db, { table }).refine((f) => ({ + table: { + columns: { + // this function usage will output different values with the same `seed` number from previous version + column1: f.interval({ isUnique: true }), + } + } +})) +``` + +**Reason for upgrade** +An older version of the generator could produce intervals like `1 minute 60 seconds` and `2 minutes 0 seconds`, treating them as distinct intervals. +However, when the `1 minute 60 seconds` interval is inserted into a PostgreSQL database, it is automatically converted to `2 minutes 0 seconds`. As a result, attempting to insert the `2 minutes 0 seconds` interval into a unique column afterwards will cause an error + +**Usage** +```ts +await seed(db, schema); +// or explicit +await seed(db, schema, { version: '2' }); +``` + +**Switch to the old version** +```ts +await seed(db, schema, { version: '1' }); +``` + +### `string` generators were changed and upgraded to v2 + +```ts +await seed(db, { table }).refine((f) => ({ + table: { + columns: { + // this function will output different values with the same `seed` number from previous version + column1: f.string(), + } + } +})) +``` + +**Reason to upgrade** + +Ability to generate a unique string based on the length of the text column (e.g., `varchar(20)`) + +#### PostgreSQL changes + +Default generators for `text`, `varchar`, `char` will output different values with the same `seed` number from previous version. + +```ts +// schema.ts +import * as p from 'drizzle-orm/pg-core' + +export const table = p.pgTable('table', { + column1: p.text(), + column2: p.varchar(), + column3: p.char() +}); + +// index.ts +... +// this will be affected with new changes +await seed(db, { table }); +``` + +**Switch to the old version** +```ts +await seed(db, schema, { version: '' }); +``` + +#### MySQL changes + +Default generators for `text`, `char`, `varchar`, `binary`, `varbinary` will output different values with the same `seed` number. + +```ts +// schema.ts +import * as p from 'drizzle-orm/mysql-core' + +export const table = p.mysqlTable('table', { + column1: p.text(), + column2: p.char(), + column3: p.varchar({ length: 256 }), + column4: p.binary(), + column5: p.varbinary({ length: 256 }), +}); + +// index.ts +... +// this will be affected with new changes +await seed(db, {table}) +``` + +**Switch to the old version** +```ts +await seed(db, schema, { version: '1' }); +``` + +#### SQLite changes + +Default generators for `text`, `numeric`, `blob`, `blobbuffer` will output different values with the same `seed` number. +```ts +// schema.ts +import * as p from 'drizzle-orm/sqlite-core' + +export const table = p.sqliteTable('table', { + column1: p.text(), + column2: p.numeric(), + column3: p.blob({ mode:'buffer' }), + column4: p.blob(), +}); + +// index.ts +... +// this will be affected with new changes +await seed(db, { table }) +``` + + +## Bug fixes +- Seeding a table with a foreign key referencing another table, without including the second table in the schema, will cause the seeding process to get stuck +- [[BUG]: seeding postgresql char column doesn't respect length option](https://github.com/drizzle-team/drizzle-orm/issues/3774) diff --git a/changelogs/drizzle-seed/0.3.0.md b/changelogs/drizzle-seed/0.3.0.md new file mode 100644 index 000000000..b2f61882c --- /dev/null +++ b/changelogs/drizzle-seed/0.3.0.md @@ -0,0 +1,40 @@ +# New features + +## Drizzle Relations support + +The `seed` function can now accept Drizzle Relations objects and treat them as foreign key constraints + + +```ts +// schema.ts +import { integer, serial, text, pgTable } from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; +export const users = pgTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), +}); +export const usersRelations = relations(users, ({ many }) => ({ + posts: many(posts), +})); +export const posts = pgTable('posts', { + id: serial('id').primaryKey(), + content: text('content').notNull(), + authorId: integer('author_id').notNull(), +}); +export const postsRelations = relations(posts, ({ one }) => ({ + author: one(users, { fields: [posts.authorId], references: [users.id] }), +})); +``` + +```ts +// index.ts +import { seed } from "drizzle-seed"; +import * as schema from './schema.ts' + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, schema); +} + +main(); +``` \ No newline at end of file diff --git a/changelogs/drizzle-typebox/0.2.0.md b/changelogs/drizzle-typebox/0.2.0.md new file mode 100644 index 000000000..aad630f7a --- /dev/null +++ b/changelogs/drizzle-typebox/0.2.0.md @@ -0,0 +1,87 @@ +This version fully updates `drizzle-typebox` integration and makes sure it's compatible with newer typebox versions + +# Breaking Changes + +> You must also have Drizzle ORM v0.38.0 or greater and Typebox v0.34.8 or greater installed. + +- When refining a field, if a schema is provided instead of a callback function, it will ignore the field's nullability and optional status. +- Some data types have more specific schemas for improved validation + +# Improvements + +Thanks to @L-Mario564 for making more updates than we expected to be shipped in this release. We'll copy his message from a PR regarding improvements made in this release: + +- Output for all packages are now unminified, makes exploring the compiled code easier when published to npm. +- Smaller footprint. Previously, we imported the column types at runtime for each dialect, meaning that for example, if you're just using Postgres then you'd likely only have drizzle-orm and drizzle-orm/pg-core in the build output of your app; however, these packages imported all dialects which could lead to mysql-core and sqlite-core being bundled as well even if they're unused in your app. This is now fixed. +- Slight performance gain. To determine the column data type we used the is function which performs a few checks to ensure the column data type matches. This was slow, as these checks would pile up every quickly when comparing all data types for many fields in a table/view. The easier and faster alternative is to simply go off of the column's columnType property. + +# New features + +- `createSelectSchema` function now also accepts views and enums. + +```ts +import { pgEnum } from 'drizzle-orm/pg-core'; +import { createSelectSchema } from 'drizzle-typebox'; +import { Value } from '@sinclair/typebox/value'; + +const roles = pgEnum('roles', ['admin', 'basic']); +const rolesSchema = createSelectSchema(roles); +const parsed: 'admin' | 'basic' = Value.Parse(rolesSchema, ...); + +const usersView = pgView('users_view').as((qb) => qb.select().from(users).where(gt(users.age, 18))); +const usersViewSchema = createSelectSchema(usersView); +const parsed: { id: number; name: string; age: number } = Value.Parse(usersViewSchema, ...); +``` + +- New function: `createUpdateSchema`, for use in updating queries. + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createUpdateSchema } from 'drizzle-typebox'; +import { Value } from '@sinclair/typebox/value'; + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const userUpdateSchema = createUpdateSchema(users); + +const user = { id: 5, name: 'John' }; +const parsed: { name?: string | undefined, age?: number | undefined } = Value.Parse(userUpdateSchema, user); // Error: `id` is a generated column, it can't be updated + +const user = { age: 35 }; +const parsed: { name?: string | undefined, age?: number | undefined } = Value.Parse(userUpdateSchema, user); // Will parse successfully +await db.update(users).set(parsed).where(eq(users.name, 'Jane')); +``` + +- New function: `createSchemaFactory`, to provide more advanced options and to avoid bloating the parameters of the other schema functions + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createSchemaFactory } from 'drizzle-typebox'; +import { t } from 'elysia'; // Extended Typebox instance + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const { createInsertSchema } = createSchemaFactory({ typeboxInstance: t }); + +const userInsertSchema = createInsertSchema(users, { + // We can now use the extended instance + name: (schema) => t.Number({ ...schema }, { error: '`name` must be a string' }) +}); +``` + +- Full support for PG arrays + +```ts +pg.dataType().array(...); + +// Schema +Type.Array(baseDataTypeSchema, { minItems: size, maxItems: size }); +``` \ No newline at end of file diff --git a/changelogs/drizzle-typebox/0.2.1.md b/changelogs/drizzle-typebox/0.2.1.md new file mode 100644 index 000000000..2d9e762c3 --- /dev/null +++ b/changelogs/drizzle-typebox/0.2.1.md @@ -0,0 +1,25 @@ +# Added support for SingleStore dialect + +```ts +import { singlestoreTable, text, int } from 'drizzle-orm/singlestore-core'; +import { createSelectSchema } from 'drizzle-typebox'; +import { Value } from '@sinclair/typebox/value'; + +const users = singlestoreTable('users', { + id: int().primaryKey(), + name: text().notNull(), + age: int().notNull() +}); + +const userSelectSchema = createSelectSchema(users); + +const rows = await db.select({ id: users.id, name: users.name }).from(users).limit(1); +const parsed: { id: number; name: string; age: number } = Value.Parse(userSelectSchema, rows[0]); // Error: `age` is not returned in the above query + +const rows = await db.select().from(users).limit(1); +const parsed: { id: number; name: string; age: number } = Value.Parse(userSelectSchema, rows[0]); // Will parse successfully +``` + +# Bug fixes + +- [[BUG]: drizzle-typebox infers integer() as TString](https://github.com/drizzle-team/drizzle-orm/issues/3756) \ No newline at end of file diff --git a/changelogs/drizzle-valibot/0.3.0.md b/changelogs/drizzle-valibot/0.3.0.md new file mode 100644 index 000000000..de8ec63b4 --- /dev/null +++ b/changelogs/drizzle-valibot/0.3.0.md @@ -0,0 +1,67 @@ +This version fully updates `drizzle-valibot` integration and makes sure it's compatible with newer valibot versions + +# Breaking Changes + +> You must also have Drizzle ORM v0.38.0 or greater and Valibot v1.0.0-beta.7 or greater installed. + +- When refining a field, if a schema is provided instead of a callback function, it will ignore the field's nullability and optional status. +- Some data types have more specific schemas for improved validation + +# Improvements + +Thanks to @L-Mario564 for making more updates than we expected to be shipped in this release. We'll copy his message from a PR regarding improvements made in this release: + +- Output for all packages are now unminified, makes exploring the compiled code easier when published to npm. +- Smaller footprint. Previously, we imported the column types at runtime for each dialect, meaning that for example, if you're just using Postgres then you'd likely only have drizzle-orm and drizzle-orm/pg-core in the build output of your app; however, these packages imported all dialects which could lead to mysql-core and sqlite-core being bundled as well even if they're unused in your app. This is now fixed. +- Slight performance gain. To determine the column data type we used the is function which performs a few checks to ensure the column data type matches. This was slow, as these checks would pile up every quickly when comparing all data types for many fields in a table/view. The easier and faster alternative is to simply go off of the column's columnType property. +- Some changes had to be made at the type level in the ORM package for better compatibility with drizzle-valibot. + +# New features + +- `createSelectSchema` function now also accepts views and enums. + +```ts copy +import { pgEnum } from 'drizzle-orm/pg-core'; +import { createSelectSchema } from 'drizzle-valibot'; +import { parse } from 'valibot'; + +const roles = pgEnum('roles', ['admin', 'basic']); +const rolesSchema = createSelectSchema(roles); +const parsed: 'admin' | 'basic' = parse(rolesSchema, ...); + +const usersView = pgView('users_view').as((qb) => qb.select().from(users).where(gt(users.age, 18))); +const usersViewSchema = createSelectSchema(usersView); +const parsed: { id: number; name: string; age: number } = parse(usersViewSchema, ...); +``` + +- New function: `createUpdateSchema`, for use in updating queries. + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createUpdateSchema } from 'drizzle-valibot'; +import { parse } from 'valibot'; + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const userUpdateSchema = createUpdateSchema(users); + +const user = { id: 5, name: 'John' }; +const parsed: { name?: string | undefined, age?: number | undefined } = parse(userUpdateSchema, user); // Error: `id` is a generated column, it can't be updated + +const user = { age: 35 }; +const parsed: { name?: string | undefined, age?: number | undefined } = parse(userUpdateSchema, user); // Will parse successfully +await db.update(users).set(parsed).where(eq(users.name, 'Jane')); +``` + +- Full support for PG arrays + +```ts +pg.dataType().array(...); + +// Schema +z.array(baseDataTypeSchema).length(size); +``` \ No newline at end of file diff --git a/changelogs/drizzle-valibot/0.3.1.md b/changelogs/drizzle-valibot/0.3.1.md new file mode 100644 index 000000000..21f05270b --- /dev/null +++ b/changelogs/drizzle-valibot/0.3.1.md @@ -0,0 +1,23 @@ +# Added support for SingleStore dialect + +```ts +import { singlestoreTable, text, int } from 'drizzle-orm/singlestore-core'; +import { createSelectSchema } from 'drizzle-valibot'; +import { parse } from 'valibot'; + +const users = singlestoreTable('users', { + id: int().primaryKey(), + name: text().notNull(), + age: int().notNull() +}); + +const userSelectSchema = createSelectSchema(users); +const rows = await db.select({ id: users.id, name: users.name }).from(users).limit(1); +const parsed: { id: number; name: string; age: number } = parse(userSelectSchema, rows[0]); // Error: `age` is not returned in the above query +const rows = await db.select().from(users).limit(1); +const parsed: { id: number; name: string; age: number } = parse(userSelectSchema, rows[0]); // Will parse successfully +``` + +# Bug fixes + +- [[BUG]: drizzle-valibot throws Type instantiation is excessively deep and possibly infinite. for refinements](https://github.com/drizzle-team/drizzle-orm/issues/3751) diff --git a/changelogs/drizzle-zod/0.6.0.md b/changelogs/drizzle-zod/0.6.0.md new file mode 100644 index 000000000..31ffd4fdb --- /dev/null +++ b/changelogs/drizzle-zod/0.6.0.md @@ -0,0 +1,85 @@ +This version fully updates `drizzle-zod` integration and makes sure it's compatible with newer zod versions + +# Breaking Changes + +> You must also have Drizzle ORM v0.38.0 or greater and Zod v3.0.0 or greater installed. + +- When refining a field, if a schema is provided instead of a callback function, it will ignore the field's nullability and optional status. +- Some data types have more specific schemas for improved validation + +# Improvements + +Thanks to @L-Mario564 for making more updates than we expected to be shipped in this release. We'll copy his message from a PR regarding improvements made in this release: + +- Output for all packages are now unminified, makes exploring the compiled code easier when published to npm. +- Smaller footprint. Previously, we imported the column types at runtime for each dialect, meaning that for example, if you're just using Postgres then you'd likely only have drizzle-orm and drizzle-orm/pg-core in the build output of your app; however, these packages imported all dialects which could lead to mysql-core and sqlite-core being bundled as well even if they're unused in your app. This is now fixed. +- Slight performance gain. To determine the column data type we used the is function which performs a few checks to ensure the column data type matches. This was slow, as these checks would pile up every quickly when comparing all data types for many fields in a table/view. The easier and faster alternative is to simply go off of the column's columnType property. + +# New features + +- `createSelectSchema` function now also accepts views and enums. + +```ts copy +import { pgEnum } from 'drizzle-orm/pg-core'; +import { createSelectSchema } from 'drizzle-zod'; + +const roles = pgEnum('roles', ['admin', 'basic']); +const rolesSchema = createSelectSchema(roles); +const parsed: 'admin' | 'basic' = rolesSchema.parse(...); + +const usersView = pgView('users_view').as((qb) => qb.select().from(users).where(gt(users.age, 18))); +const usersViewSchema = createSelectSchema(usersView); +const parsed: { id: number; name: string; age: number } = usersViewSchema.parse(...); +``` + +- New function: `createUpdateSchema`, for use in updating queries. + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createUpdateSchema } from 'drizzle-zod'; + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const userUpdateSchema = createUpdateSchema(users); + +const user = { id: 5, name: 'John' }; +const parsed: { name?: string | undefined, age?: number | undefined } = userUpdateSchema.parse(user); // Error: `id` is a generated column, it can't be updated + +const user = { age: 35 }; +const parsed: { name?: string | undefined, age?: number | undefined } = userUpdateSchema.parse(user); // Will parse successfully +await db.update(users).set(parsed).where(eq(users.name, 'Jane')); +``` + +- New function: `createSchemaFactory`, to provide more advanced options and to avoid bloating the parameters of the other schema functions + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createSchemaFactory } from 'drizzle-zod'; +import { z } from '@hono/zod-openapi'; // Extended Zod instance + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const { createInsertSchema } = createSchemaFactory({ zodInstance: z }); + +const userInsertSchema = createInsertSchema(users, { + // We can now use the extended instance + name: (schema) => schema.openapi({ example: 'John' }) +}); +``` + +- Full support for PG arrays + +```ts +pg.dataType().array(...); + +// Schema +z.array(baseDataTypeSchema).length(size); +``` \ No newline at end of file diff --git a/changelogs/drizzle-zod/0.6.1.md b/changelogs/drizzle-zod/0.6.1.md new file mode 100644 index 000000000..dd6740660 --- /dev/null +++ b/changelogs/drizzle-zod/0.6.1.md @@ -0,0 +1,26 @@ +# New Features + +## Added support for SingleStore dialect + +```ts +import { singlestoreTable, text, int } from 'drizzle-orm/singlestore-core'; +import { createSelectSchema } from 'drizzle-zod'; + +const users = singlestoreTable('users', { + id: int().primaryKey(), + name: text().notNull(), + age: int().notNull() +}); + +const userSelectSchema = createSelectSchema(users); +const rows = await db.select({ id: users.id, name: users.name }).from(users).limit(1); +const parsed: { id: number; name: string; age: number } = userSelectSchema.parse(rows[0]); // Error: `age` is not returned in the above query + +const rows = await db.select().from(users).limit(1); +const parsed: { id: number; name: string; age: number } = userSelectSchema.parse(rows[0]); // Will parse successfully +``` + +# Bug fixes + +- [[BUG]: refining schema using createSelectSchema is not working with drizzle-kit 0.6.0](https://github.com/drizzle-team/drizzle-orm/issues/3735) +- [[BUG]: drizzle-zod inferring types incorrectly](https://github.com/drizzle-team/drizzle-orm/issues/3734) \ No newline at end of file diff --git a/docs/custom-types.lite.md b/docs/custom-types.lite.md index 627aba1a4..af1371461 100644 --- a/docs/custom-types.lite.md +++ b/docs/custom-types.lite.md @@ -1,6 +1,7 @@ # Common way of defining custom types -> **Info**: For more advanced documentation about defining custom data types in PostgreSQL and MySQL please check [custom-types.md](https://github.com/drizzle-team/drizzle-orm/blob/main/docs/custom-types.md) +> [!NOTE] +> For more advanced documentation about defining custom data types in PostgreSQL and MySQL, please check [`custom-types.md`](custom-types.md). ## Examples diff --git a/docs/custom-types.md b/docs/custom-types.md index 1dffe6ce0..2a9f1a1da 100644 --- a/docs/custom-types.md +++ b/docs/custom-types.md @@ -1,7 +1,6 @@ # How to define custom types Drizzle ORM has a big set of predefined column types for different SQL databases. But still there are additional types that are not supported by Drizzle ORM (yet). That could be native pg types or extension types -
Here are some instructions on how to create and use your own types with Drizzle ORM @@ -13,14 +12,11 @@ Each type creation should use 2 classes: - `ColumnBuilder` - class, that is responsible for generating whole set of needed fields for column creation - `Column` - class, that is representing Columns itself, that is used in query generation, migration mapping, etc. -
-
+ Each module has it's own class, representing `ColumnBuilder` or `Column`: - For `pg` -> `PgColumnBuilder` and `PgColumn` - For `mysql` -> `MySqlColumnBuilder` and `MySqlColumn` - For `sqlite` -> `SQLiteColumnBuilder` and `SQLiteColumn` -
-
### Builder class explanation - (postgresql text data type example) @@ -44,7 +40,7 @@ export class PgTextBuilder } ``` -> **Warning** +> [!WARNING] > `$pgColumnBuilderBrand` should be changed and be equal to class name for new data type builder ### Column class explanation - (postgresql text data type example) @@ -104,11 +100,8 @@ export class PgText } ``` -> **Warning** +> [!WARNING] > `$pgColumnBrand` should be changed and be equal to class name for new data type ---- - -
### Full text data type for PostgreSQL example @@ -162,19 +155,13 @@ export function text( ## Custom data type example ---- - -> **Note** +> [!NOTE] > We will check example on pg module, but current pattern applies to all dialects, that are currently supported by Drizzle ORM -
- ### Setting up CITEXT datatype ---- -> **Note** - This type is available only with extensions and used for example, just to show how you could setup any data type you want. Extension support will come soon -
+> [!NOTE] +> This type is available only with extensions and used for example, just to show how you could setup any data type you want. Extension support will come soon ### CITEXT data type example @@ -224,4 +211,4 @@ You could add your created custom data types to Drizzle ORM, so everyone can use Each data type should be placed in separate file in `columns` folder and PR open with tag `new-data-type:pg` | `new-data-type:sqlite` | `new-data-type:mysql` -For more Contribution information - please check [CONTRIBUTING.md](https://github.com/drizzle-team/drizzle-orm/blob/main/CONTRIBUTING.md) +For more Contribution information - please check [CONTRIBUTING.md](../CONTRIBUTING.md) diff --git a/docs/joins.md b/docs/joins.md index e0579e3e2..2b5829aa9 100644 --- a/docs/joins.md +++ b/docs/joins.md @@ -1,7 +1,7 @@ # Drizzle ORM - Joins As with other parts of Drizzle ORM, the joins syntax is a balance between the SQL-likeness and type safety. -Here's an example of how a common `1-to-many` relationship can be modelled. +Here's an example of how a common "one-to-many" relationship can be modelled. ```typescript const users = pgTable('users', { @@ -96,7 +96,7 @@ In that case, the ORM will use dark TypeScript magic (as if it wasn't already) a This is much more convenient! Now, you can just do a single check for `row.user !== null`, and all the user fields will become available. -


+--- Note that you can group any fields in a nested object however you like, but the single check optimization will only be applied to a certain nested object if all its fields belong to the same table. So, for example, you can group the city fields, too: @@ -131,7 +131,7 @@ And the result type will look like this: } ``` -
+--- If you just need all the fields from all the tables you're selecting and joining, you can simply omit the argument of the `.select()` method altogether: @@ -139,7 +139,8 @@ If you just need all the fields from all the tables you're selecting and joining const rows = await db.select().from(cities).leftJoin(users, eq(users.cityId, cities.id)); ``` -> **Note**: in this case, the Drizzle table/column names will be used as the keys in the result object. +> [!NOTE] +> In this case, the Drizzle table/column names will be used as the keys in the result object. ```typescript { @@ -156,7 +157,7 @@ const rows = await db.select().from(cities).leftJoin(users, eq(users.cityId, cit }[] ``` -
+--- There are cases where you'd want to select all the fields from one table, but pick fields from others. In that case, instead of listing all the table fields, you can just pass a table: @@ -181,7 +182,7 @@ There are cases where you'd want to select all the fields from one table, but pi } ``` -
+--- But what happens if you group columns from multiple tables in the same nested object? Nothing, really - they will still be all individually nullable, just grouped under the same object (as you might expect!): diff --git a/drizzle-kit/.gitignore b/drizzle-kit/.gitignore index 8d2cf5a81..4916f095a 100644 --- a/drizzle-kit/.gitignore +++ b/drizzle-kit/.gitignore @@ -2,6 +2,7 @@ **/.DS_Store !src +!imports-checker !tests !vitest.config.ts !README.md @@ -13,6 +14,7 @@ !package.json !tsconfig.json !tsconfig.cli-types.json +!tsconfig.build.json !pnpm-lock.yaml !.github !build.ts diff --git a/drizzle-kit/README.md b/drizzle-kit/README.md index d2a4191b7..bd69a4d3d 100644 --- a/drizzle-kit/README.md +++ b/drizzle-kit/README.md @@ -1,21 +1,21 @@ ## Drizzle Kit -DrizzleKit - is a CLI migrator tool for DrizzleORM. It is probably one and only tool that lets you completely automatically generate SQL migrations and covers ~95% of the common cases like deletions and renames by prompting user input. +Drizzle Kit is a CLI migrator tool for Drizzle ORM. It is probably the one and only tool that lets you completely automatically generate SQL migrations and covers ~95% of the common cases like deletions and renames by prompting user input. - is a mirror repository for issues. ## Documentation -Check the full documenation on [the website](https://orm.drizzle.team/kit-docs/overview) +Check the full documentation on [the website](https://orm.drizzle.team/kit-docs/overview). ### How it works -`drizzle-kit` will traverse `schema folder` or `schema file`, generate schema snapshot and compare it to the previous version, if there's one. - Based on the difference it will generate all needed SQL migrations and if there are any `automatically unresolvable` cases like `renames` it will prompt user for input. +Drizzle Kit traverses a schema module and generates a snapshot to compare with the previous version, if there is one. +Based on the difference, it will generate all needed SQL migrations. If there are any cases that can't be resolved automatically, such as renames, it will prompt the user for input. -For schema file: +For example, for this schema module: ```typescript -// ./src/db/schema.ts +// src/db/schema.ts import { integer, pgTable, serial, text, varchar } from "drizzle-orm/pg-core"; @@ -63,7 +63,7 @@ CREATE INDEX IF NOT EXISTS users_full_name_index ON users (full_name); npm install -D drizzle-kit ``` -Running with CLI options +Running with CLI options: ```jsonc // package.json diff --git a/drizzle-kit/build.ts b/drizzle-kit/build.ts index 8616112fd..ec7fc76c0 100644 --- a/drizzle-kit/build.ts +++ b/drizzle-kit/build.ts @@ -1,3 +1,4 @@ +/// import * as esbuild from 'esbuild'; import { readFileSync, writeFileSync } from 'node:fs'; import * as tsup from 'tsup'; @@ -9,12 +10,14 @@ const driversPackages = [ 'postgres', '@vercel/postgres', '@neondatabase/serverless', + '@electric-sql/pglite', // mysql drivers 'mysql2', '@planetscale/database', // sqlite drivers '@libsql/client', 'better-sqlite3', + 'bun:sqlite', ]; esbuild.buildSync({ @@ -81,6 +84,7 @@ const main = async () => { await tsup.build({ entryPoints: ['./src/index.ts', './src/api.ts'], outDir: './dist', + external: ['bun:sqlite'], splitting: false, dts: true, format: ['cjs', 'esm'], diff --git a/drizzle-kit/imports-checker/analyze.ts b/drizzle-kit/imports-checker/analyze.ts new file mode 100644 index 000000000..b31686e16 --- /dev/null +++ b/drizzle-kit/imports-checker/analyze.ts @@ -0,0 +1,78 @@ +import { readFileSync } from 'fs'; +import type { Node } from 'ohm-js'; +import JSImports from './grammar/grammar.ohm-bundle'; + +export type CollectionItem = { + type: 'data' | 'types'; + source: string; +}; + +function recursiveRun(...args: Node[]): boolean { + for (const arg of args) { + if ( + arg.ctorName === 'Rest' + || arg.ctorName === 'comment' + || arg.ctorName === 'stringLiteral' + ) { + continue; + } + + if ( + arg.ctorName === 'ImportExpr_From' + || arg.ctorName === 'ImportExpr_NoFrom' + ) { + arg['analyze'](); + + continue; + } + + if (arg.isTerminal()) continue; + + for (const c of arg.children) { + if (!recursiveRun(c)) return false; + } + } + + return true; +} +function init(collection: CollectionItem[]) { + const semantics = JSImports.createSemantics(); + + semantics.addOperation('analyze', { + JSImports(arg0, arg1) { + recursiveRun(arg0, arg1); + }, + + ImportExpr_From(kImport, importInner, kFrom, importSource) { + const ruleName = importInner.children[0]!.ctorName; + const importType = ruleName === 'ImportInner_Type' || ruleName === 'ImportInner_Types' + ? 'types' + : 'data'; + + collection.push({ + source: importSource.children[1]!.sourceString!, + type: importType, + }); + }, + + ImportExpr_NoFrom(kImport, importSource) { + collection.push({ + source: importSource.children[1]!.sourceString!, + type: 'data', + }); + }, + }); + + return semantics; +} + +export function analyze(path: string) { + const file = readFileSync(path).toString(); + const match = JSImports.match(file, 'JSImports'); + + if (match.failed()) throw new Error(`Failed to parse file: ${path}`); + const collection: CollectionItem[] = []; + + init(collection)(match)['analyze'](); + return collection; +} diff --git a/drizzle-kit/imports-checker/checker.ts b/drizzle-kit/imports-checker/checker.ts new file mode 100644 index 000000000..d8fc4b219 --- /dev/null +++ b/drizzle-kit/imports-checker/checker.ts @@ -0,0 +1,296 @@ +import fs from 'fs'; +import m from 'micromatch'; +import { dirname, join as joinPath, relative, resolve as resolvePath } from 'path'; +import { analyze } from './analyze'; + +type External = { + file: string; + import: string; + type: 'data' | 'types'; +}; + +export type Issue = { + file: string; + imports: IssueImport[]; + accessChains: ChainLink[][]; +}; + +export type IssueImport = { + name: string; + type: 'data' | 'types'; +}; + +export type ChainLink = { + file: string; + import: string; +}; + +type ListMode = 'whitelist' | 'blacklist'; + +class ImportAnalyzer { + private localImportRegex = /^(\.?\.?\/|\.\.?$)/; + private importedFileFormatRegex = /^.*\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|json)$/i; + + private visited: Set = new Set(); + + private externals: External[] = []; + private accessChains: Record = {}; + + constructor( + private basePath: string, + private entry: string, + private listMode: ListMode, + private readonly wantedList: string[], + private localPaths: string[], + private logger?: boolean, + private ignoreTypes?: boolean, + ) {} + + private isDirectory = (path: string) => { + try { + return fs.lstatSync(path).isDirectory(); + } catch (e) { + return false; + } + }; + + private isFile = (path: string) => { + try { + return fs.lstatSync(path).isFile(); + } catch (e) { + return false; + } + }; + + private localizePath = (path: string) => relative(resolvePath(this.basePath), resolvePath(path)); + + private isCustomLocal = (importTarget: string) => + !!this.localPaths.find( + (l) => + importTarget === l + || importTarget.startsWith(l.endsWith('/') ? l : `${l}/`), + ); + private isLocal = (importTarget: string) => + this.localImportRegex.test(importTarget) + || this.isCustomLocal(importTarget); + private isTsFormat = (path: string) => this.importedFileFormatRegex.test(path); + + private resolveCustomLocalPath = ( + absoluteBase: string, + base: string, + target: string, + ): string => { + return joinPath(absoluteBase, target); + }; + + private resolveTargetFile = (path: string): string => { + if (this.isFile(path)) return path; + + const formats = [ + '.ts', + '.mts', + '.cts', + '.tsx', + '.js', + '.mjs', + '.cjs', + '.jsx', + ]; + + for (const format of formats) { + const indexPath = joinPath(path, `/index${format}`); + if (this.isFile(indexPath)) return indexPath; + + const formatFilePath = `${path}${format}`; + if (this.isFile(formatFilePath)) return formatFilePath; + } + + return path; + }; + + private resolveTargetPath = ( + absoluteBase: string, + base: string, + target: string, + ): string => { + if (this.isCustomLocal(target)) { + return this.resolveTargetFile( + this.resolveCustomLocalPath(absoluteBase, base, target), + ); + } + + const dir = this.isDirectory(base) ? base : dirname(base); + const joined = joinPath(dir, target); + + return this.resolveTargetFile(joined); + }; + + private _analyzeImports = ( + target: string = this.entry, + basePath: string = this.basePath, + accessChain: ChainLink[] = [], + ) => { + if (this.visited.has(target)) return; + + const locals: string[] = []; + + try { + if (this.logger) console.log(`${this.localizePath(target)}`); + + const imports = analyze(target); + + for (const { source: i, type } of imports) { + if (this.ignoreTypes && type === 'types') continue; + + if (this.isLocal(i)) { + locals.push(i); + + continue; + } + + this.externals.push({ + file: this.localizePath(target), + import: i, + type: type, + }); + } + } catch (e) { + throw e; + } finally { + this.visited.add(target); + } + + for (const local of locals) { + const transformedTarget = this.resolveTargetPath(basePath, target, local); + + const localChain = [ + ...accessChain, + { + file: this.localizePath(target), + import: local, + }, + ]; + + const localized = this.localizePath(transformedTarget); + + if (this.accessChains[localized]) { + this.accessChains[localized].push(localChain); + } else this.accessChains[localized] = [localChain]; + + if (this.isTsFormat(transformedTarget)) { + this._analyzeImports(transformedTarget, basePath, localChain); + } else { + throw new Error(`unrecognized: ${localized}`); + } + } + }; + + public analyzeImports = () => { + const entryLocalized = this.localizePath(this.entry); + if (!this.accessChains[entryLocalized]) { + this.accessChains[entryLocalized] = [[]]; + } + + this._analyzeImports(); + + const rawIssues = this.listMode === 'whitelist' + ? this.externals.filter((e) => !m([e.import], this.wantedList).length) + : this.externals.filter((e) => m([e.import], this.wantedList).length); + + const issueMap: Record = {}; + for (const { file, import: i, type } of rawIssues) { + if (issueMap[file]) { + issueMap[file].imports.push({ + name: i, + type, + }); + + continue; + } + + issueMap[file] = { + file, + imports: [ + { + name: i, + type, + }, + ], + accessChains: this.accessChains[file]!, + }; + } + + return { + issues: Object.entries(issueMap).map(([file, data]) => { + for (const chain of data.accessChains) { + chain.push({ + file, + import: '', + }); + } + + return data; + }), + accessChains: this.accessChains, + }; + }; +} + +export type CustomLocalPathResolver = ( + basePath: string, + path: string, + target: string, +) => string; + +export type AnalyzeImportsConfig = + & { + basePath: string; + entry: string; + logger?: boolean; + ignoreTypes?: boolean; + localPaths?: string[]; + } + & ( + | { + blackList: string[]; + } + | { + whiteList: string[]; + } + ); + +type AnyAnalyzeImportsConfig = { + basePath: string; + entry: string; + blackList?: string[]; + whiteList?: string[]; + logger?: boolean; + ignoreTypes?: boolean; + localPaths?: string[]; +}; + +export function analyzeImports(cfg: AnalyzeImportsConfig) { + const { + basePath, + blackList, + whiteList, + entry, + localPaths: localImports, + ignoreTypes, + logger, + } = cfg as AnyAnalyzeImportsConfig; + const mode = whiteList ? 'whitelist' : 'blacklist'; + const wantedList = blackList ?? whiteList!; + + const analyzer = new ImportAnalyzer( + joinPath(basePath), + joinPath(entry), + mode, + wantedList, + localImports ?? [], + logger, + ignoreTypes, + ); + + return analyzer.analyzeImports(); +} diff --git a/drizzle-kit/imports-checker/grammar/grammar.ohm b/drizzle-kit/imports-checker/grammar/grammar.ohm new file mode 100644 index 000000000..de1459942 --- /dev/null +++ b/drizzle-kit/imports-checker/grammar/grammar.ohm @@ -0,0 +1,121 @@ +JSImports { + JSImports = (Expr ";"?)* + + Expr = + | comment + | stringLiteral + | ImportExpr + | Rest + + ImportExpr = + | "import" ImportInner "from" importSource -- From + | "import" importSource -- NoFrom + + Rest = (~(ImportExpr | comment | stringLiteral) any)+ + + ImportInner = + | ("type" "{" NonemptyListOf ","? "}") -- Type + | ("{" NonemptyListOf ","? "}") -- Types + | ("{" NonemptyListOf ","? "}") -- Extended + | (identifier ("," "type"? "{" NonemptyListOf ","? "}")?) -- Mixed + | ("*" ("as" identifier)?) -- All + | (identifier ("as" identifier)?) -- Default + + + ImportExtendedSelection = TypeImport | Import + ImportExtendedSelectionTypes = TypeImport + ImportExtendedSelectionTypeless = Import + + Import = identifier ("as" identifier)? + TypeImport = "type" Import ("as" identifier)? + + identifier = letter alnum* + quote = "\"" | "'" | "`" + notQuote = ~quote any + importSource = + | "\"" notQuote+ "\"" + | "'" notQuote+ "'" + | "`" notQuote+ "`" + + lineTerminator = "\n" | "\r" | "\u2028" | "\u2029" + lineTerminatorSequence = "\n" | "\r" ~"\n" | "\u2028" | "\u2029" | "\r\n" + + comment = multiLineComment | singleLineComment + + multiLineComment = "/*" (~"*/" any)* "*/" + singleLineComment = "//" (~lineTerminator any)* + + stringLiteral = + | "\"" doubleStringCharacter* "\"" + | "'" singleStringCharacter* "'" + | "`" templateStringCharacter* "`" + doubleStringCharacter = + | ~("\"" | "\\" | lineTerminator) any -- NonEscaped + | "\\" escapeSequence -- Escaped + | lineContinuation -- LineContinuation + singleStringCharacter = + | ~("'" | "\\" | lineTerminator) any -- NonEscaped + | "\\" escapeSequence -- Escaped + | lineContinuation -- LineContinuation + templateStringCharacter = + | ~ ("`" | "\\") any -- NonEscaped + | "\\" escapeSequence -- Escaped + lineContinuation = "\\" lineTerminatorSequence + escapeSequence = unicodeEscapeSequence | hexEscapeSequence | octalEscapeSequence | characterEscapeSequence + characterEscapeSequence = singleEscapeCharacter | nonEscapeCharacter + singleEscapeCharacter = "'" | "\"" | "\\" | "b" | "f" | "n" | "r" | "t" | "v" + nonEscapeCharacter = ~(escapeCharacter | lineTerminator) any + escapeCharacter = singleEscapeCharacter | decimalDigit | "x" | "u" + octalEscapeSequence = + | zeroToThree octalDigit octalDigit -- Whole + | fourToSeven octalDigit -- EightTimesfourToSeven + | zeroToThree octalDigit ~decimalDigit -- EightTimesZeroToThree + | octalDigit ~decimalDigit -- Octal + hexEscapeSequence = "x" hexDigit hexDigit + unicodeEscapeSequence = "u" hexDigit hexDigit hexDigit hexDigit + + zeroToThree = "0".."3" + fourToSeven = "4".."7" + decimalDigit = "0".."9" + nonZeroDigit = "1".."9" + octalDigit = "0".."7" + + regularExpressionLiteral = "/" regularExpressionBody "/" regularExpressionFlags + regularExpressionBody = regularExpressionFirstChar regularExpressionChar* + regularExpressionFirstChar = + | ~("*" | "\\" | "/" | "[") regularExpressionNonTerminator + | regularExpressionBackslashSequence + | regularExpressionClass + regularExpressionChar = ~("\\" | "/" | "[") regularExpressionNonTerminator + | regularExpressionBackslashSequence + | regularExpressionClass + regularExpressionBackslashSequence = "\\" regularExpressionNonTerminator + regularExpressionNonTerminator = ~(lineTerminator) any + regularExpressionClass = "[" regularExpressionClassChar* "]" + regularExpressionClassChar = + | ~("]" | "\\") regularExpressionNonTerminator + | regularExpressionBackslashSequence + regularExpressionFlags = identifierPart* + + multiLineCommentNoNL = "/*" (~("*/" | lineTerminator) any)* "*/" + + identifierStart = + | letter | "$" | "_" + | "\\" unicodeEscapeSequence -- escaped + identifierPart = + | identifierStart | unicodeCombiningMark + | unicodeDigit | unicodeConnectorPunctuation + | "\u200C" | "\u200D" + letter += unicodeCategoryNl + unicodeCategoryNl + = "\u2160".."\u2182" | "\u3007" | "\u3021".."\u3029" + unicodeDigit (a digit) + = "\u0030".."\u0039" | "\u0660".."\u0669" | "\u06F0".."\u06F9" | "\u0966".."\u096F" | "\u09E6".."\u09EF" | "\u0A66".."\u0A6F" | "\u0AE6".."\u0AEF" | "\u0B66".."\u0B6F" | "\u0BE7".."\u0BEF" | "\u0C66".."\u0C6F" | "\u0CE6".."\u0CEF" | "\u0D66".."\u0D6F" | "\u0E50".."\u0E59" | "\u0ED0".."\u0ED9" | "\u0F20".."\u0F29" | "\uFF10".."\uFF19" + + unicodeCombiningMark (a Unicode combining mark) + = "\u0300".."\u0345" | "\u0360".."\u0361" | "\u0483".."\u0486" | "\u0591".."\u05A1" | "\u05A3".."\u05B9" | "\u05BB".."\u05BD" | "\u05BF".."\u05BF" | "\u05C1".."\u05C2" | "\u05C4".."\u05C4" | "\u064B".."\u0652" | "\u0670".."\u0670" | "\u06D6".."\u06DC" | "\u06DF".."\u06E4" | "\u06E7".."\u06E8" | "\u06EA".."\u06ED" | "\u0901".."\u0902" | "\u093C".."\u093C" | "\u0941".."\u0948" | "\u094D".."\u094D" | "\u0951".."\u0954" | "\u0962".."\u0963" | "\u0981".."\u0981" | "\u09BC".."\u09BC" | "\u09C1".."\u09C4" | "\u09CD".."\u09CD" | "\u09E2".."\u09E3" | "\u0A02".."\u0A02" | "\u0A3C".."\u0A3C" | "\u0A41".."\u0A42" | "\u0A47".."\u0A48" | "\u0A4B".."\u0A4D" | "\u0A70".."\u0A71" | "\u0A81".."\u0A82" | "\u0ABC".."\u0ABC" | "\u0AC1".."\u0AC5" | "\u0AC7".."\u0AC8" | "\u0ACD".."\u0ACD" | "\u0B01".."\u0B01" | "\u0B3C".."\u0B3C" | "\u0B3F".."\u0B3F" | "\u0B41".."\u0B43" | "\u0B4D".."\u0B4D" | "\u0B56".."\u0B56" | "\u0B82".."\u0B82" | "\u0BC0".."\u0BC0" | "\u0BCD".."\u0BCD" | "\u0C3E".."\u0C40" | "\u0C46".."\u0C48" | "\u0C4A".."\u0C4D" | "\u0C55".."\u0C56" | "\u0CBF".."\u0CBF" | "\u0CC6".."\u0CC6" | "\u0CCC".."\u0CCD" | "\u0D41".."\u0D43" | "\u0D4D".."\u0D4D" | "\u0E31".."\u0E31" | "\u0E34".."\u0E3A" | "\u0E47".."\u0E4E" | "\u0EB1".."\u0EB1" | "\u0EB4".."\u0EB9" | "\u0EBB".."\u0EBC" | "\u0EC8".."\u0ECD" | "\u0F18".."\u0F19" | "\u0F35".."\u0F35" | "\u0F37".."\u0F37" | "\u0F39".."\u0F39" | "\u0F71".."\u0F7E" | "\u0F80".."\u0F84" | "\u0F86".."\u0F87" | "\u0F90".."\u0F95" | "\u0F97".."\u0F97" | "\u0F99".."\u0FAD" | "\u0FB1".."\u0FB7" | "\u0FB9".."\u0FB9" | "\u20D0".."\u20DC" | "\u20E1".."\u20E1" | "\u302A".."\u302F" | "\u3099".."\u309A" | "\uFB1E".."\uFB1E" | "\uFE20".."\uFE23" + + unicodeConnectorPunctuation = "\u005F" | "\u203F".."\u2040" | "\u30FB" | "\uFE33".."\uFE34" | "\uFE4D".."\uFE4F" | "\uFF3F" | "\uFF65" + unicodeSpaceSeparator = "\u2000".."\u200B" | "\u3000" + +} \ No newline at end of file diff --git a/drizzle-kit/imports-checker/grammar/grammar.ohm-bundle.d.ts b/drizzle-kit/imports-checker/grammar/grammar.ohm-bundle.d.ts new file mode 100644 index 000000000..64b5dfb78 --- /dev/null +++ b/drizzle-kit/imports-checker/grammar/grammar.ohm-bundle.d.ts @@ -0,0 +1,164 @@ +// AUTOGENERATED FILE +// This file was generated from grammar.ohm by `ohm generateBundles`. + +import { BaseActionDict, Grammar, IterationNode, Node, NonterminalNode, Semantics, TerminalNode } from 'ohm-js'; + +export interface JSImportsActionDict extends BaseActionDict { + JSImports?: (this: NonterminalNode, arg0: IterationNode, arg1: IterationNode) => T; + Expr?: (this: NonterminalNode, arg0: NonterminalNode) => T; + ImportExpr_From?: ( + this: NonterminalNode, + arg0: TerminalNode, + arg1: NonterminalNode, + arg2: TerminalNode, + arg3: NonterminalNode, + ) => T; + ImportExpr_NoFrom?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode) => T; + ImportExpr?: (this: NonterminalNode, arg0: NonterminalNode) => T; + Rest?: (this: NonterminalNode, arg0: IterationNode) => T; + ImportInner_Type?: ( + this: NonterminalNode, + arg0: TerminalNode, + arg1: TerminalNode, + arg2: NonterminalNode, + arg3: IterationNode, + arg4: TerminalNode, + ) => T; + ImportInner_Types?: ( + this: NonterminalNode, + arg0: TerminalNode, + arg1: NonterminalNode, + arg2: IterationNode, + arg3: TerminalNode, + ) => T; + ImportInner_Extended?: ( + this: NonterminalNode, + arg0: TerminalNode, + arg1: NonterminalNode, + arg2: IterationNode, + arg3: TerminalNode, + ) => T; + ImportInner_Mixed?: ( + this: NonterminalNode, + arg0: NonterminalNode, + arg1: IterationNode, + arg2: IterationNode, + arg3: IterationNode, + arg4: IterationNode, + arg5: IterationNode, + arg6: IterationNode, + ) => T; + ImportInner_All?: (this: NonterminalNode, arg0: TerminalNode, arg1: IterationNode, arg2: IterationNode) => T; + ImportInner_Default?: (this: NonterminalNode, arg0: NonterminalNode, arg1: IterationNode, arg2: IterationNode) => T; + ImportInner?: (this: NonterminalNode, arg0: NonterminalNode) => T; + ImportExtendedSelection?: (this: NonterminalNode, arg0: NonterminalNode) => T; + ImportExtendedSelectionTypes?: (this: NonterminalNode, arg0: NonterminalNode) => T; + ImportExtendedSelectionTypeless?: (this: NonterminalNode, arg0: NonterminalNode) => T; + Import?: (this: NonterminalNode, arg0: NonterminalNode, arg1: IterationNode, arg2: IterationNode) => T; + TypeImport?: ( + this: NonterminalNode, + arg0: TerminalNode, + arg1: NonterminalNode, + arg2: IterationNode, + arg3: IterationNode, + ) => T; + identifier?: (this: NonterminalNode, arg0: NonterminalNode, arg1: IterationNode) => T; + quote?: (this: NonterminalNode, arg0: TerminalNode) => T; + notQuote?: (this: NonterminalNode, arg0: NonterminalNode) => T; + importSource?: (this: NonterminalNode, arg0: TerminalNode, arg1: IterationNode, arg2: TerminalNode) => T; + lineTerminator?: (this: NonterminalNode, arg0: TerminalNode) => T; + lineTerminatorSequence?: (this: NonterminalNode, arg0: TerminalNode) => T; + comment?: (this: NonterminalNode, arg0: NonterminalNode) => T; + multiLineComment?: (this: NonterminalNode, arg0: TerminalNode, arg1: IterationNode, arg2: TerminalNode) => T; + singleLineComment?: (this: NonterminalNode, arg0: TerminalNode, arg1: IterationNode) => T; + stringLiteral?: (this: NonterminalNode, arg0: TerminalNode, arg1: IterationNode, arg2: TerminalNode) => T; + doubleStringCharacter_NonEscaped?: (this: NonterminalNode, arg0: NonterminalNode) => T; + doubleStringCharacter_Escaped?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode) => T; + doubleStringCharacter_LineContinuation?: (this: NonterminalNode, arg0: NonterminalNode) => T; + doubleStringCharacter?: (this: NonterminalNode, arg0: NonterminalNode) => T; + singleStringCharacter_NonEscaped?: (this: NonterminalNode, arg0: NonterminalNode) => T; + singleStringCharacter_Escaped?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode) => T; + singleStringCharacter_LineContinuation?: (this: NonterminalNode, arg0: NonterminalNode) => T; + singleStringCharacter?: (this: NonterminalNode, arg0: NonterminalNode) => T; + templateStringCharacter_NonEscaped?: (this: NonterminalNode, arg0: NonterminalNode) => T; + templateStringCharacter_Escaped?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode) => T; + templateStringCharacter?: (this: NonterminalNode, arg0: NonterminalNode) => T; + lineContinuation?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode) => T; + escapeSequence?: (this: NonterminalNode, arg0: NonterminalNode) => T; + characterEscapeSequence?: (this: NonterminalNode, arg0: NonterminalNode) => T; + singleEscapeCharacter?: (this: NonterminalNode, arg0: TerminalNode) => T; + nonEscapeCharacter?: (this: NonterminalNode, arg0: NonterminalNode) => T; + escapeCharacter?: (this: NonterminalNode, arg0: NonterminalNode | TerminalNode) => T; + octalEscapeSequence_Whole?: ( + this: NonterminalNode, + arg0: NonterminalNode, + arg1: NonterminalNode, + arg2: NonterminalNode, + ) => T; + octalEscapeSequence_EightTimesfourToSeven?: ( + this: NonterminalNode, + arg0: NonterminalNode, + arg1: NonterminalNode, + ) => T; + octalEscapeSequence_EightTimesZeroToThree?: ( + this: NonterminalNode, + arg0: NonterminalNode, + arg1: NonterminalNode, + ) => T; + octalEscapeSequence_Octal?: (this: NonterminalNode, arg0: NonterminalNode) => T; + octalEscapeSequence?: (this: NonterminalNode, arg0: NonterminalNode) => T; + hexEscapeSequence?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode, arg2: NonterminalNode) => T; + unicodeEscapeSequence?: ( + this: NonterminalNode, + arg0: TerminalNode, + arg1: NonterminalNode, + arg2: NonterminalNode, + arg3: NonterminalNode, + arg4: NonterminalNode, + ) => T; + zeroToThree?: (this: NonterminalNode, arg0: TerminalNode) => T; + fourToSeven?: (this: NonterminalNode, arg0: TerminalNode) => T; + decimalDigit?: (this: NonterminalNode, arg0: TerminalNode) => T; + nonZeroDigit?: (this: NonterminalNode, arg0: TerminalNode) => T; + octalDigit?: (this: NonterminalNode, arg0: TerminalNode) => T; + regularExpressionLiteral?: ( + this: NonterminalNode, + arg0: TerminalNode, + arg1: NonterminalNode, + arg2: TerminalNode, + arg3: NonterminalNode, + ) => T; + regularExpressionBody?: (this: NonterminalNode, arg0: NonterminalNode, arg1: IterationNode) => T; + regularExpressionFirstChar?: (this: NonterminalNode, arg0: NonterminalNode) => T; + regularExpressionChar?: (this: NonterminalNode, arg0: NonterminalNode) => T; + regularExpressionBackslashSequence?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode) => T; + regularExpressionNonTerminator?: (this: NonterminalNode, arg0: NonterminalNode) => T; + regularExpressionClass?: (this: NonterminalNode, arg0: TerminalNode, arg1: IterationNode, arg2: TerminalNode) => T; + regularExpressionClassChar?: (this: NonterminalNode, arg0: NonterminalNode) => T; + regularExpressionFlags?: (this: NonterminalNode, arg0: IterationNode) => T; + multiLineCommentNoNL?: (this: NonterminalNode, arg0: TerminalNode, arg1: IterationNode, arg2: TerminalNode) => T; + identifierStart_escaped?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode) => T; + identifierStart?: (this: NonterminalNode, arg0: NonterminalNode | TerminalNode) => T; + identifierPart?: (this: NonterminalNode, arg0: NonterminalNode | TerminalNode) => T; + letter?: (this: NonterminalNode, arg0: NonterminalNode) => T; + unicodeCategoryNl?: (this: NonterminalNode, arg0: TerminalNode) => T; + unicodeDigit?: (this: NonterminalNode, arg0: TerminalNode) => T; + unicodeCombiningMark?: (this: NonterminalNode, arg0: TerminalNode) => T; + unicodeConnectorPunctuation?: (this: NonterminalNode, arg0: TerminalNode) => T; + unicodeSpaceSeparator?: (this: NonterminalNode, arg0: TerminalNode) => T; +} + +export interface JSImportsSemantics extends Semantics { + addOperation(name: string, actionDict: JSImportsActionDict): this; + extendOperation(name: string, actionDict: JSImportsActionDict): this; + addAttribute(name: string, actionDict: JSImportsActionDict): this; + extendAttribute(name: string, actionDict: JSImportsActionDict): this; +} + +export interface JSImportsGrammar extends Grammar { + createSemantics(): JSImportsSemantics; + extendSemantics(superSemantics: JSImportsSemantics): JSImportsSemantics; +} + +declare const grammar: JSImportsGrammar; +export default grammar; diff --git a/drizzle-kit/imports-checker/grammar/grammar.ohm-bundle.js b/drizzle-kit/imports-checker/grammar/grammar.ohm-bundle.js new file mode 100644 index 000000000..9a889d66f --- /dev/null +++ b/drizzle-kit/imports-checker/grammar/grammar.ohm-bundle.js @@ -0,0 +1,753 @@ +import { makeRecipe } from 'ohm-js'; +const result = makeRecipe([ + 'grammar', + { + source: + 'JSImports {\n JSImports = (Expr ";"?)*\n\n Expr = \n | comment\n | stringLiteral\n | ImportExpr\n | Rest\n\n ImportExpr =\n | "import" ImportInner "from" importSource -- From\n | "import" importSource -- NoFrom\n\n Rest = (~(ImportExpr | comment | stringLiteral) any)+\n\n ImportInner = \n | ("type" "{" NonemptyListOf ","? "}") -- Type\n | ("{" NonemptyListOf ","? "}") -- Types\n | ("{" NonemptyListOf ","? "}") -- Extended\n | (identifier ("," "type"? "{" NonemptyListOf ","? "}")?) -- Mixed\n | ("*" ("as" identifier)?) -- All\n | (identifier ("as" identifier)?) -- Default\n \n\n ImportExtendedSelection = TypeImport | Import\n ImportExtendedSelectionTypes = TypeImport\n ImportExtendedSelectionTypeless = Import\n\n Import = identifier ("as" identifier)?\n TypeImport = "type" Import ("as" identifier)?\n\n identifier = letter alnum*\n quote = "\\"" | "\'" | "`"\n notQuote = ~quote any\n importSource =\n | "\\"" notQuote+ "\\""\n | "\'" notQuote+ "\'"\n | "`" notQuote+ "`"\n\n lineTerminator = "\\n" | "\\r" | "\\u2028" | "\\u2029"\n lineTerminatorSequence = "\\n" | "\\r" ~"\\n" | "\\u2028" | "\\u2029" | "\\r\\n"\n \n comment = multiLineComment | singleLineComment\n\n multiLineComment = "/*" (~"*/" any)* "*/"\n singleLineComment = "//" (~lineTerminator any)*\n\n stringLiteral =\n | "\\"" doubleStringCharacter* "\\""\n | "\'" singleStringCharacter* "\'"\n | "`" templateStringCharacter* "`"\n doubleStringCharacter =\n | ~("\\"" | "\\\\" | lineTerminator) any -- NonEscaped\n | "\\\\" escapeSequence -- Escaped\n | lineContinuation -- LineContinuation\n singleStringCharacter =\n | ~("\'" | "\\\\" | lineTerminator) any -- NonEscaped\n | "\\\\" escapeSequence -- Escaped\n | lineContinuation -- LineContinuation\n templateStringCharacter = \n | ~ ("`" | "\\\\") any -- NonEscaped\n | "\\\\" escapeSequence -- Escaped\n lineContinuation = "\\\\" lineTerminatorSequence\n escapeSequence = unicodeEscapeSequence | hexEscapeSequence | octalEscapeSequence | characterEscapeSequence\n characterEscapeSequence = singleEscapeCharacter | nonEscapeCharacter\n singleEscapeCharacter = "\'" | "\\"" | "\\\\" | "b" | "f" | "n" | "r" | "t" | "v"\n nonEscapeCharacter = ~(escapeCharacter | lineTerminator) any\n escapeCharacter = singleEscapeCharacter | decimalDigit | "x" | "u"\n octalEscapeSequence =\n | zeroToThree octalDigit octalDigit -- Whole\n | fourToSeven octalDigit -- EightTimesfourToSeven\n | zeroToThree octalDigit ~decimalDigit -- EightTimesZeroToThree\n | octalDigit ~decimalDigit -- Octal\n hexEscapeSequence = "x" hexDigit hexDigit\n unicodeEscapeSequence = "u" hexDigit hexDigit hexDigit hexDigit\n\n zeroToThree = "0".."3"\n fourToSeven = "4".."7"\n decimalDigit = "0".."9"\n nonZeroDigit = "1".."9"\n octalDigit = "0".."7"\n\n regularExpressionLiteral = "/" regularExpressionBody "/" regularExpressionFlags\n regularExpressionBody = regularExpressionFirstChar regularExpressionChar*\n regularExpressionFirstChar =\n | ~("*" | "\\\\" | "/" | "[") regularExpressionNonTerminator\n | regularExpressionBackslashSequence\n | regularExpressionClass\n regularExpressionChar = ~("\\\\" | "/" | "[") regularExpressionNonTerminator\n | regularExpressionBackslashSequence\n | regularExpressionClass\n regularExpressionBackslashSequence = "\\\\" regularExpressionNonTerminator\n regularExpressionNonTerminator = ~(lineTerminator) any\n regularExpressionClass = "[" regularExpressionClassChar* "]"\n regularExpressionClassChar =\n | ~("]" | "\\\\") regularExpressionNonTerminator\n | regularExpressionBackslashSequence\n regularExpressionFlags = identifierPart*\n\n multiLineCommentNoNL = "/*" (~("*/" | lineTerminator) any)* "*/"\n\n identifierStart =\n | letter | "$" | "_"\n | "\\\\" unicodeEscapeSequence -- escaped\n identifierPart =\n | identifierStart | unicodeCombiningMark\n | unicodeDigit | unicodeConnectorPunctuation\n | "\\u200C" | "\\u200D"\n letter += unicodeCategoryNl\n unicodeCategoryNl\n = "\\u2160".."\\u2182" | "\\u3007" | "\\u3021".."\\u3029"\n unicodeDigit (a digit)\n = "\\u0030".."\\u0039" | "\\u0660".."\\u0669" | "\\u06F0".."\\u06F9" | "\\u0966".."\\u096F" | "\\u09E6".."\\u09EF" | "\\u0A66".."\\u0A6F" | "\\u0AE6".."\\u0AEF" | "\\u0B66".."\\u0B6F" | "\\u0BE7".."\\u0BEF" | "\\u0C66".."\\u0C6F" | "\\u0CE6".."\\u0CEF" | "\\u0D66".."\\u0D6F" | "\\u0E50".."\\u0E59" | "\\u0ED0".."\\u0ED9" | "\\u0F20".."\\u0F29" | "\\uFF10".."\\uFF19"\n\n unicodeCombiningMark (a Unicode combining mark)\n = "\\u0300".."\\u0345" | "\\u0360".."\\u0361" | "\\u0483".."\\u0486" | "\\u0591".."\\u05A1" | "\\u05A3".."\\u05B9" | "\\u05BB".."\\u05BD" | "\\u05BF".."\\u05BF" | "\\u05C1".."\\u05C2" | "\\u05C4".."\\u05C4" | "\\u064B".."\\u0652" | "\\u0670".."\\u0670" | "\\u06D6".."\\u06DC" | "\\u06DF".."\\u06E4" | "\\u06E7".."\\u06E8" | "\\u06EA".."\\u06ED" | "\\u0901".."\\u0902" | "\\u093C".."\\u093C" | "\\u0941".."\\u0948" | "\\u094D".."\\u094D" | "\\u0951".."\\u0954" | "\\u0962".."\\u0963" | "\\u0981".."\\u0981" | "\\u09BC".."\\u09BC" | "\\u09C1".."\\u09C4" | "\\u09CD".."\\u09CD" | "\\u09E2".."\\u09E3" | "\\u0A02".."\\u0A02" | "\\u0A3C".."\\u0A3C" | "\\u0A41".."\\u0A42" | "\\u0A47".."\\u0A48" | "\\u0A4B".."\\u0A4D" | "\\u0A70".."\\u0A71" | "\\u0A81".."\\u0A82" | "\\u0ABC".."\\u0ABC" | "\\u0AC1".."\\u0AC5" | "\\u0AC7".."\\u0AC8" | "\\u0ACD".."\\u0ACD" | "\\u0B01".."\\u0B01" | "\\u0B3C".."\\u0B3C" | "\\u0B3F".."\\u0B3F" | "\\u0B41".."\\u0B43" | "\\u0B4D".."\\u0B4D" | "\\u0B56".."\\u0B56" | "\\u0B82".."\\u0B82" | "\\u0BC0".."\\u0BC0" | "\\u0BCD".."\\u0BCD" | "\\u0C3E".."\\u0C40" | "\\u0C46".."\\u0C48" | "\\u0C4A".."\\u0C4D" | "\\u0C55".."\\u0C56" | "\\u0CBF".."\\u0CBF" | "\\u0CC6".."\\u0CC6" | "\\u0CCC".."\\u0CCD" | "\\u0D41".."\\u0D43" | "\\u0D4D".."\\u0D4D" | "\\u0E31".."\\u0E31" | "\\u0E34".."\\u0E3A" | "\\u0E47".."\\u0E4E" | "\\u0EB1".."\\u0EB1" | "\\u0EB4".."\\u0EB9" | "\\u0EBB".."\\u0EBC" | "\\u0EC8".."\\u0ECD" | "\\u0F18".."\\u0F19" | "\\u0F35".."\\u0F35" | "\\u0F37".."\\u0F37" | "\\u0F39".."\\u0F39" | "\\u0F71".."\\u0F7E" | "\\u0F80".."\\u0F84" | "\\u0F86".."\\u0F87" | "\\u0F90".."\\u0F95" | "\\u0F97".."\\u0F97" | "\\u0F99".."\\u0FAD" | "\\u0FB1".."\\u0FB7" | "\\u0FB9".."\\u0FB9" | "\\u20D0".."\\u20DC" | "\\u20E1".."\\u20E1" | "\\u302A".."\\u302F" | "\\u3099".."\\u309A" | "\\uFB1E".."\\uFB1E" | "\\uFE20".."\\uFE23"\n\n unicodeConnectorPunctuation = "\\u005F" | "\\u203F".."\\u2040" | "\\u30FB" | "\\uFE33".."\\uFE34" | "\\uFE4D".."\\uFE4F" | "\\uFF3F" | "\\uFF65"\n unicodeSpaceSeparator = "\\u2000".."\\u200B" | "\\u3000"\n\n}', + }, + 'JSImports', + null, + 'JSImports', + { + JSImports: ['define', { sourceInterval: [16, 40] }, null, [], ['star', { sourceInterval: [28, 40] }, [ + 'seq', + { sourceInterval: [29, 38] }, + ['app', { sourceInterval: [29, 33] }, 'Expr', []], + ['opt', { sourceInterval: [34, 38] }, ['terminal', { sourceInterval: [34, 37] }, ';']], + ]]], + Expr: ['define', { sourceInterval: [46, 115] }, null, [], [ + 'alt', + { sourceInterval: [58, 115] }, + ['app', { sourceInterval: [60, 67] }, 'comment', []], + ['app', { sourceInterval: [74, 87] }, 'stringLiteral', []], + ['app', { sourceInterval: [94, 104] }, 'ImportExpr', []], + ['app', { sourceInterval: [111, 115] }, 'Rest', []], + ]], + ImportExpr_From: ['define', { sourceInterval: [140, 188] }, null, [], [ + 'seq', + { sourceInterval: [140, 180] }, + ['terminal', { sourceInterval: [140, 148] }, 'import'], + ['app', { sourceInterval: [149, 160] }, 'ImportInner', []], + ['terminal', { sourceInterval: [161, 167] }, 'from'], + ['app', { sourceInterval: [168, 180] }, 'importSource', []], + ]], + ImportExpr_NoFrom: ['define', { sourceInterval: [195, 226] }, null, [], ['seq', { sourceInterval: [195, 216] }, [ + 'terminal', + { sourceInterval: [195, 203] }, + 'import', + ], ['app', { sourceInterval: [204, 216] }, 'importSource', []]]], + ImportExpr: ['define', { sourceInterval: [121, 226] }, null, [], ['alt', { sourceInterval: [138, 226] }, [ + 'app', + { sourceInterval: [140, 180] }, + 'ImportExpr_From', + [], + ], ['app', { sourceInterval: [195, 216] }, 'ImportExpr_NoFrom', []]]], + Rest: ['define', { sourceInterval: [232, 285] }, null, [], ['plus', { sourceInterval: [239, 285] }, ['seq', { + sourceInterval: [240, 283], + }, ['not', { sourceInterval: [240, 279] }, [ + 'alt', + { sourceInterval: [242, 278] }, + ['app', { sourceInterval: [242, 252] }, 'ImportExpr', []], + ['app', { sourceInterval: [255, 262] }, 'comment', []], + ['app', { sourceInterval: [265, 278] }, 'stringLiteral', []], + ]], ['app', { sourceInterval: [280, 283] }, 'any', []]]]], + ImportInner_Type: ['define', { sourceInterval: [312, 405] }, null, [], [ + 'seq', + { sourceInterval: [312, 386] }, + ['terminal', { sourceInterval: [313, 319] }, 'type'], + ['terminal', { sourceInterval: [320, 323] }, '{'], + ['app', { sourceInterval: [324, 376] }, 'NonemptyListOf', [[ + 'app', + { sourceInterval: [339, 370] }, + 'ImportExtendedSelectionTypeless', + [], + ], ['terminal', { sourceInterval: [372, 375] }, ',']]], + ['opt', { sourceInterval: [377, 381] }, ['terminal', { sourceInterval: [377, 380] }, ',']], + ['terminal', { sourceInterval: [382, 385] }, '}'], + ]], + ImportInner_Types: ['define', { sourceInterval: [412, 506] }, null, [], ['seq', { sourceInterval: [412, 476] }, [ + 'terminal', + { sourceInterval: [413, 416] }, + '{', + ], ['app', { sourceInterval: [417, 466] }, 'NonemptyListOf', [[ + 'app', + { sourceInterval: [432, 460] }, + 'ImportExtendedSelectionTypes', + [], + ], ['terminal', { sourceInterval: [462, 465] }, ',']]], ['opt', { sourceInterval: [467, 471] }, ['terminal', { + sourceInterval: [467, 470], + }, ',']], ['terminal', { sourceInterval: [472, 475] }, '}']]], + ImportInner_Extended: ['define', { sourceInterval: [513, 610] }, null, [], ['seq', { sourceInterval: [513, 572] }, [ + 'terminal', + { sourceInterval: [514, 517] }, + '{', + ], ['app', { sourceInterval: [518, 562] }, 'NonemptyListOf', [[ + 'app', + { sourceInterval: [533, 556] }, + 'ImportExtendedSelection', + [], + ], ['terminal', { sourceInterval: [558, 561] }, ',']]], ['opt', { sourceInterval: [563, 567] }, ['terminal', { + sourceInterval: [563, 566], + }, ',']], ['terminal', { sourceInterval: [568, 571] }, '}']]], + ImportInner_Mixed: ['define', { sourceInterval: [617, 711] }, null, [], ['seq', { sourceInterval: [617, 702] }, [ + 'app', + { sourceInterval: [618, 628] }, + 'identifier', + [], + ], ['opt', { sourceInterval: [629, 701] }, [ + 'seq', + { sourceInterval: [630, 699] }, + ['terminal', { sourceInterval: [630, 633] }, ','], + ['opt', { sourceInterval: [634, 641] }, ['terminal', { sourceInterval: [634, 640] }, 'type']], + ['terminal', { sourceInterval: [642, 645] }, '{'], + ['app', { sourceInterval: [646, 690] }, 'NonemptyListOf', [[ + 'app', + { sourceInterval: [661, 684] }, + 'ImportExtendedSelection', + [], + ], ['terminal', { sourceInterval: [686, 689] }, ',']]], + ['opt', { sourceInterval: [691, 695] }, ['terminal', { sourceInterval: [691, 694] }, ',']], + ['terminal', { sourceInterval: [696, 699] }, '}'], + ]]]], + ImportInner_All: ['define', { sourceInterval: [718, 810] }, null, [], ['seq', { sourceInterval: [718, 742] }, [ + 'terminal', + { sourceInterval: [719, 722] }, + '*', + ], ['opt', { sourceInterval: [723, 741] }, ['seq', { sourceInterval: [724, 739] }, ['terminal', { + sourceInterval: [724, 728], + }, 'as'], ['app', { sourceInterval: [729, 739] }, 'identifier', []]]]]], + ImportInner_Default: ['define', { sourceInterval: [817, 913] }, null, [], ['seq', { sourceInterval: [817, 848] }, [ + 'app', + { sourceInterval: [818, 828] }, + 'identifier', + [], + ], ['opt', { sourceInterval: [829, 847] }, ['seq', { sourceInterval: [830, 845] }, ['terminal', { + sourceInterval: [830, 834], + }, 'as'], ['app', { sourceInterval: [835, 845] }, 'identifier', []]]]]], + ImportInner: ['define', { sourceInterval: [291, 913] }, null, [], [ + 'alt', + { sourceInterval: [310, 913] }, + ['app', { sourceInterval: [312, 386] }, 'ImportInner_Type', []], + ['app', { sourceInterval: [412, 476] }, 'ImportInner_Types', []], + ['app', { sourceInterval: [513, 572] }, 'ImportInner_Extended', []], + ['app', { sourceInterval: [617, 702] }, 'ImportInner_Mixed', []], + ['app', { sourceInterval: [718, 742] }, 'ImportInner_All', []], + ['app', { sourceInterval: [817, 848] }, 'ImportInner_Default', []], + ]], + ImportExtendedSelection: ['define', { sourceInterval: [924, 969] }, null, [], [ + 'alt', + { sourceInterval: [950, 969] }, + ['app', { sourceInterval: [950, 960] }, 'TypeImport', []], + ['app', { sourceInterval: [963, 969] }, 'Import', []], + ]], + ImportExtendedSelectionTypes: ['define', { sourceInterval: [974, 1015] }, null, [], [ + 'app', + { sourceInterval: [1005, 1015] }, + 'TypeImport', + [], + ]], + ImportExtendedSelectionTypeless: ['define', { sourceInterval: [1020, 1060] }, null, [], [ + 'app', + { sourceInterval: [1054, 1060] }, + 'Import', + [], + ]], + Import: ['define', { sourceInterval: [1066, 1104] }, null, [], ['seq', { sourceInterval: [1075, 1104] }, [ + 'app', + { sourceInterval: [1075, 1085] }, + 'identifier', + [], + ], ['opt', { sourceInterval: [1086, 1104] }, ['seq', { sourceInterval: [1087, 1102] }, ['terminal', { + sourceInterval: [1087, 1091], + }, 'as'], ['app', { sourceInterval: [1092, 1102] }, 'identifier', []]]]]], + TypeImport: ['define', { sourceInterval: [1109, 1154] }, null, [], [ + 'seq', + { sourceInterval: [1122, 1154] }, + ['terminal', { sourceInterval: [1122, 1128] }, 'type'], + ['app', { sourceInterval: [1129, 1135] }, 'Import', []], + ['opt', { sourceInterval: [1136, 1154] }, ['seq', { sourceInterval: [1137, 1152] }, ['terminal', { + sourceInterval: [1137, 1141], + }, 'as'], ['app', { sourceInterval: [1142, 1152] }, 'identifier', []]]], + ]], + identifier: ['define', { sourceInterval: [1160, 1186] }, null, [], ['seq', { sourceInterval: [1173, 1186] }, [ + 'app', + { sourceInterval: [1173, 1179] }, + 'letter', + [], + ], ['star', { sourceInterval: [1180, 1186] }, ['app', { sourceInterval: [1180, 1185] }, 'alnum', []]]]], + quote: ['define', { sourceInterval: [1191, 1215] }, null, [], [ + 'alt', + { sourceInterval: [1199, 1215] }, + ['terminal', { sourceInterval: [1199, 1203] }, '"'], + ['terminal', { sourceInterval: [1206, 1209] }, "'"], + ['terminal', { sourceInterval: [1212, 1215] }, '`'], + ]], + notQuote: ['define', { sourceInterval: [1220, 1241] }, null, [], ['seq', { sourceInterval: [1231, 1241] }, ['not', { + sourceInterval: [1231, 1237], + }, ['app', { sourceInterval: [1232, 1237] }, 'quote', []]], ['app', { sourceInterval: [1238, 1241] }, 'any', []]]], + importSource: ['define', { sourceInterval: [1246, 1334] }, null, [], [ + 'alt', + { sourceInterval: [1265, 1334] }, + ['seq', { sourceInterval: [1267, 1286] }, ['terminal', { sourceInterval: [1267, 1271] }, '"'], ['plus', { + sourceInterval: [1272, 1281], + }, ['app', { sourceInterval: [1272, 1280] }, 'notQuote', []]], [ + 'terminal', + { sourceInterval: [1282, 1286] }, + '"', + ]], + ['seq', { sourceInterval: [1293, 1310] }, ['terminal', { sourceInterval: [1293, 1296] }, "'"], ['plus', { + sourceInterval: [1297, 1306], + }, ['app', { sourceInterval: [1297, 1305] }, 'notQuote', []]], [ + 'terminal', + { sourceInterval: [1307, 1310] }, + "'", + ]], + ['seq', { sourceInterval: [1317, 1334] }, ['terminal', { sourceInterval: [1317, 1320] }, '`'], ['plus', { + sourceInterval: [1321, 1330], + }, ['app', { sourceInterval: [1321, 1329] }, 'notQuote', []]], [ + 'terminal', + { sourceInterval: [1331, 1334] }, + '`', + ]], + ]], + lineTerminator: ['define', { sourceInterval: [1340, 1390] }, null, [], [ + 'alt', + { sourceInterval: [1357, 1390] }, + ['terminal', { sourceInterval: [1357, 1361] }, '\n'], + ['terminal', { sourceInterval: [1364, 1368] }, '\r'], + ['terminal', { sourceInterval: [1371, 1379] }, '\u2028'], + ['terminal', { sourceInterval: [1382, 1390] }, '\u2029'], + ]], + lineTerminatorSequence: ['define', { sourceInterval: [1395, 1468] }, null, [], [ + 'alt', + { sourceInterval: [1420, 1468] }, + ['terminal', { sourceInterval: [1420, 1424] }, '\n'], + ['seq', { sourceInterval: [1427, 1437] }, ['terminal', { sourceInterval: [1427, 1431] }, '\r'], ['not', { + sourceInterval: [1432, 1437], + }, ['terminal', { sourceInterval: [1433, 1437] }, '\n']]], + ['terminal', { sourceInterval: [1440, 1448] }, '\u2028'], + ['terminal', { sourceInterval: [1451, 1459] }, '\u2029'], + ['terminal', { sourceInterval: [1462, 1468] }, '\r\n'], + ]], + comment: ['define', { sourceInterval: [1478, 1524] }, null, [], ['alt', { sourceInterval: [1488, 1524] }, [ + 'app', + { sourceInterval: [1488, 1504] }, + 'multiLineComment', + [], + ], ['app', { sourceInterval: [1507, 1524] }, 'singleLineComment', []]]], + multiLineComment: ['define', { sourceInterval: [1530, 1571] }, null, [], ['seq', { sourceInterval: [1549, 1571] }, [ + 'terminal', + { sourceInterval: [1549, 1553] }, + '/*', + ], ['star', { sourceInterval: [1554, 1566] }, ['seq', { sourceInterval: [1555, 1564] }, ['not', { + sourceInterval: [1555, 1560], + }, ['terminal', { sourceInterval: [1556, 1560] }, '*/']], ['app', { sourceInterval: [1561, 1564] }, 'any', []]]], [ + 'terminal', + { sourceInterval: [1567, 1571] }, + '*/', + ]]], + singleLineComment: ['define', { sourceInterval: [1576, 1623] }, null, [], [ + 'seq', + { sourceInterval: [1596, 1623] }, + ['terminal', { sourceInterval: [1596, 1600] }, '//'], + ['star', { sourceInterval: [1601, 1623] }, ['seq', { sourceInterval: [1602, 1621] }, ['not', { + sourceInterval: [1602, 1617], + }, ['app', { sourceInterval: [1603, 1617] }, 'lineTerminator', []]], [ + 'app', + { sourceInterval: [1618, 1621] }, + 'any', + [], + ]]], + ]], + stringLiteral: ['define', { sourceInterval: [1629, 1759] }, null, [], ['alt', { sourceInterval: [1649, 1759] }, [ + 'seq', + { sourceInterval: [1651, 1683] }, + ['terminal', { sourceInterval: [1651, 1655] }, '"'], + ['star', { sourceInterval: [1656, 1678] }, [ + 'app', + { sourceInterval: [1656, 1677] }, + 'doubleStringCharacter', + [], + ]], + ['terminal', { sourceInterval: [1679, 1683] }, '"'], + ], ['seq', { sourceInterval: [1690, 1720] }, ['terminal', { sourceInterval: [1690, 1693] }, "'"], ['star', { + sourceInterval: [1694, 1716], + }, ['app', { sourceInterval: [1694, 1715] }, 'singleStringCharacter', []]], ['terminal', { + sourceInterval: [1717, 1720], + }, "'"]], ['seq', { sourceInterval: [1727, 1759] }, ['terminal', { sourceInterval: [1727, 1730] }, '`'], ['star', { + sourceInterval: [1731, 1755], + }, ['app', { sourceInterval: [1731, 1754] }, 'templateStringCharacter', []]], ['terminal', { + sourceInterval: [1756, 1759], + }, '`']]]], + doubleStringCharacter_NonEscaped: ['define', { sourceInterval: [1794, 1845] }, null, [], ['seq', { + sourceInterval: [1794, 1829], + }, ['not', { sourceInterval: [1794, 1825] }, [ + 'alt', + { sourceInterval: [1796, 1824] }, + ['terminal', { sourceInterval: [1796, 1800] }, '"'], + ['terminal', { sourceInterval: [1803, 1807] }, '\\'], + ['app', { sourceInterval: [1810, 1824] }, 'lineTerminator', []], + ]], ['app', { sourceInterval: [1826, 1829] }, 'any', []]]], + doubleStringCharacter_Escaped: ['define', { sourceInterval: [1852, 1900] }, null, [], [ + 'seq', + { sourceInterval: [1852, 1871] }, + ['terminal', { sourceInterval: [1852, 1856] }, '\\'], + ['app', { sourceInterval: [1857, 1871] }, 'escapeSequence', []], + ]], + doubleStringCharacter_LineContinuation: ['define', { sourceInterval: [1907, 1964] }, null, [], [ + 'app', + { sourceInterval: [1907, 1923] }, + 'lineContinuation', + [], + ]], + doubleStringCharacter: ['define', { sourceInterval: [1764, 1964] }, null, [], [ + 'alt', + { sourceInterval: [1792, 1964] }, + ['app', { sourceInterval: [1794, 1829] }, 'doubleStringCharacter_NonEscaped', []], + ['app', { sourceInterval: [1852, 1871] }, 'doubleStringCharacter_Escaped', []], + ['app', { sourceInterval: [1907, 1923] }, 'doubleStringCharacter_LineContinuation', []], + ]], + singleStringCharacter_NonEscaped: ['define', { sourceInterval: [1999, 2050] }, null, [], ['seq', { + sourceInterval: [1999, 2033], + }, ['not', { sourceInterval: [1999, 2029] }, [ + 'alt', + { sourceInterval: [2001, 2028] }, + ['terminal', { sourceInterval: [2001, 2004] }, "'"], + ['terminal', { sourceInterval: [2007, 2011] }, '\\'], + ['app', { sourceInterval: [2014, 2028] }, 'lineTerminator', []], + ]], ['app', { sourceInterval: [2030, 2033] }, 'any', []]]], + singleStringCharacter_Escaped: ['define', { sourceInterval: [2057, 2105] }, null, [], [ + 'seq', + { sourceInterval: [2057, 2076] }, + ['terminal', { sourceInterval: [2057, 2061] }, '\\'], + ['app', { sourceInterval: [2062, 2076] }, 'escapeSequence', []], + ]], + singleStringCharacter_LineContinuation: ['define', { sourceInterval: [2112, 2169] }, null, [], [ + 'app', + { sourceInterval: [2112, 2128] }, + 'lineContinuation', + [], + ]], + singleStringCharacter: ['define', { sourceInterval: [1969, 2169] }, null, [], [ + 'alt', + { sourceInterval: [1997, 2169] }, + ['app', { sourceInterval: [1999, 2033] }, 'singleStringCharacter_NonEscaped', []], + ['app', { sourceInterval: [2057, 2076] }, 'singleStringCharacter_Escaped', []], + ['app', { sourceInterval: [2112, 2128] }, 'singleStringCharacter_LineContinuation', []], + ]], + templateStringCharacter_NonEscaped: ['define', { sourceInterval: [2207, 2258] }, null, [], ['seq', { + sourceInterval: [2207, 2225], + }, ['not', { sourceInterval: [2207, 2221] }, ['alt', { sourceInterval: [2210, 2220] }, ['terminal', { + sourceInterval: [2210, 2213], + }, '`'], ['terminal', { sourceInterval: [2216, 2220] }, '\\']]], [ + 'app', + { sourceInterval: [2222, 2225] }, + 'any', + [], + ]]], + templateStringCharacter_Escaped: ['define', { sourceInterval: [2265, 2318] }, null, [], [ + 'seq', + { sourceInterval: [2265, 2284] }, + ['terminal', { sourceInterval: [2265, 2269] }, '\\'], + ['app', { sourceInterval: [2270, 2284] }, 'escapeSequence', []], + ]], + templateStringCharacter: ['define', { sourceInterval: [2174, 2318] }, null, [], [ + 'alt', + { sourceInterval: [2205, 2318] }, + ['app', { sourceInterval: [2207, 2225] }, 'templateStringCharacter_NonEscaped', []], + ['app', { sourceInterval: [2265, 2284] }, 'templateStringCharacter_Escaped', []], + ]], + lineContinuation: ['define', { sourceInterval: [2323, 2369] }, null, [], ['seq', { sourceInterval: [2342, 2369] }, [ + 'terminal', + { sourceInterval: [2342, 2346] }, + '\\', + ], ['app', { sourceInterval: [2347, 2369] }, 'lineTerminatorSequence', []]]], + escapeSequence: ['define', { sourceInterval: [2374, 2480] }, null, [], [ + 'alt', + { sourceInterval: [2391, 2480] }, + ['app', { sourceInterval: [2391, 2412] }, 'unicodeEscapeSequence', []], + ['app', { sourceInterval: [2415, 2432] }, 'hexEscapeSequence', []], + ['app', { sourceInterval: [2435, 2454] }, 'octalEscapeSequence', []], + ['app', { sourceInterval: [2457, 2480] }, 'characterEscapeSequence', []], + ]], + characterEscapeSequence: ['define', { sourceInterval: [2485, 2553] }, null, [], [ + 'alt', + { sourceInterval: [2511, 2553] }, + ['app', { sourceInterval: [2511, 2532] }, 'singleEscapeCharacter', []], + ['app', { sourceInterval: [2535, 2553] }, 'nonEscapeCharacter', []], + ]], + singleEscapeCharacter: ['define', { sourceInterval: [2558, 2635] }, null, [], [ + 'alt', + { sourceInterval: [2582, 2635] }, + ['terminal', { sourceInterval: [2582, 2585] }, "'"], + ['terminal', { sourceInterval: [2588, 2592] }, '"'], + ['terminal', { sourceInterval: [2595, 2599] }, '\\'], + ['terminal', { sourceInterval: [2602, 2605] }, 'b'], + ['terminal', { sourceInterval: [2608, 2611] }, 'f'], + ['terminal', { sourceInterval: [2614, 2617] }, 'n'], + ['terminal', { sourceInterval: [2620, 2623] }, 'r'], + ['terminal', { sourceInterval: [2626, 2629] }, 't'], + ['terminal', { sourceInterval: [2632, 2635] }, 'v'], + ]], + nonEscapeCharacter: ['define', { sourceInterval: [2640, 2700] }, null, [], [ + 'seq', + { sourceInterval: [2661, 2700] }, + ['not', { sourceInterval: [2661, 2696] }, ['alt', { sourceInterval: [2663, 2695] }, [ + 'app', + { sourceInterval: [2663, 2678] }, + 'escapeCharacter', + [], + ], ['app', { sourceInterval: [2681, 2695] }, 'lineTerminator', []]]], + ['app', { sourceInterval: [2697, 2700] }, 'any', []], + ]], + escapeCharacter: ['define', { sourceInterval: [2705, 2771] }, null, [], [ + 'alt', + { sourceInterval: [2723, 2771] }, + ['app', { sourceInterval: [2723, 2744] }, 'singleEscapeCharacter', []], + ['app', { sourceInterval: [2747, 2759] }, 'decimalDigit', []], + ['terminal', { sourceInterval: [2762, 2765] }, 'x'], + ['terminal', { sourceInterval: [2768, 2771] }, 'u'], + ]], + octalEscapeSequence_Whole: ['define', { sourceInterval: [2804, 2850] }, null, [], [ + 'seq', + { sourceInterval: [2804, 2837] }, + ['app', { sourceInterval: [2804, 2815] }, 'zeroToThree', []], + ['app', { sourceInterval: [2816, 2826] }, 'octalDigit', []], + ['app', { sourceInterval: [2827, 2837] }, 'octalDigit', []], + ]], + octalEscapeSequence_EightTimesfourToSeven: ['define', { sourceInterval: [2857, 2919] }, null, [], [ + 'seq', + { sourceInterval: [2857, 2879] }, + ['app', { sourceInterval: [2857, 2868] }, 'fourToSeven', []], + ['app', { sourceInterval: [2869, 2879] }, 'octalDigit', []], + ]], + octalEscapeSequence_EightTimesZeroToThree: ['define', { sourceInterval: [2926, 2988] }, null, [], [ + 'seq', + { sourceInterval: [2926, 2962] }, + ['app', { sourceInterval: [2926, 2937] }, 'zeroToThree', []], + ['app', { sourceInterval: [2938, 2948] }, 'octalDigit', []], + ['not', { sourceInterval: [2949, 2962] }, ['app', { sourceInterval: [2950, 2962] }, 'decimalDigit', []]], + ]], + octalEscapeSequence_Octal: ['define', { sourceInterval: [2995, 3041] }, null, [], [ + 'seq', + { sourceInterval: [2995, 3019] }, + ['app', { sourceInterval: [2995, 3005] }, 'octalDigit', []], + ['not', { sourceInterval: [3006, 3019] }, ['app', { sourceInterval: [3007, 3019] }, 'decimalDigit', []]], + ]], + octalEscapeSequence: ['define', { sourceInterval: [2776, 3041] }, null, [], [ + 'alt', + { sourceInterval: [2802, 3041] }, + ['app', { sourceInterval: [2804, 2837] }, 'octalEscapeSequence_Whole', []], + ['app', { sourceInterval: [2857, 2879] }, 'octalEscapeSequence_EightTimesfourToSeven', []], + ['app', { sourceInterval: [2926, 2962] }, 'octalEscapeSequence_EightTimesZeroToThree', []], + ['app', { sourceInterval: [2995, 3019] }, 'octalEscapeSequence_Octal', []], + ]], + hexEscapeSequence: ['define', { sourceInterval: [3046, 3087] }, null, [], [ + 'seq', + { sourceInterval: [3066, 3087] }, + ['terminal', { sourceInterval: [3066, 3069] }, 'x'], + ['app', { sourceInterval: [3070, 3078] }, 'hexDigit', []], + ['app', { sourceInterval: [3079, 3087] }, 'hexDigit', []], + ]], + unicodeEscapeSequence: ['define', { sourceInterval: [3092, 3155] }, null, [], [ + 'seq', + { sourceInterval: [3116, 3155] }, + ['terminal', { sourceInterval: [3116, 3119] }, 'u'], + ['app', { sourceInterval: [3120, 3128] }, 'hexDigit', []], + ['app', { sourceInterval: [3129, 3137] }, 'hexDigit', []], + ['app', { sourceInterval: [3138, 3146] }, 'hexDigit', []], + ['app', { sourceInterval: [3147, 3155] }, 'hexDigit', []], + ]], + zeroToThree: ['define', { sourceInterval: [3161, 3183] }, null, [], [ + 'range', + { sourceInterval: [3175, 3183] }, + '0', + '3', + ]], + fourToSeven: ['define', { sourceInterval: [3188, 3210] }, null, [], [ + 'range', + { sourceInterval: [3202, 3210] }, + '4', + '7', + ]], + decimalDigit: ['define', { sourceInterval: [3215, 3238] }, null, [], [ + 'range', + { sourceInterval: [3230, 3238] }, + '0', + '9', + ]], + nonZeroDigit: ['define', { sourceInterval: [3243, 3266] }, null, [], [ + 'range', + { sourceInterval: [3258, 3266] }, + '1', + '9', + ]], + octalDigit: ['define', { sourceInterval: [3271, 3292] }, null, [], [ + 'range', + { sourceInterval: [3284, 3292] }, + '0', + '7', + ]], + regularExpressionLiteral: ['define', { sourceInterval: [3298, 3377] }, null, [], [ + 'seq', + { sourceInterval: [3325, 3377] }, + ['terminal', { sourceInterval: [3325, 3328] }, '/'], + ['app', { sourceInterval: [3329, 3350] }, 'regularExpressionBody', []], + ['terminal', { sourceInterval: [3351, 3354] }, '/'], + ['app', { sourceInterval: [3355, 3377] }, 'regularExpressionFlags', []], + ]], + regularExpressionBody: ['define', { sourceInterval: [3382, 3455] }, null, [], [ + 'seq', + { sourceInterval: [3406, 3455] }, + ['app', { sourceInterval: [3406, 3432] }, 'regularExpressionFirstChar', []], + ['star', { sourceInterval: [3433, 3455] }, [ + 'app', + { sourceInterval: [3433, 3454] }, + 'regularExpressionChar', + [], + ]], + ]], + regularExpressionFirstChar: ['define', { sourceInterval: [3460, 3621] }, null, [], ['alt', { + sourceInterval: [3493, 3621], + }, ['seq', { sourceInterval: [3495, 3551] }, ['not', { sourceInterval: [3495, 3520] }, [ + 'alt', + { sourceInterval: [3497, 3519] }, + ['terminal', { sourceInterval: [3497, 3500] }, '*'], + ['terminal', { sourceInterval: [3503, 3507] }, '\\'], + ['terminal', { sourceInterval: [3510, 3513] }, '/'], + ['terminal', { sourceInterval: [3516, 3519] }, '['], + ]], ['app', { sourceInterval: [3521, 3551] }, 'regularExpressionNonTerminator', []]], [ + 'app', + { sourceInterval: [3558, 3592] }, + 'regularExpressionBackslashSequence', + [], + ], ['app', { sourceInterval: [3599, 3621] }, 'regularExpressionClass', []]]], + regularExpressionChar: ['define', { sourceInterval: [3626, 3770] }, null, [], ['alt', { + sourceInterval: [3650, 3770], + }, ['seq', { sourceInterval: [3650, 3700] }, ['not', { sourceInterval: [3650, 3669] }, [ + 'alt', + { sourceInterval: [3652, 3668] }, + ['terminal', { sourceInterval: [3652, 3656] }, '\\'], + ['terminal', { sourceInterval: [3659, 3662] }, '/'], + ['terminal', { sourceInterval: [3665, 3668] }, '['], + ]], ['app', { sourceInterval: [3670, 3700] }, 'regularExpressionNonTerminator', []]], [ + 'app', + { sourceInterval: [3707, 3741] }, + 'regularExpressionBackslashSequence', + [], + ], ['app', { sourceInterval: [3748, 3770] }, 'regularExpressionClass', []]]], + regularExpressionBackslashSequence: ['define', { sourceInterval: [3775, 3847] }, null, [], [ + 'seq', + { sourceInterval: [3812, 3847] }, + ['terminal', { sourceInterval: [3812, 3816] }, '\\'], + ['app', { sourceInterval: [3817, 3847] }, 'regularExpressionNonTerminator', []], + ]], + regularExpressionNonTerminator: ['define', { sourceInterval: [3852, 3906] }, null, [], [ + 'seq', + { sourceInterval: [3885, 3906] }, + ['not', { sourceInterval: [3885, 3902] }, ['app', { sourceInterval: [3887, 3901] }, 'lineTerminator', []]], + ['app', { sourceInterval: [3903, 3906] }, 'any', []], + ]], + regularExpressionClass: ['define', { sourceInterval: [3911, 3971] }, null, [], [ + 'seq', + { sourceInterval: [3936, 3971] }, + ['terminal', { sourceInterval: [3936, 3939] }, '['], + ['star', { sourceInterval: [3940, 3967] }, [ + 'app', + { sourceInterval: [3940, 3966] }, + 'regularExpressionClassChar', + [], + ]], + ['terminal', { sourceInterval: [3968, 3971] }, ']'], + ]], + regularExpressionClassChar: ['define', { sourceInterval: [3976, 4096] }, null, [], ['alt', { + sourceInterval: [4009, 4096], + }, ['seq', { sourceInterval: [4011, 4055] }, ['not', { sourceInterval: [4011, 4024] }, [ + 'alt', + { sourceInterval: [4013, 4023] }, + ['terminal', { sourceInterval: [4013, 4016] }, ']'], + ['terminal', { sourceInterval: [4019, 4023] }, '\\'], + ]], ['app', { sourceInterval: [4025, 4055] }, 'regularExpressionNonTerminator', []]], [ + 'app', + { sourceInterval: [4062, 4096] }, + 'regularExpressionBackslashSequence', + [], + ]]], + regularExpressionFlags: ['define', { sourceInterval: [4101, 4141] }, null, [], ['star', { + sourceInterval: [4126, 4141], + }, ['app', { sourceInterval: [4126, 4140] }, 'identifierPart', []]]], + multiLineCommentNoNL: ['define', { sourceInterval: [4147, 4211] }, null, [], [ + 'seq', + { sourceInterval: [4170, 4211] }, + ['terminal', { sourceInterval: [4170, 4174] }, '/*'], + ['star', { sourceInterval: [4175, 4206] }, ['seq', { sourceInterval: [4176, 4204] }, ['not', { + sourceInterval: [4176, 4200], + }, ['alt', { sourceInterval: [4178, 4199] }, ['terminal', { sourceInterval: [4178, 4182] }, '*/'], [ + 'app', + { sourceInterval: [4185, 4199] }, + 'lineTerminator', + [], + ]]], ['app', { sourceInterval: [4201, 4204] }, 'any', []]]], + ['terminal', { sourceInterval: [4207, 4211] }, '*/'], + ]], + identifierStart_escaped: ['define', { sourceInterval: [4266, 4303] }, null, [], [ + 'seq', + { sourceInterval: [4266, 4292] }, + ['terminal', { sourceInterval: [4266, 4270] }, '\\'], + ['app', { sourceInterval: [4271, 4292] }, 'unicodeEscapeSequence', []], + ]], + identifierStart: ['define', { sourceInterval: [4217, 4303] }, null, [], [ + 'alt', + { sourceInterval: [4239, 4303] }, + ['app', { sourceInterval: [4241, 4247] }, 'letter', []], + ['terminal', { sourceInterval: [4250, 4253] }, '$'], + ['terminal', { sourceInterval: [4256, 4259] }, '_'], + ['app', { sourceInterval: [4266, 4292] }, 'identifierStart_escaped', []], + ]], + identifierPart: ['define', { sourceInterval: [4308, 4444] }, null, [], [ + 'alt', + { sourceInterval: [4329, 4444] }, + ['app', { sourceInterval: [4331, 4346] }, 'identifierStart', []], + ['app', { sourceInterval: [4349, 4369] }, 'unicodeCombiningMark', []], + ['app', { sourceInterval: [4376, 4388] }, 'unicodeDigit', []], + ['app', { sourceInterval: [4391, 4418] }, 'unicodeConnectorPunctuation', []], + ['terminal', { sourceInterval: [4425, 4433] }, '‌'], + ['terminal', { sourceInterval: [4436, 4444] }, '‍'], + ]], + letter: ['extend', { sourceInterval: [4449, 4476] }, null, [], [ + 'app', + { sourceInterval: [4459, 4476] }, + 'unicodeCategoryNl', + [], + ]], + unicodeCategoryNl: ['define', { sourceInterval: [4481, 4555] }, null, [], [ + 'alt', + { sourceInterval: [4505, 4555] }, + ['range', { sourceInterval: [4505, 4523] }, 'Ⅰ', 'ↂ'], + ['terminal', { sourceInterval: [4526, 4534] }, '〇'], + ['range', { sourceInterval: [4537, 4555] }, '〡', '〩'], + ]], + unicodeDigit: ['define', { sourceInterval: [4560, 4922] }, 'a digit', [], [ + 'alt', + { sourceInterval: [4589, 4922] }, + ['range', { sourceInterval: [4589, 4607] }, '0', '9'], + ['range', { sourceInterval: [4610, 4628] }, '٠', '٩'], + ['range', { sourceInterval: [4631, 4649] }, '۰', '۹'], + ['range', { sourceInterval: [4652, 4670] }, '०', '९'], + ['range', { sourceInterval: [4673, 4691] }, '০', '৯'], + ['range', { sourceInterval: [4694, 4712] }, '੦', '੯'], + ['range', { sourceInterval: [4715, 4733] }, '૦', '૯'], + ['range', { sourceInterval: [4736, 4754] }, '୦', '୯'], + ['range', { sourceInterval: [4757, 4775] }, '௧', '௯'], + ['range', { sourceInterval: [4778, 4796] }, '౦', '౯'], + ['range', { sourceInterval: [4799, 4817] }, '೦', '೯'], + ['range', { sourceInterval: [4820, 4838] }, '൦', '൯'], + ['range', { sourceInterval: [4841, 4859] }, '๐', '๙'], + ['range', { sourceInterval: [4862, 4880] }, '໐', '໙'], + ['range', { sourceInterval: [4883, 4901] }, '༠', '༩'], + ['range', { sourceInterval: [4904, 4922] }, '0', '9'], + ]], + unicodeCombiningMark: ['define', { sourceInterval: [4928, 6659] }, 'a Unicode combining mark', [], [ + 'alt', + { sourceInterval: [4982, 6659] }, + ['range', { sourceInterval: [4982, 5000] }, '̀', 'ͅ'], + ['range', { sourceInterval: [5003, 5021] }, '͠', '͡'], + ['range', { sourceInterval: [5024, 5042] }, '҃', '҆'], + ['range', { sourceInterval: [5045, 5063] }, '֑', '֡'], + ['range', { sourceInterval: [5066, 5084] }, '֣', 'ֹ'], + ['range', { sourceInterval: [5087, 5105] }, 'ֻ', 'ֽ'], + ['range', { sourceInterval: [5108, 5126] }, 'ֿ', 'ֿ'], + ['range', { sourceInterval: [5129, 5147] }, 'ׁ', 'ׂ'], + ['range', { sourceInterval: [5150, 5168] }, 'ׄ', 'ׄ'], + ['range', { sourceInterval: [5171, 5189] }, 'ً', 'ْ'], + ['range', { sourceInterval: [5192, 5210] }, 'ٰ', 'ٰ'], + ['range', { sourceInterval: [5213, 5231] }, 'ۖ', 'ۜ'], + ['range', { sourceInterval: [5234, 5252] }, '۟', 'ۤ'], + ['range', { sourceInterval: [5255, 5273] }, 'ۧ', 'ۨ'], + ['range', { sourceInterval: [5276, 5294] }, '۪', 'ۭ'], + ['range', { sourceInterval: [5297, 5315] }, 'ँ', 'ं'], + ['range', { sourceInterval: [5318, 5336] }, '़', '़'], + ['range', { sourceInterval: [5339, 5357] }, 'ु', 'ै'], + ['range', { sourceInterval: [5360, 5378] }, '्', '्'], + ['range', { sourceInterval: [5381, 5399] }, '॑', '॔'], + ['range', { sourceInterval: [5402, 5420] }, 'ॢ', 'ॣ'], + ['range', { sourceInterval: [5423, 5441] }, 'ঁ', 'ঁ'], + ['range', { sourceInterval: [5444, 5462] }, '়', '়'], + ['range', { sourceInterval: [5465, 5483] }, 'ু', 'ৄ'], + ['range', { sourceInterval: [5486, 5504] }, '্', '্'], + ['range', { sourceInterval: [5507, 5525] }, 'ৢ', 'ৣ'], + ['range', { sourceInterval: [5528, 5546] }, 'ਂ', 'ਂ'], + ['range', { sourceInterval: [5549, 5567] }, '਼', '਼'], + ['range', { sourceInterval: [5570, 5588] }, 'ੁ', 'ੂ'], + ['range', { sourceInterval: [5591, 5609] }, 'ੇ', 'ੈ'], + ['range', { sourceInterval: [5612, 5630] }, 'ੋ', '੍'], + ['range', { sourceInterval: [5633, 5651] }, 'ੰ', 'ੱ'], + ['range', { sourceInterval: [5654, 5672] }, 'ઁ', 'ં'], + ['range', { sourceInterval: [5675, 5693] }, '઼', '઼'], + ['range', { sourceInterval: [5696, 5714] }, 'ુ', 'ૅ'], + ['range', { sourceInterval: [5717, 5735] }, 'ે', 'ૈ'], + ['range', { sourceInterval: [5738, 5756] }, '્', '્'], + ['range', { sourceInterval: [5759, 5777] }, 'ଁ', 'ଁ'], + ['range', { sourceInterval: [5780, 5798] }, '଼', '଼'], + ['range', { sourceInterval: [5801, 5819] }, 'ି', 'ି'], + ['range', { sourceInterval: [5822, 5840] }, 'ୁ', 'ୃ'], + ['range', { sourceInterval: [5843, 5861] }, '୍', '୍'], + ['range', { sourceInterval: [5864, 5882] }, 'ୖ', 'ୖ'], + ['range', { sourceInterval: [5885, 5903] }, 'ஂ', 'ஂ'], + ['range', { sourceInterval: [5906, 5924] }, 'ீ', 'ீ'], + ['range', { sourceInterval: [5927, 5945] }, '்', '்'], + ['range', { sourceInterval: [5948, 5966] }, 'ా', 'ీ'], + ['range', { sourceInterval: [5969, 5987] }, 'ె', 'ై'], + ['range', { sourceInterval: [5990, 6008] }, 'ొ', '్'], + ['range', { sourceInterval: [6011, 6029] }, 'ౕ', 'ౖ'], + ['range', { sourceInterval: [6032, 6050] }, 'ಿ', 'ಿ'], + ['range', { sourceInterval: [6053, 6071] }, 'ೆ', 'ೆ'], + ['range', { sourceInterval: [6074, 6092] }, 'ೌ', '್'], + ['range', { sourceInterval: [6095, 6113] }, 'ു', 'ൃ'], + ['range', { sourceInterval: [6116, 6134] }, '്', '്'], + ['range', { sourceInterval: [6137, 6155] }, 'ั', 'ั'], + ['range', { sourceInterval: [6158, 6176] }, 'ิ', 'ฺ'], + ['range', { sourceInterval: [6179, 6197] }, '็', '๎'], + ['range', { sourceInterval: [6200, 6218] }, 'ັ', 'ັ'], + ['range', { sourceInterval: [6221, 6239] }, 'ິ', 'ູ'], + ['range', { sourceInterval: [6242, 6260] }, 'ົ', 'ຼ'], + ['range', { sourceInterval: [6263, 6281] }, '່', 'ໍ'], + ['range', { sourceInterval: [6284, 6302] }, '༘', '༙'], + ['range', { sourceInterval: [6305, 6323] }, '༵', '༵'], + ['range', { sourceInterval: [6326, 6344] }, '༷', '༷'], + ['range', { sourceInterval: [6347, 6365] }, '༹', '༹'], + ['range', { sourceInterval: [6368, 6386] }, 'ཱ', 'ཾ'], + ['range', { sourceInterval: [6389, 6407] }, 'ྀ', '྄'], + ['range', { sourceInterval: [6410, 6428] }, '྆', '྇'], + ['range', { sourceInterval: [6431, 6449] }, 'ྐ', 'ྕ'], + ['range', { sourceInterval: [6452, 6470] }, 'ྗ', 'ྗ'], + ['range', { sourceInterval: [6473, 6491] }, 'ྙ', 'ྭ'], + ['range', { sourceInterval: [6494, 6512] }, 'ྱ', 'ྷ'], + ['range', { sourceInterval: [6515, 6533] }, 'ྐྵ', 'ྐྵ'], + ['range', { sourceInterval: [6536, 6554] }, '⃐', '⃜'], + ['range', { sourceInterval: [6557, 6575] }, '⃡', '⃡'], + ['range', { sourceInterval: [6578, 6596] }, '〪', '〯'], + ['range', { sourceInterval: [6599, 6617] }, '゙', '゚'], + ['range', { sourceInterval: [6620, 6638] }, 'ﬞ', 'ﬞ'], + ['range', { sourceInterval: [6641, 6659] }, '︠', '︣'], + ]], + unicodeConnectorPunctuation: ['define', { sourceInterval: [6665, 6799] }, null, [], [ + 'alt', + { sourceInterval: [6695, 6799] }, + ['terminal', { sourceInterval: [6695, 6703] }, '_'], + ['range', { sourceInterval: [6706, 6724] }, '‿', '⁀'], + ['terminal', { sourceInterval: [6727, 6735] }, '・'], + ['range', { sourceInterval: [6738, 6756] }, '︳', '︴'], + ['range', { sourceInterval: [6759, 6777] }, '﹍', '﹏'], + ['terminal', { sourceInterval: [6780, 6788] }, '_'], + ['terminal', { sourceInterval: [6791, 6799] }, '・'], + ]], + unicodeSpaceSeparator: ['define', { sourceInterval: [6804, 6857] }, null, [], [ + 'alt', + { sourceInterval: [6828, 6857] }, + ['range', { sourceInterval: [6828, 6846] }, ' ', '​'], + ['terminal', { sourceInterval: [6849, 6857] }, ' '], + ]], + }, +]); +export default result; diff --git a/drizzle-kit/imports-checker/index.ts b/drizzle-kit/imports-checker/index.ts new file mode 100644 index 000000000..7a4e90838 --- /dev/null +++ b/drizzle-kit/imports-checker/index.ts @@ -0,0 +1,48 @@ +import chalk from 'chalk'; +import { analyzeImports, ChainLink } from './checker'; + +const issues = analyzeImports({ + basePath: './drizzle-kit', + localPaths: ['src'], + whiteList: [ + '@drizzle-team/brocli', + 'json-diff', + 'path', + 'fs', + 'fs/*', + 'url', + 'zod', + 'node:*', + 'hono', + 'glob', + 'hono/*', + 'hono/**/*', + '@hono/*', + 'crypto', + 'hanji', + ], + entry: './drizzle-kit/src/cli/index.ts', + logger: true, + ignoreTypes: true, +}).issues; + +const chainToString = (chains: ChainLink[]) => { + if (chains.length === 0) throw new Error(); + + let out = chains[0]!.file + '\n'; + let indentation = 0; + for (let chain of chains) { + out += ' '.repeat(indentation) + + '└' + + chain.import + + ` ${chalk.gray(chain.file)}\n`; + indentation += 1; + } + return out; +}; + +console.log(); +for (const issue of issues) { + console.log(chalk.red(issue.imports.map((it) => it.name).join('\n'))); + console.log(issue.accessChains.map((it) => chainToString(it)).join('\n')); +} diff --git a/drizzle-kit/package.json b/drizzle-kit/package.json index c3f885722..6e2fec181 100644 --- a/drizzle-kit/package.json +++ b/drizzle-kit/package.json @@ -1,12 +1,13 @@ { "name": "drizzle-kit", - "version": "0.23.2", + "version": "0.30.2", "homepage": "https://orm.drizzle.team", "keywords": [ "drizzle", "orm", "pg", "mysql", + "singlestore", "postgresql", "postgres", "sqlite", @@ -34,15 +35,15 @@ "api": "tsx ./dev/api.ts", "migrate:old": "drizzle-kit generate:mysql", "cli": "tsx ./src/cli/index.ts", - "test": "TEST_CONFIG_PATH_PREFIX=./tests/cli/ vitest", + "test": "pnpm tsc && TEST_CONFIG_PATH_PREFIX=./tests/cli/ vitest", "build": "rm -rf ./dist && tsx build.ts && cp package.json dist/ && attw --pack dist", "build:dev": "rm -rf ./dist && tsx build.dev.ts && tsc -p tsconfig.cli-types.json && chmod +x ./dist/index.cjs", "pack": "cp package.json README.md dist/ && (cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz", - "tsc": "tsc -p tsconfig.build.json", + "tsc": "tsc -p tsconfig.build.json --noEmit", "publish": "npm publish package.tgz" }, "dependencies": { - "@drizzle-team/brocli": "^0.8.2", + "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" @@ -51,10 +52,10 @@ "@arethetypeswrong/cli": "^0.15.3", "@aws-sdk/client-rds-data": "^3.556.0", "@cloudflare/workers-types": "^4.20230518.0", - "@electric-sql/pglite": "^0.1.5", + "@electric-sql/pglite": "^0.2.12", "@hono/node-server": "^1.9.0", "@hono/zod-validator": "^0.2.1", - "@libsql/client": "^0.4.2", + "@libsql/client": "^0.10.0", "@neondatabase/serverless": "^0.9.1", "@originjs/vite-plugin-commonjs": "^1.0.3", "@planetscale/database": "^1.16.0", @@ -62,6 +63,7 @@ "@types/dockerode": "^3.3.28", "@types/glob": "^8.1.0", "@types/json-diff": "^1.0.3", + "@types/micromatch": "^4.0.9", "@types/minimatch": "^5.1.2", "@types/node": "^18.11.15", "@types/pg": "^8.10.7", @@ -74,12 +76,13 @@ "@vercel/postgres": "^0.8.0", "ava": "^5.1.0", "better-sqlite3": "^9.4.3", + "bun-types": "^0.6.6", "camelcase": "^7.0.1", "chalk": "^5.2.0", "commander": "^12.1.0", "dockerode": "^3.3.4", "dotenv": "^16.0.3", - "drizzle-kit": "0.21.2", + "drizzle-kit": "0.25.0-b1faa33", "drizzle-orm": "workspace:./drizzle-orm/dist", "env-paths": "^3.0.0", "esbuild-node-externals": "^1.9.0", @@ -91,9 +94,11 @@ "hanji": "^0.0.5", "hono": "^4.1.5", "json-diff": "1.0.6", + "micromatch": "^4.0.8", "minimatch": "^7.4.3", - "mysql2": "2.3.3", + "mysql2": "3.3.3", "node-fetch": "^3.3.2", + "ohm-js": "^17.1.0", "pg": "^8.11.5", "pluralize": "^8.0.0", "postgres": "^3.4.4", @@ -102,7 +107,7 @@ "superjson": "^2.2.1", "tsup": "^8.0.2", "tsx": "^3.12.1", - "typescript": "^5.4.3", + "typescript": "^5.6.3", "uuid": "^9.0.1", "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.4.0", diff --git a/drizzle-kit/pnpm-lock.yaml b/drizzle-kit/pnpm-lock.yaml deleted file mode 100644 index 8f4d58f55..000000000 --- a/drizzle-kit/pnpm-lock.yaml +++ /dev/null @@ -1,7603 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -patchedDependencies: - difflib@0.2.4: - hash: jq4t3ysdpnbunjeje4v7nrqn2q - path: patches/difflib@0.2.4.patch - -importers: - - .: - dependencies: - '@esbuild-kit/esm-loader': - specifier: ^2.5.5 - version: 2.6.5 - esbuild: - specifier: ^0.19.7 - version: 0.19.12 - esbuild-register: - specifier: ^3.5.0 - version: 3.5.0(esbuild@0.19.12) - devDependencies: - '@arethetypeswrong/cli': - specifier: ^0.15.3 - version: 0.15.3 - '@aws-sdk/client-rds-data': - specifier: ^3.556.0 - version: 3.577.0 - '@cloudflare/workers-types': - specifier: ^4.20230518.0 - version: 4.20240512.0 - '@electric-sql/pglite': - specifier: ^0.1.5 - version: 0.1.5 - '@hono/node-server': - specifier: ^1.9.0 - version: 1.11.1 - '@hono/zod-validator': - specifier: ^0.2.1 - version: 0.2.1(hono@4.3.9)(zod@3.23.8) - '@libsql/client': - specifier: ^0.4.2 - version: 0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3) - '@neondatabase/serverless': - specifier: ^0.9.1 - version: 0.9.3 - '@originjs/vite-plugin-commonjs': - specifier: ^1.0.3 - version: 1.0.3 - '@planetscale/database': - specifier: ^1.16.0 - version: 1.18.0 - '@types/better-sqlite3': - specifier: ^7.6.4 - version: 7.6.10 - '@types/dockerode': - specifier: ^3.3.28 - version: 3.3.29 - '@types/glob': - specifier: ^8.1.0 - version: 8.1.0 - '@types/json-diff': - specifier: ^1.0.3 - version: 1.0.3 - '@types/minimatch': - specifier: ^5.1.2 - version: 5.1.2 - '@types/node': - specifier: ^18.11.15 - version: 18.19.33 - '@types/pg': - specifier: ^8.10.7 - version: 8.11.6 - '@types/pluralize': - specifier: ^0.0.33 - version: 0.0.33 - '@types/semver': - specifier: ^7.5.5 - version: 7.5.8 - '@types/uuid': - specifier: ^9.0.8 - version: 9.0.8 - '@types/ws': - specifier: ^8.5.10 - version: 8.5.10 - '@typescript-eslint/eslint-plugin': - specifier: ^7.2.0 - version: 7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/parser': - specifier: ^7.2.0 - version: 7.10.0(eslint@8.57.0)(typescript@5.4.5) - '@vercel/postgres': - specifier: ^0.8.0 - version: 0.8.0 - ava: - specifier: ^5.1.0 - version: 5.3.1 - better-sqlite3: - specifier: ^9.4.3 - version: 9.6.0 - camelcase: - specifier: ^7.0.1 - version: 7.0.1 - chalk: - specifier: ^5.2.0 - version: 5.3.0 - commander: - specifier: ^12.1.0 - version: 12.1.0 - dockerode: - specifier: ^3.3.4 - version: 3.3.5 - dotenv: - specifier: ^16.0.3 - version: 16.4.5 - drizzle-kit: - specifier: 0.21.2 - version: 0.21.2 - drizzle-orm: - specifier: 0.32.0-85c8008 - version: 0.32.0-85c8008(@aws-sdk/client-rds-data@3.577.0)(@cloudflare/workers-types@4.20240512.0)(@electric-sql/pglite@0.1.5)(@libsql/client@0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3))(@neondatabase/serverless@0.9.3)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@vercel/postgres@0.8.0)(better-sqlite3@9.6.0)(mysql2@2.3.3)(pg@8.11.5)(postgres@3.4.4) - env-paths: - specifier: ^3.0.0 - version: 3.0.0 - esbuild-node-externals: - specifier: ^1.9.0 - version: 1.13.1(esbuild@0.19.12) - eslint: - specifier: ^8.57.0 - version: 8.57.0 - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.0) - eslint-plugin-prettier: - specifier: ^5.1.3 - version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8) - get-port: - specifier: ^6.1.2 - version: 6.1.2 - glob: - specifier: ^8.1.0 - version: 8.1.0 - hanji: - specifier: ^0.0.5 - version: 0.0.5 - hono: - specifier: ^4.1.5 - version: 4.3.9 - json-diff: - specifier: 1.0.6 - version: 1.0.6 - minimatch: - specifier: ^7.4.3 - version: 7.4.6 - mysql2: - specifier: 2.3.3 - version: 2.3.3 - node-fetch: - specifier: ^3.3.2 - version: 3.3.2 - pg: - specifier: ^8.11.5 - version: 8.11.5 - pluralize: - specifier: ^8.0.0 - version: 8.0.0 - postgres: - specifier: ^3.4.4 - version: 3.4.4 - prettier: - specifier: ^2.8.1 - version: 2.8.8 - semver: - specifier: ^7.5.4 - version: 7.6.2 - superjson: - specifier: ^2.2.1 - version: 2.2.1 - tsup: - specifier: ^8.0.2 - version: 8.0.2(postcss@8.4.38)(typescript@5.4.5) - tsx: - specifier: ^3.12.1 - version: 3.14.0 - typescript: - specifier: ^5.4.3 - version: 5.4.5 - uuid: - specifier: ^9.0.1 - version: 9.0.1 - vite-tsconfig-paths: - specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.5)(vite@5.2.11(@types/node@18.19.33)) - vitest: - specifier: ^1.4.0 - version: 1.6.0(@types/node@18.19.33) - wrangler: - specifier: ^3.22.1 - version: 3.57.0(@cloudflare/workers-types@4.20240512.0)(bufferutil@4.0.8)(utf-8-validate@6.0.3) - ws: - specifier: ^8.16.0 - version: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) - zod: - specifier: ^3.20.2 - version: 3.23.8 - zx: - specifier: ^7.2.2 - version: 7.2.3 - -packages: - - '@andrewbranch/untar.js@1.0.3': - resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==} - - '@arethetypeswrong/cli@0.15.3': - resolution: {integrity: sha512-sIMA9ZJBWDEg1+xt5RkAEflZuf8+PO8SdKj17x6PtETuUho+qlZJg4DgmKc3q+QwQ9zOB5VLK6jVRbFdNLdUIA==} - engines: {node: '>=18'} - hasBin: true - - '@arethetypeswrong/core@0.15.1': - resolution: {integrity: sha512-FYp6GBAgsNz81BkfItRz8RLZO03w5+BaeiPma1uCfmxTnxbtuMrI/dbzGiOk8VghO108uFI0oJo0OkewdSHw7g==} - engines: {node: '>=18'} - - '@aws-crypto/ie11-detection@3.0.0': - resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} - - '@aws-crypto/sha256-browser@3.0.0': - resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} - - '@aws-crypto/sha256-js@3.0.0': - resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} - - '@aws-crypto/supports-web-crypto@3.0.0': - resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} - - '@aws-crypto/util@3.0.0': - resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} - - '@aws-sdk/client-rds-data@3.577.0': - resolution: {integrity: sha512-24a27II6UkNhe2RB6ZwtQPcM3QB/DuRcKvzMmfvipgWS72Q5FEtuq3CO66IObWUel/pxi3ucE6mSvVCFnm7tBQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sso-oidc@3.577.0': - resolution: {integrity: sha512-njmKSPDWueWWYVFpFcZ2P3fI6/pdQVDa0FgCyYZhOnJLgEHZIcBBg1AsnkVWacBuLopp9XVt2m+7hO6ugY1/1g==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sso@3.577.0': - resolution: {integrity: sha512-BwujdXrydlk6UEyPmewm5GqG4nkQ6OVyRhS/SyZP/6UKSFv2/sf391Cmz0hN0itUTH1rR4XeLln8XCOtarkrzg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sts@3.577.0': - resolution: {integrity: sha512-509Kklimva1XVlhGbpTpeX3kOP6ORpm44twJxDHpa9TURbmoaxj7veWlnLCbDorxDTrbsDghvYZshvcLsojVpg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/core@3.576.0': - resolution: {integrity: sha512-KDvDlbeipSTIf+ffKtTg1m419TK7s9mZSWC8bvuZ9qx6/sjQFOXIKOVqyuli6DnfxGbvRcwoRuY99OcCH1N/0w==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-env@3.577.0': - resolution: {integrity: sha512-Jxu255j0gToMGEiqufP8ZtKI8HW90lOLjwJ3LrdlD/NLsAY0tOQf1fWc53u28hWmmNGMxmCrL2p66IOgMDhDUw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-http@3.577.0': - resolution: {integrity: sha512-n++yhCp67b9+ZRGEdY1jhamB5E/O+QsIDOPSuRmdaSGMCOd82oUEKPgIVEU1bkqxDsBxgiEWuvtfhK6sNiDS0A==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-ini@3.577.0': - resolution: {integrity: sha512-q7lHPtv6BjRvChUE3m0tIaEZKxPTaZ1B3lKxGYsFl3VLAu5N8yGCUKwuA1izf4ucT+LyKscVGqK6VDZx1ev3nw==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.577.0 - - '@aws-sdk/credential-provider-node@3.577.0': - resolution: {integrity: sha512-epZ1HOMsrXBNczc0HQpv0VMjqAEpc09DUA7Rg3gUJfn8umhML7A7bXnUyqPA+S54q397UYg1leQKdSn23OiwQQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-process@3.577.0': - resolution: {integrity: sha512-Gin6BWtOiXxIgITrJ3Nwc+Y2P1uVT6huYR4EcbA/DJUPWyO0n9y5UFLewPvVbLkRn15JeEqErBLUrHclkiOKtw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-sso@3.577.0': - resolution: {integrity: sha512-iVm5SQvS7EgZTJsRaqUOmDQpBQPPPat42SCbWFvFQOLrl8qewq8OP94hFS5w2mP62zngeYzqhJnDel79HXbxew==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.577.0': - resolution: {integrity: sha512-ZGHGNRaCtJJmszb9UTnC7izNCtRUttdPlLdMkh41KPS32vfdrBDHs1JrpbZijItRj1xKuOXsiYSXLAaHGcLh8Q==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.577.0 - - '@aws-sdk/middleware-host-header@3.577.0': - resolution: {integrity: sha512-9ca5MJz455CODIVXs0/sWmJm7t3QO4EUa1zf8pE8grLpzf0J94bz/skDWm37Pli13T3WaAQBHCTiH2gUVfCsWg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-logger@3.577.0': - resolution: {integrity: sha512-aPFGpGjTZcJYk+24bg7jT4XdIp42mFXSuPt49lw5KygefLyJM/sB0bKKqPYYivW0rcuZ9brQ58eZUNthrzYAvg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-recursion-detection@3.577.0': - resolution: {integrity: sha512-pn3ZVEd2iobKJlR3H+bDilHjgRnNrQ6HMmK9ZzZw89Ckn3Dcbv48xOv4RJvu0aU8SDLl/SNCxppKjeLDTPGBNA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-user-agent@3.577.0': - resolution: {integrity: sha512-P55HAXgwmiHHpFx5JEPvOnAbfhN7v6sWv9PBQs+z2tC7QiBcPS0cdJR6PfV7J1n4VPK52/OnrK3l9VxdQ7Ms0g==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/region-config-resolver@3.577.0': - resolution: {integrity: sha512-4ChCFACNwzqx/xjg3zgFcW8Ali6R9C95cFECKWT/7CUM1D0MGvkclSH2cLarmHCmJgU6onKkJroFtWp0kHhgyg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/token-providers@3.577.0': - resolution: {integrity: sha512-0CkIZpcC3DNQJQ1hDjm2bdSy/Xjs7Ny5YvSsacasGOkNfk+FdkiQy6N67bZX3Zbc9KIx+Nz4bu3iDeNSNplnnQ==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sso-oidc': ^3.577.0 - - '@aws-sdk/types@3.577.0': - resolution: {integrity: sha512-FT2JZES3wBKN/alfmhlo+3ZOq/XJ0C7QOZcDNrpKjB0kqYoKjhVKZ/Hx6ArR0czkKfHzBBEs6y40ebIHx2nSmA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-endpoints@3.577.0': - resolution: {integrity: sha512-FjuUz1Kdy4Zly2q/c58tpdqHd6z7iOdU/caYzoc8jwgAHBDBbIJNQLCU9hXJnPV2M8pWxQDyIZsoVwtmvErPzw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-locate-window@3.568.0': - resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-user-agent-browser@3.577.0': - resolution: {integrity: sha512-zEAzHgR6HWpZOH7xFgeJLc6/CzMcx4nxeQolZxVZoB5pPaJd3CjyRhZN0xXeZB0XIRCWmb4yJBgyiugXLNMkLA==} - - '@aws-sdk/util-user-agent-node@3.577.0': - resolution: {integrity: sha512-XqvtFjbSMtycZTWVwDe8DRWovuoMbA54nhUoZwVU6rW9OSD6NZWGR512BUGHFaWzW0Wg8++Dj10FrKTG2XtqfA==} - engines: {node: '>=16.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - - '@aws-sdk/util-utf8-browser@3.259.0': - resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} - - '@balena/dockerignore@1.0.2': - resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} - - '@cloudflare/kv-asset-handler@0.3.2': - resolution: {integrity: sha512-EeEjMobfuJrwoctj7FA1y1KEbM0+Q1xSjobIEyie9k4haVEBB7vkDvsasw1pM3rO39mL2akxIAzLMUAtrMHZhA==} - engines: {node: '>=16.13'} - - '@cloudflare/workerd-darwin-64@1.20240512.0': - resolution: {integrity: sha512-VMp+CsSHFALQiBzPdQ5dDI4T1qwLu0mQ0aeKVNDosXjueN0f3zj/lf+mFil5/9jBbG3t4mG0y+6MMnalP9Lobw==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20240512.0': - resolution: {integrity: sha512-lZktXGmzMrB5rJqY9+PmnNfv1HKlj/YLZwMjPfF0WVKHUFdvQbAHsi7NlKv6mW9uIvlZnS+K4sIkWc0MDXcRnA==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-linux-64@1.20240512.0': - resolution: {integrity: sha512-wrHvqCZZqXz6Y3MUTn/9pQNsvaoNjbJpuA6vcXsXu8iCzJi911iVW2WUEBX+MpUWD+mBIP0oXni5tTlhkokOPw==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20240512.0': - resolution: {integrity: sha512-YPezHMySL9J9tFdzxz390eBswQ//QJNYcZolz9Dgvb3FEfdpK345cE/bsWbMOqw5ws2f82l388epoenghtYvAg==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-windows-64@1.20240512.0': - resolution: {integrity: sha512-SxKapDrIYSscMR7lGIp/av0l6vokjH4xQ9ACxHgXh+OdOus9azppSmjaPyw4/ePvg7yqpkaNjf9o258IxWtvKQ==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - - '@cloudflare/workers-types@4.20240512.0': - resolution: {integrity: sha512-o2yTEWg+YK/I1t/Me+dA0oarO0aCbjibp6wSeaw52DSE9tDyKJ7S+Qdyw/XsMrKn4t8kF6f/YOba+9O4MJfW9w==} - - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@electric-sql/pglite@0.1.5': - resolution: {integrity: sha512-eymv4ONNvoPZQTvOQIi5dbpR+J5HzEv0qQH9o/y3gvNheJV/P/NFcrbsfJZYTsDKoq7DKrTiFNexsRkJKy8x9Q==} - - '@esbuild-kit/core-utils@3.3.2': - resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} - - '@esbuild-kit/esm-loader@2.6.5': - resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} - - '@esbuild-plugins/node-globals-polyfill@0.2.3': - resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} - peerDependencies: - esbuild: '*' - - '@esbuild-plugins/node-modules-polyfill@0.2.2': - resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==} - peerDependencies: - esbuild: '*' - - '@esbuild/aix-ppc64@0.19.12': - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.20.2': - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.17.19': - resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.18.20': - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.19.12': - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.20.2': - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.17.19': - resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.18.20': - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.19.12': - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.20.2': - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.17.19': - resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.18.20': - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.19.12': - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.20.2': - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.17.19': - resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.18.20': - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.19.12': - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.20.2': - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.17.19': - resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.18.20': - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.19.12': - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.20.2': - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.17.19': - resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.18.20': - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.19.12': - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.20.2': - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.17.19': - resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.18.20': - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.19.12': - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.20.2': - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.17.19': - resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.18.20': - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.19.12': - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.20.2': - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.17.19': - resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.18.20': - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.19.12': - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.20.2': - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.17.19': - resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.18.20': - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.19.12': - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.20.2': - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.14.54': - resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.17.19': - resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.18.20': - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.19.12': - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.20.2': - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.17.19': - resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.18.20': - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.19.12': - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.20.2': - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.17.19': - resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.18.20': - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.19.12': - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.20.2': - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.17.19': - resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.18.20': - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.19.12': - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.20.2': - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.17.19': - resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.18.20': - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.19.12': - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.20.2': - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.17.19': - resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.18.20': - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.19.12': - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.20.2': - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.17.19': - resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.18.20': - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.19.12': - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.20.2': - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.17.19': - resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.18.20': - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.19.12': - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.20.2': - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.17.19': - resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.18.20': - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.19.12': - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.20.2': - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.17.19': - resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.18.20': - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.19.12': - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.20.2': - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.17.19': - resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.18.20': - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.19.12': - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.20.2': - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.17.19': - resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.18.20': - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.19.12': - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.20.2': - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.10.0': - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@ewoudenberg/difflib@0.1.0': - resolution: {integrity: sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A==} - - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - - '@hono/node-server@1.11.1': - resolution: {integrity: sha512-GW1Iomhmm1o4Z+X57xGby8A35Cu9UZLL7pSMdqDBkD99U5cywff8F+8hLk5aBTzNubnsFAvWQ/fZjNwPsEn9lA==} - engines: {node: '>=18.14.1'} - - '@hono/zod-validator@0.2.1': - resolution: {integrity: sha512-HFoxln7Q6JsE64qz2WBS28SD33UB2alp3aRKmcWnNLDzEL1BLsWfbdX6e1HIiUprHYTIXf5y7ax8eYidKUwyaA==} - peerDependencies: - hono: '>=3.9.0' - zod: ^3.19.1 - - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - - '@libsql/client@0.4.3': - resolution: {integrity: sha512-AUYKnSPqAsFBVWBvmtrb4dG3pQlvTKT92eztAest9wQU2iJkabH8WzHLDb3dKFWKql7/kiCqvBQUVpozDwhekQ==} - - '@libsql/core@0.4.3': - resolution: {integrity: sha512-r28iYBtaLBW9RRgXPFh6cGCsVI/rwRlOzSOpAu/1PVTm6EJ3t233pUf97jETVHU0vjdr1d8VvV6fKAvJkokqCw==} - - '@libsql/darwin-arm64@0.2.0': - resolution: {integrity: sha512-+qyT2W/n5CFH1YZWv2mxW4Fsoo4dX9Z9M/nvbQqZ7H84J8hVegvVAsIGYzcK8xAeMEcpU5yGKB1Y9NoDY4hOSQ==} - cpu: [arm64] - os: [darwin] - - '@libsql/darwin-x64@0.2.0': - resolution: {integrity: sha512-hwmO2mF1n8oDHKFrUju6Jv+n9iFtTf5JUK+xlnIE3Td0ZwGC/O1R/Z/btZTd9nD+vsvakC8SJT7/Q6YlWIkhEw==} - cpu: [x64] - os: [darwin] - - '@libsql/hrana-client@0.5.6': - resolution: {integrity: sha512-mjQoAmejZ1atG+M3YR2ZW+rg6ceBByH/S/h17ZoYZkqbWrvohFhXyz2LFxj++ARMoY9m6w3RJJIRdJdmnEUlFg==} - - '@libsql/isomorphic-fetch@0.1.12': - resolution: {integrity: sha512-MRo4UcmjAGAa3ac56LoD5OE13m2p0lu0VEtZC2NZMcogM/jc5fU9YtMQ3qbPjFJ+u2BBjFZgMPkQaLS1dlMhpg==} - - '@libsql/isomorphic-ws@0.1.5': - resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} - - '@libsql/linux-arm64-gnu@0.2.0': - resolution: {integrity: sha512-1w2lPXIYtnBaK5t/Ej5E8x7lPiE+jP3KATI/W4yei5Z/ONJh7jQW5PJ7sYU95vTME3hWEM1FXN6kvzcpFAte7w==} - cpu: [arm64] - os: [linux] - - '@libsql/linux-arm64-musl@0.2.0': - resolution: {integrity: sha512-lkblBEJ7xuNiWNjP8DDq0rqoWccszfkUS7Efh5EjJ+GDWdCBVfh08mPofIZg0fZVLWQCY3j+VZCG1qZfATBizg==} - cpu: [arm64] - os: [linux] - - '@libsql/linux-x64-gnu@0.2.0': - resolution: {integrity: sha512-+x/d289KeJydwOhhqSxKT+6MSQTCfLltzOpTzPccsvdt5fxg8CBi+gfvEJ4/XW23Sa+9bc7zodFP0i6MOlxX7w==} - cpu: [x64] - os: [linux] - - '@libsql/linux-x64-musl@0.2.0': - resolution: {integrity: sha512-5Xn0c5A6vKf9D1ASpgk7mef//FuY7t5Lktj/eiU4n3ryxG+6WTpqstTittJUgepVjcleLPYxIhQAYeYwTYH1IQ==} - cpu: [x64] - os: [linux] - - '@libsql/win32-x64-msvc@0.2.0': - resolution: {integrity: sha512-rpK+trBIpRST15m3cMYg5aPaX7kvCIottxY7jZPINkKAaScvfbn9yulU/iZUM9YtuK96Y1ZmvwyVIK/Y5DzoMQ==} - cpu: [x64] - os: [win32] - - '@neon-rs/load@0.0.4': - resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} - - '@neondatabase/serverless@0.7.2': - resolution: {integrity: sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==} - - '@neondatabase/serverless@0.9.3': - resolution: {integrity: sha512-6ZBK8asl2Z3+ADEaELvbaVVGVlmY1oAzkxxZfpmXPKFuJhbDN+5fU3zYBamsahS/Ch1zE+CVWB3R+8QEI2LMSw==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@originjs/vite-plugin-commonjs@1.0.3': - resolution: {integrity: sha512-KuEXeGPptM2lyxdIEJ4R11+5ztipHoE7hy8ClZt3PYaOVQ/pyngd2alaSrPnwyFeOW1UagRBaQ752aA1dTMdOQ==} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@pkgr/core@0.1.1': - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - - '@planetscale/database@1.18.0': - resolution: {integrity: sha512-t2XdOfrVgcF7AW791FtdPS27NyNqcE1SpoXgk3HpziousvUMsJi4Q6NL3JyOBpsMOrvk94749o8yyonvX5quPw==} - engines: {node: '>=16'} - - '@rollup/rollup-android-arm-eabi@4.17.2': - resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.17.2': - resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.17.2': - resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.17.2': - resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': - resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.17.2': - resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.17.2': - resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.17.2': - resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': - resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.17.2': - resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.17.2': - resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.17.2': - resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.17.2': - resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.17.2': - resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.17.2': - resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.17.2': - resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} - cpu: [x64] - os: [win32] - - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@sindresorhus/is@4.6.0': - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - - '@smithy/abort-controller@3.0.0': - resolution: {integrity: sha512-p6GlFGBt9K4MYLu72YuJ523NVR4A8oHlC5M2JO6OmQqN8kAc/uh1JqLE+FizTokrSJGg0CSvC+BrsmGzKtsZKA==} - engines: {node: '>=16.0.0'} - - '@smithy/config-resolver@3.0.0': - resolution: {integrity: sha512-2GzOfADwYLQugYkKQhIyZyQlM05K+tMKvRnc6eFfZcpJGRfKoMUMYdPlBKmqHwQFXQKBrGV6cxL9oymWgDzvFw==} - engines: {node: '>=16.0.0'} - - '@smithy/core@2.0.1': - resolution: {integrity: sha512-rcMkjvwxH/bER+oZUPR0yTA0ELD6m3A+d92+CFkdF6HJFCBB1bXo7P5pm21L66XwTN01B6bUhSCQ7cymWRD8zg==} - engines: {node: '>=16.0.0'} - - '@smithy/credential-provider-imds@3.0.0': - resolution: {integrity: sha512-lfmBiFQcA3FsDAPxNfY0L7CawcWtbyWsBOHo34nF095728JLkBX4Y9q/VPPE2r7fqMVK+drmDigqE2/SSQeVRA==} - engines: {node: '>=16.0.0'} - - '@smithy/fetch-http-handler@3.0.1': - resolution: {integrity: sha512-uaH74i5BDj+rBwoQaXioKpI0SHBJFtOVwzrCpxZxphOW0ki5jhj7dXvDMYM2IJem8TpdFvS2iC08sjOblfFGFg==} - - '@smithy/hash-node@3.0.0': - resolution: {integrity: sha512-84qXstNemP3XS5jcof0el6+bDfjzuvhJPQTEfro3lgtbCtKgzPm3MgiS6ehXVPjeQ5+JS0HqmTz8f/RYfzHVxw==} - engines: {node: '>=16.0.0'} - - '@smithy/invalid-dependency@3.0.0': - resolution: {integrity: sha512-F6wBBaEFgJzj0s4KUlliIGPmqXemwP6EavgvDqYwCH40O5Xr2iMHvS8todmGVZtuJCorBkXsYLyTu4PuizVq5g==} - - '@smithy/is-array-buffer@3.0.0': - resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-content-length@3.0.0': - resolution: {integrity: sha512-3C4s4d/iGobgCtk2tnWW6+zSTOBg1PRAm2vtWZLdriwTroFbbWNSr3lcyzHdrQHnEXYCC5K52EbpfodaIUY8sg==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-endpoint@3.0.0': - resolution: {integrity: sha512-aXOAWztw/5qAfp0NcA2OWpv6ZI/E+Dh9mByif7i91D/0iyYNUcKvskmXiowKESFkuZ7PIMd3VOR4fTibZDs2OQ==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-retry@3.0.1': - resolution: {integrity: sha512-hBhSEuL841FhJBK/19WpaGk5YWSzFk/P2UaVjANGKRv3eYNO8Y1lANWgqnuPWjOyCEWMPr58vELFDWpxvRKANw==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-serde@3.0.0': - resolution: {integrity: sha512-I1vKG1foI+oPgG9r7IMY1S+xBnmAn1ISqployvqkwHoSb8VPsngHDTOgYGYBonuOKndaWRUGJZrKYYLB+Ane6w==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-stack@3.0.0': - resolution: {integrity: sha512-+H0jmyfAyHRFXm6wunskuNAqtj7yfmwFB6Fp37enytp2q047/Od9xetEaUbluyImOlGnGpaVGaVfjwawSr+i6Q==} - engines: {node: '>=16.0.0'} - - '@smithy/node-config-provider@3.0.0': - resolution: {integrity: sha512-buqfaSdDh0zo62EPLf8rGDvcpKwGpO5ho4bXS2cdFhlOta7tBkWJt+O5uiaAeICfIOfPclNOndshDNSanX2X9g==} - engines: {node: '>=16.0.0'} - - '@smithy/node-http-handler@3.0.0': - resolution: {integrity: sha512-3trD4r7NOMygwLbUJo4eodyQuypAWr7uvPnebNJ9a70dQhVn+US8j/lCnvoJS6BXfZeF7PkkkI0DemVJw+n+eQ==} - engines: {node: '>=16.0.0'} - - '@smithy/property-provider@3.0.0': - resolution: {integrity: sha512-LmbPgHBswdXCrkWWuUwBm9w72S2iLWyC/5jet9/Y9cGHtzqxi+GVjfCfahkvNV4KXEwgnH8EMpcrD9RUYe0eLQ==} - engines: {node: '>=16.0.0'} - - '@smithy/protocol-http@4.0.0': - resolution: {integrity: sha512-qOQZOEI2XLWRWBO9AgIYuHuqjZ2csyr8/IlgFDHDNuIgLAMRx2Bl8ck5U5D6Vh9DPdoaVpuzwWMa0xcdL4O/AQ==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-builder@3.0.0': - resolution: {integrity: sha512-bW8Fi0NzyfkE0TmQphDXr1AmBDbK01cA4C1Z7ggwMAU5RDz5AAv/KmoRwzQAS0kxXNf/D2ALTEgwK0U2c4LtRg==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-parser@3.0.0': - resolution: {integrity: sha512-UzHwthk0UEccV4dHzPySnBy34AWw3V9lIqUTxmozQ+wPDAO9csCWMfOLe7V9A2agNYy7xE+Pb0S6K/J23JSzfQ==} - engines: {node: '>=16.0.0'} - - '@smithy/service-error-classification@3.0.0': - resolution: {integrity: sha512-3BsBtOUt2Gsnc3X23ew+r2M71WwtpHfEDGhHYHSDg6q1t8FrWh15jT25DLajFV1H+PpxAJ6gqe9yYeRUsmSdFA==} - engines: {node: '>=16.0.0'} - - '@smithy/shared-ini-file-loader@3.0.0': - resolution: {integrity: sha512-REVw6XauXk8xE4zo5aGL7Rz4ywA8qNMUn8RtWeTRQsgAlmlvbJ7CEPBcaXU2NDC3AYBgYAXrGyWD8XrN8UGDog==} - engines: {node: '>=16.0.0'} - - '@smithy/signature-v4@3.0.0': - resolution: {integrity: sha512-kXFOkNX+BQHe2qnLxpMEaCRGap9J6tUGLzc3A9jdn+nD4JdMwCKTJ+zFwQ20GkY+mAXGatyTw3HcoUlR39HwmA==} - engines: {node: '>=16.0.0'} - - '@smithy/smithy-client@3.0.1': - resolution: {integrity: sha512-KAiFY4Y4jdHxR+4zerH/VBhaFKM8pbaVmJZ/CWJRwtM/CmwzTfXfvYwf6GoUwiHepdv+lwiOXCuOl6UBDUEINw==} - engines: {node: '>=16.0.0'} - - '@smithy/types@3.0.0': - resolution: {integrity: sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw==} - engines: {node: '>=16.0.0'} - - '@smithy/url-parser@3.0.0': - resolution: {integrity: sha512-2XLazFgUu+YOGHtWihB3FSLAfCUajVfNBXGGYjOaVKjLAuAxx3pSBY3hBgLzIgB17haf59gOG3imKqTy8mcrjw==} - - '@smithy/util-base64@3.0.0': - resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-body-length-browser@3.0.0': - resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} - - '@smithy/util-body-length-node@3.0.0': - resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-buffer-from@3.0.0': - resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-config-provider@3.0.0': - resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-defaults-mode-browser@3.0.1': - resolution: {integrity: sha512-nW5kEzdJn1Bn5TF+gOPHh2rcPli8JU9vSSXLbfg7uPnfR1TMRQqs9zlYRhIb87NeSxIbpdXOI94tvXSy+fvDYg==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-defaults-mode-node@3.0.1': - resolution: {integrity: sha512-TFk+Qb+elLc/MOhtSp+50fstyfZ6avQbgH2d96xUBpeScu+Al9elxv+UFAjaTHe0HQe5n+wem8ZLpXvU8lwV6Q==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-endpoints@2.0.0': - resolution: {integrity: sha512-+exaXzEY3DNt2qtA2OtRNSDlVrE4p32j1JSsQkzA5AdP0YtJNjkYbYhJxkFmPYcjI1abuwopOZCwUmv682QkiQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-hex-encoding@3.0.0': - resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-middleware@3.0.0': - resolution: {integrity: sha512-q5ITdOnV2pXHSVDnKWrwgSNTDBAMHLptFE07ua/5Ty5WJ11bvr0vk2a7agu7qRhrCFRQlno5u3CneU5EELK+DQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-retry@3.0.0': - resolution: {integrity: sha512-nK99bvJiziGv/UOKJlDvFF45F00WgPLKVIGUfAK+mDhzVN2hb/S33uW2Tlhg5PVBoqY7tDVqL0zmu4OxAHgo9g==} - engines: {node: '>=16.0.0'} - - '@smithy/util-stream@3.0.1': - resolution: {integrity: sha512-7F7VNNhAsfMRA8I986YdOY5fE0/T1/ZjFF6OLsqkvQVNP3vZ/szYDfGCyphb7ioA09r32K/0qbSFfNFU68aSzA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-uri-escape@3.0.0': - resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} - engines: {node: '>=16.0.0'} - - '@smithy/util-utf8@3.0.0': - resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} - engines: {node: '>=16.0.0'} - - '@types/better-sqlite3@7.6.10': - resolution: {integrity: sha512-TZBjD+yOsyrUJGmcUj6OS3JADk3+UZcNv3NOBqGkM09bZdi28fNZw8ODqbMOLfKCu7RYCO62/ldq1iHbzxqoPw==} - - '@types/docker-modem@3.0.6': - resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} - - '@types/dockerode@3.3.29': - resolution: {integrity: sha512-5PRRq/yt5OT/Jf77ltIdz4EiR9+VLnPF+HpU4xGFwUqmV24Co2HKBNW3w+slqZ1CYchbcDeqJASHDYWzZCcMiQ==} - - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - - '@types/fs-extra@11.0.4': - resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} - - '@types/glob@8.1.0': - resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} - - '@types/json-diff@1.0.3': - resolution: {integrity: sha512-Qvxm8fpRMv/1zZR3sQWImeRK2mBYJji20xF51Fq9Gt//Ed18u0x6/FNLogLS1xhfUWTEmDyqveJqn95ltB6Kvw==} - - '@types/jsonfile@6.1.4': - resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - - '@types/minimatch@5.1.2': - resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - - '@types/node-fetch@2.6.11': - resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - - '@types/node-forge@1.3.11': - resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} - - '@types/node@18.19.33': - resolution: {integrity: sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==} - - '@types/pg@8.11.6': - resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} - - '@types/pg@8.6.6': - resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} - - '@types/pluralize@0.0.33': - resolution: {integrity: sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==} - - '@types/ps-tree@1.1.6': - resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==} - - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - - '@types/ssh2@1.15.0': - resolution: {integrity: sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==} - - '@types/uuid@9.0.8': - resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - - '@types/which@3.0.3': - resolution: {integrity: sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==} - - '@types/ws@8.5.10': - resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} - - '@typescript-eslint/eslint-plugin@7.10.0': - resolution: {integrity: sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@7.10.0': - resolution: {integrity: sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@7.10.0': - resolution: {integrity: sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/type-utils@7.10.0': - resolution: {integrity: sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@7.10.0': - resolution: {integrity: sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/typescript-estree@7.10.0': - resolution: {integrity: sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@7.10.0': - resolution: {integrity: sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - - '@typescript-eslint/visitor-keys@7.10.0': - resolution: {integrity: sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - - '@vercel/postgres@0.8.0': - resolution: {integrity: sha512-/QUV9ExwaNdKooRjOQqvrKNVnRvsaXeukPNI5DB1ovUTesglfR/fparw7ngo1KUWWKIVpEj2TRrA+ObRHRdaLg==} - engines: {node: '>=14.6'} - - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} - - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} - - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} - - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} - - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - - aggregate-error@4.0.1: - resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} - engines: {node: '>=12'} - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ansi-escapes@6.2.1: - resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} - engines: {node: '>=14.16'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - ansicolors@0.3.2: - resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - array-find-index@1.0.2: - resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} - engines: {node: '>=0.10.0'} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - arrgv@1.0.2: - resolution: {integrity: sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==} - engines: {node: '>=8.0.0'} - - arrify@3.0.0: - resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} - engines: {node: '>=12'} - - as-table@1.0.55: - resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} - - asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - ava@5.3.1: - resolution: {integrity: sha512-Scv9a4gMOXB6+ni4toLuhAm9KYWEjsgBglJl+kMGI5+IVDt120CCDZyB5HNU9DjmLI2t4I0GbnxGLmmRfGTJGg==} - engines: {node: '>=14.19 <15 || >=16.15 <17 || >=18'} - hasBin: true - peerDependencies: - '@ava/typescript': '*' - peerDependenciesMeta: - '@ava/typescript': - optional: true - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - - better-sqlite3@9.6.0: - resolution: {integrity: sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - blake3-wasm@2.1.5: - resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - - blueimp-md5@2.19.0: - resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} - - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - bufferutil@4.0.8: - resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} - engines: {node: '>=6.14.2'} - - buildcheck@0.0.6: - resolution: {integrity: sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==} - engines: {node: '>=10.0.0'} - - bundle-require@4.1.0: - resolution: {integrity: sha512-FeArRFM+ziGkRViKRnSTbHZc35dgmR9yNog05Kn0+ItI59pOAISGvnnIwW1WgFZQW59IxD9QpJnUPkdIPfZuXg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.17' - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - callsites@4.1.0: - resolution: {integrity: sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==} - engines: {node: '>=12.20'} - - camelcase@7.0.1: - resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} - engines: {node: '>=14.16'} - - capnp-ts@0.7.0: - resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} - - cardinal@2.1.1: - resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} - hasBin: true - - cbor@8.1.0: - resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} - engines: {node: '>=12.19'} - - chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - - chunkd@2.0.1: - resolution: {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - ci-parallel-vars@1.0.1: - resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==} - - clean-stack@4.2.0: - resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} - engines: {node: '>=12'} - - clean-yaml-object@0.1.0: - resolution: {integrity: sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==} - engines: {node: '>=0.10.0'} - - cli-color@2.0.4: - resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==} - engines: {node: '>=0.10'} - - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - - cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - code-excerpt@4.0.0: - resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colors@1.4.0: - resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} - engines: {node: '>=0.1.90'} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} - - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - - common-path-prefix@3.0.0: - resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concordance@5.0.4: - resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} - engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} - - confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} - - convert-to-spaces@2.0.1: - resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} - - copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - - cpu-features@0.0.10: - resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} - engines: {node: '>=10.0.0'} - - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - - currently-unhandled@0.4.1: - resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} - engines: {node: '>=0.10.0'} - - d@1.0.2: - resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} - engines: {node: '>=0.12'} - - data-uri-to-buffer@2.0.2: - resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} - - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - - date-time@3.1.0: - resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} - engines: {node: '>=6'} - - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - - detect-libc@2.0.2: - resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} - engines: {node: '>=8'} - - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - difflib@0.2.4: - resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - docker-modem@3.0.8: - resolution: {integrity: sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==} - engines: {node: '>= 8.0'} - - dockerode@3.3.5: - resolution: {integrity: sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==} - engines: {node: '>= 8.0'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - - dreamopt@0.8.0: - resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==} - engines: {node: '>=0.4.0'} - - drizzle-kit@0.21.2: - resolution: {integrity: sha512-U87IhZyCt/9d0ZT/Na3KFJVY31tSxtTx/n9UMcWFpW/5c2Ede39xiCG5efNV/0iimsv97UIRtDI0ldLBW5lbcg==} - hasBin: true - - drizzle-orm@0.32.0-85c8008: - resolution: {integrity: sha512-gHLqGZz0eqAvSw4vq46sHRV8qLHxrbuCVlwaVZ1t4ntyH8csyCKEXTWO78cBJwYUpz7BCSzqVX+5ZYa/QM+/Gw==} - peerDependencies: - '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' - '@neondatabase/serverless': '>=0.1' - '@op-engineering/op-sqlite': '>=2' - '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' - '@tidbcloud/serverless': '*' - '@types/better-sqlite3': '*' - '@types/pg': '*' - '@types/react': '>=18' - '@types/sql.js': '*' - '@vercel/postgres': '>=0.8.0' - '@xata.io/client': '*' - better-sqlite3: '>=7' - bun-types: '*' - expo-sqlite: '>=13.2.0' - knex: '*' - kysely: '*' - mysql2: '>=2' - pg: '>=8' - postgres: '>=3' - react: '>=18' - sql.js: '>=1' - sqlite3: '>=5' - peerDependenciesMeta: - '@aws-sdk/client-rds-data': - optional: true - '@cloudflare/workers-types': - optional: true - '@electric-sql/pglite': - optional: true - '@libsql/client': - optional: true - '@neondatabase/serverless': - optional: true - '@op-engineering/op-sqlite': - optional: true - '@opentelemetry/api': - optional: true - '@planetscale/database': - optional: true - '@tidbcloud/serverless': - optional: true - '@types/better-sqlite3': - optional: true - '@types/pg': - optional: true - '@types/react': - optional: true - '@types/sql.js': - optional: true - '@vercel/postgres': - optional: true - '@xata.io/client': - optional: true - better-sqlite3: - optional: true - bun-types: - optional: true - expo-sqlite: - optional: true - knex: - optional: true - kysely: - optional: true - mysql2: - optional: true - pg: - optional: true - postgres: - optional: true - react: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - - duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emittery@1.0.3: - resolution: {integrity: sha512-tJdCJitoy2lrC2ldJcqN4vkqJ00lT+tOWNT1hBJjO/3FDMJa5TTIiYGCKGkn/WfCyOzUMObeohbVTj00fhiLiA==} - engines: {node: '>=14.16'} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - emojilib@2.4.0: - resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} - - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - - env-paths@3.0.0: - resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - es5-ext@0.10.64: - resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} - engines: {node: '>=0.10'} - - es6-iterator@2.0.3: - resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} - - es6-symbol@3.1.4: - resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} - engines: {node: '>=0.12'} - - es6-weak-map@2.0.3: - resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} - - esbuild-android-64@0.14.54: - resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - esbuild-android-arm64@0.14.54: - resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - esbuild-darwin-64@0.14.54: - resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - esbuild-darwin-arm64@0.14.54: - resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - esbuild-freebsd-64@0.14.54: - resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - esbuild-freebsd-arm64@0.14.54: - resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - esbuild-linux-32@0.14.54: - resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - esbuild-linux-64@0.14.54: - resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - esbuild-linux-arm64@0.14.54: - resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - esbuild-linux-arm@0.14.54: - resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - esbuild-linux-mips64le@0.14.54: - resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - esbuild-linux-ppc64le@0.14.54: - resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - esbuild-linux-riscv64@0.14.54: - resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - esbuild-linux-s390x@0.14.54: - resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - esbuild-netbsd-64@0.14.54: - resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - esbuild-node-externals@1.13.1: - resolution: {integrity: sha512-ho4Lokc6iMB1lWbb2tWJ6otien+3Kfoaxe0fy7NUNgVuLnfmlW+GRINftTVUGtTVY/dapuwUu/CvFylYNwzkMA==} - engines: {node: '>=12'} - peerDependencies: - esbuild: 0.12 - 0.21 - - esbuild-openbsd-64@0.14.54: - resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - esbuild-register@3.5.0: - resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} - peerDependencies: - esbuild: '>=0.12 <1' - - esbuild-sunos-64@0.14.54: - resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - esbuild-windows-32@0.14.54: - resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - esbuild-windows-64@0.14.54: - resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - esbuild-windows-arm64@0.14.54: - resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - esbuild@0.14.54: - resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.17.19: - resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} - engines: {node: '>=12'} - hasBin: true - - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-plugin-prettier@5.1.3: - resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '*' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - - esniff@2.0.1: - resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} - engines: {node: '>=0.10'} - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@0.6.1: - resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - event-emitter@0.3.5: - resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} - - event-stream@3.3.4: - resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - - exit-hook@2.2.1: - resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} - engines: {node: '>=6'} - - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - - ext@1.7.0: - resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fast-xml-parser@4.2.5: - resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} - hasBin: true - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - - figures@5.0.0: - resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} - engines: {node: '>=14'} - - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - find-up@6.3.0: - resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} - - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - - from@0.1.7: - resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - - fs-extra@11.2.0: - resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} - engines: {node: '>=14.14'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - fx@34.0.0: - resolution: {integrity: sha512-/fZih3/WLsrtlaj2mahjWxAmyuikmcl3D5kKPqLtFmEilLsy9wp0+/vEmfvYXXhwJc+ajtCFDCf+yttXmPMHSQ==} - hasBin: true - - generate-function@2.3.1: - resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - - get-port@6.1.2: - resolution: {integrity: sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - get-source@2.0.12: - resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} - - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - - glob@10.3.15: - resolution: {integrity: sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==} - engines: {node: '>=16 || 14 >=14.18'} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - - glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globby@13.2.2: - resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - hanji@0.0.5: - resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - heap@0.2.7: - resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} - - hono@4.3.9: - resolution: {integrity: sha512-6c5LVE23HnIS8iBhY+XPmYJlPeeClznOi7mBNsAsJCgxo8Ciz75LTjqRUf5wv4RYq8kL+1KPLUZHCtKmbZssNg==} - engines: {node: '>=16.0.0'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore-by-default@2.1.0: - resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==} - engines: {node: '>=10 <11 || >=12 <13 || >=14'} - - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - irregular-plurals@3.5.0: - resolution: {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} - engines: {node: '>=8'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} - - is-error@2.2.2: - resolution: {integrity: sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - - is-promise@2.2.2: - resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - is-property@1.0.2: - resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - - is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} - engines: {node: '>=12.13'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - - js-string-escape@1.0.1: - resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} - engines: {node: '>= 0.8'} - - js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-diff@0.9.0: - resolution: {integrity: sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==} - hasBin: true - - json-diff@1.0.6: - resolution: {integrity: sha512-tcFIPRdlc35YkYdGxcamJjllUhXWv4n2rK9oJ2RsAzV4FBkuV4ojKEDgcZ+kpKxDmJKv+PFK65+1tVVOnSeEqA==} - hasBin: true - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - libsql@0.2.0: - resolution: {integrity: sha512-ELBRqhpJx5Dap0187zKQnntZyk4EjlDHSrjIVL8t+fQ5e8IxbQTeYgZgigMjB1EvrETdkm0Y0VxBGhzPQ+t0Jg==} - cpu: [x64, arm64] - os: [darwin, linux, win32] - - lilconfig@3.1.1: - resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - load-json-file@7.0.1: - resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - - lodash.throttle@4.1.1: - resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - long@4.0.0: - resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} - - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - - lru-cache@10.2.2: - resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} - engines: {node: 14 || >=16.14} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - lru-queue@0.1.0: - resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - - magic-string@0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - - map-age-cleaner@0.1.3: - resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==} - engines: {node: '>=6'} - - map-stream@0.1.0: - resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - - marked-terminal@6.2.0: - resolution: {integrity: sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==} - engines: {node: '>=16.0.0'} - peerDependencies: - marked: '>=1 <12' - - marked@9.1.6: - resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} - engines: {node: '>= 16'} - hasBin: true - - matcher@5.0.0: - resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - md5-hex@3.0.1: - resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} - engines: {node: '>=8'} - - mem@9.0.2: - resolution: {integrity: sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==} - engines: {node: '>=12.20'} - - memoizee@0.4.15: - resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - miniflare@3.20240512.0: - resolution: {integrity: sha512-X0PlKR0AROKpxFoJNmRtCMIuJxj+ngEcyTOlEokj2rAQ0TBwUhB4/1uiPvdI6ofW5NugPOD1uomAv+gLjwsLDQ==} - engines: {node: '>=16.13'} - hasBin: true - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - - minimatch@7.4.6: - resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} - engines: {node: '>=10'} - - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.1: - resolution: {integrity: sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==} - engines: {node: '>=16 || 14 >=14.17'} - - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - - mlly@1.7.0: - resolution: {integrity: sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==} - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - - mysql2@2.3.3: - resolution: {integrity: sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==} - engines: {node: '>= 8.0'} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - named-placeholders@1.1.3: - resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} - engines: {node: '>=12.0.0'} - - nan@2.19.0: - resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} - - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - next-tick@1.1.0: - resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - - node-abi@3.62.0: - resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==} - engines: {node: '>=10'} - - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - - node-emoji@2.1.3: - resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} - engines: {node: '>=18'} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-fetch@3.3.1: - resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - node-forge@1.3.1: - resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} - engines: {node: '>= 6.13.0'} - - node-gyp-build@4.8.1: - resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} - hasBin: true - - nofilter@3.1.0: - resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} - engines: {node: '>=12.19'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - p-defer@1.0.0: - resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} - engines: {node: '>=4'} - - p-event@5.0.1: - resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-map@5.5.0: - resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} - engines: {node: '>=12'} - - p-timeout@5.1.0: - resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} - engines: {node: '>=12'} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-ms@3.0.0: - resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} - engines: {node: '>=12'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-to-regexp@6.2.2: - resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - - pause-stream@0.0.11: - resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - - pg-cloudflare@1.1.1: - resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} - - pg-connection-string@2.6.4: - resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-numeric@1.0.2: - resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} - engines: {node: '>=4'} - - pg-pool@3.6.2: - resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.6.1: - resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg-types@4.0.2: - resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} - engines: {node: '>=10'} - - pg@8.11.5: - resolution: {integrity: sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==} - engines: {node: '>= 8.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - - pkg-conf@4.0.0: - resolution: {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - pkg-types@1.1.1: - resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==} - - plur@5.1.0: - resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} - - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-array@3.0.2: - resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} - engines: {node: '>=12'} - - postgres-bytea@1.0.0: - resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} - engines: {node: '>=0.10.0'} - - postgres-bytea@3.0.0: - resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} - engines: {node: '>= 6'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-date@2.1.0: - resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} - engines: {node: '>=12'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - postgres-interval@3.0.0: - resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} - engines: {node: '>=12'} - - postgres-range@1.1.4: - resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - - postgres@3.4.4: - resolution: {integrity: sha512-IbyN+9KslkqcXa8AO9fxpk97PA4pzewvpi2B3Dwy9u4zpV32QicaEdgmF3eSQUzdRk7ttDHQejNgAEr4XoeH4A==} - engines: {node: '>=12'} - - prebuild-install@7.1.2: - resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} - engines: {node: '>=10'} - hasBin: true - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - pretty-ms@8.0.0: - resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} - engines: {node: '>=14.16'} - - printable-characters@1.0.42: - resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} - - ps-tree@1.2.0: - resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} - engines: {node: '>= 0.10'} - hasBin: true - - pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - redeyed@2.1.1: - resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - - rollup-plugin-inject@3.0.2: - resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} - deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. - - rollup-plugin-node-polyfills@0.2.1: - resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} - - rollup-pluginutils@2.8.2: - resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} - - rollup@4.17.2: - resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - selfsigned@2.4.1: - resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} - engines: {node: '>=10'} - - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} - engines: {node: '>=10'} - hasBin: true - - seq-queue@0.0.5: - resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} - - serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - skin-tone@2.0.0: - resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} - engines: {node: '>=8'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} - - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - - sourcemap-codec@1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - deprecated: Please use @jridgewell/sourcemap-codec instead - - split-ca@1.0.1: - resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - split@0.3.3: - resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - sqlstring@2.3.3: - resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} - engines: {node: '>= 0.6'} - - ssh2@1.15.0: - resolution: {integrity: sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==} - engines: {node: '>=10.16.0'} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - stacktracey@2.1.8: - resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} - - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - - stoppable@1.1.0: - resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} - engines: {node: '>=4', npm: '>=6'} - - stream-combiner@0.0.4: - resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - - strnum@1.0.5: - resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} - - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - superjson@2.2.1: - resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} - engines: {node: '>=16'} - - supertap@3.0.1: - resolution: {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-hyperlinks@3.0.0: - resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} - engines: {node: '>=14.18'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - synckit@0.8.8: - resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} - engines: {node: ^14.18.0 || >=16.0.0} - - tar-fs@2.0.1: - resolution: {integrity: sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==} - - tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - temp-dir@3.0.0: - resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} - engines: {node: '>=14.16'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - time-zone@1.0.0: - resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} - engines: {node: '>=4'} - - timers-ext@0.1.7: - resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} - - tinybench@2.8.0: - resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} - - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} - engines: {node: '>=14.0.0'} - - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} - engines: {node: '>=14.0.0'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - ts-expose-internals-conditionally@1.0.0-empty.0: - resolution: {integrity: sha512-F8m9NOF6ZhdOClDVdlM8gj3fDCav4ZIFSs/EI3ksQbAAXVSCN/Jh5OCJDDZWBuBy9psFc6jULGDlPwjMYMhJDw==} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - tsconfck@3.0.3: - resolution: {integrity: sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - - tsup@8.0.2: - resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - - tsx@3.14.0: - resolution: {integrity: sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==} - hasBin: true - - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type@2.7.2: - resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} - - typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} - engines: {node: '>=14.17'} - hasBin: true - - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - undici@5.28.4: - resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} - engines: {node: '>=14.0'} - - unicode-emoji-modifier-base@1.0.0: - resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} - engines: {node: '>=4'} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - utf-8-validate@6.0.3: - resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} - engines: {node: '>=6.14.2'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - - validate-npm-package-name@5.0.1: - resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite-tsconfig-paths@4.3.2: - resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@5.2.11: - resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - - webpod@0.0.2: - resolution: {integrity: sha512-cSwwQIeg8v4i3p4ajHhwgR7N6VyxAf+KYSSsY6Pd3aETE+xEU4vbitz7qQkB0I321xnhDdgtxuiSfk5r/FVtjg==} - hasBin: true - - well-known-symbols@2.0.0: - resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} - engines: {node: '>=6'} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - which@3.0.1: - resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - - why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} - engines: {node: '>=8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - workerd@1.20240512.0: - resolution: {integrity: sha512-VUBmR1PscAPHEE0OF/G2K7/H1gnr9aDWWZzdkIgWfNKkv8dKFCT75H+GJtUHjfwqz3rYCzaNZmatSXOpLGpF8A==} - engines: {node: '>=16'} - hasBin: true - - wrangler@3.57.0: - resolution: {integrity: sha512-izK3AZtlFoTq8N0EZjLOQ7hqwsjaXCc1cbNKuhsLJjDX1jB1YZBDPhIhtXL4VVzkJAcH+0Zw2gguOePFCHNaxw==} - engines: {node: '>=16.17.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20240512.0 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - ws@8.14.2: - resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.17.0: - resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - - xxhash-wasm@1.0.2: - resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yaml@2.4.2: - resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==} - engines: {node: '>= 14'} - hasBin: true - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - - youch@3.3.3: - resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==} - - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - - zx@7.2.3: - resolution: {integrity: sha512-QODu38nLlYXg/B/Gw7ZKiZrvPkEsjPN3LQ5JFXM7h0JvwhEdPNNl+4Ao1y4+o3CLNiDUNcwzQYZ4/Ko7kKzCMA==} - engines: {node: '>= 16.0.0'} - hasBin: true - -snapshots: - - '@andrewbranch/untar.js@1.0.3': {} - - '@arethetypeswrong/cli@0.15.3': - dependencies: - '@arethetypeswrong/core': 0.15.1 - chalk: 4.1.2 - cli-table3: 0.6.5 - commander: 10.0.1 - marked: 9.1.6 - marked-terminal: 6.2.0(marked@9.1.6) - semver: 7.6.2 - - '@arethetypeswrong/core@0.15.1': - dependencies: - '@andrewbranch/untar.js': 1.0.3 - fflate: 0.8.2 - semver: 7.6.2 - ts-expose-internals-conditionally: 1.0.0-empty.0 - typescript: 5.3.3 - validate-npm-package-name: 5.0.1 - - '@aws-crypto/ie11-detection@3.0.0': - dependencies: - tslib: 1.14.1 - - '@aws-crypto/sha256-browser@3.0.0': - dependencies: - '@aws-crypto/ie11-detection': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-crypto/supports-web-crypto': 3.0.0 - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-locate-window': 3.568.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - - '@aws-crypto/sha256-js@3.0.0': - dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.577.0 - tslib: 1.14.1 - - '@aws-crypto/supports-web-crypto@3.0.0': - dependencies: - tslib: 1.14.1 - - '@aws-crypto/util@3.0.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - - '@aws-sdk/client-rds-data@3.577.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/client-sts': 3.577.0 - '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/middleware-host-header': 3.577.0 - '@aws-sdk/middleware-logger': 3.577.0 - '@aws-sdk/middleware-recursion-detection': 3.577.0 - '@aws-sdk/middleware-user-agent': 3.577.0 - '@aws-sdk/region-config-resolver': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@aws-sdk/util-user-agent-browser': 3.577.0 - '@aws-sdk/util-user-agent-node': 3.577.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.1 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.1 - '@smithy/util-defaults-mode-node': 3.0.1 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.577.0 - '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/middleware-host-header': 3.577.0 - '@aws-sdk/middleware-logger': 3.577.0 - '@aws-sdk/middleware-recursion-detection': 3.577.0 - '@aws-sdk/middleware-user-agent': 3.577.0 - '@aws-sdk/region-config-resolver': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@aws-sdk/util-user-agent-browser': 3.577.0 - '@aws-sdk/util-user-agent-node': 3.577.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.1 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.1 - '@smithy/util-defaults-mode-node': 3.0.1 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sts' - - aws-crt - - '@aws-sdk/client-sso@3.577.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/core': 3.576.0 - '@aws-sdk/middleware-host-header': 3.577.0 - '@aws-sdk/middleware-logger': 3.577.0 - '@aws-sdk/middleware-recursion-detection': 3.577.0 - '@aws-sdk/middleware-user-agent': 3.577.0 - '@aws-sdk/region-config-resolver': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@aws-sdk/util-user-agent-browser': 3.577.0 - '@aws-sdk/util-user-agent-node': 3.577.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.1 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.1 - '@smithy/util-defaults-mode-node': 3.0.1 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sts@3.577.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/middleware-host-header': 3.577.0 - '@aws-sdk/middleware-logger': 3.577.0 - '@aws-sdk/middleware-recursion-detection': 3.577.0 - '@aws-sdk/middleware-user-agent': 3.577.0 - '@aws-sdk/region-config-resolver': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@aws-sdk/util-user-agent-browser': 3.577.0 - '@aws-sdk/util-user-agent-node': 3.577.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.1 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.1 - '@smithy/util-defaults-mode-node': 3.0.1 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/core@3.576.0': - dependencies: - '@smithy/core': 2.0.1 - '@smithy/protocol-http': 4.0.0 - '@smithy/signature-v4': 3.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - fast-xml-parser: 4.2.5 - tslib: 2.6.2 - - '@aws-sdk/credential-provider-env@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/credential-provider-http@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/node-http-handler': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/util-stream': 3.0.1 - tslib: 2.6.2 - - '@aws-sdk/credential-provider-ini@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0)': - dependencies: - '@aws-sdk/client-sts': 3.577.0 - '@aws-sdk/credential-provider-env': 3.577.0 - '@aws-sdk/credential-provider-process': 3.577.0 - '@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) - '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/types': 3.577.0 - '@smithy/credential-provider-imds': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - - '@aws-sdk/credential-provider-node@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0)': - dependencies: - '@aws-sdk/credential-provider-env': 3.577.0 - '@aws-sdk/credential-provider-http': 3.577.0 - '@aws-sdk/credential-provider-ini': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/credential-provider-process': 3.577.0 - '@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) - '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/types': 3.577.0 - '@smithy/credential-provider-imds': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - '@aws-sdk/client-sts' - - aws-crt - - '@aws-sdk/credential-provider-process@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/credential-provider-sso@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))': - dependencies: - '@aws-sdk/client-sso': 3.577.0 - '@aws-sdk/token-providers': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - - '@aws-sdk/credential-provider-web-identity@3.577.0(@aws-sdk/client-sts@3.577.0)': - dependencies: - '@aws-sdk/client-sts': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/middleware-host-header@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/middleware-logger@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/middleware-recursion-detection@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/middleware-user-agent@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/region-config-resolver@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))': - dependencies: - '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/types@3.577.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/util-endpoints@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/types': 3.0.0 - '@smithy/util-endpoints': 2.0.0 - tslib: 2.6.2 - - '@aws-sdk/util-locate-window@3.568.0': - dependencies: - tslib: 2.6.2 - - '@aws-sdk/util-user-agent-browser@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/types': 3.0.0 - bowser: 2.11.0 - tslib: 2.6.2 - - '@aws-sdk/util-user-agent-node@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/util-utf8-browser@3.259.0': - dependencies: - tslib: 2.6.2 - - '@balena/dockerignore@1.0.2': {} - - '@cloudflare/kv-asset-handler@0.3.2': - dependencies: - mime: 3.0.0 - - '@cloudflare/workerd-darwin-64@1.20240512.0': - optional: true - - '@cloudflare/workerd-darwin-arm64@1.20240512.0': - optional: true - - '@cloudflare/workerd-linux-64@1.20240512.0': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20240512.0': - optional: true - - '@cloudflare/workerd-windows-64@1.20240512.0': - optional: true - - '@cloudflare/workers-types@4.20240512.0': {} - - '@colors/colors@1.5.0': - optional: true - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - - '@electric-sql/pglite@0.1.5': {} - - '@esbuild-kit/core-utils@3.3.2': - dependencies: - esbuild: 0.18.20 - source-map-support: 0.5.21 - - '@esbuild-kit/esm-loader@2.6.5': - dependencies: - '@esbuild-kit/core-utils': 3.3.2 - get-tsconfig: 4.7.5 - - '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': - dependencies: - esbuild: 0.17.19 - - '@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19)': - dependencies: - esbuild: 0.17.19 - escape-string-regexp: 4.0.0 - rollup-plugin-node-polyfills: 0.2.1 - - '@esbuild/aix-ppc64@0.19.12': - optional: true - - '@esbuild/aix-ppc64@0.20.2': - optional: true - - '@esbuild/android-arm64@0.17.19': - optional: true - - '@esbuild/android-arm64@0.18.20': - optional: true - - '@esbuild/android-arm64@0.19.12': - optional: true - - '@esbuild/android-arm64@0.20.2': - optional: true - - '@esbuild/android-arm@0.17.19': - optional: true - - '@esbuild/android-arm@0.18.20': - optional: true - - '@esbuild/android-arm@0.19.12': - optional: true - - '@esbuild/android-arm@0.20.2': - optional: true - - '@esbuild/android-x64@0.17.19': - optional: true - - '@esbuild/android-x64@0.18.20': - optional: true - - '@esbuild/android-x64@0.19.12': - optional: true - - '@esbuild/android-x64@0.20.2': - optional: true - - '@esbuild/darwin-arm64@0.17.19': - optional: true - - '@esbuild/darwin-arm64@0.18.20': - optional: true - - '@esbuild/darwin-arm64@0.19.12': - optional: true - - '@esbuild/darwin-arm64@0.20.2': - optional: true - - '@esbuild/darwin-x64@0.17.19': - optional: true - - '@esbuild/darwin-x64@0.18.20': - optional: true - - '@esbuild/darwin-x64@0.19.12': - optional: true - - '@esbuild/darwin-x64@0.20.2': - optional: true - - '@esbuild/freebsd-arm64@0.17.19': - optional: true - - '@esbuild/freebsd-arm64@0.18.20': - optional: true - - '@esbuild/freebsd-arm64@0.19.12': - optional: true - - '@esbuild/freebsd-arm64@0.20.2': - optional: true - - '@esbuild/freebsd-x64@0.17.19': - optional: true - - '@esbuild/freebsd-x64@0.18.20': - optional: true - - '@esbuild/freebsd-x64@0.19.12': - optional: true - - '@esbuild/freebsd-x64@0.20.2': - optional: true - - '@esbuild/linux-arm64@0.17.19': - optional: true - - '@esbuild/linux-arm64@0.18.20': - optional: true - - '@esbuild/linux-arm64@0.19.12': - optional: true - - '@esbuild/linux-arm64@0.20.2': - optional: true - - '@esbuild/linux-arm@0.17.19': - optional: true - - '@esbuild/linux-arm@0.18.20': - optional: true - - '@esbuild/linux-arm@0.19.12': - optional: true - - '@esbuild/linux-arm@0.20.2': - optional: true - - '@esbuild/linux-ia32@0.17.19': - optional: true - - '@esbuild/linux-ia32@0.18.20': - optional: true - - '@esbuild/linux-ia32@0.19.12': - optional: true - - '@esbuild/linux-ia32@0.20.2': - optional: true - - '@esbuild/linux-loong64@0.14.54': - optional: true - - '@esbuild/linux-loong64@0.17.19': - optional: true - - '@esbuild/linux-loong64@0.18.20': - optional: true - - '@esbuild/linux-loong64@0.19.12': - optional: true - - '@esbuild/linux-loong64@0.20.2': - optional: true - - '@esbuild/linux-mips64el@0.17.19': - optional: true - - '@esbuild/linux-mips64el@0.18.20': - optional: true - - '@esbuild/linux-mips64el@0.19.12': - optional: true - - '@esbuild/linux-mips64el@0.20.2': - optional: true - - '@esbuild/linux-ppc64@0.17.19': - optional: true - - '@esbuild/linux-ppc64@0.18.20': - optional: true - - '@esbuild/linux-ppc64@0.19.12': - optional: true - - '@esbuild/linux-ppc64@0.20.2': - optional: true - - '@esbuild/linux-riscv64@0.17.19': - optional: true - - '@esbuild/linux-riscv64@0.18.20': - optional: true - - '@esbuild/linux-riscv64@0.19.12': - optional: true - - '@esbuild/linux-riscv64@0.20.2': - optional: true - - '@esbuild/linux-s390x@0.17.19': - optional: true - - '@esbuild/linux-s390x@0.18.20': - optional: true - - '@esbuild/linux-s390x@0.19.12': - optional: true - - '@esbuild/linux-s390x@0.20.2': - optional: true - - '@esbuild/linux-x64@0.17.19': - optional: true - - '@esbuild/linux-x64@0.18.20': - optional: true - - '@esbuild/linux-x64@0.19.12': - optional: true - - '@esbuild/linux-x64@0.20.2': - optional: true - - '@esbuild/netbsd-x64@0.17.19': - optional: true - - '@esbuild/netbsd-x64@0.18.20': - optional: true - - '@esbuild/netbsd-x64@0.19.12': - optional: true - - '@esbuild/netbsd-x64@0.20.2': - optional: true - - '@esbuild/openbsd-x64@0.17.19': - optional: true - - '@esbuild/openbsd-x64@0.18.20': - optional: true - - '@esbuild/openbsd-x64@0.19.12': - optional: true - - '@esbuild/openbsd-x64@0.20.2': - optional: true - - '@esbuild/sunos-x64@0.17.19': - optional: true - - '@esbuild/sunos-x64@0.18.20': - optional: true - - '@esbuild/sunos-x64@0.19.12': - optional: true - - '@esbuild/sunos-x64@0.20.2': - optional: true - - '@esbuild/win32-arm64@0.17.19': - optional: true - - '@esbuild/win32-arm64@0.18.20': - optional: true - - '@esbuild/win32-arm64@0.19.12': - optional: true - - '@esbuild/win32-arm64@0.20.2': - optional: true - - '@esbuild/win32-ia32@0.17.19': - optional: true - - '@esbuild/win32-ia32@0.18.20': - optional: true - - '@esbuild/win32-ia32@0.19.12': - optional: true - - '@esbuild/win32-ia32@0.20.2': - optional: true - - '@esbuild/win32-x64@0.17.19': - optional: true - - '@esbuild/win32-x64@0.18.20': - optional: true - - '@esbuild/win32-x64@0.19.12': - optional: true - - '@esbuild/win32-x64@0.20.2': - optional: true - - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': - dependencies: - eslint: 8.57.0 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.10.0': {} - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.1 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.0': {} - - '@ewoudenberg/difflib@0.1.0': - dependencies: - heap: 0.2.7 - - '@fastify/busboy@2.1.1': {} - - '@hono/node-server@1.11.1': {} - - '@hono/zod-validator@0.2.1(hono@4.3.9)(zod@3.23.8)': - dependencies: - hono: 4.3.9 - zod: 3.23.8 - - '@humanwhocodes/config-array@0.11.14': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - - '@jridgewell/gen-mapping@0.3.5': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.4.15': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - - '@libsql/client@0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3)': - dependencies: - '@libsql/core': 0.4.3 - '@libsql/hrana-client': 0.5.6(bufferutil@4.0.8)(utf-8-validate@6.0.3) - js-base64: 3.7.7 - optionalDependencies: - libsql: 0.2.0 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@libsql/core@0.4.3': - dependencies: - js-base64: 3.7.7 - - '@libsql/darwin-arm64@0.2.0': - optional: true - - '@libsql/darwin-x64@0.2.0': - optional: true - - '@libsql/hrana-client@0.5.6(bufferutil@4.0.8)(utf-8-validate@6.0.3)': - dependencies: - '@libsql/isomorphic-fetch': 0.1.12 - '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3) - js-base64: 3.7.7 - node-fetch: 3.3.2 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@libsql/isomorphic-fetch@0.1.12': - dependencies: - '@types/node-fetch': 2.6.11 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - - '@libsql/isomorphic-ws@0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3)': - dependencies: - '@types/ws': 8.5.10 - ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@libsql/linux-arm64-gnu@0.2.0': - optional: true - - '@libsql/linux-arm64-musl@0.2.0': - optional: true - - '@libsql/linux-x64-gnu@0.2.0': - optional: true - - '@libsql/linux-x64-musl@0.2.0': - optional: true - - '@libsql/win32-x64-msvc@0.2.0': - optional: true - - '@neon-rs/load@0.0.4': - optional: true - - '@neondatabase/serverless@0.7.2': - dependencies: - '@types/pg': 8.6.6 - - '@neondatabase/serverless@0.9.3': - dependencies: - '@types/pg': 8.11.6 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - - '@originjs/vite-plugin-commonjs@1.0.3': - dependencies: - esbuild: 0.14.54 - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@pkgr/core@0.1.1': {} - - '@planetscale/database@1.18.0': {} - - '@rollup/rollup-android-arm-eabi@4.17.2': - optional: true - - '@rollup/rollup-android-arm64@4.17.2': - optional: true - - '@rollup/rollup-darwin-arm64@4.17.2': - optional: true - - '@rollup/rollup-darwin-x64@4.17.2': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.17.2': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.17.2': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-x64-musl@4.17.2': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.17.2': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.17.2': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.17.2': - optional: true - - '@sinclair/typebox@0.27.8': {} - - '@sindresorhus/is@4.6.0': {} - - '@smithy/abort-controller@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/config-resolver@3.0.0': - dependencies: - '@smithy/node-config-provider': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 - - '@smithy/core@2.0.1': - dependencies: - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 - - '@smithy/credential-provider-imds@3.0.0': - dependencies: - '@smithy/node-config-provider': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - tslib: 2.6.2 - - '@smithy/fetch-http-handler@3.0.1': - dependencies: - '@smithy/protocol-http': 4.0.0 - '@smithy/querystring-builder': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-base64': 3.0.0 - tslib: 2.6.2 - - '@smithy/hash-node@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - - '@smithy/invalid-dependency@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/is-array-buffer@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/middleware-content-length@3.0.0': - dependencies: - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/middleware-endpoint@3.0.0': - dependencies: - '@smithy/middleware-serde': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 - - '@smithy/middleware-retry@3.0.1': - dependencies: - '@smithy/node-config-provider': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/service-error-classification': 3.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - tslib: 2.6.2 - uuid: 9.0.1 - - '@smithy/middleware-serde@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/middleware-stack@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/node-config-provider@3.0.0': - dependencies: - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/node-http-handler@3.0.0': - dependencies: - '@smithy/abort-controller': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/querystring-builder': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/property-provider@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/protocol-http@4.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/querystring-builder@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - '@smithy/util-uri-escape': 3.0.0 - tslib: 2.6.2 - - '@smithy/querystring-parser@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/service-error-classification@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - - '@smithy/shared-ini-file-loader@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/signature-v4@3.0.0': - dependencies: - '@smithy/is-array-buffer': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-uri-escape': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - - '@smithy/smithy-client@3.0.1': - dependencies: - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-stream': 3.0.1 - tslib: 2.6.2 - - '@smithy/types@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/url-parser@3.0.0': - dependencies: - '@smithy/querystring-parser': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-base64@3.0.0': - dependencies: - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-body-length-browser@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-body-length-node@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-buffer-from@3.0.0': - dependencies: - '@smithy/is-array-buffer': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-config-provider@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-defaults-mode-browser@3.0.1': - dependencies: - '@smithy/property-provider': 3.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - bowser: 2.11.0 - tslib: 2.6.2 - - '@smithy/util-defaults-mode-node@3.0.1': - dependencies: - '@smithy/config-resolver': 3.0.0 - '@smithy/credential-provider-imds': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-endpoints@2.0.0': - dependencies: - '@smithy/node-config-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-hex-encoding@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-middleware@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-retry@3.0.0': - dependencies: - '@smithy/service-error-classification': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-stream@3.0.1': - dependencies: - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/node-http-handler': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-uri-escape@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-utf8@3.0.0': - dependencies: - '@smithy/util-buffer-from': 3.0.0 - tslib: 2.6.2 - - '@types/better-sqlite3@7.6.10': - dependencies: - '@types/node': 18.19.33 - - '@types/docker-modem@3.0.6': - dependencies: - '@types/node': 18.19.33 - '@types/ssh2': 1.15.0 - - '@types/dockerode@3.3.29': - dependencies: - '@types/docker-modem': 3.0.6 - '@types/node': 18.19.33 - '@types/ssh2': 1.15.0 - - '@types/estree@1.0.5': {} - - '@types/fs-extra@11.0.4': - dependencies: - '@types/jsonfile': 6.1.4 - '@types/node': 18.19.33 - - '@types/glob@8.1.0': - dependencies: - '@types/minimatch': 5.1.2 - '@types/node': 18.19.33 - - '@types/json-diff@1.0.3': {} - - '@types/jsonfile@6.1.4': - dependencies: - '@types/node': 18.19.33 - - '@types/minimatch@5.1.2': {} - - '@types/minimist@1.2.5': {} - - '@types/node-fetch@2.6.11': - dependencies: - '@types/node': 18.19.33 - form-data: 4.0.0 - - '@types/node-forge@1.3.11': - dependencies: - '@types/node': 18.19.33 - - '@types/node@18.19.33': - dependencies: - undici-types: 5.26.5 - - '@types/pg@8.11.6': - dependencies: - '@types/node': 18.19.33 - pg-protocol: 1.6.1 - pg-types: 4.0.2 - - '@types/pg@8.6.6': - dependencies: - '@types/node': 18.19.33 - pg-protocol: 1.6.1 - pg-types: 2.2.0 - - '@types/pluralize@0.0.33': {} - - '@types/ps-tree@1.1.6': {} - - '@types/semver@7.5.8': {} - - '@types/ssh2@1.15.0': - dependencies: - '@types/node': 18.19.33 - - '@types/uuid@9.0.8': {} - - '@types/which@3.0.3': {} - - '@types/ws@8.5.10': - dependencies: - '@types/node': 18.19.33 - - '@typescript-eslint/eslint-plugin@7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 7.10.0 - '@typescript-eslint/type-utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.10.0 - eslint: 8.57.0 - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@typescript-eslint/scope-manager': 7.10.0 - '@typescript-eslint/types': 7.10.0 - '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.10.0 - debug: 4.3.4 - eslint: 8.57.0 - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@7.10.0': - dependencies: - '@typescript-eslint/types': 7.10.0 - '@typescript-eslint/visitor-keys': 7.10.0 - - '@typescript-eslint/type-utils@7.10.0(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) - '@typescript-eslint/utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) - debug: 4.3.4 - eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@7.10.0': {} - - '@typescript-eslint/typescript-estree@7.10.0(typescript@5.4.5)': - dependencies: - '@typescript-eslint/types': 7.10.0 - '@typescript-eslint/visitor-keys': 7.10.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.6.2 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@7.10.0(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@typescript-eslint/scope-manager': 7.10.0 - '@typescript-eslint/types': 7.10.0 - '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) - eslint: 8.57.0 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@7.10.0': - dependencies: - '@typescript-eslint/types': 7.10.0 - eslint-visitor-keys: 3.4.3 - - '@ungap/structured-clone@1.2.0': {} - - '@vercel/postgres@0.8.0': - dependencies: - '@neondatabase/serverless': 0.7.2 - bufferutil: 4.0.8 - utf-8-validate: 6.0.3 - ws: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) - - '@vitest/expect@1.6.0': - dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.4.1 - - '@vitest/runner@1.6.0': - dependencies: - '@vitest/utils': 1.6.0 - p-limit: 5.0.0 - pathe: 1.1.2 - - '@vitest/snapshot@1.6.0': - dependencies: - magic-string: 0.30.10 - pathe: 1.1.2 - pretty-format: 29.7.0 - - '@vitest/spy@1.6.0': - dependencies: - tinyspy: 2.2.1 - - '@vitest/utils@1.6.0': - dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 - - acorn-jsx@5.3.2(acorn@8.11.3): - dependencies: - acorn: 8.11.3 - - acorn-walk@8.3.2: {} - - acorn@8.11.3: {} - - aggregate-error@4.0.1: - dependencies: - clean-stack: 4.2.0 - indent-string: 5.0.0 - - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-escapes@6.2.1: {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.0.1: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - ansi-styles@6.2.1: {} - - ansicolors@0.3.2: {} - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - argparse@2.0.1: {} - - array-find-index@1.0.2: {} - - array-union@2.1.0: {} - - arrgv@1.0.2: {} - - arrify@3.0.0: {} - - as-table@1.0.55: - dependencies: - printable-characters: 1.0.42 - - asn1@0.2.6: - dependencies: - safer-buffer: 2.1.2 - - assertion-error@1.1.0: {} - - asynckit@0.4.0: {} - - ava@5.3.1: - dependencies: - acorn: 8.11.3 - acorn-walk: 8.3.2 - ansi-styles: 6.2.1 - arrgv: 1.0.2 - arrify: 3.0.0 - callsites: 4.1.0 - cbor: 8.1.0 - chalk: 5.3.0 - chokidar: 3.6.0 - chunkd: 2.0.1 - ci-info: 3.9.0 - ci-parallel-vars: 1.0.1 - clean-yaml-object: 0.1.0 - cli-truncate: 3.1.0 - code-excerpt: 4.0.0 - common-path-prefix: 3.0.0 - concordance: 5.0.4 - currently-unhandled: 0.4.1 - debug: 4.3.4 - emittery: 1.0.3 - figures: 5.0.0 - globby: 13.2.2 - ignore-by-default: 2.1.0 - indent-string: 5.0.0 - is-error: 2.2.2 - is-plain-object: 5.0.0 - is-promise: 4.0.0 - matcher: 5.0.0 - mem: 9.0.2 - ms: 2.1.3 - p-event: 5.0.1 - p-map: 5.5.0 - picomatch: 2.3.1 - pkg-conf: 4.0.0 - plur: 5.1.0 - pretty-ms: 8.0.0 - resolve-cwd: 3.0.0 - stack-utils: 2.0.6 - strip-ansi: 7.1.0 - supertap: 3.0.1 - temp-dir: 3.0.0 - write-file-atomic: 5.0.1 - yargs: 17.7.2 - transitivePeerDependencies: - - supports-color - - balanced-match@1.0.2: {} - - base64-js@1.5.1: {} - - bcrypt-pbkdf@1.0.2: - dependencies: - tweetnacl: 0.14.5 - - better-sqlite3@9.6.0: - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.2 - - binary-extensions@2.3.0: {} - - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - - blake3-wasm@2.1.5: {} - - blueimp-md5@2.19.0: {} - - bowser@2.11.0: {} - - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.1: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.2: - dependencies: - fill-range: 7.1.1 - - buffer-from@1.1.2: {} - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bufferutil@4.0.8: - dependencies: - node-gyp-build: 4.8.1 - - buildcheck@0.0.6: - optional: true - - bundle-require@4.1.0(esbuild@0.19.12): - dependencies: - esbuild: 0.19.12 - load-tsconfig: 0.2.5 - - cac@6.7.14: {} - - callsites@3.1.0: {} - - callsites@4.1.0: {} - - camelcase@7.0.1: {} - - capnp-ts@0.7.0: - dependencies: - debug: 4.3.4 - tslib: 2.6.2 - transitivePeerDependencies: - - supports-color - - cardinal@2.1.1: - dependencies: - ansicolors: 0.3.2 - redeyed: 2.1.1 - - cbor@8.1.0: - dependencies: - nofilter: 3.1.0 - - chai@4.4.1: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.3 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@5.3.0: {} - - char-regex@1.0.2: {} - - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chownr@1.1.4: {} - - chunkd@2.0.1: {} - - ci-info@3.9.0: {} - - ci-parallel-vars@1.0.1: {} - - clean-stack@4.2.0: - dependencies: - escape-string-regexp: 5.0.0 - - clean-yaml-object@0.1.0: {} - - cli-color@2.0.4: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-iterator: 2.0.3 - memoizee: 0.4.15 - timers-ext: 0.1.7 - - cli-table3@0.6.5: - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - - cli-truncate@3.1.0: - dependencies: - slice-ansi: 5.0.0 - string-width: 5.1.2 - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - code-excerpt@4.0.0: - dependencies: - convert-to-spaces: 2.0.1 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colors@1.4.0: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - commander@10.0.1: {} - - commander@12.1.0: {} - - commander@4.1.1: {} - - commander@9.5.0: {} - - common-path-prefix@3.0.0: {} - - concat-map@0.0.1: {} - - concordance@5.0.4: - dependencies: - date-time: 3.1.0 - esutils: 2.0.3 - fast-diff: 1.3.0 - js-string-escape: 1.0.1 - lodash: 4.17.21 - md5-hex: 3.0.1 - semver: 7.6.2 - well-known-symbols: 2.0.0 - - confbox@0.1.7: {} - - convert-to-spaces@2.0.1: {} - - cookie@0.5.0: {} - - copy-anything@3.0.5: - dependencies: - is-what: 4.1.16 - - cpu-features@0.0.10: - dependencies: - buildcheck: 0.0.6 - nan: 2.19.0 - optional: true - - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - currently-unhandled@0.4.1: - dependencies: - array-find-index: 1.0.2 - - d@1.0.2: - dependencies: - es5-ext: 0.10.64 - type: 2.7.2 - - data-uri-to-buffer@2.0.2: {} - - data-uri-to-buffer@4.0.1: {} - - date-time@3.1.0: - dependencies: - time-zone: 1.0.0 - - debug@4.3.4: - dependencies: - ms: 2.1.2 - - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - deep-eql@4.1.3: - dependencies: - type-detect: 4.0.8 - - deep-extend@0.6.0: {} - - deep-is@0.1.4: {} - - delayed-stream@1.0.0: {} - - denque@2.1.0: {} - - detect-libc@2.0.2: - optional: true - - detect-libc@2.0.3: {} - - diff-sequences@29.6.3: {} - - difflib@0.2.4(patch_hash=jq4t3ysdpnbunjeje4v7nrqn2q): - dependencies: - heap: 0.2.7 - - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - - docker-modem@3.0.8: - dependencies: - debug: 4.3.4 - readable-stream: 3.6.2 - split-ca: 1.0.1 - ssh2: 1.15.0 - transitivePeerDependencies: - - supports-color - - dockerode@3.3.5: - dependencies: - '@balena/dockerignore': 1.0.2 - docker-modem: 3.0.8 - tar-fs: 2.0.1 - transitivePeerDependencies: - - supports-color - - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - - dotenv@16.4.5: {} - - dreamopt@0.8.0: - dependencies: - wordwrap: 1.0.0 - - drizzle-kit@0.21.2: - dependencies: - '@esbuild-kit/esm-loader': 2.6.5 - commander: 9.5.0 - env-paths: 3.0.0 - esbuild: 0.19.12 - esbuild-register: 3.5.0(esbuild@0.19.12) - glob: 8.1.0 - hanji: 0.0.5 - json-diff: 0.9.0 - zod: 3.23.8 - transitivePeerDependencies: - - supports-color - - drizzle-orm@0.32.0-85c8008(@aws-sdk/client-rds-data@3.577.0)(@cloudflare/workers-types@4.20240512.0)(@electric-sql/pglite@0.1.5)(@libsql/client@0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3))(@neondatabase/serverless@0.9.3)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@vercel/postgres@0.8.0)(better-sqlite3@9.6.0)(mysql2@2.3.3)(pg@8.11.5)(postgres@3.4.4): - optionalDependencies: - '@aws-sdk/client-rds-data': 3.577.0 - '@cloudflare/workers-types': 4.20240512.0 - '@electric-sql/pglite': 0.1.5 - '@libsql/client': 0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3) - '@neondatabase/serverless': 0.9.3 - '@planetscale/database': 1.18.0 - '@types/better-sqlite3': 7.6.10 - '@types/pg': 8.11.6 - '@vercel/postgres': 0.8.0 - better-sqlite3: 9.6.0 - mysql2: 2.3.3 - pg: 8.11.5 - postgres: 3.4.4 - - duplexer@0.1.2: {} - - eastasianwidth@0.2.0: {} - - emittery@1.0.3: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - emojilib@2.4.0: {} - - end-of-stream@1.4.4: - dependencies: - once: 1.4.0 - - env-paths@3.0.0: {} - - es5-ext@0.10.64: - dependencies: - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - esniff: 2.0.1 - next-tick: 1.1.0 - - es6-iterator@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-symbol: 3.1.4 - - es6-symbol@3.1.4: - dependencies: - d: 1.0.2 - ext: 1.7.0 - - es6-weak-map@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - - esbuild-android-64@0.14.54: - optional: true - - esbuild-android-arm64@0.14.54: - optional: true - - esbuild-darwin-64@0.14.54: - optional: true - - esbuild-darwin-arm64@0.14.54: - optional: true - - esbuild-freebsd-64@0.14.54: - optional: true - - esbuild-freebsd-arm64@0.14.54: - optional: true - - esbuild-linux-32@0.14.54: - optional: true - - esbuild-linux-64@0.14.54: - optional: true - - esbuild-linux-arm64@0.14.54: - optional: true - - esbuild-linux-arm@0.14.54: - optional: true - - esbuild-linux-mips64le@0.14.54: - optional: true - - esbuild-linux-ppc64le@0.14.54: - optional: true - - esbuild-linux-riscv64@0.14.54: - optional: true - - esbuild-linux-s390x@0.14.54: - optional: true - - esbuild-netbsd-64@0.14.54: - optional: true - - esbuild-node-externals@1.13.1(esbuild@0.19.12): - dependencies: - esbuild: 0.19.12 - find-up: 5.0.0 - tslib: 2.6.2 - - esbuild-openbsd-64@0.14.54: - optional: true - - esbuild-register@3.5.0(esbuild@0.19.12): - dependencies: - debug: 4.3.4 - esbuild: 0.19.12 - transitivePeerDependencies: - - supports-color - - esbuild-sunos-64@0.14.54: - optional: true - - esbuild-windows-32@0.14.54: - optional: true - - esbuild-windows-64@0.14.54: - optional: true - - esbuild-windows-arm64@0.14.54: - optional: true - - esbuild@0.14.54: - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - - esbuild@0.17.19: - optionalDependencies: - '@esbuild/android-arm': 0.17.19 - '@esbuild/android-arm64': 0.17.19 - '@esbuild/android-x64': 0.17.19 - '@esbuild/darwin-arm64': 0.17.19 - '@esbuild/darwin-x64': 0.17.19 - '@esbuild/freebsd-arm64': 0.17.19 - '@esbuild/freebsd-x64': 0.17.19 - '@esbuild/linux-arm': 0.17.19 - '@esbuild/linux-arm64': 0.17.19 - '@esbuild/linux-ia32': 0.17.19 - '@esbuild/linux-loong64': 0.17.19 - '@esbuild/linux-mips64el': 0.17.19 - '@esbuild/linux-ppc64': 0.17.19 - '@esbuild/linux-riscv64': 0.17.19 - '@esbuild/linux-s390x': 0.17.19 - '@esbuild/linux-x64': 0.17.19 - '@esbuild/netbsd-x64': 0.17.19 - '@esbuild/openbsd-x64': 0.17.19 - '@esbuild/sunos-x64': 0.17.19 - '@esbuild/win32-arm64': 0.17.19 - '@esbuild/win32-ia32': 0.17.19 - '@esbuild/win32-x64': 0.17.19 - - esbuild@0.18.20: - optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 - - esbuild@0.19.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 - - esbuild@0.20.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 - - escalade@3.1.2: {} - - escape-string-regexp@2.0.0: {} - - escape-string-regexp@4.0.0: {} - - escape-string-regexp@5.0.0: {} - - eslint-config-prettier@9.1.0(eslint@8.57.0): - dependencies: - eslint: 8.57.0 - - eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8): - dependencies: - eslint: 8.57.0 - prettier: 2.8.8 - prettier-linter-helpers: 1.0.0 - synckit: 0.8.8 - optionalDependencies: - eslint-config-prettier: 9.1.0(eslint@8.57.0) - - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint@8.57.0: - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.10.0 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.1 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - esniff@2.0.1: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - event-emitter: 0.3.5 - type: 2.7.2 - - espree@9.6.1: - dependencies: - acorn: 8.11.3 - acorn-jsx: 5.3.2(acorn@8.11.3) - eslint-visitor-keys: 3.4.3 - - esprima@4.0.1: {} - - esquery@1.5.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-walker@0.6.1: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.5 - - esutils@2.0.3: {} - - event-emitter@0.3.5: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - - event-stream@3.3.4: - dependencies: - duplexer: 0.1.2 - from: 0.1.7 - map-stream: 0.1.0 - pause-stream: 0.0.11 - split: 0.3.3 - stream-combiner: 0.0.4 - through: 2.3.8 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - execa@8.0.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - - exit-hook@2.2.1: {} - - expand-template@2.0.3: {} - - ext@1.7.0: - dependencies: - type: 2.7.2 - - fast-deep-equal@3.1.3: {} - - fast-diff@1.3.0: {} - - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fast-xml-parser@4.2.5: - dependencies: - strnum: 1.0.5 - - fastq@1.17.1: - dependencies: - reusify: 1.0.4 - - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - - fflate@0.8.2: {} - - figures@5.0.0: - dependencies: - escape-string-regexp: 5.0.0 - is-unicode-supported: 1.3.0 - - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - - file-uri-to-path@1.0.0: {} - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - find-up@6.3.0: - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - - flat-cache@3.2.0: - dependencies: - flatted: 3.3.1 - keyv: 4.5.4 - rimraf: 3.0.2 - - flatted@3.3.1: {} - - foreground-child@3.1.1: - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 - - from@0.1.7: {} - - fs-constants@1.0.0: {} - - fs-extra@11.2.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - fx@34.0.0: {} - - generate-function@2.3.1: - dependencies: - is-property: 1.0.2 - - get-caller-file@2.0.5: {} - - get-func-name@2.0.2: {} - - get-port@6.1.2: {} - - get-source@2.0.12: - dependencies: - data-uri-to-buffer: 2.0.2 - source-map: 0.6.1 - - get-stream@6.0.1: {} - - get-stream@8.0.1: {} - - get-tsconfig@4.7.5: - dependencies: - resolve-pkg-maps: 1.0.0 - - github-from-package@0.0.0: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob-to-regexp@0.4.1: {} - - glob@10.3.15: - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.4 - minipass: 7.1.1 - path-scurry: 1.11.1 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - glob@8.1.0: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 3.0.0 - - globby@13.2.2: - dependencies: - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 4.0.0 - - globrex@0.1.2: {} - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - - hanji@0.0.5: - dependencies: - lodash.throttle: 4.1.1 - sisteransi: 1.0.5 - - has-flag@4.0.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - heap@0.2.7: {} - - hono@4.3.9: {} - - human-signals@2.1.0: {} - - human-signals@5.0.0: {} - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - ieee754@1.2.1: {} - - ignore-by-default@2.1.0: {} - - ignore@5.3.1: {} - - import-fresh@3.3.0: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - indent-string@5.0.0: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - ini@1.3.8: {} - - irregular-plurals@3.5.0: {} - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-core-module@2.13.1: - dependencies: - hasown: 2.0.2 - - is-error@2.2.2: {} - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-fullwidth-code-point@4.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - is-path-inside@3.0.3: {} - - is-plain-object@5.0.0: {} - - is-promise@2.2.2: {} - - is-promise@4.0.0: {} - - is-property@1.0.2: {} - - is-stream@2.0.1: {} - - is-stream@3.0.0: {} - - is-unicode-supported@1.3.0: {} - - is-what@4.1.16: {} - - isexe@2.0.0: {} - - jackspeak@2.3.6: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - joycon@3.1.1: {} - - js-base64@3.7.7: {} - - js-string-escape@1.0.1: {} - - js-tokens@9.0.0: {} - - js-yaml@3.14.1: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - - json-buffer@3.0.1: {} - - json-diff@0.9.0: - dependencies: - cli-color: 2.0.4 - difflib: 0.2.4(patch_hash=jq4t3ysdpnbunjeje4v7nrqn2q) - dreamopt: 0.8.0 - - json-diff@1.0.6: - dependencies: - '@ewoudenberg/difflib': 0.1.0 - colors: 1.4.0 - dreamopt: 0.8.0 - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - libsql@0.2.0: - dependencies: - '@neon-rs/load': 0.0.4 - detect-libc: 2.0.2 - optionalDependencies: - '@libsql/darwin-arm64': 0.2.0 - '@libsql/darwin-x64': 0.2.0 - '@libsql/linux-arm64-gnu': 0.2.0 - '@libsql/linux-arm64-musl': 0.2.0 - '@libsql/linux-x64-gnu': 0.2.0 - '@libsql/linux-x64-musl': 0.2.0 - '@libsql/win32-x64-msvc': 0.2.0 - optional: true - - lilconfig@3.1.1: {} - - lines-and-columns@1.2.4: {} - - load-json-file@7.0.1: {} - - load-tsconfig@0.2.5: {} - - local-pkg@0.5.0: - dependencies: - mlly: 1.7.0 - pkg-types: 1.1.1 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - locate-path@7.2.0: - dependencies: - p-locate: 6.0.0 - - lodash.merge@4.6.2: {} - - lodash.sortby@4.7.0: {} - - lodash.throttle@4.1.1: {} - - lodash@4.17.21: {} - - long@4.0.0: {} - - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 - - lru-cache@10.2.2: {} - - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - - lru-cache@7.18.3: {} - - lru-queue@0.1.0: - dependencies: - es5-ext: 0.10.64 - - magic-string@0.25.9: - dependencies: - sourcemap-codec: 1.4.8 - - magic-string@0.30.10: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - - map-age-cleaner@0.1.3: - dependencies: - p-defer: 1.0.0 - - map-stream@0.1.0: {} - - marked-terminal@6.2.0(marked@9.1.6): - dependencies: - ansi-escapes: 6.2.1 - cardinal: 2.1.1 - chalk: 5.3.0 - cli-table3: 0.6.5 - marked: 9.1.6 - node-emoji: 2.1.3 - supports-hyperlinks: 3.0.0 - - marked@9.1.6: {} - - matcher@5.0.0: - dependencies: - escape-string-regexp: 5.0.0 - - md5-hex@3.0.1: - dependencies: - blueimp-md5: 2.19.0 - - mem@9.0.2: - dependencies: - map-age-cleaner: 0.1.3 - mimic-fn: 4.0.0 - - memoizee@0.4.15: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-weak-map: 2.0.3 - event-emitter: 0.3.5 - is-promise: 2.2.2 - lru-queue: 0.1.0 - next-tick: 1.1.0 - timers-ext: 0.1.7 - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - micromatch@4.0.5: - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime@3.0.0: {} - - mimic-fn@2.1.0: {} - - mimic-fn@4.0.0: {} - - mimic-response@3.1.0: {} - - miniflare@3.20240512.0(bufferutil@4.0.8)(utf-8-validate@6.0.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - acorn: 8.11.3 - acorn-walk: 8.3.2 - capnp-ts: 0.7.0 - exit-hook: 2.2.1 - glob-to-regexp: 0.4.1 - stoppable: 1.1.0 - undici: 5.28.4 - workerd: 1.20240512.0 - ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) - youch: 3.3.3 - zod: 3.23.8 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - - minimatch@5.1.6: - dependencies: - brace-expansion: 2.0.1 - - minimatch@7.4.6: - dependencies: - brace-expansion: 2.0.1 - - minimatch@9.0.4: - dependencies: - brace-expansion: 2.0.1 - - minimist@1.2.8: {} - - minipass@7.1.1: {} - - mkdirp-classic@0.5.3: {} - - mlly@1.7.0: - dependencies: - acorn: 8.11.3 - pathe: 1.1.2 - pkg-types: 1.1.1 - ufo: 1.5.3 - - ms@2.1.2: {} - - ms@2.1.3: {} - - mustache@4.2.0: {} - - mysql2@2.3.3: - dependencies: - denque: 2.1.0 - generate-function: 2.3.1 - iconv-lite: 0.6.3 - long: 4.0.0 - lru-cache: 6.0.0 - named-placeholders: 1.1.3 - seq-queue: 0.0.5 - sqlstring: 2.3.3 - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - named-placeholders@1.1.3: - dependencies: - lru-cache: 7.18.3 - - nan@2.19.0: - optional: true - - nanoid@3.3.7: {} - - napi-build-utils@1.0.2: {} - - natural-compare@1.4.0: {} - - next-tick@1.1.0: {} - - node-abi@3.62.0: - dependencies: - semver: 7.6.2 - - node-domexception@1.0.0: {} - - node-emoji@2.1.3: - dependencies: - '@sindresorhus/is': 4.6.0 - char-regex: 1.0.2 - emojilib: 2.4.0 - skin-tone: 2.0.0 - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-fetch@3.3.1: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - - node-forge@1.3.1: {} - - node-gyp-build@4.8.1: {} - - nofilter@3.1.0: {} - - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - - object-assign@4.1.1: {} - - obuf@1.1.2: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - p-defer@1.0.0: {} - - p-event@5.0.1: - dependencies: - p-timeout: 5.1.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-limit@4.0.0: - dependencies: - yocto-queue: 1.0.0 - - p-limit@5.0.0: - dependencies: - yocto-queue: 1.0.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-locate@6.0.0: - dependencies: - p-limit: 4.0.0 - - p-map@5.5.0: - dependencies: - aggregate-error: 4.0.1 - - p-timeout@5.1.0: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-ms@3.0.0: {} - - path-exists@4.0.0: {} - - path-exists@5.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-key@4.0.0: {} - - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.2.2 - minipass: 7.1.1 - - path-to-regexp@6.2.2: {} - - path-type@4.0.0: {} - - pathe@1.1.2: {} - - pathval@1.1.1: {} - - pause-stream@0.0.11: - dependencies: - through: 2.3.8 - - pg-cloudflare@1.1.1: - optional: true - - pg-connection-string@2.6.4: {} - - pg-int8@1.0.1: {} - - pg-numeric@1.0.2: {} - - pg-pool@3.6.2(pg@8.11.5): - dependencies: - pg: 8.11.5 - - pg-protocol@1.6.1: {} - - pg-types@2.2.0: - dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.0 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 - - pg-types@4.0.2: - dependencies: - pg-int8: 1.0.1 - pg-numeric: 1.0.2 - postgres-array: 3.0.2 - postgres-bytea: 3.0.0 - postgres-date: 2.1.0 - postgres-interval: 3.0.0 - postgres-range: 1.1.4 - - pg@8.11.5: - dependencies: - pg-connection-string: 2.6.4 - pg-pool: 3.6.2(pg@8.11.5) - pg-protocol: 1.6.1 - pg-types: 2.2.0 - pgpass: 1.0.5 - optionalDependencies: - pg-cloudflare: 1.1.1 - - pgpass@1.0.5: - dependencies: - split2: 4.2.0 - - picocolors@1.0.1: {} - - picomatch@2.3.1: {} - - pirates@4.0.6: {} - - pkg-conf@4.0.0: - dependencies: - find-up: 6.3.0 - load-json-file: 7.0.1 - - pkg-types@1.1.1: - dependencies: - confbox: 0.1.7 - mlly: 1.7.0 - pathe: 1.1.2 - - plur@5.1.0: - dependencies: - irregular-plurals: 3.5.0 - - pluralize@8.0.0: {} - - postcss-load-config@4.0.2(postcss@8.4.38): - dependencies: - lilconfig: 3.1.1 - yaml: 2.4.2 - optionalDependencies: - postcss: 8.4.38 - - postcss@8.4.38: - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - - postgres-array@2.0.0: {} - - postgres-array@3.0.2: {} - - postgres-bytea@1.0.0: {} - - postgres-bytea@3.0.0: - dependencies: - obuf: 1.1.2 - - postgres-date@1.0.7: {} - - postgres-date@2.1.0: {} - - postgres-interval@1.2.0: - dependencies: - xtend: 4.0.2 - - postgres-interval@3.0.0: {} - - postgres-range@1.1.4: {} - - postgres@3.4.4: {} - - prebuild-install@7.1.2: - dependencies: - detect-libc: 2.0.3 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 1.0.2 - node-abi: 3.62.0 - pump: 3.0.0 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.1 - tunnel-agent: 0.6.0 - - prelude-ls@1.2.1: {} - - prettier-linter-helpers@1.0.0: - dependencies: - fast-diff: 1.3.0 - - prettier@2.8.8: {} - - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - - pretty-ms@8.0.0: - dependencies: - parse-ms: 3.0.0 - - printable-characters@1.0.42: {} - - ps-tree@1.2.0: - dependencies: - event-stream: 3.3.4 - - pump@3.0.0: - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - - punycode@2.3.1: {} - - queue-microtask@1.2.3: {} - - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - - react-is@18.3.1: {} - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - redeyed@2.1.1: - dependencies: - esprima: 4.0.1 - - require-directory@2.1.1: {} - - resolve-cwd@3.0.0: - dependencies: - resolve-from: 5.0.0 - - resolve-from@4.0.0: {} - - resolve-from@5.0.0: {} - - resolve-pkg-maps@1.0.0: {} - - resolve.exports@2.0.2: {} - - resolve@1.22.8: - dependencies: - is-core-module: 2.13.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - reusify@1.0.4: {} - - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - - rollup-plugin-inject@3.0.2: - dependencies: - estree-walker: 0.6.1 - magic-string: 0.25.9 - rollup-pluginutils: 2.8.2 - - rollup-plugin-node-polyfills@0.2.1: - dependencies: - rollup-plugin-inject: 3.0.2 - - rollup-pluginutils@2.8.2: - dependencies: - estree-walker: 0.6.1 - - rollup@4.17.2: - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.17.2 - '@rollup/rollup-android-arm64': 4.17.2 - '@rollup/rollup-darwin-arm64': 4.17.2 - '@rollup/rollup-darwin-x64': 4.17.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.17.2 - '@rollup/rollup-linux-arm-musleabihf': 4.17.2 - '@rollup/rollup-linux-arm64-gnu': 4.17.2 - '@rollup/rollup-linux-arm64-musl': 4.17.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.17.2 - '@rollup/rollup-linux-riscv64-gnu': 4.17.2 - '@rollup/rollup-linux-s390x-gnu': 4.17.2 - '@rollup/rollup-linux-x64-gnu': 4.17.2 - '@rollup/rollup-linux-x64-musl': 4.17.2 - '@rollup/rollup-win32-arm64-msvc': 4.17.2 - '@rollup/rollup-win32-ia32-msvc': 4.17.2 - '@rollup/rollup-win32-x64-msvc': 4.17.2 - fsevents: 2.3.3 - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - safe-buffer@5.2.1: {} - - safer-buffer@2.1.2: {} - - selfsigned@2.4.1: - dependencies: - '@types/node-forge': 1.3.11 - node-forge: 1.3.1 - - semver@7.6.2: {} - - seq-queue@0.0.5: {} - - serialize-error@7.0.1: - dependencies: - type-fest: 0.13.1 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - siginfo@2.0.0: {} - - signal-exit@3.0.7: {} - - signal-exit@4.1.0: {} - - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - - sisteransi@1.0.5: {} - - skin-tone@2.0.0: - dependencies: - unicode-emoji-modifier-base: 1.0.0 - - slash@3.0.0: {} - - slash@4.0.0: {} - - slice-ansi@5.0.0: - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 4.0.0 - - source-map-js@1.2.0: {} - - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - source-map@0.8.0-beta.0: - dependencies: - whatwg-url: 7.1.0 - - sourcemap-codec@1.4.8: {} - - split-ca@1.0.1: {} - - split2@4.2.0: {} - - split@0.3.3: - dependencies: - through: 2.3.8 - - sprintf-js@1.0.3: {} - - sqlstring@2.3.3: {} - - ssh2@1.15.0: - dependencies: - asn1: 0.2.6 - bcrypt-pbkdf: 1.0.2 - optionalDependencies: - cpu-features: 0.0.10 - nan: 2.19.0 - - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - - stackback@0.0.2: {} - - stacktracey@2.1.8: - dependencies: - as-table: 1.0.55 - get-source: 2.0.12 - - std-env@3.7.0: {} - - stoppable@1.1.0: {} - - stream-combiner@0.0.4: - dependencies: - duplexer: 0.1.2 - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.0.1 - - strip-final-newline@2.0.0: {} - - strip-final-newline@3.0.0: {} - - strip-json-comments@2.0.1: {} - - strip-json-comments@3.1.1: {} - - strip-literal@2.1.0: - dependencies: - js-tokens: 9.0.0 - - strnum@1.0.5: {} - - sucrase@3.35.0: - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - commander: 4.1.1 - glob: 10.3.15 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - - superjson@2.2.1: - dependencies: - copy-anything: 3.0.5 - - supertap@3.0.1: - dependencies: - indent-string: 5.0.0 - js-yaml: 3.14.1 - serialize-error: 7.0.1 - strip-ansi: 7.1.0 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-hyperlinks@3.0.0: - dependencies: - has-flag: 4.0.0 - supports-color: 7.2.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - synckit@0.8.8: - dependencies: - '@pkgr/core': 0.1.1 - tslib: 2.6.2 - - tar-fs@2.0.1: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.0 - tar-stream: 2.2.0 - - tar-fs@2.1.1: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.0 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.4 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - - temp-dir@3.0.0: {} - - text-table@0.2.0: {} - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - through@2.3.8: {} - - time-zone@1.0.0: {} - - timers-ext@0.1.7: - dependencies: - es5-ext: 0.10.64 - next-tick: 1.1.0 - - tinybench@2.8.0: {} - - tinypool@0.8.4: {} - - tinyspy@2.2.1: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - tr46@0.0.3: {} - - tr46@1.0.1: - dependencies: - punycode: 2.3.1 - - tree-kill@1.2.2: {} - - ts-api-utils@1.3.0(typescript@5.4.5): - dependencies: - typescript: 5.4.5 - - ts-expose-internals-conditionally@1.0.0-empty.0: {} - - ts-interface-checker@0.1.13: {} - - tsconfck@3.0.3(typescript@5.4.5): - optionalDependencies: - typescript: 5.4.5 - - tslib@1.14.1: {} - - tslib@2.6.2: {} - - tsup@8.0.2(postcss@8.4.38)(typescript@5.4.5): - dependencies: - bundle-require: 4.1.0(esbuild@0.19.12) - cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.3.4 - esbuild: 0.19.12 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.38) - resolve-from: 5.0.0 - rollup: 4.17.2 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.4.38 - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - ts-node - - tsx@3.14.0: - dependencies: - esbuild: 0.18.20 - get-tsconfig: 4.7.5 - source-map-support: 0.5.21 - optionalDependencies: - fsevents: 2.3.3 - - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - - tweetnacl@0.14.5: {} - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - type-detect@4.0.8: {} - - type-fest@0.13.1: {} - - type-fest@0.20.2: {} - - type@2.7.2: {} - - typescript@5.3.3: {} - - typescript@5.4.5: {} - - ufo@1.5.3: {} - - undici-types@5.26.5: {} - - undici@5.28.4: - dependencies: - '@fastify/busboy': 2.1.1 - - unicode-emoji-modifier-base@1.0.0: {} - - universalify@2.0.1: {} - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - utf-8-validate@6.0.3: - dependencies: - node-gyp-build: 4.8.1 - - util-deprecate@1.0.2: {} - - uuid@9.0.1: {} - - validate-npm-package-name@5.0.1: {} - - vite-node@1.6.0(@types/node@18.19.33): - dependencies: - cac: 6.7.14 - debug: 4.3.4 - pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.2.11(@types/node@18.19.33) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - vite-tsconfig-paths@4.3.2(typescript@5.4.5)(vite@5.2.11(@types/node@18.19.33)): - dependencies: - debug: 4.3.4 - globrex: 0.1.2 - tsconfck: 3.0.3(typescript@5.4.5) - optionalDependencies: - vite: 5.2.11(@types/node@18.19.33) - transitivePeerDependencies: - - supports-color - - typescript - - vite@5.2.11(@types/node@18.19.33): - dependencies: - esbuild: 0.20.2 - postcss: 8.4.38 - rollup: 4.17.2 - optionalDependencies: - '@types/node': 18.19.33 - fsevents: 2.3.3 - - vitest@1.6.0(@types/node@18.19.33): - dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - acorn-walk: 8.3.2 - chai: 4.4.1 - debug: 4.3.4 - execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.10 - pathe: 1.1.2 - picocolors: 1.0.1 - std-env: 3.7.0 - strip-literal: 2.1.0 - tinybench: 2.8.0 - tinypool: 0.8.4 - vite: 5.2.11(@types/node@18.19.33) - vite-node: 1.6.0(@types/node@18.19.33) - why-is-node-running: 2.2.2 - optionalDependencies: - '@types/node': 18.19.33 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - web-streams-polyfill@3.3.3: {} - - webidl-conversions@3.0.1: {} - - webidl-conversions@4.0.2: {} - - webpod@0.0.2: {} - - well-known-symbols@2.0.0: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - whatwg-url@7.1.0: - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - which@3.0.1: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.2.2: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - word-wrap@1.2.5: {} - - wordwrap@1.0.0: {} - - workerd@1.20240512.0: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20240512.0 - '@cloudflare/workerd-darwin-arm64': 1.20240512.0 - '@cloudflare/workerd-linux-64': 1.20240512.0 - '@cloudflare/workerd-linux-arm64': 1.20240512.0 - '@cloudflare/workerd-windows-64': 1.20240512.0 - - wrangler@3.57.0(@cloudflare/workers-types@4.20240512.0)(bufferutil@4.0.8)(utf-8-validate@6.0.3): - dependencies: - '@cloudflare/kv-asset-handler': 0.3.2 - '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) - '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) - blake3-wasm: 2.1.5 - chokidar: 3.6.0 - esbuild: 0.17.19 - miniflare: 3.20240512.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) - nanoid: 3.3.7 - path-to-regexp: 6.2.2 - resolve: 1.22.8 - resolve.exports: 2.0.2 - selfsigned: 2.4.1 - source-map: 0.6.1 - xxhash-wasm: 1.0.2 - optionalDependencies: - '@cloudflare/workers-types': 4.20240512.0 - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - wrappy@1.0.2: {} - - write-file-atomic@5.0.1: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 4.1.0 - - ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3): - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 6.0.3 - - ws@8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3): - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 6.0.3 - - xtend@4.0.2: {} - - xxhash-wasm@1.0.2: {} - - y18n@5.0.8: {} - - yallist@4.0.0: {} - - yaml@2.4.2: {} - - yargs-parser@21.1.1: {} - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.1.2 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yocto-queue@0.1.0: {} - - yocto-queue@1.0.0: {} - - youch@3.3.3: - dependencies: - cookie: 0.5.0 - mustache: 4.2.0 - stacktracey: 2.1.8 - - zod@3.23.8: {} - - zx@7.2.3: - dependencies: - '@types/fs-extra': 11.0.4 - '@types/minimist': 1.2.5 - '@types/node': 18.19.33 - '@types/ps-tree': 1.1.6 - '@types/which': 3.0.3 - chalk: 5.3.0 - fs-extra: 11.2.0 - fx: 34.0.0 - globby: 13.2.2 - minimist: 1.2.8 - node-fetch: 3.3.1 - ps-tree: 1.2.0 - webpod: 0.0.2 - which: 3.0.1 - yaml: 2.4.2 diff --git a/drizzle-kit/schema.ts b/drizzle-kit/schema.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/drizzle-kit/src/@types/utils.ts b/drizzle-kit/src/@types/utils.ts index 3f14151a4..e71d45b89 100644 --- a/drizzle-kit/src/@types/utils.ts +++ b/drizzle-kit/src/@types/utils.ts @@ -4,6 +4,8 @@ declare global { squashSpaces(): string; capitalise(): string; camelCase(): string; + snake_case(): string; + concatIf(it: string, condition: boolean): string; } @@ -11,6 +13,7 @@ declare global { random(): T; } } + import camelcase from 'camelcase'; String.prototype.trimChar = function(char: string) { @@ -44,6 +47,10 @@ String.prototype.concatIf = function(it: string, condition: boolean) { return condition ? `${this}${it}` : String(this); }; +String.prototype.snake_case = function() { + return this && this.length > 0 ? `${this.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)}` : String(this); +}; + Array.prototype.random = function() { return this[~~(Math.random() * this.length)]; }; diff --git a/drizzle-kit/src/api.ts b/drizzle-kit/src/api.ts index 06f6dc1c0..18107bd34 100644 --- a/drizzle-kit/src/api.ts +++ b/drizzle-kit/src/api.ts @@ -1,38 +1,55 @@ import { randomUUID } from 'crypto'; -import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; import { LibSQLDatabase } from 'drizzle-orm/libsql'; import type { MySql2Database } from 'drizzle-orm/mysql2'; import { PgDatabase } from 'drizzle-orm/pg-core'; +import { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore'; import { columnsResolver, enumsResolver, + indPolicyResolver, + mySqlViewsResolver, + policyResolver, + roleResolver, schemasResolver, sequencesResolver, + sqliteViewsResolver, tablesResolver, + viewsResolver, } from './cli/commands/migrate'; import { pgPushIntrospect } from './cli/commands/pgIntrospect'; import { pgSuggestions } from './cli/commands/pgPushUtils'; -import { updateUpToV6 as upPgV6 } from './cli/commands/pgUp'; +import { updateUpToV6 as upPgV6, updateUpToV7 as upPgV7 } from './cli/commands/pgUp'; import { sqlitePushIntrospect } from './cli/commands/sqliteIntrospect'; import { logSuggestionsAndReturn } from './cli/commands/sqlitePushUtils'; +import type { CasingType } from './cli/validations/common'; +import { getTablesFilterByExtensions } from './extensions/getTablesFilterByExtensions'; import { originUUID } from './global'; +import type { Config } from './index'; import { fillPgSnapshot } from './migrationPreparator'; import { MySqlSchema as MySQLSchemaKit, mysqlSchema, squashMysqlScheme } from './serializer/mysqlSchema'; import { generateMySqlSnapshot } from './serializer/mysqlSerializer'; import { prepareFromExports } from './serializer/pgImports'; import { PgSchema as PgSchemaKit, pgSchema, squashPgScheme } from './serializer/pgSchema'; import { generatePgSnapshot } from './serializer/pgSerializer'; +import { + SingleStoreSchema as SingleStoreSchemaKit, + singlestoreSchema, + squashSingleStoreScheme, +} from './serializer/singlestoreSchema'; +import { generateSingleStoreSnapshot } from './serializer/singlestoreSerializer'; import { SQLiteSchema as SQLiteSchemaKit, sqliteSchema, squashSqliteScheme } from './serializer/sqliteSchema'; import { generateSqliteSnapshot } from './serializer/sqliteSerializer'; import type { DB, SQLiteDB } from './utils'; export type DrizzleSnapshotJSON = PgSchemaKit; export type DrizzleSQLiteSnapshotJSON = SQLiteSchemaKit; export type DrizzleMySQLSnapshotJSON = MySQLSchemaKit; +export type DrizzleSingleStoreSnapshotJSON = SingleStoreSchemaKit; export const generateDrizzleJson = ( imports: Record, prevId?: string, schemaFilters?: string[], + casing?: CasingType, ): PgSchemaKit => { const prepared = prepareFromExports(imports); @@ -43,6 +60,11 @@ export const generateDrizzleJson = ( prepared.enums, prepared.schemas, prepared.sequences, + prepared.roles, + prepared.policies, + prepared.views, + prepared.matViews, + casing, schemaFilters, ); @@ -71,8 +93,12 @@ export const generateMigration = async ( schemasResolver, enumsResolver, sequencesResolver, + policyResolver, + indPolicyResolver, + roleResolver, tablesResolver, columnsResolver, + viewsResolver, validatedPrev, validatedCur, ); @@ -84,9 +110,14 @@ export const pushSchema = async ( imports: Record, drizzleInstance: PgDatabase, schemaFilters?: string[], + tablesFilter?: string[], + extensionsFilters?: Config['extensionsFilters'], ) => { const { applyPgSnapshotsDiff } = await import('./snapshotsDiffer'); const { sql } = await import('drizzle-orm'); + const filters = (tablesFilter ?? []).concat( + getTablesFilterByExtensions({ extensionsFilters, dialect: 'postgresql' }), + ); const db: DB = { query: async (query: string, params?: any[]) => { @@ -98,8 +129,9 @@ export const pushSchema = async ( const cur = generateDrizzleJson(imports); const { schema: prev } = await pgPushIntrospect( db, - [], + filters, schemaFilters ?? ['public'], + undefined, ); const validatedPrev = pgSchema.parse(prev); @@ -114,8 +146,12 @@ export const pushSchema = async ( schemasResolver, enumsResolver, sequencesResolver, + policyResolver, + indPolicyResolver, + roleResolver, tablesResolver, columnsResolver, + viewsResolver, validatedPrev, validatedCur, 'push', @@ -140,6 +176,7 @@ export const pushSchema = async ( export const generateSQLiteDrizzleJson = async ( imports: Record, prevId?: string, + casing?: CasingType, ): Promise => { const { prepareFromExports } = await import('./serializer/sqliteImports'); @@ -147,7 +184,7 @@ export const generateSQLiteDrizzleJson = async ( const id = randomUUID(); - const snapshot = generateSqliteSnapshot(prepared.tables); + const snapshot = generateSqliteSnapshot(prepared.tables, prepared.views, casing); return { ...snapshot, @@ -173,6 +210,7 @@ export const generateSQLiteMigration = async ( squashedCur, tablesResolver, columnsResolver, + sqliteViewsResolver, validatedPrev, validatedCur, ); @@ -213,6 +251,7 @@ export const pushSQLiteSchema = async ( squashedCur, tablesResolver, columnsResolver, + sqliteViewsResolver, validatedPrev, validatedCur, 'push', @@ -243,6 +282,7 @@ export const pushSQLiteSchema = async ( export const generateMySQLDrizzleJson = async ( imports: Record, prevId?: string, + casing?: CasingType, ): Promise => { const { prepareFromExports } = await import('./serializer/mysqlImports'); @@ -250,7 +290,7 @@ export const generateMySQLDrizzleJson = async ( const id = randomUUID(); - const snapshot = generateMySqlSnapshot(prepared.tables); + const snapshot = generateMySqlSnapshot(prepared.tables, prepared.views, casing); return { ...snapshot, @@ -276,6 +316,7 @@ export const generateMySQLMigration = async ( squashedCur, tablesResolver, columnsResolver, + mySqlViewsResolver, validatedPrev, validatedCur, ); @@ -317,6 +358,113 @@ export const pushMySQLSchema = async ( squashedCur, tablesResolver, columnsResolver, + mySqlViewsResolver, + validatedPrev, + validatedCur, + 'push', + ); + + const { shouldAskForApprove, statementsToExecute, infoToPrint } = await logSuggestionsAndReturn( + db, + statements, + validatedCur, + ); + + return { + hasDataLoss: shouldAskForApprove, + warnings: infoToPrint, + statementsToExecute, + apply: async () => { + for (const dStmnt of statementsToExecute) { + await db.query(dStmnt); + } + }, + }; +}; + +// SingleStore + +export const generateSingleStoreDrizzleJson = async ( + imports: Record, + prevId?: string, + casing?: CasingType, +): Promise => { + const { prepareFromExports } = await import('./serializer/singlestoreImports'); + + const prepared = prepareFromExports(imports); + + const id = randomUUID(); + + const snapshot = generateSingleStoreSnapshot(prepared.tables, /* prepared.views, */ casing); + + return { + ...snapshot, + id, + prevId: prevId ?? originUUID, + }; +}; + +export const generateSingleStoreMigration = async ( + prev: DrizzleSingleStoreSnapshotJSON, + cur: DrizzleSingleStoreSnapshotJSON, +) => { + const { applySingleStoreSnapshotsDiff } = await import('./snapshotsDiffer'); + + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); + + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { sqlStatements } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + /* singleStoreViewsResolver, */ + validatedPrev, + validatedCur, + 'push', + ); + + return sqlStatements; +}; + +export const pushSingleStoreSchema = async ( + imports: Record, + drizzleInstance: SingleStoreDriverDatabase, + databaseName: string, +) => { + const { applySingleStoreSnapshotsDiff } = await import('./snapshotsDiffer'); + const { logSuggestionsAndReturn } = await import( + './cli/commands/singlestorePushUtils' + ); + const { singlestorePushIntrospect } = await import( + './cli/commands/singlestoreIntrospect' + ); + const { sql } = await import('drizzle-orm'); + + const db: DB = { + query: async (query: string) => { + const res = await drizzleInstance.execute(sql.raw(query)); + return res[0] as unknown as any[]; + }, + }; + const cur = await generateSingleStoreDrizzleJson(imports); + const { schema: prev } = await singlestorePushIntrospect(db, databaseName, []); + + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); + + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { statements } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + /* singleStoreViewsResolver, */ validatedPrev, validatedCur, 'push', @@ -341,5 +489,11 @@ export const pushMySQLSchema = async ( }; export const upPgSnapshot = (snapshot: Record) => { - return upPgV6(snapshot); + if (snapshot.version === '5') { + return upPgV7(upPgV6(snapshot)); + } + if (snapshot.version === '6') { + return upPgV7(snapshot); + } + return snapshot; }; diff --git a/drizzle-kit/src/cli/commands/introspect.ts b/drizzle-kit/src/cli/commands/introspect.ts index 61ba0b44a..101eb617a 100644 --- a/drizzle-kit/src/cli/commands/introspect.ts +++ b/drizzle-kit/src/cli/commands/introspect.ts @@ -4,29 +4,47 @@ import { render, renderWithTask } from 'hanji'; import { Minimatch } from 'minimatch'; import { join } from 'path'; import { plural, singular } from 'pluralize'; +import { drySingleStore, SingleStoreSchema, squashSingleStoreScheme } from 'src/serializer/singlestoreSchema'; import { assertUnreachable, originUUID } from '../../global'; import { schemaToTypeScript as mysqlSchemaToTypeScript } from '../../introspect-mysql'; import { paramNameFor, schemaToTypeScript as postgresSchemaToTypeScript } from '../../introspect-pg'; +import { schemaToTypeScript as singlestoreSchemaToTypeScript } from '../../introspect-singlestore'; import { schemaToTypeScript as sqliteSchemaToTypeScript } from '../../introspect-sqlite'; import { dryMySql, MySqlSchema, squashMysqlScheme } from '../../serializer/mysqlSchema'; import { fromDatabase as fromMysqlDatabase } from '../../serializer/mysqlSerializer'; import { dryPg, type PgSchema, squashPgScheme } from '../../serializer/pgSchema'; import { fromDatabase as fromPostgresDatabase } from '../../serializer/pgSerializer'; +import { fromDatabase as fromSingleStoreDatabase } from '../../serializer/singlestoreSerializer'; import { drySQLite, type SQLiteSchema, squashSqliteScheme } from '../../serializer/sqliteSchema'; import { fromDatabase as fromSqliteDatabase } from '../../serializer/sqliteSerializer'; -import { applyMysqlSnapshotsDiff, applyPgSnapshotsDiff, applySqliteSnapshotsDiff } from '../../snapshotsDiffer'; +import { + applyLibSQLSnapshotsDiff, + applyMysqlSnapshotsDiff, + applyPgSnapshotsDiff, + applySingleStoreSnapshotsDiff, + applySqliteSnapshotsDiff, +} from '../../snapshotsDiffer'; import { prepareOutFolder } from '../../utils'; +import { Entities } from '../validations/cli'; import type { Casing, Prefix } from '../validations/common'; +import { LibSQLCredentials } from '../validations/libsql'; import type { MysqlCredentials } from '../validations/mysql'; import type { PostgresCredentials } from '../validations/postgres'; +import { SingleStoreCredentials } from '../validations/singlestore'; import type { SqliteCredentials } from '../validations/sqlite'; import { IntrospectProgress } from '../views'; import { columnsResolver, enumsResolver, + indPolicyResolver, + mySqlViewsResolver, + policyResolver, + roleResolver, schemasResolver, sequencesResolver, + sqliteViewsResolver, tablesResolver, + viewsResolver, writeResult, } from './migrate'; @@ -38,6 +56,7 @@ export const introspectPostgres = async ( tablesFilter: string[], schemasFilter: string[], prefix: Prefix, + entities: Entities, ) => { const { preparePostgresDB } = await import('../connections'); const db = await preparePostgresDB(credentials); @@ -70,11 +89,18 @@ export const introspectPostgres = async ( }; const progress = new IntrospectProgress(true); + const res = await renderWithTask( progress, - fromPostgresDatabase(db, filter, schemasFilter, (stage, count, status) => { - progress.update(stage, count, status); - }), + fromPostgresDatabase( + db, + filter, + schemasFilter, + entities, + (stage, count, status) => { + progress.update(stage, count, status); + }, + ), ); const schema = { id: originUUID, prevId: '', ...res } as PgSchema; @@ -97,8 +123,12 @@ export const introspectPostgres = async ( schemasResolver, enumsResolver, sequencesResolver, + policyResolver, + indPolicyResolver, + roleResolver, tablesResolver, columnsResolver, + viewsResolver, dryPg, schema, ); @@ -128,14 +158,14 @@ export const introspectPostgres = async ( chalk.green( '✓', ) - }] You schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`, + }] Your schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`, ); render( `[${ chalk.green( '✓', ) - }] You relations file is ready ➜ ${ + }] Your relations file is ready ➜ ${ chalk.bold.underline.blue( relationsFile, ) @@ -201,7 +231,7 @@ export const introspectMysql = async ( writeFileSync(relationsFile, relationsTs.file); console.log(); - const { snapshots, journal } = prepareOutFolder(out, 'postgresql'); + const { snapshots, journal } = prepareOutFolder(out, 'mysql'); if (snapshots.length === 0) { const { sqlStatements, _meta } = await applyMysqlSnapshotsDiff( @@ -209,6 +239,7 @@ export const introspectMysql = async ( squashMysqlScheme(schema), tablesResolver, columnsResolver, + mySqlViewsResolver, dryMySql, schema, ); @@ -238,14 +269,14 @@ export const introspectMysql = async ( chalk.green( '✓', ) - }] You schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`, + }] Your schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`, ); render( `[${ chalk.green( '✓', ) - }] You relations file is ready ➜ ${ + }] Your relations file is ready ➜ ${ chalk.bold.underline.blue( relationsFile, ) @@ -254,6 +285,103 @@ export const introspectMysql = async ( process.exit(0); }; +export const introspectSingleStore = async ( + casing: Casing, + out: string, + breakpoints: boolean, + credentials: SingleStoreCredentials, + tablesFilter: string[], + prefix: Prefix, +) => { + const { connectToSingleStore } = await import('../connections'); + const { db, database } = await connectToSingleStore(credentials); + + const matchers = tablesFilter.map((it) => { + return new Minimatch(it); + }); + + const filter = (tableName: string) => { + if (matchers.length === 0) return true; + + let flags: boolean[] = []; + + for (let matcher of matchers) { + if (matcher.negate) { + if (!matcher.match(tableName)) { + flags.push(false); + } + } + + if (matcher.match(tableName)) { + flags.push(true); + } + } + + if (flags.length > 0) { + return flags.every(Boolean); + } + return false; + }; + + const progress = new IntrospectProgress(); + const res = await renderWithTask( + progress, + fromSingleStoreDatabase(db, database, filter, (stage, count, status) => { + progress.update(stage, count, status); + }), + ); + + const schema = { id: originUUID, prevId: '', ...res } as SingleStoreSchema; + const ts = singlestoreSchemaToTypeScript(schema, casing); + const { internal, ...schemaWithoutInternals } = schema; + + const schemaFile = join(out, 'schema.ts'); + writeFileSync(schemaFile, ts.file); + console.log(); + + const { snapshots, journal } = prepareOutFolder(out, 'postgresql'); + + if (snapshots.length === 0) { + const { sqlStatements, _meta } = await applySingleStoreSnapshotsDiff( + squashSingleStoreScheme(drySingleStore), + squashSingleStoreScheme(schema), + tablesResolver, + columnsResolver, + /* singleStoreViewsResolver, */ + drySingleStore, + schema, + ); + + writeResult({ + cur: schema, + sqlStatements, + journal, + _meta, + outFolder: out, + breakpoints, + type: 'introspect', + prefixMode: prefix, + }); + } else { + render( + `[${ + chalk.blue( + 'i', + ) + }] No SQL generated, you already have migrations in project`, + ); + } + + render( + `[${ + chalk.green( + '✓', + ) + }] You schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`, + ); + process.exit(0); +}; + export const introspectSqlite = async ( casing: Casing, out: string, @@ -312,7 +440,7 @@ export const introspectSqlite = async ( writeFileSync(relationsFile, relationsTs.file); console.log(); - const { snapshots, journal } = prepareOutFolder(out, 'postgresql'); + const { snapshots, journal } = prepareOutFolder(out, 'sqlite'); if (snapshots.length === 0) { const { sqlStatements, _meta } = await applySqliteSnapshotsDiff( @@ -320,6 +448,7 @@ export const introspectSqlite = async ( squashSqliteScheme(schema), tablesResolver, columnsResolver, + sqliteViewsResolver, drySQLite, schema, ); @@ -365,6 +494,118 @@ export const introspectSqlite = async ( process.exit(0); }; +export const introspectLibSQL = async ( + casing: Casing, + out: string, + breakpoints: boolean, + credentials: LibSQLCredentials, + tablesFilter: string[], + prefix: Prefix, +) => { + const { connectToLibSQL } = await import('../connections'); + const db = await connectToLibSQL(credentials); + + const matchers = tablesFilter.map((it) => { + return new Minimatch(it); + }); + + const filter = (tableName: string) => { + if (matchers.length === 0) return true; + + let flags: boolean[] = []; + + for (let matcher of matchers) { + if (matcher.negate) { + if (!matcher.match(tableName)) { + flags.push(false); + } + } + + if (matcher.match(tableName)) { + flags.push(true); + } + } + + if (flags.length > 0) { + return flags.every(Boolean); + } + return false; + }; + + const progress = new IntrospectProgress(); + const res = await renderWithTask( + progress, + fromSqliteDatabase(db, filter, (stage, count, status) => { + progress.update(stage, count, status); + }), + ); + + const schema = { id: originUUID, prevId: '', ...res } as SQLiteSchema; + const ts = sqliteSchemaToTypeScript(schema, casing); + const relationsTs = relationsToTypeScript(schema, casing); + + // check orm and orm-pg api version + + const schemaFile = join(out, 'schema.ts'); + writeFileSync(schemaFile, ts.file); + const relationsFile = join(out, 'relations.ts'); + writeFileSync(relationsFile, relationsTs.file); + console.log(); + + const { snapshots, journal } = prepareOutFolder(out, 'sqlite'); + + if (snapshots.length === 0) { + const { sqlStatements, _meta } = await applyLibSQLSnapshotsDiff( + squashSqliteScheme(drySQLite), + squashSqliteScheme(schema), + tablesResolver, + columnsResolver, + sqliteViewsResolver, + drySQLite, + schema, + ); + + writeResult({ + cur: schema, + sqlStatements, + journal, + _meta, + outFolder: out, + breakpoints, + type: 'introspect', + prefixMode: prefix, + }); + } else { + render( + `[${ + chalk.blue( + 'i', + ) + }] No SQL generated, you already have migrations in project`, + ); + } + + render( + `[${ + chalk.green( + '✓', + ) + }] Your schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`, + ); + render( + `[${ + chalk.green( + '✓', + ) + }] Your relations file is ready ➜ ${ + chalk.bold.underline.blue( + relationsFile, + ) + } 🚀`, + ); + process.exit(0); +}; + const withCasing = (value: string, casing: Casing) => { if (casing === 'preserve') { return value; diff --git a/drizzle-kit/src/cli/commands/libSqlPushUtils.ts b/drizzle-kit/src/cli/commands/libSqlPushUtils.ts new file mode 100644 index 000000000..31e90c872 --- /dev/null +++ b/drizzle-kit/src/cli/commands/libSqlPushUtils.ts @@ -0,0 +1,355 @@ +import chalk from 'chalk'; + +import { JsonStatement } from 'src/jsonStatements'; +import { findAddedAndRemoved, SQLiteDB } from 'src/utils'; +import { SQLiteSchemaInternal, SQLiteSchemaSquashed, SQLiteSquasher } from '../../serializer/sqliteSchema'; +import { + CreateSqliteIndexConvertor, + fromJson, + LibSQLModifyColumn, + SQLiteCreateTableConvertor, + SQLiteDropTableConvertor, + SqliteRenameTableConvertor, +} from '../../sqlgenerator'; + +export const getOldTableName = ( + tableName: string, + meta: SQLiteSchemaInternal['_meta'], +) => { + for (const key of Object.keys(meta.tables)) { + const value = meta.tables[key]; + if (`"${tableName}"` === value) { + return key.substring(1, key.length - 1); + } + } + return tableName; +}; + +export const _moveDataStatements = ( + tableName: string, + json: SQLiteSchemaSquashed, + dataLoss: boolean = false, +) => { + const statements: string[] = []; + + const newTableName = `__new_${tableName}`; + + // create table statement from a new json2 with proper name + const tableColumns = Object.values(json.tables[tableName].columns); + const referenceData = Object.values(json.tables[tableName].foreignKeys); + const compositePKs = Object.values( + json.tables[tableName].compositePrimaryKeys, + ).map((it) => SQLiteSquasher.unsquashPK(it)); + const checkConstraints = Object.values(json.tables[tableName].checkConstraints); + + const fks = referenceData.map((it) => SQLiteSquasher.unsquashPushFK(it)); + + const mappedCheckConstraints: string[] = checkConstraints.map((it) => + it.replaceAll(`"${tableName}".`, `"${newTableName}".`) + .replaceAll(`\`${tableName}\`.`, `\`${newTableName}\`.`) + .replaceAll(`${tableName}.`, `${newTableName}.`) + .replaceAll(`'${tableName}'.`, `\`${newTableName}\`.`) + ); + + // create new table + statements.push( + new SQLiteCreateTableConvertor().convert({ + type: 'sqlite_create_table', + tableName: newTableName, + columns: tableColumns, + referenceData: fks, + compositePKs, + checkConstraints: mappedCheckConstraints, + }), + ); + + // move data + if (!dataLoss) { + const columns = Object.keys(json.tables[tableName].columns).map( + (c) => `"${c}"`, + ); + + statements.push( + `INSERT INTO \`${newTableName}\`(${ + columns.join( + ', ', + ) + }) SELECT ${columns.join(', ')} FROM \`${tableName}\`;`, + ); + } + + statements.push( + new SQLiteDropTableConvertor().convert({ + type: 'drop_table', + tableName: tableName, + schema: '', + }), + ); + + // rename table + statements.push( + new SqliteRenameTableConvertor().convert({ + fromSchema: '', + tableNameFrom: newTableName, + tableNameTo: tableName, + toSchema: '', + type: 'rename_table', + }), + ); + + for (const idx of Object.values(json.tables[tableName].indexes)) { + statements.push( + new CreateSqliteIndexConvertor().convert({ + type: 'create_index', + tableName: tableName, + schema: '', + data: idx, + }), + ); + } + return statements; +}; + +export const libSqlLogSuggestionsAndReturn = async ( + connection: SQLiteDB, + statements: JsonStatement[], + json1: SQLiteSchemaSquashed, + json2: SQLiteSchemaSquashed, + meta: SQLiteSchemaInternal['_meta'], +) => { + let shouldAskForApprove = false; + const statementsToExecute: string[] = []; + const infoToPrint: string[] = []; + + const tablesToRemove: string[] = []; + const columnsToRemove: string[] = []; + const tablesToTruncate: string[] = []; + + for (const statement of statements) { + if (statement.type === 'drop_table') { + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.tableName, + ) + } table with ${count} items`, + ); + tablesToRemove.push(statement.tableName); + shouldAskForApprove = true; + } + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if (statement.type === 'alter_table_drop_column') { + const tableName = statement.tableName; + + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.columnName, + ) + } column in ${tableName} table with ${count} items`, + ); + columnsToRemove.push(`${tableName}_${statement.columnName}`); + shouldAskForApprove = true; + } + + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if ( + statement.type === 'sqlite_alter_table_add_column' + && statement.column.notNull + && !statement.column.default + ) { + const newTableName = statement.tableName; + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${newTableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + statement.column.name, + ) + } column without default value, which contains ${count} items`, + ); + + tablesToTruncate.push(newTableName); + statementsToExecute.push(`delete from ${newTableName};`); + + shouldAskForApprove = true; + } + + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if (statement.type === 'alter_table_alter_column_set_notnull') { + const tableName = statement.tableName; + + if ( + statement.type === 'alter_table_alter_column_set_notnull' + && typeof statement.columnDefault === 'undefined' + ) { + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to add not-null constraint to ${ + chalk.underline( + statement.columnName, + ) + } column without default value, which contains ${count} items`, + ); + + tablesToTruncate.push(tableName); + statementsToExecute.push(`delete from \`${tableName}\``); + shouldAskForApprove = true; + } + } + + const modifyStatements = new LibSQLModifyColumn().convert(statement, json2); + + statementsToExecute.push( + ...(Array.isArray(modifyStatements) ? modifyStatements : [modifyStatements]), + ); + } else if (statement.type === 'recreate_table') { + const tableName = statement.tableName; + + let dataLoss = false; + + const oldTableName = getOldTableName(tableName, meta); + + const prevColumnNames = Object.keys(json1.tables[oldTableName].columns); + const currentColumnNames = Object.keys(json2.tables[tableName].columns); + const { removedColumns, addedColumns } = findAddedAndRemoved( + prevColumnNames, + currentColumnNames, + ); + + if (removedColumns.length) { + for (const removedColumn of removedColumns) { + const res = await connection.query<{ count: string }>( + `select count(\`${tableName}\`.\`${removedColumn}\`) as count from \`${tableName}\``, + ); + + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + removedColumn, + ) + } column in ${tableName} table with ${count} items`, + ); + columnsToRemove.push(removedColumn); + shouldAskForApprove = true; + } + } + } + + if (addedColumns.length) { + for (const addedColumn of addedColumns) { + const [res] = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, + ); + + const columnConf = json2.tables[tableName].columns[addedColumn]; + + const count = Number(res.count); + if (count > 0 && columnConf.notNull && !columnConf.default) { + dataLoss = true; + + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + addedColumn, + ) + } column without default value to table, which contains ${count} items`, + ); + shouldAskForApprove = true; + tablesToTruncate.push(tableName); + + statementsToExecute.push(`DELETE FROM \`${tableName}\`;`); + } + } + } + + // check if some tables referencing current for pragma + const tablesReferencingCurrent: string[] = []; + + for (const table of Object.values(json2.tables)) { + const tablesRefs = Object.values(json2.tables[table.name].foreignKeys) + .filter((t) => SQLiteSquasher.unsquashPushFK(t).tableTo === tableName) + .map((it) => SQLiteSquasher.unsquashPushFK(it).tableFrom); + + tablesReferencingCurrent.push(...tablesRefs); + } + + if (!tablesReferencingCurrent.length) { + statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss)); + continue; + } + + // recreate table + statementsToExecute.push( + ..._moveDataStatements(tableName, json2, dataLoss), + ); + } else if ( + statement.type === 'alter_table_alter_column_set_generated' + || statement.type === 'alter_table_alter_column_drop_generated' + ) { + const tableName = statement.tableName; + + const res = await connection.query<{ count: string }>( + `select count("${statement.columnName}") as count from \`${tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.columnName, + ) + } column in ${tableName} table with ${count} items`, + ); + columnsToRemove.push(`${tableName}_${statement.columnName}`); + shouldAskForApprove = true; + } + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else { + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } + } + + return { + statementsToExecute: [...new Set(statementsToExecute)], + shouldAskForApprove, + infoToPrint, + columnsToRemove: [...new Set(columnsToRemove)], + tablesToTruncate: [...new Set(tablesToTruncate)], + tablesToRemove: [...new Set(tablesToRemove)], + }; +}; diff --git a/drizzle-kit/src/cli/commands/migrate.ts b/drizzle-kit/src/cli/commands/migrate.ts index 8ef469fa1..8c62a5edb 100644 --- a/drizzle-kit/src/cli/commands/migrate.ts +++ b/drizzle-kit/src/cli/commands/migrate.ts @@ -4,6 +4,8 @@ import { prepareMySqlMigrationSnapshot, preparePgDbPushSnapshot, preparePgMigrationSnapshot, + prepareSingleStoreDbPushSnapshot, + prepareSingleStoreMigrationSnapshot, prepareSQLiteDbPushSnapshot, prepareSqliteMigrationSnapshot, } from '../../migrationPreparator'; @@ -11,28 +13,37 @@ import { import chalk from 'chalk'; import { render } from 'hanji'; import path, { join } from 'path'; +import { SingleStoreSchema, singlestoreSchema, squashSingleStoreScheme } from 'src/serializer/singlestoreSchema'; import { TypeOf } from 'zod'; import type { CommonSchema } from '../../schemaValidator'; -import { MySqlSchema, mysqlSchema, squashMysqlScheme } from '../../serializer/mysqlSchema'; -import { PgSchema, pgSchema, squashPgScheme } from '../../serializer/pgSchema'; -import { SQLiteSchema, sqliteSchema, squashSqliteScheme } from '../../serializer/sqliteSchema'; +import { MySqlSchema, mysqlSchema, squashMysqlScheme, ViewSquashed } from '../../serializer/mysqlSchema'; +import { PgSchema, pgSchema, Policy, Role, squashPgScheme, View } from '../../serializer/pgSchema'; +import { SQLiteSchema, sqliteSchema, squashSqliteScheme, View as SQLiteView } from '../../serializer/sqliteSchema'; import { + applyLibSQLSnapshotsDiff, applyMysqlSnapshotsDiff, applyPgSnapshotsDiff, + applySingleStoreSnapshotsDiff, applySqliteSnapshotsDiff, Column, ColumnsResolverInput, ColumnsResolverOutput, Enum, + PolicyResolverInput, + PolicyResolverOutput, ResolverInput, ResolverOutput, ResolverOutputWithMoved, + RolesResolverInput, + RolesResolverOutput, Sequence, Table, + TablePolicyResolverInput, + TablePolicyResolverOutput, } from '../../snapshotsDiffer'; import { assertV1OutFolder, Journal, prepareMigrationFolder } from '../../utils'; import { prepareMigrationMetadata } from '../../utils/words'; -import { Prefix } from '../validations/common'; +import { CasingType, Driver, Prefix } from '../validations/common'; import { withStyle } from '../validations/outputs'; import { isRenamePromptItem, @@ -40,9 +51,10 @@ import { ResolveColumnSelect, ResolveSchemasSelect, ResolveSelect, + ResolveSelectNamed, schema, } from '../views'; -import { GenerateConfig } from './utils'; +import { ExportConfig, GenerateConfig } from './utils'; export type Named = { name: string; @@ -91,6 +103,94 @@ export const tablesResolver = async ( } }; +export const viewsResolver = async ( + input: ResolverInput, +): Promise> => { + try { + const { created, deleted, moved, renamed } = await promptNamedWithSchemasConflict( + input.created, + input.deleted, + 'view', + ); + + return { + created: created, + deleted: deleted, + moved: moved, + renamed: renamed, + }; + } catch (e) { + console.error(e); + throw e; + } +}; + +export const mySqlViewsResolver = async ( + input: ResolverInput, +): Promise> => { + try { + const { created, deleted, moved, renamed } = await promptNamedWithSchemasConflict( + input.created, + input.deleted, + 'view', + ); + + return { + created: created, + deleted: deleted, + moved: moved, + renamed: renamed, + }; + } catch (e) { + console.error(e); + throw e; + } +}; + +/* export const singleStoreViewsResolver = async ( + input: ResolverInput, +): Promise> => { + try { + const { created, deleted, moved, renamed } = await promptNamedWithSchemasConflict( + input.created, + input.deleted, + 'view', + ); + + return { + created: created, + deleted: deleted, + moved: moved, + renamed: renamed, + }; + } catch (e) { + console.error(e); + throw e; + } +}; */ + +export const sqliteViewsResolver = async ( + input: ResolverInput, +): Promise> => { + try { + const { created, deleted, moved, renamed } = await promptNamedWithSchemasConflict( + input.created, + input.deleted, + 'view', + ); + + return { + created: created, + deleted: deleted, + moved: moved, + renamed: renamed, + }; + } catch (e) { + console.error(e); + throw e; + } +}; + export const sequencesResolver = async ( input: ResolverInput, ): Promise> => { @@ -113,6 +213,53 @@ export const sequencesResolver = async ( } }; +export const roleResolver = async ( + input: RolesResolverInput, +): Promise> => { + const result = await promptNamedConflict( + input.created, + input.deleted, + 'role', + ); + return { + created: result.created, + deleted: result.deleted, + renamed: result.renamed, + }; +}; + +export const policyResolver = async ( + input: TablePolicyResolverInput, +): Promise> => { + const result = await promptColumnsConflicts( + input.tableName, + input.created, + input.deleted, + ); + return { + tableName: input.tableName, + schema: input.schema, + created: result.created, + deleted: result.deleted, + renamed: result.renamed, + }; +}; + +export const indPolicyResolver = async ( + input: PolicyResolverInput, +): Promise> => { + const result = await promptNamedConflict( + input.created, + input.deleted, + 'policy', + ); + return { + created: result.created, + deleted: result.deleted, + renamed: result.renamed, + }; +}; + export const enumsResolver = async ( input: ResolverInput, ): Promise> => { @@ -155,6 +302,7 @@ export const columnsResolver = async ( export const prepareAndMigratePg = async (config: GenerateConfig) => { const outFolder = config.out; const schemaPath = config.schema; + const casing = config.casing; try { assertV1OutFolder(outFolder); @@ -167,6 +315,7 @@ export const prepareAndMigratePg = async (config: GenerateConfig) => { const { prev, cur, custom } = await preparePgMigrationSnapshot( snapshots, schemaPath, + casing, ); const validatedPrev = pgSchema.parse(prev); @@ -195,8 +344,12 @@ export const prepareAndMigratePg = async (config: GenerateConfig) => { schemasResolver, enumsResolver, sequencesResolver, + policyResolver, + indPolicyResolver, + roleResolver, tablesResolver, columnsResolver, + viewsResolver, validatedPrev, validatedCur, ); @@ -215,17 +368,48 @@ export const prepareAndMigratePg = async (config: GenerateConfig) => { } }; +export const prepareAndExportPg = async (config: ExportConfig) => { + const schemaPath = config.schema; + + try { + const { prev, cur } = await preparePgMigrationSnapshot( + [], // no snapshots before + schemaPath, + undefined, + ); + + const validatedPrev = pgSchema.parse(prev); + const validatedCur = pgSchema.parse(cur); + + const squashedPrev = squashPgScheme(validatedPrev); + const squashedCur = squashPgScheme(validatedCur); + + const { sqlStatements } = await applyPgSnapshotsDiff( + squashedPrev, + squashedCur, + schemasResolver, + enumsResolver, + sequencesResolver, + policyResolver, + indPolicyResolver, + roleResolver, + tablesResolver, + columnsResolver, + viewsResolver, + validatedPrev, + validatedCur, + ); + + console.log(sqlStatements.join('\n')); + } catch (e) { + console.error(e); + } +}; + export const preparePgPush = async ( - schemaPath: string | string[], - snapshot: PgSchema, - schemaFilter: string[], + cur: PgSchema, + prev: PgSchema, ) => { - const { prev, cur } = await preparePgDbPushSnapshot( - snapshot, - schemaPath, - schemaFilter, - ); - const validatedPrev = pgSchema.parse(prev); const validatedCur = pgSchema.parse(cur); @@ -238,8 +422,12 @@ export const preparePgPush = async ( schemasResolver, enumsResolver, sequencesResolver, + policyResolver, + indPolicyResolver, + roleResolver, tablesResolver, columnsResolver, + viewsResolver, validatedPrev, validatedCur, 'push', @@ -248,66 +436,345 @@ export const preparePgPush = async ( return { sqlStatements, statements, squashedPrev, squashedCur }; }; -// Not needed for now -function mysqlSchemaSuggestions( - curSchema: TypeOf, - prevSchema: TypeOf, -) { - const suggestions: string[] = []; - const usedSuggestions: string[] = []; - const suggestionTypes = { - serial: withStyle.errorWarning( - `We deprecated the use of 'serial' for MySQL starting from version 0.20.0. In MySQL, 'serial' is simply an alias for 'bigint unsigned not null auto_increment unique,' which creates all constraints and indexes for you. This may make the process less explicit for both users and drizzle-kit push commands`, - ), - }; +// Not needed for now +function mysqlSchemaSuggestions( + curSchema: TypeOf, + prevSchema: TypeOf, +) { + const suggestions: string[] = []; + const usedSuggestions: string[] = []; + const suggestionTypes = { + serial: withStyle.errorWarning( + `We deprecated the use of 'serial' for MySQL starting from version 0.20.0. In MySQL, 'serial' is simply an alias for 'bigint unsigned not null auto_increment unique,' which creates all constraints and indexes for you. This may make the process less explicit for both users and drizzle-kit push commands`, + ), + }; + + for (const table of Object.values(curSchema.tables)) { + for (const column of Object.values(table.columns)) { + if (column.type === 'serial') { + if (!usedSuggestions.includes('serial')) { + suggestions.push(suggestionTypes['serial']); + } + + const uniqueForSerial = Object.values( + prevSchema.tables[table.name].uniqueConstraints, + ).find((it) => it.columns[0] === column.name); + + suggestions.push( + `\n` + + withStyle.suggestion( + `We are suggesting to change ${ + chalk.blue( + column.name, + ) + } column in ${ + chalk.blueBright( + table.name, + ) + } table from serial to bigint unsigned\n\n${ + chalk.blueBright( + `bigint("${column.name}", { mode: "number", unsigned: true }).notNull().autoincrement().unique(${ + uniqueForSerial?.name ? `"${uniqueForSerial?.name}"` : '' + })`, + ) + }`, + ), + ); + } + } + } + + return suggestions; +} + +// Intersect with prepareAnMigrate +export const prepareMySQLPush = async ( + schemaPath: string | string[], + snapshot: MySqlSchema, + casing: CasingType | undefined, +) => { + try { + const { prev, cur } = await prepareMySqlDbPushSnapshot( + snapshot, + schemaPath, + casing, + ); + + const validatedPrev = mysqlSchema.parse(prev); + const validatedCur = mysqlSchema.parse(cur); + + const squashedPrev = squashMysqlScheme(validatedPrev); + const squashedCur = squashMysqlScheme(validatedCur); + + const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + mySqlViewsResolver, + validatedPrev, + validatedCur, + 'push', + ); + + return { sqlStatements, statements, validatedCur, validatedPrev }; + } catch (e) { + console.error(e); + process.exit(1); + } +}; + +export const prepareAndMigrateMysql = async (config: GenerateConfig) => { + const outFolder = config.out; + const schemaPath = config.schema; + const casing = config.casing; + + try { + // TODO: remove + assertV1OutFolder(outFolder); + + const { snapshots, journal } = prepareMigrationFolder(outFolder, 'mysql'); + const { prev, cur, custom } = await prepareMySqlMigrationSnapshot( + snapshots, + schemaPath, + casing, + ); + + const validatedPrev = mysqlSchema.parse(prev); + const validatedCur = mysqlSchema.parse(cur); + + if (config.custom) { + writeResult({ + cur: custom, + sqlStatements: [], + journal, + outFolder, + name: config.name, + breakpoints: config.breakpoints, + type: 'custom', + prefixMode: config.prefix, + }); + return; + } + + const squashedPrev = squashMysqlScheme(validatedPrev); + const squashedCur = squashMysqlScheme(validatedCur); + + const { sqlStatements, statements, _meta } = await applyMysqlSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + mySqlViewsResolver, + validatedPrev, + validatedCur, + ); + + writeResult({ + cur, + sqlStatements, + journal, + _meta, + outFolder, + name: config.name, + breakpoints: config.breakpoints, + prefixMode: config.prefix, + }); + } catch (e) { + console.error(e); + } +}; + +// Not needed for now +function singleStoreSchemaSuggestions( + curSchema: TypeOf, + prevSchema: TypeOf, +) { + const suggestions: string[] = []; + const usedSuggestions: string[] = []; + const suggestionTypes = { + // TODO: Check if SingleStore has serial type + serial: withStyle.errorWarning( + `We deprecated the use of 'serial' for SingleStore starting from version 0.20.0. In SingleStore, 'serial' is simply an alias for 'bigint unsigned not null auto_increment unique,' which creates all constraints and indexes for you. This may make the process less explicit for both users and drizzle-kit push commands`, + ), + }; + + for (const table of Object.values(curSchema.tables)) { + for (const column of Object.values(table.columns)) { + if (column.type === 'serial') { + if (!usedSuggestions.includes('serial')) { + suggestions.push(suggestionTypes['serial']); + } + + const uniqueForSerial = Object.values( + prevSchema.tables[table.name].uniqueConstraints, + ).find((it) => it.columns[0] === column.name); + + suggestions.push( + `\n` + + withStyle.suggestion( + `We are suggesting to change ${ + chalk.blue( + column.name, + ) + } column in ${ + chalk.blueBright( + table.name, + ) + } table from serial to bigint unsigned\n\n${ + chalk.blueBright( + `bigint("${column.name}", { mode: "number", unsigned: true }).notNull().autoincrement().unique(${ + uniqueForSerial?.name ? `"${uniqueForSerial?.name}"` : '' + })`, + ) + }`, + ), + ); + } + } + } + + return suggestions; +} + +// Intersect with prepareAnMigrate +export const prepareSingleStorePush = async ( + schemaPath: string | string[], + snapshot: SingleStoreSchema, + casing: CasingType | undefined, +) => { + try { + const { prev, cur } = await prepareSingleStoreDbPushSnapshot( + snapshot, + schemaPath, + casing, + ); + + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); + + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + /* singleStoreViewsResolver, */ + validatedPrev, + validatedCur, + 'push', + ); + + return { sqlStatements, statements, validatedCur, validatedPrev }; + } catch (e) { + console.error(e); + process.exit(1); + } +}; + +export const prepareAndMigrateSingleStore = async (config: GenerateConfig) => { + const outFolder = config.out; + const schemaPath = config.schema; + const casing = config.casing; + + try { + // TODO: remove + assertV1OutFolder(outFolder); + + const { snapshots, journal } = prepareMigrationFolder(outFolder, 'singlestore'); + const { prev, cur, custom } = await prepareSingleStoreMigrationSnapshot( + snapshots, + schemaPath, + casing, + ); + + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); + + if (config.custom) { + writeResult({ + cur: custom, + sqlStatements: [], + journal, + outFolder, + name: config.name, + breakpoints: config.breakpoints, + type: 'custom', + prefixMode: config.prefix, + }); + return; + } + + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { sqlStatements, _meta } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + /* singleStoreViewsResolver, */ + validatedPrev, + validatedCur, + ); + + writeResult({ + cur, + sqlStatements, + journal, + _meta, + outFolder, + name: config.name, + breakpoints: config.breakpoints, + prefixMode: config.prefix, + }); + } catch (e) { + console.error(e); + } +}; + +export const prepareAndExportSinglestore = async (config: ExportConfig) => { + const schemaPath = config.schema; - for (const table of Object.values(curSchema.tables)) { - for (const column of Object.values(table.columns)) { - if (column.type === 'serial') { - if (!usedSuggestions.includes('serial')) { - suggestions.push(suggestionTypes['serial']); - } + try { + const { prev, cur } = await prepareSingleStoreMigrationSnapshot( + [], + schemaPath, + undefined, + ); - const uniqueForSerial = Object.values( - prevSchema.tables[table.name].uniqueConstraints, - ).find((it) => it.columns[0] === column.name); + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); - suggestions.push( - `\n` - + withStyle.suggestion( - `We are suggesting to change ${ - chalk.blue( - column.name, - ) - } column in ${ - chalk.blueBright( - table.name, - ) - } table from serial to bigint unsigned\n\n${ - chalk.blueBright( - `bigint("${column.name}", { mode: "number", unsigned: true }).notNull().autoincrement().unique(${ - uniqueForSerial?.name ? `"${uniqueForSerial?.name}"` : '' - })`, - ) - }`, - ), - ); - } - } + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { sqlStatements, _meta } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + /* singleStoreViewsResolver, */ + validatedPrev, + validatedCur, + ); + + console.log(sqlStatements.join('\n')); + } catch (e) { + console.error(e); } +}; - return suggestions; -} +export const prepareAndExportMysql = async (config: ExportConfig) => { + const schemaPath = config.schema; -// Intersect with prepareAnMigrate -export const prepareMySQLPush = async ( - schemaPath: string | string[], - snapshot: MySqlSchema, -) => { try { - const { prev, cur } = await prepareMySqlDbPushSnapshot( - snapshot, + const { prev, cur, custom } = await prepareMySqlMigrationSnapshot( + [], schemaPath, + undefined, ); const validatedPrev = mysqlSchema.parse(prev); @@ -316,39 +783,39 @@ export const prepareMySQLPush = async ( const squashedPrev = squashMysqlScheme(validatedPrev); const squashedCur = squashMysqlScheme(validatedCur); - const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + const { sqlStatements, statements, _meta } = await applyMysqlSnapshotsDiff( squashedPrev, squashedCur, tablesResolver, columnsResolver, + mySqlViewsResolver, validatedPrev, validatedCur, - 'push', ); - return { sqlStatements, statements, validatedCur, validatedPrev }; + console.log(sqlStatements.join('\n')); } catch (e) { console.error(e); - process.exit(1); } }; -export const prepareAndMigrateMysql = async (config: GenerateConfig) => { +export const prepareAndMigrateSqlite = async (config: GenerateConfig) => { const outFolder = config.out; const schemaPath = config.schema; + const casing = config.casing; try { - // TODO: remove assertV1OutFolder(outFolder); - const { snapshots, journal } = prepareMigrationFolder(outFolder, 'mysql'); - const { prev, cur, custom } = await prepareMySqlMigrationSnapshot( + const { snapshots, journal } = prepareMigrationFolder(outFolder, 'sqlite'); + const { prev, cur, custom } = await prepareSqliteMigrationSnapshot( snapshots, schemaPath, + casing, ); - const validatedPrev = mysqlSchema.parse(prev); - const validatedCur = mysqlSchema.parse(cur); + const validatedPrev = sqliteSchema.parse(prev); + const validatedCur = sqliteSchema.parse(cur); if (config.custom) { writeResult({ @@ -358,20 +825,22 @@ export const prepareAndMigrateMysql = async (config: GenerateConfig) => { outFolder, name: config.name, breakpoints: config.breakpoints, + bundle: config.bundle, type: 'custom', prefixMode: config.prefix, }); return; } - const squashedPrev = squashMysqlScheme(validatedPrev); - const squashedCur = squashMysqlScheme(validatedCur); + const squashedPrev = squashSqliteScheme(validatedPrev); + const squashedCur = squashSqliteScheme(validatedCur); - const { sqlStatements, statements, _meta } = await applyMysqlSnapshotsDiff( + const { sqlStatements, _meta } = await applySqliteSnapshotsDiff( squashedPrev, squashedCur, tablesResolver, columnsResolver, + sqliteViewsResolver, validatedPrev, validatedCur, ); @@ -384,16 +853,51 @@ export const prepareAndMigrateMysql = async (config: GenerateConfig) => { outFolder, name: config.name, breakpoints: config.breakpoints, + bundle: config.bundle, prefixMode: config.prefix, + driver: config.driver, }); } catch (e) { console.error(e); } }; -export const prepareAndMigrateSqlite = async (config: GenerateConfig) => { +export const prepareAndExportSqlite = async (config: ExportConfig) => { + const schemaPath = config.schema; + + try { + const { prev, cur } = await prepareSqliteMigrationSnapshot( + [], + schemaPath, + undefined, + ); + + const validatedPrev = sqliteSchema.parse(prev); + const validatedCur = sqliteSchema.parse(cur); + + const squashedPrev = squashSqliteScheme(validatedPrev); + const squashedCur = squashSqliteScheme(validatedCur); + + const { sqlStatements, _meta } = await applySqliteSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + sqliteViewsResolver, + validatedPrev, + validatedCur, + ); + + console.log(sqlStatements.join('\n')); + } catch (e) { + console.error(e); + } +}; + +export const prepareAndMigrateLibSQL = async (config: GenerateConfig) => { const outFolder = config.out; const schemaPath = config.schema; + const casing = config.casing; try { assertV1OutFolder(outFolder); @@ -402,6 +906,7 @@ export const prepareAndMigrateSqlite = async (config: GenerateConfig) => { const { prev, cur, custom } = await prepareSqliteMigrationSnapshot( snapshots, schemaPath, + casing, ); const validatedPrev = sqliteSchema.parse(prev); @@ -425,11 +930,12 @@ export const prepareAndMigrateSqlite = async (config: GenerateConfig) => { const squashedPrev = squashSqliteScheme(validatedPrev); const squashedCur = squashSqliteScheme(validatedCur); - const { sqlStatements, _meta } = await applySqliteSnapshotsDiff( + const { sqlStatements, _meta } = await applyLibSQLSnapshotsDiff( squashedPrev, squashedCur, tablesResolver, columnsResolver, + sqliteViewsResolver, validatedPrev, validatedCur, ); @@ -450,11 +956,44 @@ export const prepareAndMigrateSqlite = async (config: GenerateConfig) => { } }; +export const prepareAndExportLibSQL = async (config: ExportConfig) => { + const schemaPath = config.schema; + + try { + const { prev, cur, custom } = await prepareSqliteMigrationSnapshot( + [], + schemaPath, + undefined, + ); + + const validatedPrev = sqliteSchema.parse(prev); + const validatedCur = sqliteSchema.parse(cur); + + const squashedPrev = squashSqliteScheme(validatedPrev); + const squashedCur = squashSqliteScheme(validatedCur); + + const { sqlStatements, _meta } = await applyLibSQLSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + sqliteViewsResolver, + validatedPrev, + validatedCur, + ); + + console.log(sqlStatements.join('\n')); + } catch (e) { + console.error(e); + } +}; + export const prepareSQLitePush = async ( schemaPath: string | string[], snapshot: SQLiteSchema, + casing: CasingType | undefined, ) => { - const { prev, cur } = await prepareSQLiteDbPushSnapshot(snapshot, schemaPath); + const { prev, cur } = await prepareSQLiteDbPushSnapshot(snapshot, schemaPath, casing); const validatedPrev = sqliteSchema.parse(prev); const validatedCur = sqliteSchema.parse(cur); @@ -467,6 +1006,40 @@ export const prepareSQLitePush = async ( squashedCur, tablesResolver, columnsResolver, + sqliteViewsResolver, + validatedPrev, + validatedCur, + 'push', + ); + + return { + sqlStatements, + statements, + squashedPrev, + squashedCur, + meta: _meta, + }; +}; + +export const prepareLibSQLPush = async ( + schemaPath: string | string[], + snapshot: SQLiteSchema, + casing: CasingType | undefined, +) => { + const { prev, cur } = await prepareSQLiteDbPushSnapshot(snapshot, schemaPath, casing); + + const validatedPrev = sqliteSchema.parse(prev); + const validatedCur = sqliteSchema.parse(cur); + + const squashedPrev = squashSqliteScheme(validatedPrev, 'push'); + const squashedCur = squashSqliteScheme(validatedCur, 'push'); + + const { sqlStatements, statements, _meta } = await applyLibSQLSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + sqliteViewsResolver, validatedPrev, validatedCur, 'push', @@ -557,10 +1130,82 @@ export const promptColumnsConflicts = async ( return result; }; +export const promptNamedConflict = async ( + newItems: T[], + missingItems: T[], + entity: 'role' | 'policy', +): Promise<{ + created: T[]; + renamed: { from: T; to: T }[]; + deleted: T[]; +}> => { + if (missingItems.length === 0 || newItems.length === 0) { + return { + created: newItems, + renamed: [], + deleted: missingItems, + }; + } + + const result: { + created: T[]; + renamed: { from: T; to: T }[]; + deleted: T[]; + } = { created: [], renamed: [], deleted: [] }; + let index = 0; + let leftMissing = [...missingItems]; + do { + const created = newItems[index]; + const renames: RenamePropmtItem[] = leftMissing.map((it) => { + return { from: it, to: created }; + }); + + const promptData: (RenamePropmtItem | T)[] = [created, ...renames]; + + const { status, data } = await render( + new ResolveSelectNamed(created, promptData, entity), + ); + if (status === 'aborted') { + console.error('ERROR'); + process.exit(1); + } + + if (isRenamePromptItem(data)) { + console.log( + `${chalk.yellow('~')} ${data.from.name} › ${data.to.name} ${ + chalk.gray( + `${entity} will be renamed/moved`, + ) + }`, + ); + + if (data.from.name !== data.to.name) { + result.renamed.push(data); + } + + delete leftMissing[leftMissing.indexOf(data.from)]; + leftMissing = leftMissing.filter(Boolean); + } else { + console.log( + `${chalk.green('+')} ${data.name} ${ + chalk.gray( + `${entity} will be created`, + ) + }`, + ); + result.created.push(created); + } + index += 1; + } while (index < newItems.length); + console.log(chalk.gray(`--- all ${entity} conflicts resolved ---\n`)); + result.deleted.push(...leftMissing); + return result; +}; + export const promptNamedWithSchemasConflict = async ( newItems: T[], missingItems: T[], - entity: 'table' | 'enum' | 'sequence', + entity: 'table' | 'enum' | 'sequence' | 'view', ): Promise<{ created: T[]; renamed: { from: T; to: T }[]; @@ -723,6 +1368,7 @@ export const writeResult = ({ bundle = false, type = 'none', prefixMode, + driver, }: { cur: CommonSchema; sqlStatements: string[]; @@ -734,6 +1380,7 @@ export const writeResult = ({ name?: string; bundle?: boolean; type?: 'introspect' | 'custom' | 'none'; + driver?: Driver; }) => { if (type === 'none') { console.log(schema(cur)); @@ -776,7 +1423,7 @@ export const writeResult = ({ if (type === 'custom') { console.log('Prepared empty file for your custom SQL migration!'); - sql = '-- Custom SQL migration file, put you code below! --'; + sql = '-- Custom SQL migration file, put your code below! --'; } journal.entries.push({ @@ -791,9 +1438,9 @@ export const writeResult = ({ fs.writeFileSync(`${outFolder}/${tag}.sql`, sql); - // js file with .sql imports for React Native / Expo + // js file with .sql imports for React Native / Expo and Durable Sqlite Objects if (bundle) { - const js = embeddedMigrations(journal); + const js = embeddedMigrations(journal, driver); fs.writeFileSync(`${outFolder}/migrations.js`, js); } @@ -810,9 +1457,11 @@ export const writeResult = ({ ); }; -export const embeddedMigrations = (journal: Journal) => { - let content = - '// This file is required for Expo/React Native SQLite migrations - https://orm.drizzle.team/quick-sqlite/expo\n\n'; +export const embeddedMigrations = (journal: Journal, driver?: Driver) => { + let content = driver === 'expo' + ? '// This file is required for Expo/React Native SQLite migrations - https://orm.drizzle.team/quick-sqlite/expo\n\n' + : ''; + content += "import journal from './meta/_journal.json';\n"; journal.entries.forEach((entry) => { content += `import m${entry.idx.toString().padStart(4, '0')} from './${entry.tag}.sql';\n`; diff --git a/drizzle-kit/src/cli/commands/mysqlUp.ts b/drizzle-kit/src/cli/commands/mysqlUp.ts index 6c7d2ebe5..8b467090b 100644 --- a/drizzle-kit/src/cli/commands/mysqlUp.ts +++ b/drizzle-kit/src/cli/commands/mysqlUp.ts @@ -82,6 +82,7 @@ export const upMySqlHandlerV4toV5 = (obj: MySqlSchemaV4): MySqlSchemaV5 => { columns: mappedColumns, compositePrimaryKeys: {}, uniqueConstraints: {}, + checkConstraint: {}, }; } diff --git a/drizzle-kit/src/cli/commands/pgIntrospect.ts b/drizzle-kit/src/cli/commands/pgIntrospect.ts index dbd3ba238..02867fae9 100644 --- a/drizzle-kit/src/cli/commands/pgIntrospect.ts +++ b/drizzle-kit/src/cli/commands/pgIntrospect.ts @@ -1,15 +1,18 @@ import { renderWithTask } from 'hanji'; import { Minimatch } from 'minimatch'; import { originUUID } from '../../global'; -import type { PgSchema } from '../../serializer/pgSchema'; +import type { PgSchema, PgSchemaInternal } from '../../serializer/pgSchema'; import { fromDatabase } from '../../serializer/pgSerializer'; import type { DB } from '../../utils'; +import { Entities } from '../validations/cli'; import { ProgressView } from '../views'; export const pgPushIntrospect = async ( db: DB, filters: string[], schemaFilters: string[], + entities: Entities, + tsSchema?: PgSchemaInternal, ) => { const matchers = filters.map((it) => { return new Minimatch(it); @@ -43,7 +46,7 @@ export const pgPushIntrospect = async ( ); const res = await renderWithTask( progress, - fromDatabase(db, filter, schemaFilters), + fromDatabase(db, filter, schemaFilters, entities, undefined, tsSchema), ); const schema = { id: originUUID, prevId: '', ...res } as PgSchema; diff --git a/drizzle-kit/src/cli/commands/pgPushUtils.ts b/drizzle-kit/src/cli/commands/pgPushUtils.ts index eee0dc954..05322f738 100644 --- a/drizzle-kit/src/cli/commands/pgPushUtils.ts +++ b/drizzle-kit/src/cli/commands/pgPushUtils.ts @@ -47,15 +47,9 @@ function tableNameWithSchemaFrom( renamedSchemas: Record, renamedTables: Record, ) { - const newSchemaName = schema - ? renamedSchemas[schema] - ? renamedSchemas[schema] - : schema - : undefined; + const newSchemaName = schema ? (renamedSchemas[schema] ? renamedSchemas[schema] : schema) : undefined; - const newTableName = renamedTables[ - concatSchemaAndTableName(newSchemaName, tableName) - ] + const newTableName = renamedTables[concatSchemaAndTableName(newSchemaName, tableName)] ? renamedTables[concatSchemaAndTableName(newSchemaName, tableName)] : tableName; @@ -71,6 +65,7 @@ export const pgSuggestions = async (db: DB, statements: JsonStatement[]) => { const columnsToRemove: string[] = []; const schemasToRemove: string[] = []; const tablesToTruncate: string[] = []; + const matViewsToRemove: string[] = []; let renamedSchemas: Record = {}; let renamedTables: Record = {}; @@ -79,53 +74,44 @@ export const pgSuggestions = async (db: DB, statements: JsonStatement[]) => { if (statement.type === 'rename_schema') { renamedSchemas[statement.to] = statement.from; } else if (statement.type === 'rename_table') { - renamedTables[ - concatSchemaAndTableName(statement.toSchema, statement.tableNameTo) - ] = statement.tableNameFrom; + renamedTables[concatSchemaAndTableName(statement.toSchema, statement.tableNameTo)] = statement.tableNameFrom; } else if (statement.type === 'drop_table') { const res = await db.query( `select count(*) as count from ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) }`, ); const count = Number(res[0].count); if (count > 0) { - infoToPrint.push( - `· You're about to delete ${ - chalk.underline( - statement.tableName, - ) - } table with ${count} items`, - ); + infoToPrint.push(`· You're about to delete ${chalk.underline(statement.tableName)} table with ${count} items`); // statementsToExecute.push( // `truncate table ${tableNameWithSchemaFrom(statement)} cascade;` // ); tablesToRemove.push(statement.tableName); shouldAskForApprove = true; } + } else if (statement.type === 'drop_view' && statement.materialized) { + const res = await db.query(`select count(*) as count from "${statement.schema ?? 'public'}"."${statement.name}"`); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete "${chalk.underline(statement.name)}" materialized view with ${count} items`, + ); + + matViewsToRemove.push(statement.name); + shouldAskForApprove = true; + } } else if (statement.type === 'alter_table_drop_column') { const res = await db.query( `select count(*) as count from ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) }`, ); const count = Number(res[0].count); if (count > 0) { infoToPrint.push( `· You're about to delete ${ - chalk.underline( - statement.columnName, - ) + chalk.underline(statement.columnName) } column in ${statement.tableName} table with ${count} items`, ); columnsToRemove.push(`${statement.tableName}_${statement.columnName}`); @@ -137,48 +123,30 @@ export const pgSuggestions = async (db: DB, statements: JsonStatement[]) => { ); const count = Number(res[0].count); if (count > 0) { - infoToPrint.push( - `· You're about to delete ${ - chalk.underline( - statement.name, - ) - } schema with ${count} tables`, - ); + infoToPrint.push(`· You're about to delete ${chalk.underline(statement.name)} schema with ${count} tables`); schemasToRemove.push(statement.name); shouldAskForApprove = true; } } else if (statement.type === 'alter_table_alter_column_set_type') { const res = await db.query( `select count(*) as count from ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) }`, ); const count = Number(res[0].count); if (count > 0) { infoToPrint.push( - `· You're about to change ${ - chalk.underline( - statement.columnName, - ) - } column type from ${ + `· You're about to change ${chalk.underline(statement.columnName)} column type from ${ + chalk.underline(statement.oldDataType) + } to ${ chalk.underline( - statement.oldDataType, + statement.newDataType, ) - } to ${chalk.underline(statement.newDataType)} with ${count} items`, + } with ${count} items`, ); statementsToExecute.push( `truncate table ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) } cascade;`, ); tablesToTruncate.push(statement.tableName); @@ -187,21 +155,14 @@ export const pgSuggestions = async (db: DB, statements: JsonStatement[]) => { } else if (statement.type === 'alter_table_alter_column_drop_pk') { const res = await db.query( `select count(*) as count from ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) }`, ); const count = Number(res[0].count); if (count > 0) { infoToPrint.push( `· You're about to change ${ - chalk.underline( - statement.tableName, - ) + chalk.underline(statement.tableName) } primary key. This statements may fail and you table may left without primary key`, ); @@ -219,9 +180,7 @@ export const pgSuggestions = async (db: DB, statements: JsonStatement[]) => { const pkNameResponse = await db.query( `SELECT constraint_name FROM information_schema.table_constraints WHERE table_schema = '${ - typeof statement.schema === 'undefined' || statement.schema === '' - ? 'public' - : statement.schema + typeof statement.schema === 'undefined' || statement.schema === '' ? 'public' : statement.schema }' AND table_name = '${statement.tableName}' AND constraint_type = 'PRIMARY KEY';`, @@ -233,39 +192,24 @@ export const pgSuggestions = async (db: DB, statements: JsonStatement[]) => { // we will generate statement for drop pk here and not after all if-else statements continue; } else if (statement.type === 'alter_table_add_column') { - if ( - statement.column.notNull - && typeof statement.column.default === 'undefined' - ) { + if (statement.column.notNull && typeof statement.column.default === 'undefined') { const res = await db.query( `select count(*) as count from ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) }`, ); const count = Number(res[0].count); if (count > 0) { infoToPrint.push( `· You're about to add not-null ${ - chalk.underline( - statement.column.name, - ) + chalk.underline(statement.column.name) } column without default value, which contains ${count} items`, ); tablesToTruncate.push(statement.tableName); statementsToExecute.push( `truncate table ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) } cascade;`, ); @@ -275,12 +219,7 @@ export const pgSuggestions = async (db: DB, statements: JsonStatement[]) => { } else if (statement.type === 'create_unique_constraint') { const res = await db.query( `select count(*) as count from ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) }`, ); const count = Number(res[0].count); @@ -298,48 +237,30 @@ export const pgSuggestions = async (db: DB, statements: JsonStatement[]) => { } table?\n`, ); const { status, data } = await render( - new Select([ - 'No, add the constraint without truncating the table', - `Yes, truncate the table`, - ]), + new Select(['No, add the constraint without truncating the table', `Yes, truncate the table`]), ); if (data?.index === 1) { tablesToTruncate.push(statement.tableName); statementsToExecute.push( `truncate table ${ - tableNameWithSchemaFrom( - statement.schema, - statement.tableName, - renamedSchemas, - renamedTables, - ) + tableNameWithSchemaFrom(statement.schema, statement.tableName, renamedSchemas, renamedTables) } cascade;`, ); shouldAskForApprove = true; } } } - const stmnt = fromJson([statement], 'postgresql'); + const stmnt = fromJson([statement], 'postgresql', 'push'); if (typeof stmnt !== 'undefined') { - if (statement.type === 'drop_table') { - statementsToExecute.push( - `DROP TABLE ${ - concatSchemaAndTableName( - statement.schema, - statement.tableName, - ) - } CASCADE;`, - ); - } else { - statementsToExecute.push(...stmnt); - } + statementsToExecute.push(...stmnt); } } return { - statementsToExecute, + statementsToExecute: [...new Set(statementsToExecute)], shouldAskForApprove, infoToPrint, + matViewsToRemove: [...new Set(matViewsToRemove)], columnsToRemove: [...new Set(columnsToRemove)], schemasToRemove: [...new Set(schemasToRemove)], tablesToTruncate: [...new Set(tablesToTruncate)], diff --git a/drizzle-kit/src/cli/commands/pgUp.ts b/drizzle-kit/src/cli/commands/pgUp.ts index f3faaeb62..52a2fc4a1 100644 --- a/drizzle-kit/src/cli/commands/pgUp.ts +++ b/drizzle-kit/src/cli/commands/pgUp.ts @@ -93,7 +93,7 @@ export const updateUpToV7 = (json: Record): PgSchema => { return [idx[0], { columns: mappedColumns, with: {}, ...rest }]; }), ); - return [it[0], { ...table, indexes: mappedIndexes }]; + return [it[0], { ...table, indexes: mappedIndexes, policies: {}, isRLSEnabled: false, checkConstraints: {} }]; }), ); @@ -103,6 +103,9 @@ export const updateUpToV7 = (json: Record): PgSchema => { dialect: 'postgresql', sequences: {}, tables: tables, + policies: {}, + views: {}, + roles: {}, }; }; diff --git a/drizzle-kit/src/cli/commands/push.ts b/drizzle-kit/src/cli/commands/push.ts index e48a5da9e..0c82fe026 100644 --- a/drizzle-kit/src/cli/commands/push.ts +++ b/drizzle-kit/src/cli/commands/push.ts @@ -1,13 +1,27 @@ import chalk from 'chalk'; +import { randomUUID } from 'crypto'; import { render } from 'hanji'; +import { serializePg } from 'src/serializer'; import { fromJson } from '../../sqlgenerator'; import { Select } from '../selector-ui'; +import { Entities } from '../validations/cli'; +import { CasingType } from '../validations/common'; +import { LibSQLCredentials } from '../validations/libsql'; import type { MysqlCredentials } from '../validations/mysql'; import { withStyle } from '../validations/outputs'; import type { PostgresCredentials } from '../validations/postgres'; +import { SingleStoreCredentials } from '../validations/singlestore'; import type { SqliteCredentials } from '../validations/sqlite'; -import { filterStatements, logSuggestionsAndReturn } from './mysqlPushUtils'; +import { libSqlLogSuggestionsAndReturn } from './libSqlPushUtils'; +import { + filterStatements as mySqlFilterStatements, + logSuggestionsAndReturn as mySqlLogSuggestionsAndReturn, +} from './mysqlPushUtils'; import { pgSuggestions } from './pgPushUtils'; +import { + filterStatements as singleStoreFilterStatements, + logSuggestionsAndReturn as singleStoreLogSuggestionsAndReturn, +} from './singlestorePushUtils'; import { logSuggestionsAndReturn as sqliteSuggestions } from './sqlitePushUtils'; export const mysqlPush = async ( @@ -17,6 +31,7 @@ export const mysqlPush = async ( strict: boolean, verbose: boolean, force: boolean, + casing: CasingType | undefined, ) => { const { connectToMySQL } = await import('../connections'); const { mysqlPushIntrospect } = await import('./mysqlIntrospect'); @@ -26,9 +41,9 @@ export const mysqlPush = async ( const { schema } = await mysqlPushIntrospect(db, database, tablesFilter); const { prepareMySQLPush } = await import('./migrate'); - const statements = await prepareMySQLPush(schemaPath, schema); + const statements = await prepareMySQLPush(schemaPath, schema, casing); - const filteredStatements = filterStatements( + const filteredStatements = mySqlFilterStatements( statements.statements ?? [], statements.validatedCur, statements.validatedPrev, @@ -45,8 +60,7 @@ export const mysqlPush = async ( tablesToRemove, tablesToTruncate, infoToPrint, - schemasToRemove, - } = await logSuggestionsAndReturn( + } = await mySqlLogSuggestionsAndReturn( db, filteredStatements, statements.validatedCur, @@ -69,7 +83,150 @@ export const mysqlPush = async ( if (verbose) { console.log(); - // console.log(chalk.gray('Verbose logs:')); + console.log( + withStyle.warning('You are about to execute current statements:'), + ); + console.log(); + console.log( + [...uniqueSqlStatementsToExecute, ...uniqueFilteredSqlStatements] + .map((s) => chalk.blue(s)) + .join('\n'), + ); + console.log(); + } + + if (!force && strict) { + if (!shouldAskForApprove) { + const { status, data } = await render( + new Select(['No, abort', `Yes, I want to execute all statements`]), + ); + if (data?.index === 0) { + render(`[${chalk.red('x')}] All changes were aborted`); + process.exit(0); + } + } + } + + if (!force && shouldAskForApprove) { + console.log(withStyle.warning('Found data-loss statements:')); + console.log(infoToPrint.join('\n')); + console.log(); + console.log( + chalk.red.bold( + 'THIS ACTION WILL CAUSE DATA LOSS AND CANNOT BE REVERTED\n', + ), + ); + + console.log(chalk.white('Do you still want to push changes?')); + + const { status, data } = await render( + new Select([ + 'No, abort', + `Yes, I want to${ + tablesToRemove.length > 0 + ? ` remove ${tablesToRemove.length} ${tablesToRemove.length > 1 ? 'tables' : 'table'},` + : ' ' + }${ + columnsToRemove.length > 0 + ? ` remove ${columnsToRemove.length} ${columnsToRemove.length > 1 ? 'columns' : 'column'},` + : ' ' + }${ + tablesToTruncate.length > 0 + ? ` truncate ${tablesToTruncate.length} ${tablesToTruncate.length > 1 ? 'tables' : 'table'}` + : '' + }` + .replace(/(^,)|(,$)/g, '') + .replace(/ +(?= )/g, ''), + ]), + ); + if (data?.index === 0) { + render(`[${chalk.red('x')}] All changes were aborted`); + process.exit(0); + } + } + + for (const dStmnt of uniqueSqlStatementsToExecute) { + await db.query(dStmnt); + } + + for (const statement of uniqueFilteredSqlStatements) { + await db.query(statement); + } + if (filteredStatements.length > 0) { + render(`[${chalk.green('✓')}] Changes applied`); + } else { + render(`[${chalk.blue('i')}] No changes detected`); + } + } + } catch (e) { + console.log(e); + } +}; + +export const singlestorePush = async ( + schemaPath: string | string[], + credentials: SingleStoreCredentials, + tablesFilter: string[], + strict: boolean, + verbose: boolean, + force: boolean, + casing: CasingType | undefined, +) => { + const { connectToSingleStore } = await import('../connections'); + const { singlestorePushIntrospect } = await import('./singlestoreIntrospect'); + + const { db, database } = await connectToSingleStore(credentials); + + const { schema } = await singlestorePushIntrospect( + db, + database, + tablesFilter, + ); + const { prepareSingleStorePush } = await import('./migrate'); + + const statements = await prepareSingleStorePush(schemaPath, schema, casing); + + const filteredStatements = singleStoreFilterStatements( + statements.statements ?? [], + statements.validatedCur, + statements.validatedPrev, + ); + + try { + if (filteredStatements.length === 0) { + render(`[${chalk.blue('i')}] No changes detected`); + } else { + const { + shouldAskForApprove, + statementsToExecute, + columnsToRemove, + tablesToRemove, + tablesToTruncate, + infoToPrint, + schemasToRemove, + } = await singleStoreLogSuggestionsAndReturn( + db, + filteredStatements, + statements.validatedCur, + ); + + const filteredSqlStatements = fromJson(filteredStatements, 'singlestore'); + + const uniqueSqlStatementsToExecute: string[] = []; + statementsToExecute.forEach((ss) => { + if (!uniqueSqlStatementsToExecute.includes(ss)) { + uniqueSqlStatementsToExecute.push(ss); + } + }); + const uniqueFilteredSqlStatements: string[] = []; + filteredSqlStatements.forEach((ss) => { + if (!uniqueFilteredSqlStatements.includes(ss)) { + uniqueFilteredSqlStatements.push(ss); + } + }); + + if (verbose) { + console.log(); console.log( withStyle.warning('You are about to execute current statements:'), ); @@ -157,17 +314,24 @@ export const pgPush = async ( credentials: PostgresCredentials, tablesFilter: string[], schemasFilter: string[], + entities: Entities, force: boolean, + casing: CasingType | undefined, ) => { const { preparePostgresDB } = await import('../connections'); const { pgPushIntrospect } = await import('./pgIntrospect'); const db = await preparePostgresDB(credentials); - const { schema } = await pgPushIntrospect(db, tablesFilter, schemasFilter); + const serialized = await serializePg(schemaPath, casing, schemasFilter); + + const { schema } = await pgPushIntrospect(db, tablesFilter, schemasFilter, entities, serialized); const { preparePgPush } = await import('./migrate'); - const statements = await preparePgPush(schemaPath, schema, schemasFilter); + const statements = await preparePgPush( + { id: randomUUID(), prevId: schema.id, ...serialized }, + schema, + ); try { if (statements.sqlStatements.length === 0) { @@ -179,6 +343,7 @@ export const pgPush = async ( statementsToExecute, columnsToRemove, tablesToRemove, + matViewsToRemove, tablesToTruncate, infoToPrint, schemasToRemove, @@ -234,6 +399,12 @@ export const pgPush = async ( tablesToTruncate.length > 0 ? ` truncate ${tablesToTruncate.length} ${tablesToTruncate.length > 1 ? 'tables' : 'table'}` : '' + }${ + matViewsToRemove.length > 0 + ? ` remove ${matViewsToRemove.length} ${ + matViewsToRemove.length > 1 ? 'materialized views' : 'materialize view' + },` + : ' ' }` .replace(/(^,)|(,$)/g, '') .replace(/ +(?= )/g, ''), @@ -267,6 +438,7 @@ export const sqlitePush = async ( credentials: SqliteCredentials, tablesFilter: string[], force: boolean, + casing: CasingType | undefined, ) => { const { connectToSQLite } = await import('../connections'); const { sqlitePushIntrospect } = await import('./sqliteIntrospect'); @@ -275,7 +447,7 @@ export const sqlitePush = async ( const { schema } = await sqlitePushIntrospect(db, tablesFilter); const { prepareSQLitePush } = await import('./migrate'); - const statements = await prepareSQLitePush(schemaPath, schema); + const statements = await prepareSQLitePush(schemaPath, schema, casing); if (statements.sqlStatements.length === 0) { render(`\n[${chalk.blue('i')}] No changes detected`); @@ -291,8 +463,8 @@ export const sqlitePush = async ( } = await sqliteSuggestions( db, statements.statements, - statements.squashedCur, statements.squashedPrev, + statements.squashedCur, statements.meta!, ); @@ -361,21 +533,126 @@ export const sqlitePush = async ( render(`\n[${chalk.blue('i')}] No changes detected`); } else { if (!('driver' in credentials)) { - await db.query('begin'); + await db.run('begin'); try { for (const dStmnt of statementsToExecute) { - await db.query(dStmnt); + await db.run(dStmnt); } - await db.query('commit'); + await db.run('commit'); } catch (e) { console.error(e); - await db.query('rollback'); + await db.run('rollback'); process.exit(1); } - } else if (credentials.driver === 'turso') { - await db.batch!(statementsToExecute.map((it) => ({ query: it }))); } render(`[${chalk.green('✓')}] Changes applied`); } } }; + +export const libSQLPush = async ( + schemaPath: string | string[], + verbose: boolean, + strict: boolean, + credentials: LibSQLCredentials, + tablesFilter: string[], + force: boolean, + casing: CasingType | undefined, +) => { + const { connectToLibSQL } = await import('../connections'); + const { sqlitePushIntrospect } = await import('./sqliteIntrospect'); + + const db = await connectToLibSQL(credentials); + const { schema } = await sqlitePushIntrospect(db, tablesFilter); + + const { prepareLibSQLPush } = await import('./migrate'); + + const statements = await prepareLibSQLPush(schemaPath, schema, casing); + + if (statements.sqlStatements.length === 0) { + render(`\n[${chalk.blue('i')}] No changes detected`); + } else { + const { + shouldAskForApprove, + statementsToExecute, + columnsToRemove, + tablesToRemove, + tablesToTruncate, + infoToPrint, + } = await libSqlLogSuggestionsAndReturn( + db, + statements.statements, + statements.squashedPrev, + statements.squashedCur, + statements.meta!, + ); + + if (verbose && statementsToExecute.length > 0) { + console.log(); + console.log( + withStyle.warning('You are about to execute current statements:'), + ); + console.log(); + console.log(statementsToExecute.map((s) => chalk.blue(s)).join('\n')); + console.log(); + } + + if (!force && strict) { + if (!shouldAskForApprove) { + const { status, data } = await render( + new Select(['No, abort', `Yes, I want to execute all statements`]), + ); + if (data?.index === 0) { + render(`[${chalk.red('x')}] All changes were aborted`); + process.exit(0); + } + } + } + + if (!force && shouldAskForApprove) { + console.log(withStyle.warning('Found data-loss statements:')); + console.log(infoToPrint.join('\n')); + console.log(); + console.log( + chalk.red.bold( + 'THIS ACTION WILL CAUSE DATA LOSS AND CANNOT BE REVERTED\n', + ), + ); + + console.log(chalk.white('Do you still want to push changes?')); + + const { status, data } = await render( + new Select([ + 'No, abort', + `Yes, I want to${ + tablesToRemove.length > 0 + ? ` remove ${tablesToRemove.length} ${tablesToRemove.length > 1 ? 'tables' : 'table'},` + : ' ' + }${ + columnsToRemove.length > 0 + ? ` remove ${columnsToRemove.length} ${columnsToRemove.length > 1 ? 'columns' : 'column'},` + : ' ' + }${ + tablesToTruncate.length > 0 + ? ` truncate ${tablesToTruncate.length} ${tablesToTruncate.length > 1 ? 'tables' : 'table'}` + : '' + }` + .trimEnd() + .replace(/(^,)|(,$)/g, '') + .replace(/ +(?= )/g, ''), + ]), + ); + if (data?.index === 0) { + render(`[${chalk.red('x')}] All changes were aborted`); + process.exit(0); + } + } + + if (statementsToExecute.length === 0) { + render(`\n[${chalk.blue('i')}] No changes detected`); + } else { + await db.batchWithPragma!(statementsToExecute); + render(`[${chalk.green('✓')}] Changes applied`); + } + } +}; diff --git a/drizzle-kit/src/cli/commands/singlestoreIntrospect.ts b/drizzle-kit/src/cli/commands/singlestoreIntrospect.ts new file mode 100644 index 000000000..27d8c59c5 --- /dev/null +++ b/drizzle-kit/src/cli/commands/singlestoreIntrospect.ts @@ -0,0 +1,53 @@ +import { renderWithTask } from 'hanji'; +import { Minimatch } from 'minimatch'; +import { originUUID } from '../../global'; +import type { SingleStoreSchema } from '../../serializer/singlestoreSchema'; +import { fromDatabase } from '../../serializer/singlestoreSerializer'; +import type { DB } from '../../utils'; +import { ProgressView } from '../views'; + +export const singlestorePushIntrospect = async ( + db: DB, + databaseName: string, + filters: string[], +) => { + const matchers = filters.map((it) => { + return new Minimatch(it); + }); + + const filter = (tableName: string) => { + if (matchers.length === 0) return true; + + let flags: boolean[] = []; + + for (let matcher of matchers) { + if (matcher.negate) { + if (!matcher.match(tableName)) { + flags.push(false); + } + } + + if (matcher.match(tableName)) { + flags.push(true); + } + } + + if (flags.length > 0) { + return flags.every(Boolean); + } + return false; + }; + + const progress = new ProgressView( + 'Pulling schema from database...', + 'Pulling schema from database...', + ); + const res = await renderWithTask( + progress, + fromDatabase(db, databaseName, filter), + ); + + const schema = { id: originUUID, prevId: '', ...res } as SingleStoreSchema; + const { internal, ...schemaWithoutInternals } = schema; + return { schema: schemaWithoutInternals }; +}; diff --git a/drizzle-kit/src/cli/commands/singlestorePushUtils.ts b/drizzle-kit/src/cli/commands/singlestorePushUtils.ts new file mode 100644 index 000000000..80fad9b2d --- /dev/null +++ b/drizzle-kit/src/cli/commands/singlestorePushUtils.ts @@ -0,0 +1,352 @@ +import chalk from 'chalk'; +import { render } from 'hanji'; +import { TypeOf } from 'zod'; +import { JsonAlterColumnTypeStatement, JsonStatement } from '../../jsonStatements'; +import { singlestoreSchema, SingleStoreSquasher } from '../../serializer/singlestoreSchema'; +import type { DB } from '../../utils'; +import { Select } from '../selector-ui'; +import { withStyle } from '../validations/outputs'; + +export const filterStatements = ( + statements: JsonStatement[], + currentSchema: TypeOf, + prevSchema: TypeOf, +) => { + return statements.filter((statement) => { + if (statement.type === 'alter_table_alter_column_set_type') { + // Don't need to handle it on migrations step and introspection + // but for both it should be skipped + if ( + statement.oldDataType.startsWith('tinyint') + && statement.newDataType.startsWith('boolean') + ) { + return false; + } + + if ( + statement.oldDataType.startsWith('bigint unsigned') + && statement.newDataType.startsWith('serial') + ) { + return false; + } + + if ( + statement.oldDataType.startsWith('serial') + && statement.newDataType.startsWith('bigint unsigned') + ) { + return false; + } + } else if (statement.type === 'alter_table_alter_column_set_default') { + if ( + statement.newDefaultValue === false + && statement.oldDefaultValue === 0 + && statement.newDataType === 'boolean' + ) { + return false; + } + if ( + statement.newDefaultValue === true + && statement.oldDefaultValue === 1 + && statement.newDataType === 'boolean' + ) { + return false; + } + } else if (statement.type === 'delete_unique_constraint') { + const unsquashed = SingleStoreSquasher.unsquashUnique(statement.data); + // only if constraint was removed from a serial column, than treat it as removed + // const serialStatement = statements.find( + // (it) => it.type === "alter_table_alter_column_set_type" + // ) as JsonAlterColumnTypeStatement; + // if ( + // serialStatement?.oldDataType.startsWith("bigint unsigned") && + // serialStatement?.newDataType.startsWith("serial") && + // serialStatement.columnName === + // SingleStoreSquasher.unsquashUnique(statement.data).columns[0] + // ) { + // return false; + // } + // Check if uniqueindex was only on this column, that is serial + + // if now serial and was not serial and was unique index + if ( + unsquashed.columns.length === 1 + && currentSchema.tables[statement.tableName].columns[unsquashed.columns[0]] + .type === 'serial' + && prevSchema.tables[statement.tableName].columns[unsquashed.columns[0]] + .type === 'serial' + && currentSchema.tables[statement.tableName].columns[unsquashed.columns[0]] + .name === unsquashed.columns[0] + ) { + return false; + } + } else if (statement.type === 'alter_table_alter_column_drop_notnull') { + // only if constraint was removed from a serial column, than treat it as removed + const serialStatement = statements.find( + (it) => it.type === 'alter_table_alter_column_set_type', + ) as JsonAlterColumnTypeStatement; + if ( + serialStatement?.oldDataType.startsWith('bigint unsigned') + && serialStatement?.newDataType.startsWith('serial') + && serialStatement.columnName === statement.columnName + && serialStatement.tableName === statement.tableName + ) { + return false; + } + if (statement.newDataType === 'serial' && !statement.columnNotNull) { + return false; + } + if (statement.columnAutoIncrement) { + return false; + } + } + + return true; + }); +}; + +export const logSuggestionsAndReturn = async ( + db: DB, + statements: JsonStatement[], + json2: TypeOf, +) => { + let shouldAskForApprove = false; + const statementsToExecute: string[] = []; + const infoToPrint: string[] = []; + + const tablesToRemove: string[] = []; + const columnsToRemove: string[] = []; + const schemasToRemove: string[] = []; + const tablesToTruncate: string[] = []; + + for (const statement of statements) { + if (statement.type === 'drop_table') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.tableName, + ) + } table with ${count} items`, + ); + tablesToRemove.push(statement.tableName); + shouldAskForApprove = true; + } + } else if (statement.type === 'alter_table_drop_column') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.columnName, + ) + } column in ${statement.tableName} table with ${count} items`, + ); + columnsToRemove.push(`${statement.tableName}_${statement.columnName}`); + shouldAskForApprove = true; + } + } else if (statement.type === 'drop_schema') { + const res = await db.query( + `select count(*) as count from information_schema.tables where table_schema = \`${statement.name}\`;`, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.name, + ) + } schema with ${count} tables`, + ); + schemasToRemove.push(statement.name); + shouldAskForApprove = true; + } + } else if (statement.type === 'alter_table_alter_column_set_type') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to change ${ + chalk.underline( + statement.columnName, + ) + } column type from ${ + chalk.underline( + statement.oldDataType, + ) + } to ${chalk.underline(statement.newDataType)} with ${count} items`, + ); + statementsToExecute.push(`truncate table ${statement.tableName};`); + tablesToTruncate.push(statement.tableName); + shouldAskForApprove = true; + } + } else if (statement.type === 'alter_table_alter_column_drop_default') { + if (statement.columnNotNull) { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to remove default value from ${ + chalk.underline( + statement.columnName, + ) + } not-null column with ${count} items`, + ); + + tablesToTruncate.push(statement.tableName); + statementsToExecute.push(`truncate table ${statement.tableName};`); + + shouldAskForApprove = true; + } + } + // shouldAskForApprove = true; + } else if (statement.type === 'alter_table_alter_column_set_notnull') { + if (typeof statement.columnDefault === 'undefined') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to set not-null constraint to ${ + chalk.underline( + statement.columnName, + ) + } column without default, which contains ${count} items`, + ); + + tablesToTruncate.push(statement.tableName); + statementsToExecute.push(`truncate table ${statement.tableName};`); + + shouldAskForApprove = true; + } + } + } else if (statement.type === 'alter_table_alter_column_drop_pk') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + + // if drop pk and json2 has autoincrement in table -> exit process with error + if ( + Object.values(json2.tables[statement.tableName].columns).filter( + (column) => column.autoincrement, + ).length > 0 + ) { + console.log( + `${ + withStyle.errorWarning( + `You have removed the primary key from a ${statement.tableName} table without removing the auto-increment property from this table. As the database error states: 'there can be only one auto column, and it must be defined as a key. Make sure to remove autoincrement from ${statement.tableName} table`, + ) + }`, + ); + process.exit(1); + } + + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to change ${ + chalk.underline( + statement.tableName, + ) + } primary key. This statements may fail and you table may left without primary key`, + ); + + tablesToTruncate.push(statement.tableName); + shouldAskForApprove = true; + } + } else if (statement.type === 'delete_composite_pk') { + // if drop pk and json2 has autoincrement in table -> exit process with error + if ( + Object.values(json2.tables[statement.tableName].columns).filter( + (column) => column.autoincrement, + ).length > 0 + ) { + console.log( + `${ + withStyle.errorWarning( + `You have removed the primary key from a ${statement.tableName} table without removing the auto-increment property from this table. As the database error states: 'there can be only one auto column, and it must be defined as a key. Make sure to remove autoincrement from ${statement.tableName} table`, + ) + }`, + ); + process.exit(1); + } + } else if (statement.type === 'alter_table_add_column') { + if ( + statement.column.notNull + && typeof statement.column.default === 'undefined' + ) { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + statement.column.name, + ) + } column without default value, which contains ${count} items`, + ); + + tablesToTruncate.push(statement.tableName); + statementsToExecute.push(`truncate table ${statement.tableName};`); + + shouldAskForApprove = true; + } + } + } else if (statement.type === 'create_unique_constraint') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + const unsquashedUnique = SingleStoreSquasher.unsquashUnique(statement.data); + console.log( + `· You're about to add ${ + chalk.underline( + unsquashedUnique.name, + ) + } unique constraint to the table, which contains ${count} items. If this statement fails, you will receive an error from the database. Do you want to truncate ${ + chalk.underline( + statement.tableName, + ) + } table?\n`, + ); + const { status, data } = await render( + new Select([ + 'No, add the constraint without truncating the table', + `Yes, truncate the table`, + ]), + ); + if (data?.index === 1) { + tablesToTruncate.push(statement.tableName); + statementsToExecute.push(`truncate table ${statement.tableName};`); + shouldAskForApprove = true; + } + } + } + } + + return { + statementsToExecute, + shouldAskForApprove, + infoToPrint, + columnsToRemove: [...new Set(columnsToRemove)], + schemasToRemove: [...new Set(schemasToRemove)], + tablesToTruncate: [...new Set(tablesToTruncate)], + tablesToRemove: [...new Set(tablesToRemove)], + }; +}; diff --git a/drizzle-kit/src/cli/commands/singlestoreUp.ts b/drizzle-kit/src/cli/commands/singlestoreUp.ts new file mode 100644 index 000000000..dc5004ed0 --- /dev/null +++ b/drizzle-kit/src/cli/commands/singlestoreUp.ts @@ -0,0 +1 @@ +export const upSinglestoreHandler = (out: string) => {}; diff --git a/drizzle-kit/src/cli/commands/sqlitePushUtils.ts b/drizzle-kit/src/cli/commands/sqlitePushUtils.ts index 451f035a7..a18b36945 100644 --- a/drizzle-kit/src/cli/commands/sqlitePushUtils.ts +++ b/drizzle-kit/src/cli/commands/sqlitePushUtils.ts @@ -10,7 +10,7 @@ import { } from '../../sqlgenerator'; import type { JsonStatement } from '../../jsonStatements'; -import type { DB, SQLiteDB } from '../../utils'; +import { findAddedAndRemoved, type SQLiteDB } from '../../utils'; export const _moveDataStatements = ( tableName: string, @@ -19,16 +19,7 @@ export const _moveDataStatements = ( ) => { const statements: string[] = []; - // rename table to __old_${tablename} - statements.push( - new SqliteRenameTableConvertor().convert({ - type: 'rename_table', - tableNameFrom: tableName, - tableNameTo: `__old_push_${tableName}`, - fromSchema: '', - toSchema: '', - }), - ); + const newTableName = `__new_${tableName}`; // create table statement from a new json2 with proper name const tableColumns = Object.values(json.tables[tableName].columns); @@ -36,34 +27,63 @@ export const _moveDataStatements = ( const compositePKs = Object.values( json.tables[tableName].compositePrimaryKeys, ).map((it) => SQLiteSquasher.unsquashPK(it)); + const checkConstraints = Object.values(json.tables[tableName].checkConstraints); + + const mappedCheckConstraints: string[] = checkConstraints.map((it) => + it.replaceAll(`"${tableName}".`, `"${newTableName}".`) + .replaceAll(`\`${tableName}\`.`, `\`${newTableName}\`.`) + .replaceAll(`${tableName}.`, `${newTableName}.`) + .replaceAll(`'${tableName}'.`, `\`${newTableName}\`.`) + ); const fks = referenceData.map((it) => SQLiteSquasher.unsquashPushFK(it)); + // create new table statements.push( new SQLiteCreateTableConvertor().convert({ type: 'sqlite_create_table', - tableName: tableName, + tableName: newTableName, columns: tableColumns, referenceData: fks, compositePKs, + checkConstraints: mappedCheckConstraints, }), ); // move data if (!dataLoss) { + const columns = Object.keys(json.tables[tableName].columns).map( + (c) => `"${c}"`, + ); + statements.push( - `INSERT INTO "${tableName}" SELECT * FROM "__old_push_${tableName}";`, + `INSERT INTO \`${newTableName}\`(${ + columns.join( + ', ', + ) + }) SELECT ${columns.join(', ')} FROM \`${tableName}\`;`, ); } - // drop table with name __old_${tablename} + statements.push( new SQLiteDropTableConvertor().convert({ type: 'drop_table', - tableName: `__old_push_${tableName}`, + tableName: tableName, schema: '', }), ); + // rename table + statements.push( + new SqliteRenameTableConvertor().convert({ + fromSchema: '', + tableNameFrom: newTableName, + tableNameTo: tableName, + toSchema: '', + type: 'rename_table', + }), + ); + for (const idx of Object.values(json.tables[tableName].indexes)) { statements.push( new CreateSqliteIndexConvertor().convert({ @@ -120,8 +140,6 @@ export const logSuggestionsAndReturn = async ( const schemasToRemove: string[] = []; const tablesToTruncate: string[] = []; - const tablesContext: Record = {}; - for (const statement of statements) { if (statement.type === 'drop_table') { const res = await connection.query<{ count: string }>( @@ -139,248 +157,159 @@ export const logSuggestionsAndReturn = async ( tablesToRemove.push(statement.tableName); shouldAskForApprove = true; } - const stmnt = fromJson([statement], 'sqlite')[0]; - statementsToExecute.push(stmnt); - } else if (statement.type === 'alter_table_drop_column') { - const newTableName = getOldTableName(statement.tableName, meta); - - const columnIsPartOfPk = Object.values( - json1.tables[newTableName].compositePrimaryKeys, - ).find((c) => SQLiteSquasher.unsquashPK(c).includes(statement.columnName)); - const columnIsPartOfIndex = Object.values( - json1.tables[newTableName].indexes, - ).find((c) => SQLiteSquasher.unsquashIdx(c).columns.includes(statement.columnName)); - - const columnIsPk = json1.tables[newTableName].columns[statement.columnName].primaryKey; - - const columnIsPartOfFk = Object.values( - json1.tables[newTableName].foreignKeys, - ).find((t) => - SQLiteSquasher.unsquashPushFK(t).columnsFrom.includes( - statement.columnName, - ) + const fromJsonStatement = fromJson([statement], 'sqlite', 'push'); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), ); + } else if (statement.type === 'alter_table_drop_column') { + const tableName = statement.tableName; + const columnName = statement.columnName; const res = await connection.query<{ count: string }>( - `select count(*) as count from \`${newTableName}\``, + `select count(\`${tableName}\`.\`${columnName}\`) as count from \`${tableName}\``, ); const count = Number(res[0].count); if (count > 0) { infoToPrint.push( `· You're about to delete ${ chalk.underline( - statement.columnName, + columnName, ) - } column in ${newTableName} table with ${count} items`, + } column in ${tableName} table with ${count} items`, ); - columnsToRemove.push(`${newTableName}_${statement.columnName}`); + columnsToRemove.push(`${tableName}_${statement.columnName}`); shouldAskForApprove = true; } - if ( - columnIsPk - || columnIsPartOfPk - || columnIsPartOfIndex - || columnIsPartOfFk - ) { - tablesContext[newTableName] = [ - ..._moveDataStatements(statement.tableName, json2, true), - ]; - // check table that have fk to this table - - const tablesReferncingCurrent: string[] = []; - - for (const table of Object.values(json1.tables)) { - const tablesRefs = Object.values(json1.tables[table.name].foreignKeys) - .filter( - (t) => SQLiteSquasher.unsquashPushFK(t).tableTo === newTableName, + const fromJsonStatement = fromJson([statement], 'sqlite', 'push'); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if ( + statement.type === 'sqlite_alter_table_add_column' + && (statement.column.notNull && !statement.column.default) + ) { + const tableName = statement.tableName; + const columnName = statement.column.name; + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + columnName, ) - .map((t) => SQLiteSquasher.unsquashPushFK(t).tableFrom); - - tablesReferncingCurrent.push(...tablesRefs); - } - - const uniqueTableRefs = [...new Set(tablesReferncingCurrent)]; - - for (const table of uniqueTableRefs) { - if (typeof tablesContext[table] === 'undefined') { - tablesContext[table] = [..._moveDataStatements(table, json2)]; - } - } - } else { - if (typeof tablesContext[newTableName] === 'undefined') { - const stmnt = fromJson([statement], 'sqlite')[0]; - statementsToExecute.push(stmnt); - } - } - } else if (statement.type === 'sqlite_alter_table_add_column') { - const newTableName = getOldTableName(statement.tableName, meta); - if (statement.column.notNull && !statement.column.default) { - const res = await connection.query<{ count: string }>( - `select count(*) as count from \`${newTableName}\``, + } column without default value, which contains ${count} items`, ); - const count = Number(res[0].count); - if (count > 0) { - infoToPrint.push( - `· You're about to add not-null ${ - chalk.underline( - statement.column.name, - ) - } column without default value, which contains ${count} items`, - ); - tablesToTruncate.push(newTableName); - statementsToExecute.push(`delete from ${newTableName};`); + tablesToTruncate.push(tableName); + statementsToExecute.push(`delete from ${tableName};`); - shouldAskForApprove = true; - } + shouldAskForApprove = true; } - if (statement.column.primaryKey) { - tablesContext[newTableName] = [ - ..._moveDataStatements(statement.tableName, json2, true), - ]; - const tablesReferncingCurrent: string[] = []; - - for (const table of Object.values(json1.tables)) { - const tablesRefs = Object.values(json1.tables[table.name].foreignKeys) - .filter( - (t) => SQLiteSquasher.unsquashPushFK(t).tableTo === newTableName, - ) - .map((t) => SQLiteSquasher.unsquashPushFK(t).tableFrom); - tablesReferncingCurrent.push(...tablesRefs); - } + const fromJsonStatement = fromJson([statement], 'sqlite', 'push'); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if (statement.type === 'recreate_table') { + const tableName = statement.tableName; + const oldTableName = getOldTableName(tableName, meta); - const uniqueTableRefs = [...new Set(tablesReferncingCurrent)]; + let dataLoss = false; - for (const table of uniqueTableRefs) { - if (typeof tablesContext[table] === 'undefined') { - tablesContext[table] = [..._moveDataStatements(table, json2)]; - } - } - } else { - if (typeof tablesContext[newTableName] === 'undefined') { - const stmnt = fromJson([statement], 'sqlite')[0]; - statementsToExecute.push(stmnt); - } - } - } else if ( - statement.type === 'alter_table_alter_column_set_type' - || statement.type === 'alter_table_alter_column_set_default' - || statement.type === 'alter_table_alter_column_drop_default' - || statement.type === 'alter_table_alter_column_set_notnull' - || statement.type === 'alter_table_alter_column_drop_notnull' - || statement.type === 'alter_table_alter_column_drop_autoincrement' - || statement.type === 'alter_table_alter_column_set_autoincrement' - || statement.type === 'alter_table_alter_column_drop_pk' - || statement.type === 'alter_table_alter_column_set_pk' - ) { - if ( - !( - statement.type === 'alter_table_alter_column_set_notnull' - && statement.columnPk - ) - ) { - const newTableName = getOldTableName(statement.tableName, meta); - if ( - statement.type === 'alter_table_alter_column_set_notnull' - && typeof statement.columnDefault === 'undefined' - ) { + const prevColumnNames = Object.keys(json1.tables[oldTableName].columns); + const currentColumnNames = Object.keys(json2.tables[tableName].columns); + const { removedColumns, addedColumns } = findAddedAndRemoved( + prevColumnNames, + currentColumnNames, + ); + + if (removedColumns.length) { + for (const removedColumn of removedColumns) { const res = await connection.query<{ count: string }>( - `select count(*) as count from \`${newTableName}\``, + `select count(\`${tableName}\`.\`${removedColumn}\`) as count from \`${tableName}\``, ); + const count = Number(res[0].count); if (count > 0) { infoToPrint.push( - `· You're about to add not-null constraint to ${ + `· You're about to delete ${ chalk.underline( - statement.columnName, + removedColumn, ) - } column without default value, which contains ${count} items`, + } column in ${tableName} table with ${count} items`, ); - - tablesToTruncate.push(newTableName); + columnsToRemove.push(removedColumn); shouldAskForApprove = true; } - tablesContext[newTableName] = _moveDataStatements( - statement.tableName, - json1, - true, + } + } + + if (addedColumns.length) { + for (const addedColumn of addedColumns) { + const [res] = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, ); - } else { - if (typeof tablesContext[newTableName] === 'undefined') { - tablesContext[newTableName] = _moveDataStatements( - statement.tableName, - json1, + + const columnConf = json2.tables[tableName].columns[addedColumn]; + + const count = Number(res.count); + if (count > 0 && columnConf.notNull && !columnConf.default) { + dataLoss = true; + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + addedColumn, + ) + } column without default value to table, which contains ${count} items`, ); + shouldAskForApprove = true; + tablesToTruncate.push(tableName); + + statementsToExecute.push(`DELETE FROM \`${tableName}\`;`); } } + } - const tablesReferncingCurrent: string[] = []; + // check if some tables referencing current for pragma + const tablesReferencingCurrent: string[] = []; - for (const table of Object.values(json1.tables)) { - const tablesRefs = Object.values(json1.tables[table.name].foreignKeys) - .filter( - (t) => SQLiteSquasher.unsquashPushFK(t).tableTo === newTableName, - ) - .map((t) => { - return getNewTableName( - SQLiteSquasher.unsquashPushFK(t).tableFrom, - meta, - ); - }); - - tablesReferncingCurrent.push(...tablesRefs); - } + for (const table of Object.values(json2.tables)) { + const tablesRefs = Object.values(json2.tables[table.name].foreignKeys) + .filter((t) => SQLiteSquasher.unsquashPushFK(t).tableTo === tableName) + .map((it) => SQLiteSquasher.unsquashPushFK(it).tableFrom); - const uniqueTableRefs = [...new Set(tablesReferncingCurrent)]; + tablesReferencingCurrent.push(...tablesRefs); + } - for (const table of uniqueTableRefs) { - if (typeof tablesContext[table] === 'undefined') { - tablesContext[table] = [..._moveDataStatements(table, json1)]; - } - } + if (!tablesReferencingCurrent.length) { + statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss)); + continue; } - } else if ( - statement.type === 'create_reference' - || statement.type === 'delete_reference' - || statement.type === 'alter_reference' - ) { - const fk = SQLiteSquasher.unsquashPushFK(statement.data); - if (typeof tablesContext[statement.tableName] === 'undefined') { - tablesContext[statement.tableName] = _moveDataStatements( - statement.tableName, - json2, - ); + const [{ foreign_keys: pragmaState }] = await connection.query<{ + foreign_keys: number; + }>(`PRAGMA foreign_keys;`); + + if (pragmaState) { + statementsToExecute.push(`PRAGMA foreign_keys=OFF;`); } - } else if ( - statement.type === 'create_composite_pk' - || statement.type === 'alter_composite_pk' - || statement.type === 'delete_composite_pk' - || statement.type === 'create_unique_constraint' - || statement.type === 'delete_unique_constraint' - ) { - const newTableName = getOldTableName(statement.tableName, meta); - if (typeof tablesContext[newTableName] === 'undefined') { - tablesContext[newTableName] = _moveDataStatements( - statement.tableName, - json2, - ); + statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss)); + if (pragmaState) { + statementsToExecute.push(`PRAGMA foreign_keys=ON;`); } } else { - const stmnt = fromJson([statement], 'sqlite'); - if (typeof stmnt !== 'undefined') { - statementsToExecute.push(...stmnt); - } + const fromJsonStatement = fromJson([statement], 'sqlite', 'push'); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); } } - for (const context of Object.values(tablesContext)) { - statementsToExecute.push(...context); - } - return { statementsToExecute, shouldAskForApprove, diff --git a/drizzle-kit/src/cli/commands/sqliteUp.ts b/drizzle-kit/src/cli/commands/sqliteUp.ts index b76b9e2cd..aaa1fa7b9 100644 --- a/drizzle-kit/src/cli/commands/sqliteUp.ts +++ b/drizzle-kit/src/cli/commands/sqliteUp.ts @@ -47,5 +47,6 @@ const updateUpToV6 = (json: Record): SQLiteSchema => { version: '6', dialect: 'sqlite', tables: tables, + views: {}, }; }; diff --git a/drizzle-kit/src/cli/commands/utils.ts b/drizzle-kit/src/cli/commands/utils.ts index 9f65318a6..cb5c75886 100644 --- a/drizzle-kit/src/cli/commands/utils.ts +++ b/drizzle-kit/src/cli/commands/utils.ts @@ -3,12 +3,14 @@ import { existsSync } from 'fs'; import { render } from 'hanji'; import { join, resolve } from 'path'; import { object, string } from 'zod'; +import { getTablesFilterByExtensions } from '../../extensions/getTablesFilterByExtensions'; import { assertUnreachable } from '../../global'; import { type Dialect, dialect } from '../../schemaValidator'; import { prepareFilenames } from '../../serializer'; -import { pullParams, pushParams } from '../validations/cli'; +import { Entities, pullParams, pushParams } from '../validations/cli'; import { Casing, + CasingType, CliConfig, configCommonSchema, configMigrations, @@ -16,6 +18,8 @@ import { Prefix, wrapParam, } from '../validations/common'; +import { LibSQLCredentials, libSQLCredentials } from '../validations/libsql'; +import { printConfigConnectionIssues as printIssuesLibSql } from '../validations/libsql'; import { MysqlCredentials, mysqlCredentials, @@ -27,6 +31,11 @@ import { postgresCredentials, printConfigConnectionIssues as printIssuesPg, } from '../validations/postgres'; +import { + printConfigConnectionIssues as printIssuesSingleStore, + SingleStoreCredentials, + singlestoreCredentials, +} from '../validations/singlestore'; import { printConfigConnectionIssues as printIssuesSqlite, SqliteCredentials, @@ -79,7 +88,7 @@ export const safeRegister = async () => { export const prepareCheckParams = async ( options: { config?: string; - dialect: Dialect; + dialect?: Dialect; out?: string; }, from: 'cli' | 'config', @@ -122,6 +131,14 @@ export type GenerateConfig = { prefix: Prefix; custom: boolean; bundle: boolean; + casing?: CasingType; + driver?: Driver; +}; + +export type ExportConfig = { + dialect: Dialect; + schema: string | string[]; + sql: boolean; }; export const prepareGenerateConfig = async ( @@ -135,12 +152,13 @@ export const prepareGenerateConfig = async ( dialect?: Dialect; driver?: Driver; prefix?: Prefix; + casing?: CasingType; }, from: 'config' | 'cli', ): Promise => { const config = from === 'config' ? await drizzleConfigFromFile(options.config) : options; - const { schema, out, breakpoints, dialect, driver } = config; + const { schema, out, breakpoints, dialect, driver, casing } = config; if (!schema || !dialect) { console.log(error('Please provide required params:')); @@ -164,10 +182,44 @@ export const prepareGenerateConfig = async ( name: options.name, custom: options.custom || false, prefix, - breakpoints: breakpoints || true, + breakpoints: breakpoints ?? true, schema: schema, out: out || 'drizzle', - bundle: driver === 'expo', + bundle: driver === 'expo' || driver === 'durable-sqlite', + casing, + driver, + }; +}; + +export const prepareExportConfig = async ( + options: { + config?: string; + schema?: string; + dialect?: Dialect; + sql: boolean; + }, + from: 'config' | 'cli', +): Promise => { + const config = from === 'config' ? await drizzleConfigFromFile(options.config, true) : options; + + const { schema, dialect, sql } = config; + + if (!schema || !dialect) { + console.log(error('Please provide required params:')); + console.log(wrapParam('schema', schema)); + console.log(wrapParam('dialect', dialect)); + process.exit(1); + } + + const fileNames = prepareFilenames(schema); + if (fileNames.length === 0) { + render(`[${chalk.blue('i')}] No schema file in ${schema} was found`); + process.exit(0); + } + return { + dialect: dialect, + schema: schema, + sql: sql, }; }; @@ -211,6 +263,14 @@ export const preparePushConfig = async ( dialect: 'sqlite'; credentials: SqliteCredentials; } + | { + dialect: 'turso'; + credentials: LibSQLCredentials; + } + | { + dialect: 'singlestore'; + credentials: SingleStoreCredentials; + } ) & { schemaPath: string | string[]; verbose: boolean; @@ -218,6 +278,8 @@ export const preparePushConfig = async ( force: boolean; tablesFilter: string[]; schemasFilter: string[]; + casing?: CasingType; + entities?: Entities; } > => { const raw = flattenDatabaseCredentials( @@ -261,16 +323,7 @@ export const preparePushConfig = async ( : schemasFilterConfig : []; - if (config.extensionsFilters) { - if ( - config.extensionsFilters.includes('postgis') - && config.dialect === 'postgresql' - ) { - tablesFilter.push( - ...['!geography_columns', '!geometry_columns', '!spatial_ref_sys'], - ); - } - } + tablesFilter.push(...getTablesFilterByExtensions(config)); if (config.dialect === 'postgresql') { const parsed = postgresCredentials.safeParse(config); @@ -286,8 +339,10 @@ export const preparePushConfig = async ( verbose: config.verbose ?? false, force: (options.force as boolean) ?? false, credentials: parsed.data, + casing: config.casing, tablesFilter, schemasFilter, + entities: config.entities, }; } @@ -304,6 +359,26 @@ export const preparePushConfig = async ( verbose: config.verbose ?? false, force: (options.force as boolean) ?? false, credentials: parsed.data, + casing: config.casing, + tablesFilter, + schemasFilter, + }; + } + + if (config.dialect === 'singlestore') { + const parsed = singlestoreCredentials.safeParse(config); + if (!parsed.success) { + printIssuesSingleStore(config); + process.exit(1); + } + + return { + dialect: 'singlestore', + schemaPath: config.schema, + strict: config.strict ?? false, + verbose: config.verbose ?? false, + force: (options.force as boolean) ?? false, + credentials: parsed.data, tablesFilter, schemasFilter, }; @@ -322,6 +397,26 @@ export const preparePushConfig = async ( verbose: config.verbose ?? false, force: (options.force as boolean) ?? false, credentials: parsed.data, + casing: config.casing, + tablesFilter, + schemasFilter, + }; + } + + if (config.dialect === 'turso') { + const parsed = libSQLCredentials.safeParse(config); + if (!parsed.success) { + printIssuesSqlite(config, 'pull'); + process.exit(1); + } + return { + dialect: 'turso', + schemaPath: config.schema, + strict: config.strict ?? false, + verbose: config.verbose ?? false, + force: (options.force as boolean) ?? false, + credentials: parsed.data, + casing: config.casing, tablesFilter, schemasFilter, }; @@ -347,6 +442,14 @@ export const preparePullConfig = async ( dialect: 'sqlite'; credentials: SqliteCredentials; } + | { + dialect: 'turso'; + credentials: LibSQLCredentials; + } + | { + dialect: 'singlestore'; + credentials: SingleStoreCredentials; + } ) & { out: string; breakpoints: boolean; @@ -354,6 +457,7 @@ export const preparePullConfig = async ( tablesFilter: string[]; schemasFilter: string[]; prefix: Prefix; + entities: Entities; } > => { const raw = flattenPull( @@ -408,11 +512,12 @@ export const preparePullConfig = async ( dialect: 'postgresql', out: config.out, breakpoints: config.breakpoints, - casing: config.introspectCasing, + casing: config.casing, credentials: parsed.data, tablesFilter, schemasFilter, - prefix: config.database?.prefix || 'index', + prefix: config.migrations?.prefix || 'index', + entities: config.entities, }; } @@ -426,11 +531,32 @@ export const preparePullConfig = async ( dialect: 'mysql', out: config.out, breakpoints: config.breakpoints, - casing: config.introspectCasing, + casing: config.casing, credentials: parsed.data, tablesFilter, schemasFilter, - prefix: config.database?.prefix || 'index', + prefix: config.migrations?.prefix || 'index', + entities: config.entities, + }; + } + + if (dialect === 'singlestore') { + const parsed = singlestoreCredentials.safeParse(config); + if (!parsed.success) { + printIssuesSingleStore(config); + process.exit(1); + } + + return { + dialect: 'singlestore', + out: config.out, + breakpoints: config.breakpoints, + casing: config.casing, + credentials: parsed.data, + tablesFilter, + schemasFilter, + prefix: config.migrations?.prefix || 'index', + entities: config.entities, }; } @@ -444,11 +570,31 @@ export const preparePullConfig = async ( dialect: 'sqlite', out: config.out, breakpoints: config.breakpoints, - casing: config.introspectCasing, + casing: config.casing, + credentials: parsed.data, + tablesFilter, + schemasFilter, + prefix: config.migrations?.prefix || 'index', + entities: config.entities, + }; + } + + if (dialect === 'turso') { + const parsed = libSQLCredentials.safeParse(config); + if (!parsed.success) { + printIssuesLibSql(config, 'pull'); + process.exit(1); + } + return { + dialect, + out: config.out, + breakpoints: config.breakpoints, + casing: config.casing, credentials: parsed.data, tablesFilter, schemasFilter, - prefix: config.database?.prefix || 'index', + prefix: config.migrations?.prefix || 'index', + entities: config.entities, }; } @@ -505,6 +651,23 @@ export const prepareStudioConfig = async (options: Record) => { credentials, }; } + + if (dialect === 'singlestore') { + const parsed = singlestoreCredentials.safeParse(flattened); + if (!parsed.success) { + printIssuesSingleStore(flattened as Record); + process.exit(1); + } + const credentials = parsed.data; + return { + dialect, + schema, + host, + port, + credentials, + }; + } + if (dialect === 'sqlite') { const parsed = sqliteCredentials.safeParse(flattened); if (!parsed.success) { @@ -521,6 +684,22 @@ export const prepareStudioConfig = async (options: Record) => { }; } + if (dialect === 'turso') { + const parsed = libSQLCredentials.safeParse(flattened); + if (!parsed.success) { + printIssuesLibSql(flattened as Record, 'studio'); + process.exit(1); + } + const credentials = parsed.data; + return { + dialect, + schema, + host, + port, + credentials, + }; + } + assertUnreachable(dialect); }; @@ -574,6 +753,23 @@ export const prepareMigrateConfig = async (configPath: string | undefined) => { table, }; } + + if (dialect === 'singlestore') { + const parsed = singlestoreCredentials.safeParse(flattened); + if (!parsed.success) { + printIssuesSingleStore(flattened as Record); + process.exit(1); + } + const credentials = parsed.data; + return { + dialect, + out, + credentials, + schema, + table, + }; + } + if (dialect === 'sqlite') { const parsed = sqliteCredentials.safeParse(flattened); if (!parsed.success) { @@ -589,12 +785,28 @@ export const prepareMigrateConfig = async (configPath: string | undefined) => { table, }; } + if (dialect === 'turso') { + const parsed = libSQLCredentials.safeParse(flattened); + if (!parsed.success) { + printIssuesLibSql(flattened as Record, 'migrate'); + process.exit(1); + } + const credentials = parsed.data; + return { + dialect, + out, + credentials, + schema, + table, + }; + } assertUnreachable(dialect); }; export const drizzleConfigFromFile = async ( configPath?: string, + isExport?: boolean, ): Promise => { const prefix = process.env.TEST_CONFIG_PATH_PREFIX || ''; @@ -610,7 +822,7 @@ export const drizzleConfigFromFile = async ( ? 'drizzle.config.js' : 'drizzle.config.json'; - if (!configPath) { + if (!configPath && !isExport) { console.log( chalk.gray( `No config path provided, using default '${defaultConfigPath}'`, @@ -625,7 +837,8 @@ export const drizzleConfigFromFile = async ( process.exit(1); } - console.log(chalk.grey(`Reading config file '${path}'`)); + if (!isExport) console.log(chalk.grey(`Reading config file '${path}'`)); + const { unregister } = await safeRegister(); const required = require(`${path}`); const content = required.default ?? required; @@ -634,6 +847,7 @@ export const drizzleConfigFromFile = async ( // --- get response and then check by each dialect independently const res = configCommonSchema.safeParse(content); if (!res.success) { + console.log(res.error); if (!('dialect' in content)) { console.log(error("Please specify 'dialect' param in config file")); } diff --git a/drizzle-kit/src/cli/connections.ts b/drizzle-kit/src/cli/connections.ts index 02f3e8411..f2cf4817c 100644 --- a/drizzle-kit/src/cli/connections.ts +++ b/drizzle-kit/src/cli/connections.ts @@ -5,11 +5,21 @@ import fetch from 'node-fetch'; import ws from 'ws'; import { assertUnreachable } from '../global'; import type { ProxyParams } from '../serializer/studio'; -import { type DB, normaliseSQLiteUrl, type Proxy, type SQLiteDB, type SqliteProxy } from '../utils'; +import { + type DB, + LibSQLDB, + normalisePGliteUrl, + normaliseSQLiteUrl, + type Proxy, + type SQLiteDB, + type SqliteProxy, +} from '../utils'; import { assertPackages, checkPackage } from './utils'; +import { LibSQLCredentials } from './validations/libsql'; import type { MysqlCredentials } from './validations/mysql'; import { withStyle } from './validations/outputs'; import type { PostgresCredentials } from './validations/postgres'; +import { SingleStoreCredentials } from './validations/singlestore'; import type { SqliteCredentials } from './validations/sqlite'; export const preparePostgresDB = async ( @@ -21,7 +31,8 @@ export const preparePostgresDB = async ( } > => { if ('driver' in credentials) { - if (credentials.driver === 'aws-data-api') { + const { driver } = credentials; + if (driver === 'aws-data-api') { assertPackages('@aws-sdk/client-rds-data'); const { RDSDataClient, ExecuteStatementCommand, TypeHint } = await import( '@aws-sdk/client-rds-data' @@ -47,7 +58,7 @@ export const preparePostgresDB = async ( ); const db = drizzle(rdsClient, config); - const migrateFn = async (config: string | MigrationConfig) => { + const migrateFn = async (config: MigrationConfig) => { return migrate(db, config); }; @@ -92,12 +103,51 @@ export const preparePostgresDB = async ( }; } - assertUnreachable(credentials.driver); + if (driver === 'pglite') { + assertPackages('@electric-sql/pglite'); + const { PGlite, types } = await import('@electric-sql/pglite'); + const { drizzle } = await import('drizzle-orm/pglite'); + const { migrate } = await import('drizzle-orm/pglite/migrator'); + + const pglite = new PGlite(normalisePGliteUrl(credentials.url)); + await pglite.waitReady; + const drzl = drizzle(pglite); + const migrateFn = async (config: MigrationConfig) => { + return migrate(drzl, config); + }; + + const parsers = { + [types.TIMESTAMP]: (value: any) => value, + [types.TIMESTAMPTZ]: (value: any) => value, + [types.INTERVAL]: (value: any) => value, + [types.DATE]: (value: any) => value, + }; + + const query = async (sql: string, params: any[] = []) => { + const result = await pglite.query(sql, params, { + parsers, + }); + return result.rows as T[]; + }; + + const proxy = async (params: ProxyParams) => { + const preparedParams = preparePGliteParams(params.params); + const result = await pglite.query(params.sql, preparedParams, { + rowMode: params.mode, + parsers, + }); + return result.rows; + }; + + return { query, proxy, migrate: migrateFn }; + } + + assertUnreachable(driver); } if (await checkPackage('pg')) { console.log(withStyle.info(`Using 'pg' driver for database querying`)); - const pg = await import('pg'); + const { default: pg } = await import('pg'); const { drizzle } = await import('drizzle-orm/node-postgres'); const { migrate } = await import('drizzle-orm/node-postgres/migrator'); @@ -111,17 +161,42 @@ export const preparePostgresDB = async ( : credentials.ssl : {}; + // Override pg default date parsers + const types: { getTypeParser: typeof pg.types.getTypeParser } = { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === pg.types.builtins.TIMESTAMPTZ) { + return (val) => val; + } + if (typeId === pg.types.builtins.TIMESTAMP) { + return (val) => val; + } + if (typeId === pg.types.builtins.DATE) { + return (val) => val; + } + if (typeId === pg.types.builtins.INTERVAL) { + return (val) => val; + } + // @ts-ignore + return pg.types.getTypeParser(typeId, format); + }, + }; + const client = 'url' in credentials - ? new pg.default.Pool({ connectionString: credentials.url, max: 1 }) - : new pg.default.Pool({ ...credentials, ssl, max: 1 }); + ? new pg.Pool({ connectionString: credentials.url, max: 1 }) + : new pg.Pool({ ...credentials, ssl, max: 1 }); const db = drizzle(client); - const migrateFn = async (config: string | MigrationConfig) => { + const migrateFn = async (config: MigrationConfig) => { return migrate(db, config); }; const query = async (sql: string, params?: any[]) => { - const result = await client.query(sql, params ?? []); + const result = await client.query({ + text: sql, + values: params ?? [], + types, + }); return result.rows; }; @@ -130,6 +205,7 @@ export const preparePostgresDB = async ( text: params.sql, values: params.params, ...(params.mode === 'array' && { rowMode: 'array' }), + types, }); return result.rows; }; @@ -150,8 +226,18 @@ export const preparePostgresDB = async ( ? postgres.default(credentials.url, { max: 1 }) : postgres.default({ ...credentials, max: 1 }); + const transparentParser = (val: any) => val; + + // Override postgres.js default date parsers: https://github.com/porsager/postgres/discussions/761 + for (const type of ['1184', '1082', '1083', '1114']) { + client.options.parsers[type as any] = transparentParser; + client.options.serializers[type as any] = transparentParser; + } + client.options.serializers['114'] = transparentParser; + client.options.serializers['3802'] = transparentParser; + const db = drizzle(client); - const migrateFn = async (config: string | MigrationConfig) => { + const migrateFn = async (config: MigrationConfig) => { return migrate(db, config); }; @@ -179,7 +265,7 @@ export const preparePostgresDB = async ( "'@vercel/postgres' can only connect to remote Neon/Vercel Postgres/Supabase instances through a websocket", ), ); - const { VercelPool } = await import('@vercel/postgres'); + const { VercelPool, types: pgTypes } = await import('@vercel/postgres'); const { drizzle } = await import('drizzle-orm/vercel-postgres'); const { migrate } = await import('drizzle-orm/vercel-postgres/migrator'); const ssl = 'ssl' in credentials @@ -192,6 +278,27 @@ export const preparePostgresDB = async ( : credentials.ssl : {}; + // Override @vercel/postgres default date parsers + const types: { getTypeParser: typeof pgTypes.getTypeParser } = { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === pgTypes.builtins.TIMESTAMPTZ) { + return (val: any) => val; + } + if (typeId === pgTypes.builtins.TIMESTAMP) { + return (val: any) => val; + } + if (typeId === pgTypes.builtins.DATE) { + return (val: any) => val; + } + if (typeId === pgTypes.builtins.INTERVAL) { + return (val: any) => val; + } + // @ts-ignore + return pgTypes.getTypeParser(typeId, format); + }, + }; + const client = 'url' in credentials ? new VercelPool({ connectionString: credentials.url }) : new VercelPool({ ...credentials, ssl }); @@ -199,12 +306,16 @@ export const preparePostgresDB = async ( await client.connect(); const db = drizzle(client); - const migrateFn = async (config: string | MigrationConfig) => { + const migrateFn = async (config: MigrationConfig) => { return migrate(db, config); }; const query = async (sql: string, params?: any[]) => { - const result = await client.query(sql, params ?? []); + const result = await client.query({ + text: sql, + values: params ?? [], + types, + }); return result.rows; }; @@ -213,6 +324,7 @@ export const preparePostgresDB = async ( text: params.sql, values: params.params, ...(params.mode === 'array' && { rowMode: 'array' }), + types, }); return result.rows; }; @@ -231,7 +343,7 @@ export const preparePostgresDB = async ( "'@neondatabase/serverless' can only connect to remote Neon/Vercel Postgres/Supabase instances through a websocket", ), ); - const { Pool, neonConfig } = await import('@neondatabase/serverless'); + const { Pool, neonConfig, types: pgTypes } = await import('@neondatabase/serverless'); const { drizzle } = await import('drizzle-orm/neon-serverless'); const { migrate } = await import('drizzle-orm/neon-serverless/migrator'); @@ -245,18 +357,43 @@ export const preparePostgresDB = async ( : credentials.ssl : {}; + // Override @neondatabase/serverless default date parsers + const types: { getTypeParser: typeof pgTypes.getTypeParser } = { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === pgTypes.builtins.TIMESTAMPTZ) { + return (val: any) => val; + } + if (typeId === pgTypes.builtins.TIMESTAMP) { + return (val: any) => val; + } + if (typeId === pgTypes.builtins.DATE) { + return (val: any) => val; + } + if (typeId === pgTypes.builtins.INTERVAL) { + return (val: any) => val; + } + // @ts-ignore + return pgTypes.getTypeParser(typeId, format); + }, + }; + const client = 'url' in credentials ? new Pool({ connectionString: credentials.url, max: 1 }) : new Pool({ ...credentials, max: 1, ssl }); neonConfig.webSocketConstructor = ws; const db = drizzle(client); - const migrateFn = async (config: string | MigrationConfig) => { + const migrateFn = async (config: MigrationConfig) => { return migrate(db, config); }; const query = async (sql: string, params?: any[]) => { - const result = await client.query(sql, params ?? []); + const result = await client.query({ + text: sql, + values: params ?? [], + types, + }); return result.rows; }; @@ -265,6 +402,7 @@ export const preparePostgresDB = async ( text: params.sql, values: params.params, ...(params.mode === 'array' && { rowMode: 'array' }), + types, }); return result.rows; }; @@ -278,6 +416,85 @@ export const preparePostgresDB = async ( process.exit(1); }; +const parseSingleStoreCredentials = (credentials: SingleStoreCredentials) => { + if ('url' in credentials) { + const url = credentials.url; + + const connectionUrl = new URL(url); + const pathname = connectionUrl.pathname; + + const database = pathname.split('/')[pathname.split('/').length - 1]; + if (!database) { + console.error( + 'You should specify a database name in connection string (singlestore://USER:PASSWORD@HOST:PORT/DATABASE)', + ); + process.exit(1); + } + return { database, url }; + } else { + return { + database: credentials.database, + credentials, + }; + } +}; + +export const connectToSingleStore = async ( + it: SingleStoreCredentials, +): Promise<{ + db: DB; + proxy: Proxy; + database: string; + migrate: (config: MigrationConfig) => Promise; +}> => { + const result = parseSingleStoreCredentials(it); + + if (await checkPackage('mysql2')) { + const { createConnection } = await import('mysql2/promise'); + const { drizzle } = await import('drizzle-orm/singlestore'); + const { migrate } = await import('drizzle-orm/singlestore/migrator'); + + const connection = result.url + ? await createConnection(result.url) + : await createConnection(result.credentials!); // needed for some reason! + + const db = drizzle(connection); + const migrateFn = async (config: MigrationConfig) => { + return migrate(db, config); + }; + + await connection.connect(); + const query: DB['query'] = async ( + sql: string, + params?: any[], + ): Promise => { + const res = await connection.execute(sql, params); + return res[0] as any; + }; + + const proxy: Proxy = async (params: ProxyParams) => { + const result = await connection.query({ + sql: params.sql, + values: params.params, + rowsAsArray: params.mode === 'array', + }); + return result[0] as any[]; + }; + + return { + db: { query }, + proxy, + database: result.database, + migrate: migrateFn, + }; + } + + console.error( + "To connect to SingleStore database - please install 'singlestore' driver", + ); + process.exit(1); +}; + const parseMysqlCredentials = (credentials: MysqlCredentials) => { if ('url' in credentials) { const url = credentials.url; @@ -325,12 +542,23 @@ export const connectToMySQL = async ( return migrate(db, config); }; + const typeCast = (field: any, next: any) => { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }; + await connection.connect(); const query: DB['query'] = async ( sql: string, params?: any[], ): Promise => { - const res = await connection.execute(sql, params); + const res = await connection.execute({ + sql, + values: params, + typeCast, + }); return res[0] as any; }; @@ -339,6 +567,7 @@ export const connectToMySQL = async ( sql: params.sql, values: params.params, rowsAsArray: params.mode === 'array', + typeCast, }); return result[0] as any[]; }; @@ -352,13 +581,13 @@ export const connectToMySQL = async ( } if (await checkPackage('@planetscale/database')) { - const { connect } = await import('@planetscale/database'); + const { Client } = await import('@planetscale/database'); const { drizzle } = await import('drizzle-orm/planetscale-serverless'); const { migrate } = await import( 'drizzle-orm/planetscale-serverless/migrator' ); - const connection = connect(result); + const connection = new Client(result); const db = drizzle(connection); const migrateFn = async (config: MigrationConfig) => { @@ -415,6 +644,25 @@ const prepareSqliteParams = (params: any[], driver?: string) => { }); }; +const preparePGliteParams = (params: any[]) => { + return params.map((param) => { + if ( + param + && typeof param === 'object' + && 'type' in param + && 'value' in param + && param.type === 'binary' + ) { + const value = typeof param.value === 'object' + ? JSON.stringify(param.value) + : (param.value as string); + + return value; + } + return param; + }); +}; + export const connectToSQLite = async ( credentials: SqliteCredentials, ): Promise< @@ -424,56 +672,7 @@ export const connectToSQLite = async ( > => { if ('driver' in credentials) { const { driver } = credentials; - if (driver === 'turso') { - assertPackages('@libsql/client'); - const { createClient } = await import('@libsql/client'); - const { drizzle } = await import('drizzle-orm/libsql'); - const { migrate } = await import('drizzle-orm/libsql/migrator'); - - const client = createClient({ - url: credentials.url, - authToken: credentials.authToken, - }); - - const drzl = drizzle(client); - const migrateFn = async (config: MigrationConfig) => { - return migrate(drzl, config); - }; - - const db: SQLiteDB = { - query: async (sql: string, params?: any[]) => { - const res = await client.execute({ sql, args: params || [] }); - return res.rows as T[]; - }, - run: async (query: string) => { - await client.execute(query); - }, - batch: async ( - queries: { query: string; values?: any[] | undefined }[], - ) => { - await client.batch( - queries.map((it) => ({ sql: it.query, args: it.values ?? [] })), - ); - }, - }; - const proxy: SqliteProxy = { - proxy: async (params: ProxyParams) => { - const preparedParams = prepareSqliteParams(params.params); - const result = await client.execute({ - sql: params.sql, - args: preparedParams, - }); - - if (params.mode === 'array') { - return result.rows.map((row) => Object.values(row)); - } else { - return result.rows; - } - }, - }; - - return { ...db, ...proxy, migrate: migrateFn }; - } else if (driver === 'd1-http') { + if (driver === 'd1-http') { const { drizzle } = await import('drizzle-orm/sqlite-proxy'); const { migrate } = await import('drizzle-orm/sqlite-proxy/migrator'); @@ -650,8 +849,66 @@ export const connectToSQLite = async ( }; return { ...db, ...proxy, migrate: migrateFn }; } + console.log( "Please install either 'better-sqlite3' or '@libsql/client' for Drizzle Kit to connect to SQLite databases", ); process.exit(1); }; + +export const connectToLibSQL = async (credentials: LibSQLCredentials): Promise< + & LibSQLDB + & SqliteProxy + & { migrate: (config: MigrationConfig) => Promise } +> => { + if (await checkPackage('@libsql/client')) { + const { createClient } = await import('@libsql/client'); + const { drizzle } = await import('drizzle-orm/libsql'); + const { migrate } = await import('drizzle-orm/libsql/migrator'); + + const client = createClient({ + url: normaliseSQLiteUrl(credentials.url, 'libsql'), + authToken: credentials.authToken, + }); + const drzl = drizzle(client); + const migrateFn = async (config: MigrationConfig) => { + return migrate(drzl, config); + }; + + const db: LibSQLDB = { + query: async (sql: string, params?: any[]) => { + const res = await client.execute({ sql, args: params || [] }); + return res.rows as T[]; + }, + run: async (query: string) => { + await client.execute(query); + }, + batchWithPragma: async (queries: string[]) => { + await client.migrate(queries); + }, + }; + + const proxy: SqliteProxy = { + proxy: async (params: ProxyParams) => { + const preparedParams = prepareSqliteParams(params.params); + const result = await client.execute({ + sql: params.sql, + args: preparedParams, + }); + + if (params.mode === 'array') { + return result.rows.map((row) => Object.values(row)); + } else { + return result.rows; + } + }, + }; + + return { ...db, ...proxy, migrate: migrateFn }; + } + + console.log( + "Please install '@libsql/client' for Drizzle Kit to connect to LibSQL databases", + ); + process.exit(1); +}; diff --git a/drizzle-kit/src/cli/index.ts b/drizzle-kit/src/cli/index.ts index a7272ffef..42730be1d 100644 --- a/drizzle-kit/src/cli/index.ts +++ b/drizzle-kit/src/cli/index.ts @@ -1,6 +1,6 @@ import { command, run } from '@drizzle-team/brocli'; import chalk from 'chalk'; -import { check, drop, generate, migrate, pull, push, studio, up } from './schema'; +import { check, drop, exportRaw, generate, migrate, pull, push, studio, up } from './schema'; import { ormCoreVersions } from './utils'; const version = async () => { @@ -42,6 +42,7 @@ const legacy = [ legacyCommand('check:sqlite', 'check'), ]; -run([generate, migrate, pull, push, studio, up, check, drop, ...legacy], { +run([generate, migrate, pull, push, studio, up, check, drop, exportRaw, ...legacy], { + name: 'drizzle-kit', version: version, }); diff --git a/drizzle-kit/src/cli/schema.ts b/drizzle-kit/src/cli/schema.ts index 0e88a7023..110f9d633 100644 --- a/drizzle-kit/src/cli/schema.ts +++ b/drizzle-kit/src/cli/schema.ts @@ -1,36 +1,40 @@ +import { boolean, command, number, string } from '@drizzle-team/brocli'; import chalk from 'chalk'; -import { checkHandler } from './commands/check'; -import { assertOrmCoreVersion, assertPackages, assertStudioNodeVersion, ormVersionGt } from './utils'; +import 'dotenv/config'; +import { mkdirSync } from 'fs'; +import { renderWithTask } from 'hanji'; +import { dialects } from 'src/schemaValidator'; import '../@types/utils'; +import { assertUnreachable } from '../global'; +import { type Setup } from '../serializer/studio'; import { assertV1OutFolder } from '../utils'; +import { certs } from '../utils/certs'; +import { checkHandler } from './commands/check'; import { dropMigration } from './commands/drop'; import { upMysqlHandler } from './commands/mysqlUp'; import { upPgHandler } from './commands/pgUp'; +import { upSinglestoreHandler } from './commands/singlestoreUp'; import { upSqliteHandler } from './commands/sqliteUp'; import { prepareCheckParams, prepareDropParams, + prepareExportConfig, prepareGenerateConfig, prepareMigrateConfig, preparePullConfig, preparePushConfig, prepareStudioConfig, } from './commands/utils'; +import { assertOrmCoreVersion, assertPackages, assertStudioNodeVersion, ormVersionGt } from './utils'; import { assertCollisions, drivers, prefixes } from './validations/common'; import { withStyle } from './validations/outputs'; -import 'dotenv/config'; -import { boolean, command, number, string } from '@drizzle-team/brocli'; -import { mkdirSync } from 'fs'; -import { renderWithTask } from 'hanji'; -import { dialects } from 'src/schemaValidator'; -import { assertUnreachable } from '../global'; -import type { Setup } from '../serializer/studio'; -import { certs } from '../utils/certs'; import { grey, MigrateProgress } from './views'; const optionDialect = string('dialect') .enum(...dialects) - .desc(`Database dialect: 'postgresql', 'mysql' or 'sqlite'`); + .desc( + `Database dialect: 'postgresql', 'mysql', 'sqlite', 'turso' or 'singlestore'`, + ); const optionOut = string().desc("Output folder, 'drizzle' by default"); const optionConfig = string().desc('Path to drizzle config file'); const optionBreakpoints = boolean().desc( @@ -41,12 +45,15 @@ const optionDriver = string() .enum(...drivers) .desc('Database driver'); +const optionCasing = string().enum('camelCase', 'snake_case').desc('Casing for serialization'); + export const generate = command({ name: 'generate', options: { config: optionConfig, dialect: optionDialect, driver: optionDriver, + casing: optionCasing, schema: string().desc('Path to a schema file or folder'), out: optionOut, name: string().desc('Migration file name'), @@ -63,7 +70,7 @@ export const generate = command({ 'generate', opts, ['prefix', 'name', 'custom'], - ['driver', 'breakpoints', 'schema', 'out', 'dialect'], + ['driver', 'breakpoints', 'schema', 'out', 'dialect', 'casing'], ); return prepareGenerateConfig(opts, from); }, @@ -77,6 +84,8 @@ export const generate = command({ prepareAndMigratePg, prepareAndMigrateMysql, prepareAndMigrateSqlite, + prepareAndMigrateLibSQL, + prepareAndMigrateSingleStore, } = await import('./commands/migrate'); const dialect = opts.dialect; @@ -86,6 +95,10 @@ export const generate = command({ await prepareAndMigrateMysql(opts); } else if (dialect === 'sqlite') { await prepareAndMigrateSqlite(opts); + } else if (dialect === 'turso') { + await prepareAndMigrateLibSQL(opts); + } else if (dialect === 'singlestore') { + await prepareAndMigrateSingleStore(opts); } else { assertUnreachable(dialect); } @@ -108,15 +121,23 @@ export const migrate = command({ try { if (dialect === 'postgresql') { if ('driver' in credentials) { - if (credentials.driver === 'aws-data-api') { + const { driver } = credentials; + if (driver === 'aws-data-api') { if (!(await ormVersionGt('0.30.10'))) { console.log( "To use 'aws-data-api' driver - please update drizzle-orm to the latest version", ); process.exit(1); } + } else if (driver === 'pglite') { + if (!(await ormVersionGt('0.30.6'))) { + console.log( + "To use 'pglite' driver - please update drizzle-orm to the latest version", + ); + process.exit(1); + } } else { - assertUnreachable(credentials.driver); + assertUnreachable(driver); } } const { preparePostgresDB } = await import('./connections'); @@ -140,6 +161,17 @@ export const migrate = command({ migrationsSchema: schema, }), ); + } else if (dialect === 'singlestore') { + const { connectToSingleStore } = await import('./connections'); + const { migrate } = await connectToSingleStore(credentials); + await renderWithTask( + new MigrateProgress(), + migrate({ + migrationsFolder: out, + migrationsTable: table, + migrationsSchema: schema, + }), + ); } else if (dialect === 'sqlite') { const { connectToSQLite } = await import('./connections'); const { migrate } = await connectToSQLite(credentials); @@ -151,6 +183,17 @@ export const migrate = command({ migrationsSchema: schema, }), ); + } else if (dialect === 'turso') { + const { connectToLibSQL } = await import('./connections'); + const { migrate } = await connectToLibSQL(credentials); + await renderWithTask( + new MigrateProgress(), + migrate({ + migrationsFolder: opts.out, + migrationsTable: table, + migrationsSchema: schema, + }), + ); } else { assertUnreachable(dialect); } @@ -190,6 +233,7 @@ export const push = command({ options: { config: optionConfig, dialect: optionDialect, + casing: optionCasing, schema: string().desc('Path to a schema file or folder'), ...optionsFilters, ...optionsDatabaseCredentials, @@ -223,6 +267,7 @@ export const push = command({ 'schemaFilters', 'extensionsFilters', 'tablesFilter', + 'casing', ], ); @@ -241,6 +286,8 @@ export const push = command({ tablesFilter, schemasFilter, force, + casing, + entities, } = config; try { @@ -253,18 +300,27 @@ export const push = command({ strict, verbose, force, + casing, ); } else if (dialect === 'postgresql') { if ('driver' in credentials) { - if (credentials.driver === 'aws-data-api') { + const { driver } = credentials; + if (driver === 'aws-data-api') { if (!(await ormVersionGt('0.30.10'))) { console.log( "To use 'aws-data-api' driver - please update drizzle-orm to the latest version", ); process.exit(1); } + } else if (driver === 'pglite') { + if (!(await ormVersionGt('0.30.6'))) { + console.log( + "To use 'pglite' driver - please update drizzle-orm to the latest version", + ); + process.exit(1); + } } else { - assertUnreachable(credentials.driver); + assertUnreachable(driver); } } @@ -276,7 +332,9 @@ export const push = command({ credentials, tablesFilter, schemasFilter, + entities, force, + casing, ); } else if (dialect === 'sqlite') { const { sqlitePush } = await import('./commands/push'); @@ -287,6 +345,29 @@ export const push = command({ credentials, tablesFilter, force, + casing, + ); + } else if (dialect === 'turso') { + const { libSQLPush } = await import('./commands/push'); + await libSQLPush( + schemaPath, + verbose, + strict, + credentials, + tablesFilter, + force, + casing, + ); + } else if (dialect === 'singlestore') { + const { singlestorePush } = await import('./commands/push'); + await singlestorePush( + schemaPath, + credentials, + tablesFilter, + strict, + verbose, + force, + casing, ); } else { assertUnreachable(dialect); @@ -343,9 +424,13 @@ export const up = command({ upMysqlHandler(out); } - if (dialect === 'sqlite') { + if (dialect === 'sqlite' || dialect === 'turso') { upSqliteHandler(out); } + + if (dialect === 'singlestore') { + upSinglestoreHandler(out); + } }, }); @@ -400,6 +485,7 @@ export const pull = command({ tablesFilter, schemasFilter, prefix, + entities, } = config; mkdirSync(out, { recursive: true }); @@ -417,15 +503,23 @@ export const pull = command({ try { if (dialect === 'postgresql') { if ('driver' in credentials) { - if (credentials.driver === 'aws-data-api') { + const { driver } = credentials; + if (driver === 'aws-data-api') { if (!(await ormVersionGt('0.30.10'))) { console.log( "To use 'aws-data-api' driver - please update drizzle-orm to the latest version", ); process.exit(1); } + } else if (driver === 'pglite') { + if (!(await ormVersionGt('0.30.6'))) { + console.log( + "To use 'pglite' driver - please update drizzle-orm to the latest version", + ); + process.exit(1); + } } else { - assertUnreachable(credentials.driver); + assertUnreachable(driver); } } @@ -438,6 +532,7 @@ export const pull = command({ tablesFilter, schemasFilter, prefix, + entities, ); } else if (dialect === 'mysql') { const { introspectMysql } = await import('./commands/introspect'); @@ -459,6 +554,26 @@ export const pull = command({ tablesFilter, prefix, ); + } else if (dialect === 'turso') { + const { introspectLibSQL } = await import('./commands/introspect'); + await introspectLibSQL( + casing, + out, + breakpoints, + credentials, + tablesFilter, + prefix, + ); + } else if (dialect === 'singlestore') { + const { introspectSingleStore } = await import('./commands/introspect'); + await introspectSingleStore( + casing, + out, + breakpoints, + credentials, + tablesFilter, + prefix, + ); } else { assertUnreachable(dialect); } @@ -519,21 +634,32 @@ export const studio = command({ drizzleForMySQL, prepareSQLiteSchema, drizzleForSQLite, + prepareSingleStoreSchema, + drizzleForSingleStore, + drizzleForLibSQL, } = await import('../serializer/studio'); let setup: Setup; try { if (dialect === 'postgresql') { if ('driver' in credentials) { - if (credentials.driver === 'aws-data-api') { + const { driver } = credentials; + if (driver === 'aws-data-api') { if (!(await ormVersionGt('0.30.10'))) { console.log( "To use 'aws-data-api' driver - please update drizzle-orm to the latest version", ); process.exit(1); } + } else if (driver === 'pglite') { + if (!(await ormVersionGt('0.30.6'))) { + console.log( + "To use 'pglite' driver - please update drizzle-orm to the latest version", + ); + process.exit(1); + } } else { - assertUnreachable(credentials.driver); + assertUnreachable(driver); } } @@ -551,6 +677,21 @@ export const studio = command({ ? await prepareSQLiteSchema(schemaPath) : { schema: {}, relations: {}, files: [] }; setup = await drizzleForSQLite(credentials, schema, relations, files); + } else if (dialect === 'turso') { + const { schema, relations, files } = schemaPath + ? await prepareSQLiteSchema(schemaPath) + : { schema: {}, relations: {}, files: [] }; + setup = await drizzleForLibSQL(credentials, schema, relations, files); + } else if (dialect === 'singlestore') { + const { schema, relations, files } = schemaPath + ? await prepareSingleStoreSchema(schemaPath) + : { schema: {}, relations: {}, files: [] }; + setup = await drizzleForSingleStore( + credentials, + schema, + relations, + files, + ); } else { assertUnreachable(dialect); } @@ -607,3 +748,47 @@ export const studio = command({ } }, }); + +export const exportRaw = command({ + name: 'export', + desc: 'Generate diff between current state and empty state in specified formats: sql', + options: { + sql: boolean('sql').default(true).desc('Generate as sql'), + config: optionConfig, + dialect: optionDialect, + schema: string().desc('Path to a schema file or folder'), + }, + transform: async (opts) => { + const from = assertCollisions('export', opts, ['sql'], ['dialect', 'schema']); + return prepareExportConfig(opts, from); + }, + handler: async (opts) => { + await assertOrmCoreVersion(); + await assertPackages('drizzle-orm'); + + const { + prepareAndExportPg, + prepareAndExportMysql, + prepareAndExportSqlite, + prepareAndExportLibSQL, + prepareAndExportSinglestore, + } = await import( + './commands/migrate' + ); + + const dialect = opts.dialect; + if (dialect === 'postgresql') { + await prepareAndExportPg(opts); + } else if (dialect === 'mysql') { + await prepareAndExportMysql(opts); + } else if (dialect === 'sqlite') { + await prepareAndExportSqlite(opts); + } else if (dialect === 'turso') { + await prepareAndExportLibSQL(opts); + } else if (dialect === 'singlestore') { + await prepareAndExportSinglestore(opts); + } else { + assertUnreachable(dialect); + } + }, +}); diff --git a/drizzle-kit/src/cli/utils.ts b/drizzle-kit/src/cli/utils.ts index f7e7a2ae9..a4c28851e 100644 --- a/drizzle-kit/src/cli/utils.ts +++ b/drizzle-kit/src/cli/utils.ts @@ -74,7 +74,7 @@ export const assertEitherPackage = async ( process.exit(1); }; -const requiredApiVersion = 7; +const requiredApiVersion = 10; export const assertOrmCoreVersion = async () => { try { const { compatibilityVersion } = await import('drizzle-orm/version'); diff --git a/drizzle-kit/src/cli/validations/cli.ts b/drizzle-kit/src/cli/validations/cli.ts index 67e118a98..a2a5ad81f 100644 --- a/drizzle-kit/src/cli/validations/cli.ts +++ b/drizzle-kit/src/cli/validations/cli.ts @@ -1,6 +1,6 @@ -import { boolean, intersection, literal, object, string, TypeOf, union } from 'zod'; +import { array, boolean, intersection, literal, object, string, TypeOf, union } from 'zod'; import { dialect } from '../../schemaValidator'; -import { casing, prefix } from './common'; +import { casing, casingType, prefix } from './common'; export const cliConfigGenerate = object({ dialect: dialect.optional(), @@ -17,6 +17,7 @@ export type CliConfigGenerate = TypeOf; export const pushParams = object({ dialect: dialect, + casing: casingType.optional(), schema: union([string(), string().array()]), tablesFilter: union([string(), string().array()]).optional(), schemaFilter: union([string(), string().array()]) @@ -25,6 +26,13 @@ export const pushParams = object({ extensionsFilters: literal('postgis').array().optional(), verbose: boolean().optional(), strict: boolean().optional(), + entities: object({ + roles: boolean().or(object({ + provider: string().optional(), + include: string().array().optional(), + exclude: string().array().optional(), + })).optional().default(false), + }).optional(), }).passthrough(); export type PushParams = TypeOf; @@ -38,13 +46,22 @@ export const pullParams = object({ .optional() .default(['public']), extensionsFilters: literal('postgis').array().optional(), - introspectCasing: casing, + casing, breakpoints: boolean().optional().default(true), - database: object({ + migrations: object({ prefix: prefix.optional().default('index'), }).optional(), + entities: object({ + roles: boolean().or(object({ + provider: string().optional(), + include: string().array().optional(), + exclude: string().array().optional(), + })).optional().default(false), + }).optional(), }).passthrough(); +export type Entities = TypeOf['entities']; + export type PullParams = TypeOf; export const configCheck = object({ diff --git a/drizzle-kit/src/cli/validations/common.ts b/drizzle-kit/src/cli/validations/common.ts index e800afbc5..721f6effa 100644 --- a/drizzle-kit/src/cli/validations/common.ts +++ b/drizzle-kit/src/cli/validations/common.ts @@ -10,7 +10,8 @@ export type Commands = | 'check' | 'up' | 'drop' - | 'push'; + | 'push' + | 'export'; type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; type IsUnion = [T] extends [UnionToIntersection] ? false : true; @@ -61,9 +62,14 @@ export const assertCollisions = < }; export const sqliteDriversLiterals = [ - literal('turso'), literal('d1-http'), literal('expo'), + literal('durable-sqlite'), +] as const; + +export const postgresqlDriversLiterals = [ + literal('aws-data-api'), + literal('pglite'), ] as const; export const prefixes = [ @@ -80,8 +86,12 @@ export type Prefix = (typeof prefixes)[number]; const _: Prefix = '' as TypeOf; } +export const casingTypes = ['snake_case', 'camelCase'] as const; +export const casingType = enum_(casingTypes); +export type CasingType = (typeof casingTypes)[number]; + export const sqliteDriver = union(sqliteDriversLiterals); -export const postgresDriver = literal('aws-data-api'); +export const postgresDriver = union(postgresqlDriversLiterals); export const driver = union([sqliteDriver, postgresDriver]); export const configMigrations = object({ @@ -101,6 +111,8 @@ export const configCommonSchema = object({ schemaFilter: union([string(), string().array()]).default(['public']), migrations: configMigrations, dbCredentials: any().optional(), + casing: casingType.optional(), + sql: boolean().default(true), }).passthrough(); export const casing = union([literal('camel'), literal('preserve')]).default( @@ -151,7 +163,7 @@ export const configPushSchema = object({ }); export type CliConfig = TypeOf; -export const drivers = ['turso', 'd1-http', 'expo', 'aws-data-api'] as const; +export const drivers = ['d1-http', 'expo', 'aws-data-api', 'pglite', 'durable-sqlite'] as const; export type Driver = (typeof drivers)[number]; const _: Driver = '' as TypeOf; diff --git a/drizzle-kit/src/cli/validations/libsql.ts b/drizzle-kit/src/cli/validations/libsql.ts new file mode 100644 index 000000000..a9b03c168 --- /dev/null +++ b/drizzle-kit/src/cli/validations/libsql.ts @@ -0,0 +1,27 @@ +import { softAssertUnreachable } from 'src/global'; +import { object, string, TypeOf } from 'zod'; +import { error } from '../views'; +import { wrapParam } from './common'; + +export const libSQLCredentials = object({ + url: string().min(1), + authToken: string().min(1).optional(), +}); + +export type LibSQLCredentials = { + url: string; + authToken?: string; +}; + +const _: LibSQLCredentials = {} as TypeOf; + +export const printConfigConnectionIssues = ( + options: Record, + command: 'generate' | 'migrate' | 'push' | 'pull' | 'studio', +) => { + let text = `Please provide required params for 'turso' dialect:\n`; + console.log(error(text)); + console.log(wrapParam('url', options.url)); + console.log(wrapParam('authToken', options.authToken, true, 'secret')); + process.exit(1); +}; diff --git a/drizzle-kit/src/cli/validations/outputs.ts b/drizzle-kit/src/cli/validations/outputs.ts index 6b92829d5..6e9d520dd 100644 --- a/drizzle-kit/src/cli/validations/outputs.ts +++ b/drizzle-kit/src/cli/validations/outputs.ts @@ -26,7 +26,7 @@ export const outputs = { ), noDialect: () => withStyle.error( - `Please specify 'dialect' param in config, either of 'pg', 'mysql' or 'sqlite'`, + `Please specify 'dialect' param in config, either of 'postgresql', 'mysql', 'sqlite', turso or singlestore`, ), }, common: { @@ -79,4 +79,13 @@ export const outputs = { introspect: {}, push: {}, }, + singlestore: { + connection: { + driver: () => withStyle.error(`Only "mysql2" is available options for "--driver"`), + required: () => + withStyle.error( + `Either "url" or "host", "database" are required for database connection`, + ), + }, + }, }; diff --git a/drizzle-kit/src/cli/validations/postgres.ts b/drizzle-kit/src/cli/validations/postgres.ts index 3dd02b4f3..658760c61 100644 --- a/drizzle-kit/src/cli/validations/postgres.ts +++ b/drizzle-kit/src/cli/validations/postgres.ts @@ -35,6 +35,10 @@ export const postgresCredentials = union([ secretArn: string().min(1), resourceArn: string().min(1), }), + object({ + driver: literal('pglite'), + url: string().min(1), + }), ]); export type PostgresCredentials = TypeOf; diff --git a/drizzle-kit/src/cli/validations/singlestore.ts b/drizzle-kit/src/cli/validations/singlestore.ts new file mode 100644 index 000000000..ebe0cc5f0 --- /dev/null +++ b/drizzle-kit/src/cli/validations/singlestore.ts @@ -0,0 +1,61 @@ +import { boolean, coerce, object, string, TypeOf, union } from 'zod'; +import { error } from '../views'; +import { wrapParam } from './common'; +import { outputs } from './outputs'; + +export const singlestoreCredentials = union([ + object({ + host: string().min(1), + port: coerce.number().min(1).optional(), + user: string().min(1).optional(), + password: string().min(1).optional(), + database: string().min(1), + ssl: union([ + string(), + object({ + pfx: string().optional(), + key: string().optional(), + passphrase: string().optional(), + cert: string().optional(), + ca: union([string(), string().array()]).optional(), + crl: union([string(), string().array()]).optional(), + ciphers: string().optional(), + rejectUnauthorized: boolean().optional(), + }), + ]).optional(), + }), + object({ + url: string().min(1), + }), +]); + +export type SingleStoreCredentials = TypeOf; + +export const printCliConnectionIssues = (options: any) => { + const { uri, host, database } = options || {}; + + if (!uri && (!host || !database)) { + console.log(outputs.singlestore.connection.required()); + } +}; + +export const printConfigConnectionIssues = ( + options: Record, +) => { + if ('url' in options) { + let text = `Please provide required params for SingleStore driver:\n`; + console.log(error(text)); + console.log(wrapParam('url', options.url, false, 'url')); + process.exit(1); + } + + let text = `Please provide required params for SingleStore driver:\n`; + console.log(error(text)); + console.log(wrapParam('host', options.host)); + console.log(wrapParam('port', options.port, true)); + console.log(wrapParam('user', options.user, true)); + console.log(wrapParam('password', options.password, true, 'secret')); + console.log(wrapParam('database', options.database)); + console.log(wrapParam('ssl', options.ssl, true)); + process.exit(1); +}; diff --git a/drizzle-kit/src/cli/validations/sqlite.ts b/drizzle-kit/src/cli/validations/sqlite.ts index b6ad062d5..bb16492c3 100644 --- a/drizzle-kit/src/cli/validations/sqlite.ts +++ b/drizzle-kit/src/cli/validations/sqlite.ts @@ -25,11 +25,6 @@ export const sqliteCredentials = union([ ]); export type SqliteCredentials = - | { - driver: 'turso'; - url: string; - authToken: string; - } | { driver: 'd1-http'; accountId: string; @@ -77,12 +72,27 @@ export const printConfigConnectionIssues = ( console.log(wrapParam('databaseId', options.databaseId)); console.log(wrapParam('token', options.token, false, 'secret')); process.exit(1); - } else if (driver === 'turso') { - let text = `Please provide required params for Turso driver:\n`; - console.log(error(text)); - console.log(wrapParam('url', options.url)); - console.log(wrapParam('authToken', options.authToken, false, 'secret')); - return; + } else if (driver === 'durable-sqlite') { + if (command === 'migrate') { + console.log( + error( + `You can't use 'migrate' command with SQLite Durable Objects`, + ), + ); + } else if (command === 'studio') { + console.log( + error( + `You can't use 'migrate' command with SQLite Durable Objects`, + ), + ); + } else if (command === 'pull') { + console.log(error("You can't use 'pull' command with SQLite Durable Objects")); + } else if (command === 'push') { + console.log(error("You can't use 'push' command with SQLite Durable Objects")); + } else { + console.log(error('Unexpected error with SQLite Durable Object driver 🤔')); + } + process.exit(1); } else { softAssertUnreachable(driver); } diff --git a/drizzle-kit/src/cli/views.ts b/drizzle-kit/src/cli/views.ts index 56e0331df..9106d31cd 100644 --- a/drizzle-kit/src/cli/views.ts +++ b/drizzle-kit/src/cli/views.ts @@ -32,7 +32,13 @@ export const schema = (schema: CommonSchema): string => { .map((t) => { const columnsCount = Object.values(t.columns).length; const indexesCount = Object.values(t.indexes).length; - const foreignKeys = Object.values(t.foreignKeys).length; + let foreignKeys: number = 0; + // Singlestore doesn't have foreign keys + if (schema.dialect !== 'singlestore') { + // @ts-expect-error + foreignKeys = Object.values(t.foreignKeys).length; + } + return `${chalk.bold.blue(t.name)} ${ chalk.gray( `${columnsCount} columns ${indexesCount} indexes ${foreignKeys} fks`, @@ -158,6 +164,77 @@ export const tableKey = (it: NamedWithSchema) => { : `${it.schema}.${it.name}`; }; +export class ResolveSelectNamed extends Prompt< + RenamePropmtItem | T +> { + private readonly state: SelectState | T>; + + constructor( + private readonly base: T, + data: (RenamePropmtItem | T)[], + private readonly entityType: 'role' | 'policy', + ) { + super(); + this.on('attach', (terminal) => terminal.toggleCursor('hide')); + this.state = new SelectState(data); + this.state.bind(this); + this.base = base; + } + + render(status: 'idle' | 'submitted' | 'aborted'): string { + if (status === 'submitted' || status === 'aborted') { + return ''; + } + const key = this.base.name; + + let text = `\nIs ${chalk.bold.blue(key)} ${this.entityType} created or renamed from another ${this.entityType}?\n`; + + const isSelectedRenamed = isRenamePromptItem( + this.state.items[this.state.selectedIdx], + ); + + const selectedPrefix = isSelectedRenamed + ? chalk.yellow('❯ ') + : chalk.green('❯ '); + + const labelLength: number = this.state.items + .filter((it) => isRenamePromptItem(it)) + .map((_) => { + const it = _ as RenamePropmtItem; + const keyFrom = it.from.name; + return key.length + 3 + keyFrom.length; + }) + .reduce((a, b) => { + if (a > b) { + return a; + } + return b; + }, 0); + + const entityType = this.entityType; + this.state.items.forEach((it, idx) => { + const isSelected = idx === this.state.selectedIdx; + const isRenamed = isRenamePromptItem(it); + + const title = isRenamed + ? `${it.from.name} › ${it.to.name}`.padEnd(labelLength, ' ') + : it.name.padEnd(labelLength, ' '); + + const label = isRenamed + ? `${chalk.yellow('~')} ${title} ${chalk.gray(`rename ${entityType}`)}` + : `${chalk.green('+')} ${title} ${chalk.gray(`create ${entityType}`)}`; + + text += isSelected ? `${selectedPrefix}${label}` : ` ${label}`; + text += idx != this.state.items.length - 1 ? '\n' : ''; + }); + return text; + } + + result(): RenamePropmtItem | T { + return this.state.items[this.state.selectedIdx]!; + } +} + export class ResolveSelect extends Prompt< RenamePropmtItem | T > { @@ -166,7 +243,7 @@ export class ResolveSelect extends Prompt< constructor( private readonly base: T, data: (RenamePropmtItem | T)[], - private readonly entityType: 'table' | 'enum' | 'sequence', + private readonly entityType: 'table' | 'enum' | 'sequence' | 'view' | 'role', ) { super(); this.on('attach', (terminal) => terminal.toggleCursor('hide')); @@ -330,7 +407,11 @@ export type IntrospectStage = | 'columns' | 'enums' | 'indexes' - | 'fks'; + | 'policies' + | 'checks' + | 'fks' + | 'views'; + type IntrospectState = { [key in IntrospectStage]: { count: number; @@ -369,6 +450,21 @@ export class IntrospectProgress extends TaskView { name: 'foreign keys', status: 'fetching', }, + policies: { + count: 0, + name: 'policies', + status: 'fetching', + }, + checks: { + count: 0, + name: 'check constraints', + status: 'fetching', + }, + views: { + count: 0, + name: 'views', + status: 'fetching', + }, }; constructor(private readonly hasEnums: boolean = false) { @@ -422,6 +518,10 @@ export class IntrospectProgress extends TaskView { info += this.hasEnums ? this.statusText(spin, this.state.enums) : ''; info += this.statusText(spin, this.state.indexes); info += this.statusText(spin, this.state.fks); + info += this.statusText(spin, this.state.policies); + info += this.statusText(spin, this.state.checks); + info += this.statusText(spin, this.state.views); + return info; } } diff --git a/drizzle-kit/src/extensions/getTablesFilterByExtensions.ts b/drizzle-kit/src/extensions/getTablesFilterByExtensions.ts new file mode 100644 index 000000000..80321fc6a --- /dev/null +++ b/drizzle-kit/src/extensions/getTablesFilterByExtensions.ts @@ -0,0 +1,16 @@ +import type { Config } from '../index'; + +export const getTablesFilterByExtensions = ({ + extensionsFilters, + dialect, +}: Pick): string[] => { + if (extensionsFilters) { + if ( + extensionsFilters.includes('postgis') + && dialect === 'postgresql' + ) { + return ['!geography_columns', '!geometry_columns', '!spatial_ref_sys']; + } + } + return []; +}; diff --git a/drizzle-kit/src/index.ts b/drizzle-kit/src/index.ts index 08c302ac3..4c55f3eb6 100644 --- a/drizzle-kit/src/index.ts +++ b/drizzle-kit/src/index.ts @@ -23,7 +23,7 @@ type Verify = U; * **Config** usage: * * `dialect` - mandatory and is responsible for explicitly providing a databse dialect you are using for all the commands - * *Possible values*: `postgresql`, `mysql`, `sqlite` + * *Possible values*: `postgresql`, `mysql`, `sqlite`, `singlestore * * See https://orm.drizzle.team/kit-docs/config-reference#dialect * @@ -40,7 +40,7 @@ type Verify = U; * * --- * `driver` - optional param that is responsible for explicitly providing a driver to use when accessing a database - * *Possible values*: `aws-data-api`, `d1-http`, `expo`, `turso` + * *Possible values*: `aws-data-api`, `d1-http`, `expo`, `turso`, `pglite` * If you don't use AWS Data API, D1, Turso or Expo - ypu don't need this driver. You can check a driver strategy choice here: https://orm.drizzle.team/kit-docs/upgrade-21 * * See https://orm.drizzle.team/kit-docs/config-reference#driver @@ -64,7 +64,7 @@ type Verify = U; * * `breakpoints` - param lets you enable/disable SQL statement breakpoints in generated migrations. * It’s optional and true by default, it’s necessary to properly apply migrations on databases, - * that do not support multiple DDL alternation statements in one transaction(MySQL, SQLite) and + * that do not support multiple DDL alternation statements in one transaction(MySQL, SQLite, SingleStore) and * Drizzle ORM has to apply them sequentially one by one. * * See https://orm.drizzle.team/kit-docs/config-reference#breakpoints @@ -117,6 +117,7 @@ export type Config = schema?: string | string[]; verbose?: boolean; strict?: boolean; + casing?: 'camelCase' | 'snake_case'; migrations?: { table?: string; schema?: string; @@ -125,18 +126,20 @@ export type Config = introspect?: { casing: 'camel' | 'preserve'; }; + entities?: { + roles?: boolean | { provider?: 'supabase' | 'neon' | string & {}; exclude?: string[]; include?: string[] }; + }; } & ( | { - dialect: Verify; - driver: Verify; + dialect: Verify; dbCredentials: { url: string; authToken?: string; }; } | { - dialect: 'sqlite'; + dialect: Verify; dbCredentials: { url: string; }; @@ -171,6 +174,13 @@ export type Config = resourceArn: string; }; } + | { + dialect: Verify; + driver: Verify; + dbCredentials: { + url: string; + }; + } | { dialect: Verify; dbCredentials: @@ -199,7 +209,26 @@ export type Config = dialect: Verify; driver: Verify; } + | { + dialect: Verify; + driver: Verify; + } | {} + | { + dialect: Verify; + dbCredentials: + | { + host: string; + port?: number; + user?: string; + password?: string; + database: string; + ssl?: string | SslOptions; + } + | { + url: string; + }; + } ); /** @@ -209,7 +238,7 @@ export type Config = * **Config** usage: * * `dialect` - mandatory and is responsible for explicitly providing a databse dialect you are using for all the commands - * *Possible values*: `postgresql`, `mysql`, `sqlite` + * *Possible values*: `postgresql`, `mysql`, `sqlite`, `singlestore` * * See https://orm.drizzle.team/kit-docs/config-reference#dialect * @@ -226,7 +255,7 @@ export type Config = * * --- * `driver` - optional param that is responsible for explicitly providing a driver to use when accessing a database - * *Possible values*: `aws-data-api`, `d1-http`, `expo`, `turso` + * *Possible values*: `aws-data-api`, `d1-http`, `expo`, `turso`, `pglite` * If you don't use AWS Data API, D1, Turso or Expo - ypu don't need this driver. You can check a driver strategy choice here: https://orm.drizzle.team/kit-docs/upgrade-21 * * See https://orm.drizzle.team/kit-docs/config-reference#driver @@ -250,7 +279,7 @@ export type Config = * * `breakpoints` - param lets you enable/disable SQL statement breakpoints in generated migrations. * It’s optional and true by default, it’s necessary to properly apply migrations on databases, - * that do not support multiple DDL alternation statements in one transaction(MySQL, SQLite) and + * that do not support multiple DDL alternation statements in one transaction(MySQL, SQLite, SingleStore) and * Drizzle ORM has to apply them sequentially one by one. * * See https://orm.drizzle.team/kit-docs/config-reference#breakpoints diff --git a/drizzle-kit/src/introspect-mysql.ts b/drizzle-kit/src/introspect-mysql.ts index fb1c71428..005a2af42 100644 --- a/drizzle-kit/src/introspect-mysql.ts +++ b/drizzle-kit/src/introspect-mysql.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ +import { toCamelCase } from 'drizzle-orm/casing'; import './@types/utils'; import type { Casing } from './cli/validations/common'; +import { assertUnreachable } from './global'; import { + CheckConstraint, Column, ForeignKey, Index, @@ -11,9 +14,7 @@ import { UniqueConstraint, } from './serializer/mysqlSchema'; import { indexName } from './serializer/mysqlSerializer'; - -// time precision to fsp -// {mode: "string"} for timestamp by default +import { unescapeSingleQuotes } from './utils'; const mysqlImportsList = new Set([ 'mysqlTable', @@ -101,15 +102,33 @@ const importsPatch = { const relations = new Set(); +const escapeColumnKey = (value: string) => { + if (/^(?![a-zA-Z_$][a-zA-Z0-9_$]*$).+$/.test(value)) { + return `"${value}"`; + } + return value; +}; + const prepareCasing = (casing?: Casing) => (value: string) => { - if (typeof casing === 'undefined') { - return value; + if (casing === 'preserve') { + return escapeColumnKey(value); } if (casing === 'camel') { - return value.camelCase(); + return escapeColumnKey(value.camelCase()); } - return value; + assertUnreachable(casing); +}; + +const dbColumnName = ({ name, casing, withMode = false }: { name: string; casing: Casing; withMode?: boolean }) => { + if (casing === 'preserve') { + return ''; + } + if (casing === 'camel') { + return toCamelCase(name) === name ? '' : withMode ? `"${name}", ` : `"${name}"`; + } + + assertUnreachable(casing); }; export const schemaToTypeScript = ( @@ -135,11 +154,15 @@ export const schemaToTypeScript = ( const uniqueImports = Object.values(it.uniqueConstraints).map( (it) => 'unique', ); + const checkImports = Object.values(it.checkConstraint).map( + (it) => 'check', + ); res.mysql.push(...idxImports); res.mysql.push(...fkImpots); res.mysql.push(...pkImports); res.mysql.push(...uniqueImports); + res.mysql.push(...checkImports); const columnImports = Object.values(it.columns) .map((col) => { @@ -153,6 +176,13 @@ export const schemaToTypeScript = ( patched = patched.startsWith('datetime(') ? 'datetime' : patched; patched = patched.startsWith('varbinary(') ? 'varbinary' : patched; patched = patched.startsWith('int(') ? 'int' : patched; + patched = patched.startsWith('double(') ? 'double' : patched; + patched = patched.startsWith('float(') ? 'float' : patched; + patched = patched.startsWith('int unsigned') ? 'int' : patched; + patched = patched.startsWith('tinyint unsigned') ? 'tinyint' : patched; + patched = patched.startsWith('smallint unsigned') ? 'smallint' : patched; + patched = patched.startsWith('mediumint unsigned') ? 'mediumint' : patched; + patched = patched.startsWith('bigint unsigned') ? 'bigint' : patched; return patched; }) .filter((type) => { @@ -165,6 +195,37 @@ export const schemaToTypeScript = ( { mysql: [] as string[] }, ); + Object.values(schema.views).forEach((it) => { + imports.mysql.push('mysqlView'); + + const columnImports = Object.values(it.columns) + .map((col) => { + let patched = importsPatch[col.type] ?? col.type; + patched = patched.startsWith('varchar(') ? 'varchar' : patched; + patched = patched.startsWith('char(') ? 'char' : patched; + patched = patched.startsWith('binary(') ? 'binary' : patched; + patched = patched.startsWith('decimal(') ? 'decimal' : patched; + patched = patched.startsWith('smallint(') ? 'smallint' : patched; + patched = patched.startsWith('enum(') ? 'mysqlEnum' : patched; + patched = patched.startsWith('datetime(') ? 'datetime' : patched; + patched = patched.startsWith('varbinary(') ? 'varbinary' : patched; + patched = patched.startsWith('int(') ? 'int' : patched; + patched = patched.startsWith('double(') ? 'double' : patched; + patched = patched.startsWith('float(') ? 'float' : patched; + patched = patched.startsWith('int unsigned') ? 'int' : patched; + patched = patched.startsWith('tinyint unsigned') ? 'tinyint' : patched; + patched = patched.startsWith('smallint unsigned') ? 'smallint' : patched; + patched = patched.startsWith('mediumint unsigned') ? 'mediumint' : patched; + patched = patched.startsWith('bigint unsigned') ? 'bigint' : patched; + return patched; + }) + .filter((type) => { + return mysqlImportsList.has(type); + }); + + imports.mysql.push(...columnImports); + }); + const tableStatements = Object.values(schema.tables).map((table) => { const func = 'mysqlTable'; let statement = ''; @@ -180,6 +241,7 @@ export const schemaToTypeScript = ( Object.values(table.columns), Object.values(table.foreignKeys), withCasing, + casing, table.name, schema, ); @@ -195,10 +257,10 @@ export const schemaToTypeScript = ( || filteredFKs.length > 0 || Object.keys(table.compositePrimaryKeys).length > 0 || Object.keys(table.uniqueConstraints).length > 0 + || Object.keys(table.checkConstraint).length > 0 ) { statement += ',\n'; - statement += '(table) => {\n'; - statement += '\treturn {\n'; + statement += '(table) => ['; statement += createTableIndexes( table.name, Object.values(table.indexes), @@ -213,14 +275,48 @@ export const schemaToTypeScript = ( Object.values(table.uniqueConstraints), withCasing, ); - statement += '\t}\n'; - statement += '}'; + statement += createTableChecks( + Object.values(table.checkConstraint), + withCasing, + ); + statement += '\n]'; } statement += ');'; return statement; }); + const viewsStatements = Object.values(schema.views).map((view) => { + const { columns, name, algorithm, definition, sqlSecurity, withCheckOption } = view; + const func = 'mysqlView'; + let statement = ''; + + if (imports.mysql.includes(withCasing(name))) { + statement = `// Table name is in conflict with ${ + withCasing( + view.name, + ) + } import.\n// Please change to any other name, that is not in imports list\n`; + } + statement += `export const ${withCasing(name)} = ${func}("${name}", {\n`; + statement += createTableColumns( + Object.values(columns), + [], + withCasing, + casing, + name, + schema, + ); + statement += '})'; + + statement += algorithm ? `.algorithm("${algorithm}")` : ''; + statement += sqlSecurity ? `.sqlSecurity("${sqlSecurity}")` : ''; + statement += withCheckOption ? `.withCheckOption("${withCheckOption}")` : ''; + statement += `.as(sql\`${definition?.replaceAll('`', '\\`')}\`);`; + + return statement; + }); + const uniqueMySqlImports = [ 'mysqlTable', 'mysqlSchema', @@ -235,6 +331,8 @@ export const schemaToTypeScript = ( let decalrations = ''; decalrations += tableStatements.join('\n\n'); + decalrations += '\n'; + decalrations += viewsStatements.join('\n\n'); const file = importsTs + decalrations; @@ -290,6 +388,7 @@ const column = ( type: string, name: string, casing: (value: string) => string, + rawCasing: Casing, defaultValue?: any, autoincrement?: boolean, onUpdate?: boolean, @@ -301,12 +400,15 @@ const column = ( } if (lowered === 'serial') { - return `${casing(name)}: serial("${name}")`; + return `${casing(name)}: serial(${dbColumnName({ name, casing: rawCasing })})`; } if (lowered.startsWith('int')) { const isUnsigned = lowered.startsWith('int unsigned'); - let out = `${casing(name)}: int("${name}"${isUnsigned ? ', { unsigned: true }' : ''})`; + const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); + let out = `${casing(name)}: int(${columnName}${ + isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' + })`; out += autoincrement ? `.autoincrement()` : ''; out += typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -316,8 +418,11 @@ const column = ( if (lowered.startsWith('tinyint')) { const isUnsigned = lowered.startsWith('tinyint unsigned'); + const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); // let out = `${name.camelCase()}: tinyint("${name}")`; - let out: string = `${casing(name)}: tinyint("${name}"${isUnsigned ? ', { unsigned: true }' : ''})`; + let out: string = `${casing(name)}: tinyint(${columnName}${ + isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' + })`; out += autoincrement ? `.autoincrement()` : ''; out += typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -327,7 +432,10 @@ const column = ( if (lowered.startsWith('smallint')) { const isUnsigned = lowered.startsWith('smallint unsigned'); - let out = `${casing(name)}: smallint("${name}"${isUnsigned ? ', { unsigned: true }' : ''})`; + const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); + let out = `${casing(name)}: smallint(${columnName}${ + isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' + })`; out += autoincrement ? `.autoincrement()` : ''; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -337,7 +445,10 @@ const column = ( if (lowered.startsWith('mediumint')) { const isUnsigned = lowered.startsWith('mediumint unsigned'); - let out = `${casing(name)}: mediumint("${name}"${isUnsigned ? ', { unsigned: true }' : ''})`; + const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); + let out = `${casing(name)}: mediumint(${columnName}${ + isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' + })`; out += autoincrement ? `.autoincrement()` : ''; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -347,7 +458,9 @@ const column = ( if (lowered.startsWith('bigint')) { const isUnsigned = lowered.startsWith('bigint unsigned'); - let out = `${casing(name)}: bigint("${name}", { mode: "number"${isUnsigned ? ', unsigned: true' : ''} })`; + let out = `${casing(name)}: bigint(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ mode: "number"${ + isUnsigned ? ', unsigned: true' : '' + } })`; out += autoincrement ? `.autoincrement()` : ''; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -356,7 +469,7 @@ const column = ( } if (lowered === 'boolean') { - let out = `${casing(name)}: boolean("${name}")`; + let out = `${casing(name)}: boolean(${dbColumnName({ name, casing: rawCasing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; @@ -365,19 +478,27 @@ const column = ( if (lowered.startsWith('double')) { let params: - | { precision: string | undefined; scale: string | undefined } + | { precision?: string; scale?: string; unsigned?: boolean } | undefined; - if (lowered.length > 6) { + if (lowered.length > (lowered.includes('unsigned') ? 15 : 6)) { const [precision, scale] = lowered - .slice(7, lowered.length - 1) + .slice(7, lowered.length - (1 + (lowered.includes('unsigned') ? 9 : 0))) .split(','); params = { precision, scale }; } + if (lowered.includes('unsigned')) { + params = { ...(params ?? {}), unsigned: true }; + } + + const timeConfigParams = params ? timeConfig(params) : undefined; + let out = params - ? `${casing(name)}: double("${name}", ${timeConfig(params)})` - : `${casing(name)}: double("${name}")`; + ? `${casing(name)}: double(${ + dbColumnName({ name, casing: rawCasing, withMode: timeConfigParams !== undefined }) + }${timeConfig(params)})` + : `${casing(name)}: double(${dbColumnName({ name, casing: rawCasing })})`; // let out = `${name.camelCase()}: double("${name}")`; out += defaultValue @@ -386,8 +507,23 @@ const column = ( return out; } - if (lowered === 'float') { - let out = `${casing(name)}: float("${name}")`; + if (lowered.startsWith('float')) { + let params: + | { precision?: string; scale?: string; unsigned?: boolean } + | undefined; + + if (lowered.length > (lowered.includes('unsigned') ? 14 : 5)) { + const [precision, scale] = lowered + .slice(6, lowered.length - (1 + (lowered.includes('unsigned') ? 9 : 0))) + .split(','); + params = { precision, scale }; + } + + if (lowered.includes('unsigned')) { + params = { ...(params ?? {}), unsigned: true }; + } + + let out = `${casing(name)}: float(${dbColumnName({ name, casing: rawCasing })}${params ? timeConfig(params) : ''})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; @@ -395,7 +531,7 @@ const column = ( } if (lowered === 'real') { - let out = `${casing(name)}: real("${name}")`; + let out = `${casing(name)}: real(${dbColumnName({ name, casing: rawCasing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; @@ -412,8 +548,10 @@ const column = ( const params = timeConfig({ fsp, mode: "'string'" }); let out = params - ? `${casing(name)}: timestamp("${name}", ${params})` - : `${casing(name)}: timestamp("${name}")`; + ? `${casing(name)}: timestamp(${ + dbColumnName({ name, casing: rawCasing, withMode: params !== undefined }) + }${params})` + : `${casing(name)}: timestamp(${dbColumnName({ name, casing: rawCasing })})`; // mysql has only CURRENT_TIMESTAMP, as I found from docs. But will leave now() for just a case defaultValue = defaultValue === 'now()' || defaultValue === '(CURRENT_TIMESTAMP)' @@ -440,8 +578,8 @@ const column = ( const params = timeConfig({ fsp }); let out = params - ? `${casing(name)}: time("${name}", ${params})` - : `${casing(name)}: time("${name}")`; + ? `${casing(name)}: time(${dbColumnName({ name, casing: rawCasing, withMode: params !== undefined })}${params})` + : `${casing(name)}: time(${dbColumnName({ name, casing: rawCasing })})`; defaultValue = defaultValue === 'now()' ? '.defaultNow()' @@ -458,7 +596,7 @@ const column = ( casing( name, ) - }: date("${name}", { mode: 'string' })`; + }: date(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ mode: 'string' })`; defaultValue = defaultValue === 'now()' ? '.defaultNow()' @@ -472,7 +610,7 @@ const column = ( // in mysql text can't have default value. Will leave it in case smth ;) if (lowered === 'text') { - let out = `${casing(name)}: text("${name}")`; + let out = `${casing(name)}: text(${dbColumnName({ name, casing: rawCasing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; @@ -481,7 +619,7 @@ const column = ( // in mysql text can't have default value. Will leave it in case smth ;) if (lowered === 'tinytext') { - let out = `${casing(name)}: tinytext("${name}")`; + let out = `${casing(name)}: tinytext(${dbColumnName({ name, casing: rawCasing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; @@ -490,7 +628,7 @@ const column = ( // in mysql text can't have default value. Will leave it in case smth ;) if (lowered === 'mediumtext') { - let out = `${casing(name)}: mediumtext("${name}")`; + let out = `${casing(name)}: mediumtext(${dbColumnName({ name, casing: rawCasing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; @@ -499,7 +637,7 @@ const column = ( // in mysql text can't have default value. Will leave it in case smth ;) if (lowered === 'longtext') { - let out = `${casing(name)}: longtext("${name}")`; + let out = `${casing(name)}: longtext(${dbColumnName({ name, casing: rawCasing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; @@ -507,7 +645,7 @@ const column = ( } if (lowered === 'year') { - let out = `${casing(name)}: year("${name}")`; + let out = `${casing(name)}: year(${dbColumnName({ name, casing: rawCasing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; @@ -516,7 +654,7 @@ const column = ( // in mysql json can't have default value. Will leave it in case smth ;) if (lowered === 'json') { - let out = `${casing(name)}: json("${name}")`; + let out = `${casing(name)}: json(${dbColumnName({ name, casing: rawCasing })})`; out += defaultValue ? `.default(${mapColumnDefaultForJson(defaultValue)})` @@ -530,15 +668,16 @@ const column = ( casing( name, ) - }: varchar("${name}", { length: ${ + }: varchar(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ length: ${ lowered.substring( 'varchar'.length + 1, lowered.length - 1, ) } })`; + const mappedDefaultValue = mapColumnDefault(defaultValue, isExpression); out += defaultValue - ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + ? `.default(${isExpression ? mappedDefaultValue : unescapeSingleQuotes(mappedDefaultValue, true)})` : ''; return out; } @@ -548,7 +687,7 @@ const column = ( casing( name, ) - }: char("${name}", { length: ${ + }: char(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ length: ${ lowered.substring( 'char'.length + 1, lowered.length - 1, @@ -573,13 +712,13 @@ const column = ( casing( name, ) - }: datetime("${name}", { mode: 'string', fsp: ${ + }: datetime(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ mode: 'string', fsp: ${ lowered.substring( 'datetime'.length + 1, lowered.length - 1, ) } })` - : `${casing(name)}: datetime("${name}", { mode: 'string'})`; + : `${casing(name)}: datetime(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ mode: 'string'})`; defaultValue = defaultValue === 'now()' ? '.defaultNow()' @@ -593,19 +732,27 @@ const column = ( if (lowered.startsWith('decimal')) { let params: - | { precision: string | undefined; scale: string | undefined } + | { precision?: string; scale?: string; unsigned?: boolean } | undefined; - if (lowered.length > 7) { + if (lowered.length > (lowered.includes('unsigned') ? 16 : 7)) { const [precision, scale] = lowered - .slice(8, lowered.length - 1) + .slice(8, lowered.length - (1 + (lowered.includes('unsigned') ? 9 : 0))) .split(','); params = { precision, scale }; } + if (lowered.includes('unsigned')) { + params = { ...(params ?? {}), unsigned: true }; + } + + const timeConfigParams = params ? timeConfig(params) : undefined; + let out = params - ? `${casing(name)}: decimal("${name}", ${timeConfig(params)})` - : `${casing(name)}: decimal("${name}")`; + ? `${casing(name)}: decimal(${ + dbColumnName({ name, casing: rawCasing, withMode: timeConfigParams !== undefined }) + }${timeConfigParams})` + : `${casing(name)}: decimal(${dbColumnName({ name, casing: rawCasing })})`; defaultValue = typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -625,8 +772,8 @@ const column = ( const params = binaryConfig({ length }); let out = params - ? `${casing(name)}: binary("${name}", ${params})` - : `${casing(name)}: binary("${name}")`; + ? `${casing(name)}: binary(${dbColumnName({ name, casing: rawCasing, withMode: params !== undefined })}${params})` + : `${casing(name)}: binary(${dbColumnName({ name, casing: rawCasing })})`; defaultValue = defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -637,10 +784,15 @@ const column = ( } if (lowered.startsWith('enum')) { - const values = lowered.substring('enum'.length + 1, lowered.length - 1); - let out = `${casing(name)}: mysqlEnum("${name}", [${values}])`; + const values = lowered + .substring('enum'.length + 1, lowered.length - 1) + .split(',') + .map((v) => unescapeSingleQuotes(v, true)) + .join(','); + let out = `${casing(name)}: mysqlEnum(${dbColumnName({ name, casing: rawCasing, withMode: true })}[${values}])`; + const mappedDefaultValue = mapColumnDefault(defaultValue, isExpression); out += defaultValue - ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + ? `.default(${isExpression ? mappedDefaultValue : unescapeSingleQuotes(mappedDefaultValue, true)})` : ''; return out; } @@ -655,8 +807,10 @@ const column = ( const params = binaryConfig({ length }); let out = params - ? `${casing(name)}: varbinary("${name}", ${params})` - : `${casing(name)}: varbinary("${name}")`; + ? `${casing(name)}: varbinary(${ + dbColumnName({ name, casing: rawCasing, withMode: params !== undefined }) + }${params})` + : `${casing(name)}: varbinary(${dbColumnName({ name, casing: rawCasing })})`; defaultValue = defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -674,6 +828,7 @@ const createTableColumns = ( columns: Column[], fks: ForeignKey[], casing: (val: string) => string, + rawCasing: Casing, tableName: string, schema: MySqlSchemaInternal, ): string => { @@ -699,6 +854,7 @@ const createTableColumns = ( it.type, it.name, casing, + rawCasing, it.default, it.autoincrement, it.onUpdate, @@ -771,7 +927,7 @@ const createTableIndexes = ( const indexGeneratedName = indexName(tableName, it.columns); const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += it.isUnique ? 'uniqueIndex(' : 'index('; statement += `${escapedIndexName})`; statement += `.on(${ @@ -779,7 +935,6 @@ const createTableIndexes = ( .map((it) => `table.${casing(it)}`) .join(', ') }),`; - statement += `\n`; }); return statement; @@ -794,7 +949,7 @@ const createTableUniques = ( unqs.forEach((it) => { const idxKey = casing(it.name); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'unique('; statement += `"${it.name}")`; statement += `.on(${ @@ -802,7 +957,23 @@ const createTableUniques = ( .map((it) => `table.${casing(it)}`) .join(', ') }),`; - statement += `\n`; + }); + + return statement; +}; + +const createTableChecks = ( + checks: CheckConstraint[], + casing: (value: string) => string, +): string => { + let statement = ''; + + checks.forEach((it) => { + statement += `\n\t`; + statement += 'check('; + statement += `"${it.name}", `; + statement += `sql\`${it.value.replace(/`/g, '\\`')}\`)`; + statement += `,`; }); return statement; @@ -817,7 +988,7 @@ const createTablePKs = ( pks.forEach((it) => { let idxKey = casing(it.name); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'primaryKey({ columns: ['; statement += `${ it.columns @@ -827,7 +998,6 @@ const createTablePKs = ( .join(', ') }]${it.name ? `, name: "${it.name}"` : ''}}`; statement += '),'; - statement += `\n`; }); return statement; @@ -842,7 +1012,8 @@ const createTableFKs = ( fks.forEach((it) => { const isSelf = it.tableTo === it.tableFrom; const tableTo = isSelf ? 'table' : `${casing(it.tableTo)}`; - statement += `\t\t${casing(it.name)}: foreignKey({\n`; + statement += `\n\t`; + statement += `foreignKey({\n`; statement += `\t\t\tcolumns: [${ it.columnsFrom .map((i) => `table.${casing(i)}`) @@ -864,7 +1035,7 @@ const createTableFKs = ( ? `.onDelete("${it.onDelete}")` : ''; - statement += `,\n`; + statement += `,`; }); return statement; diff --git a/drizzle-kit/src/introspect-pg.ts b/drizzle-kit/src/introspect-pg.ts index d20a96562..4bb65ee0c 100644 --- a/drizzle-kit/src/introspect-pg.ts +++ b/drizzle-kit/src/introspect-pg.ts @@ -8,21 +8,23 @@ import { Relation, Relations, } from 'drizzle-orm/relations'; -import { plural, singular } from 'pluralize'; import './@types/utils'; +import { toCamelCase } from 'drizzle-orm/casing'; import { Casing } from './cli/validations/common'; -import { vectorOps } from './extensions/vector'; import { assertUnreachable } from './global'; import { + CheckConstraint, Column, ForeignKey, Index, PgKitInternals, PgSchemaInternal, + Policy, PrimaryKey, UniqueConstraint, } from './serializer/pgSchema'; import { indexName } from './serializer/pgSerializer'; +import { unescapeSingleQuotes } from './utils'; const pgImportsList = new Set([ 'pgTable', @@ -133,13 +135,19 @@ const intervalConfig = (str: string) => { if (keys.length === 0) return; let statement = '{ '; - statement += keys - .map((it: keyof typeof json) => `${it}: ${json[it]}`) - .join(', '); + statement += keys.map((it: keyof typeof json) => `${it}: ${json[it]}`).join(', '); statement += ' }'; return statement; }; +const mapColumnDefault = (defaultValue: any, isExpression?: boolean) => { + if (isExpression) { + return `sql\`${defaultValue}\``; + } + + return defaultValue; +}; + const importsPatch = { 'double precision': 'doublePrecision', 'timestamp without time zone': 'timestamp', @@ -150,12 +158,30 @@ const importsPatch = { const relations = new Set(); +const escapeColumnKey = (value: string) => { + if (/^(?![a-zA-Z_$][a-zA-Z0-9_$]*$).+$/.test(value)) { + return `"${value}"`; + } + return value; +}; + const withCasing = (value: string, casing: Casing) => { if (casing === 'preserve') { - return value; + return escapeColumnKey(value); + } + if (casing === 'camel') { + return escapeColumnKey(value.camelCase()); + } + + assertUnreachable(casing); +}; + +const dbColumnName = ({ name, casing, withMode = false }: { name: string; casing: Casing; withMode?: boolean }) => { + if (casing === 'preserve') { + return ''; } if (casing === 'camel') { - return value.camelCase(); + return toCamelCase(name) === name ? '' : withMode ? `"${name}", ` : `"${name}"`; } assertUnreachable(casing); @@ -181,10 +207,7 @@ export const relationsToTypeScriptForStudio = ( ...relations, }; - const relationsConfig = extractTablesRelationalConfig( - relationalSchema, - createTableRelationsHelpers, - ); + const relationsConfig = extractTablesRelationalConfig(relationalSchema, createTableRelationsHelpers); let result = ''; @@ -213,45 +236,29 @@ export const relationsToTypeScriptForStudio = ( if (is(relation, Many)) { hasMany = true; relationsObjAsStr += `\t\t${relation.fieldName}: many(${ - relationsConfig.tableNamesMap[relation.referencedTableName].split( - '.', - )[1] - }${ - typeof relation.relationName !== 'undefined' - ? `, { relationName: "${relation.relationName}"}` - : '' - }),`; + relationsConfig.tableNamesMap[relation.referencedTableName].split('.')[1] + }${typeof relation.relationName !== 'undefined' ? `, { relationName: "${relation.relationName}"}` : ''}),`; } if (is(relation, One)) { hasOne = true; relationsObjAsStr += `\t\t${relation.fieldName}: one(${ - relationsConfig.tableNamesMap[relation.referencedTableName].split( - '.', - )[1] + relationsConfig.tableNamesMap[relation.referencedTableName].split('.')[1] }, { fields: [${ relation.config?.fields.map( (c) => - `${ - relationsConfig.tableNamesMap[ - getTableName(relation.sourceTable) - ].split('.')[1] - }.${findColumnKey(relation.sourceTable, c.name)}`, + `${relationsConfig.tableNamesMap[getTableName(relation.sourceTable)].split('.')[1]}.${ + findColumnKey(relation.sourceTable, c.name) + }`, ) }], references: [${ relation.config?.references.map( (c) => - `${ - relationsConfig.tableNamesMap[ - getTableName(relation.referencedTable) - ].split('.')[1] - }.${findColumnKey(relation.referencedTable, c.name)}`, + `${relationsConfig.tableNamesMap[getTableName(relation.referencedTable)].split('.')[1]}.${ + findColumnKey(relation.referencedTable, c.name) + }`, ) - }]${ - typeof relation.relationName !== 'undefined' - ? `, relationName: "${relation.relationName}"` - : '' - }}),`; + }]${typeof relation.relationName !== 'undefined' ? `, relationName: "${relation.relationName}"` : ''}}),`; } }); @@ -267,15 +274,39 @@ export const relationsToTypeScriptForStudio = ( return result; }; +function generateIdentityParams(identity: Column['identity']) { + let paramsObj = `{ name: "${identity!.name}"`; + if (identity?.startWith) { + paramsObj += `, startWith: ${identity.startWith}`; + } + if (identity?.increment) { + paramsObj += `, increment: ${identity.increment}`; + } + if (identity?.minValue) { + paramsObj += `, minValue: ${identity.minValue}`; + } + if (identity?.maxValue) { + paramsObj += `, maxValue: ${identity.maxValue}`; + } + if (identity?.cache) { + paramsObj += `, cache: ${identity.cache}`; + } + if (identity?.cycle) { + paramsObj += `, cycle: true`; + } + paramsObj += ' }'; + if (identity?.type === 'always') { + return `.generatedAlwaysAsIdentity(${paramsObj})`; + } + return `.generatedByDefaultAsIdentity(${paramsObj})`; +} + export const paramNameFor = (name: string, schema?: string) => { const schemaSuffix = schema && schema !== 'public' ? `In${schema.capitalise()}` : ''; return `${name}${schemaSuffix}`; }; -export const schemaToTypeScript = ( - schema: PgSchemaInternal, - casing: Casing, -) => { +export const schemaToTypeScript = (schema: PgSchemaInternal, casing: Casing) => { // collectFKs Object.values(schema.tables).forEach((table) => { Object.values(table.foreignKeys).forEach((fk) => { @@ -290,22 +321,27 @@ export const schemaToTypeScript = ( }), ); - const enumTypes = new Set(Object.values(schema.enums).map((it) => it.name)); + const enumTypes = Object.values(schema.enums).reduce((acc, cur) => { + acc.add(`${cur.schema}.${cur.name}`); + return acc; + }, new Set()); const imports = Object.values(schema.tables).reduce( (res, it) => { - const idxImports = Object.values(it.indexes).map((idx) => idx.isUnique ? 'uniqueIndex' : 'index'); + const idxImports = Object.values(it.indexes).map((idx) => (idx.isUnique ? 'uniqueIndex' : 'index')); const fkImpots = Object.values(it.foreignKeys).map((it) => 'foreignKey'); - if ( - Object.values(it.foreignKeys).some((it) => isCyclic(it) && !isSelf(it)) - ) { + if (Object.values(it.foreignKeys).some((it) => isCyclic(it) && !isSelf(it))) { res.pg.push('type AnyPgColumn'); } - const pkImports = Object.values(it.compositePrimaryKeys).map( - (it) => 'primaryKey', + const pkImports = Object.values(it.compositePrimaryKeys).map((it) => 'primaryKey'); + const uniqueImports = Object.values(it.uniqueConstraints).map((it) => 'unique'); + + const checkImports = Object.values(it.checkConstraints).map( + (it) => 'check', ); - const uniqueImports = Object.values(it.uniqueConstraints).map( - (it) => 'unique', + + const policiesImports = Object.values(it.policies).map( + (it) => 'pgPolicy', ); if (it.schema && it.schema !== 'public' && it.schema !== '') { @@ -316,14 +352,13 @@ export const schemaToTypeScript = ( res.pg.push(...fkImpots); res.pg.push(...pkImports); res.pg.push(...uniqueImports); - - if (enumTypes.size > 0) { - res.pg.push('pgEnum'); - } + res.pg.push(...policiesImports); + res.pg.push(...checkImports); const columnImports = Object.values(it.columns) .map((col) => { - let patched: string = importsPatch[col.type] || col.type; + let patched: string = (importsPatch[col.type] || col.type).replace('[]', ''); + patched = patched === 'double precision' ? 'doublePrecision' : patched; patched = patched.startsWith('varchar(') ? 'varchar' : patched; patched = patched.startsWith('char(') ? 'char' : patched; patched = patched.startsWith('numeric(') ? 'numeric' : patched; @@ -343,6 +378,55 @@ export const schemaToTypeScript = ( { pg: [] as string[] }, ); + Object.values(schema.views).forEach((it) => { + if (it.schema && it.schema !== 'public' && it.schema !== '') { + imports.pg.push('pgSchema'); + } else if (it.schema === 'public') { + it.materialized ? imports.pg.push('pgMaterializedView') : imports.pg.push('pgView'); + } + + Object.values(it.columns).forEach(() => { + const columnImports = Object.values(it.columns) + .map((col) => { + let patched: string = (importsPatch[col.type] || col.type).replace('[]', ''); + patched = patched === 'double precision' ? 'doublePrecision' : patched; + patched = patched.startsWith('varchar(') ? 'varchar' : patched; + patched = patched.startsWith('char(') ? 'char' : patched; + patched = patched.startsWith('numeric(') ? 'numeric' : patched; + patched = patched.startsWith('time(') ? 'time' : patched; + patched = patched.startsWith('timestamp(') ? 'timestamp' : patched; + patched = patched.startsWith('vector(') ? 'vector' : patched; + patched = patched.startsWith('geometry(') ? 'geometry' : patched; + return patched; + }) + .filter((type) => { + return pgImportsList.has(type); + }); + + imports.pg.push(...columnImports); + }); + }); + + Object.values(schema.sequences).forEach((it) => { + if (it.schema && it.schema !== 'public' && it.schema !== '') { + imports.pg.push('pgSchema'); + } else if (it.schema === 'public') { + imports.pg.push('pgSequence'); + } + }); + + Object.values(schema.enums).forEach((it) => { + if (it.schema && it.schema !== 'public' && it.schema !== '') { + imports.pg.push('pgSchema'); + } else if (it.schema === 'public') { + imports.pg.push('pgEnum'); + } + }); + + if (Object.keys(schema.roles).length > 0) { + imports.pg.push('pgRole'); + } + const enumStatements = Object.values(schema.enums) .map((it) => { const enumSchema = schemas[it.schema]; @@ -352,13 +436,50 @@ export const schemaToTypeScript = ( const func = enumSchema ? `${enumSchema}.enum` : 'pgEnum'; const values = Object.values(it.values) - .map((it) => `'${it}'`) + .map((it) => `'${unescapeSingleQuotes(it, false)}'`) .join(', '); return `export const ${withCasing(paramName, casing)} = ${func}("${it.name}", [${values}])\n`; }) .join('') .concat('\n'); + const sequencesStatements = Object.values(schema.sequences) + .map((it) => { + const seqSchema = schemas[it.schema]; + const paramName = paramNameFor(it.name, seqSchema); + + const func = seqSchema ? `${seqSchema}.sequence` : 'pgSequence'; + + let params = ''; + + if (it.startWith) { + params += `, startWith: "${it.startWith}"`; + } + if (it.increment) { + params += `, increment: "${it.increment}"`; + } + if (it.minValue) { + params += `, minValue: "${it.minValue}"`; + } + if (it.maxValue) { + params += `, maxValue: "${it.maxValue}"`; + } + if (it.cache) { + params += `, cache: "${it.cache}"`; + } + if (it.cycle) { + params += `, cycle: true`; + } else { + params += `, cycle: false`; + } + + return `export const ${withCasing(paramName, casing)} = ${func}("${it.name}"${ + params ? `, { ${params.trimChar(',')} }` : '' + })\n`; + }) + .join('') + .concat(''); + const schemaStatements = Object.entries(schemas) // .filter((it) => it[0] !== "public") .map((it) => { @@ -366,6 +487,24 @@ export const schemaToTypeScript = ( }) .join(''); + const rolesNameToTsKey: Record = {}; + + const rolesStatements = Object.entries(schema.roles) + .map((it) => { + const fields = it[1]; + rolesNameToTsKey[fields.name] = it[0]; + return `export const ${withCasing(it[0], casing)} = pgRole("${fields.name}", ${ + !fields.createDb && !fields.createRole && fields.inherit + ? '' + : `${ + `, { ${fields.createDb ? `createDb: true,` : ''}${fields.createRole ? ` createRole: true,` : ''}${ + !fields.inherit ? ` inherit: false ` : '' + }`.trimChar(',') + }}` + } );\n`; + }) + .join(''); + const tableStatements = Object.values(schema.tables).map((table) => { const tableSchema = schemas[table.schema]; const paramName = paramNameFor(table.name, tableSchema); @@ -384,25 +523,23 @@ export const schemaToTypeScript = ( statement += '}'; // more than 2 fields or self reference or cyclic - const filteredFKs = Object.values(table.foreignKeys).filter((it) => { - return it.columnsFrom.length > 1 || isSelf(it); - }); + // Andrii: I switched this one off until we will get custom names in .references() + // const filteredFKs = Object.values(table.foreignKeys).filter((it) => { + // return it.columnsFrom.length > 1 || isSelf(it); + // }); if ( Object.keys(table.indexes).length > 0 - || filteredFKs.length > 0 + || Object.values(table.foreignKeys).length > 0 + || Object.values(table.policies).length > 0 || Object.keys(table.compositePrimaryKeys).length > 0 || Object.keys(table.uniqueConstraints).length > 0 + || Object.keys(table.checkConstraints).length > 0 ) { - statement += ',\n'; - statement += '(table) => {\n'; - statement += '\treturn {\n'; - statement += createTableIndexes( - table.name, - Object.values(table.indexes), - casing, - ); - statement += createTableFKs(Object.values(filteredFKs), schemas, casing); + statement += ', '; + statement += '(table) => ['; + statement += createTableIndexes(table.name, Object.values(table.indexes), casing); + statement += createTableFKs(Object.values(table.foreignKeys), schemas, casing); statement += createTablePKs( Object.values(table.compositePrimaryKeys), casing, @@ -411,14 +548,59 @@ export const schemaToTypeScript = ( Object.values(table.uniqueConstraints), casing, ); - statement += '\t}\n'; - statement += '}'; + statement += createTablePolicies( + Object.values(table.policies), + casing, + rolesNameToTsKey, + ); + statement += createTableChecks( + Object.values(table.checkConstraints), + casing, + ); + statement += '\n]'; } statement += ');'; return statement; }); + const viewsStatements = Object.values(schema.views) + .map((it) => { + const viewSchema = schemas[it.schema]; + + const paramName = paramNameFor(it.name, viewSchema); + + const func = viewSchema + ? (it.materialized ? `${viewSchema}.materializedView` : `${viewSchema}.view`) + : it.materialized + ? 'pgMaterializedView' + : 'pgView'; + + const withOption = it.with ?? ''; + + const as = `sql\`${it.definition}\``; + + const tablespace = it.tablespace ?? ''; + + const columns = createTableColumns( + '', + Object.values(it.columns), + [], + enumTypes, + schemas, + casing, + schema.internal, + ); + + let statement = `export const ${withCasing(paramName, casing)} = ${func}("${it.name}", {${columns}})`; + statement += tablespace ? `.tablespace("${tablespace}")` : ''; + statement += withOption ? `.with(${JSON.stringify(withOption)})` : ''; + statement += `.as(${as});`; + + return statement; + }) + .join('\n\n'); + const uniquePgImports = ['pgTable', ...new Set(imports.pg)]; const importsTs = `import { ${ @@ -426,12 +608,16 @@ export const schemaToTypeScript = ( ', ', ) } } from "drizzle-orm/pg-core" - import { sql } from "drizzle-orm"\n\n`; +import { sql } from "drizzle-orm"\n\n`; let decalrations = schemaStatements; + decalrations += rolesStatements; decalrations += enumStatements; + decalrations += sequencesStatements; decalrations += '\n'; decalrations += tableStatements.join('\n\n'); + decalrations += '\n'; + decalrations += viewsStatements; const file = importsTs + decalrations; @@ -459,103 +645,278 @@ const isSelf = (fk: ForeignKey) => { return fk.tableFrom === fk.tableTo; }; +const buildArrayDefault = (defaultValue: string, typeName: string): string => { + if (typeof defaultValue === 'string' && !(defaultValue.startsWith('{') || defaultValue.startsWith("'{"))) { + return `sql\`${defaultValue}\``; + } + defaultValue = defaultValue.substring(2, defaultValue.length - 2); + return `[${ + defaultValue + .split(/\s*,\s*/g) + .map((value) => { + // if (['integer', 'smallint', 'bigint', 'double precision', 'real'].includes(typeName)) { + // return value; + // } else if (typeName === 'interval') { + // return value.replaceAll('"', "'"); + // } else if (typeName === 'boolean') { + // return value === 't' ? 'true' : 'false'; + if (typeName === 'json' || typeName === 'jsonb') { + return value.substring(1, value.length - 1).replaceAll('\\', ''); + } + return value; + // } + }) + .join(', ') + }]`; +}; + +const mapDefault = ( + tableName: string, + type: string, + name: string, + enumTypes: Set, + typeSchema: string, + defaultValue?: any, + internals?: PgKitInternals, +) => { + const isExpression = internals?.tables[tableName]?.columns[name]?.isDefaultAnExpression ?? false; + const isArray = internals?.tables[tableName]?.columns[name]?.isArray ?? false; + const lowered = type.toLowerCase().replace('[]', ''); + + if (isArray) { + return typeof defaultValue !== 'undefined' ? `.default(${buildArrayDefault(defaultValue, lowered)})` : ''; + } + + if (enumTypes.has(`${typeSchema}.${type.replace('[]', '')}`)) { + return typeof defaultValue !== 'undefined' + ? `.default(${mapColumnDefault(unescapeSingleQuotes(defaultValue, true), isExpression)})` + : ''; + } + + if (lowered.startsWith('integer')) { + return typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; + } + + if (lowered.startsWith('smallint')) { + return typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; + } + + if (lowered.startsWith('bigint')) { + return typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; + } + + if (lowered.startsWith('boolean')) { + return typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; + } + + if (lowered.startsWith('double precision')) { + return typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; + } + + if (lowered.startsWith('real')) { + return typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; + } + + if (lowered.startsWith('uuid')) { + return defaultValue === 'gen_random_uuid()' + ? '.defaultRandom()' + : defaultValue + ? `.default(sql\`${defaultValue}\`)` + : ''; + } + + if (lowered.startsWith('numeric')) { + defaultValue = defaultValue + ? (defaultValue.startsWith(`'`) && defaultValue.endsWith(`'`) + ? defaultValue.substring(1, defaultValue.length - 1) + : defaultValue) + : undefined; + return defaultValue ? `.default('${mapColumnDefault(defaultValue, isExpression)}')` : ''; + } + + if (lowered.startsWith('timestamp')) { + return defaultValue === 'now()' + ? '.defaultNow()' + : /^'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?([+-]\d{2}(:\d{2})?)?'$/.test(defaultValue) // Matches 'YYYY-MM-DD HH:MI:SS', 'YYYY-MM-DD HH:MI:SS.FFFFFF', 'YYYY-MM-DD HH:MI:SS+TZ', 'YYYY-MM-DD HH:MI:SS.FFFFFF+TZ' and 'YYYY-MM-DD HH:MI:SS+HH:MI' + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : defaultValue + ? `.default(sql\`${defaultValue}\`)` + : ''; + } + + if (lowered.startsWith('time')) { + return defaultValue === 'now()' + ? '.defaultNow()' + : /^'\d{2}:\d{2}(:\d{2})?(\.\d+)?'$/.test(defaultValue) // Matches 'HH:MI', 'HH:MI:SS' and 'HH:MI:SS.FFFFFF' + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : defaultValue + ? `.default(sql\`${defaultValue}\`)` + : ''; + } + + if (lowered.startsWith('interval')) { + return defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; + } + + if (lowered === 'date') { + return defaultValue === 'now()' + ? '.defaultNow()' + : /^'\d{4}-\d{2}-\d{2}'$/.test(defaultValue) // Matches 'YYYY-MM-DD' + ? `.default(${defaultValue})` + : defaultValue + ? `.default(sql\`${defaultValue}\`)` + : ''; + } + + if (lowered.startsWith('text')) { + return typeof defaultValue !== 'undefined' + ? `.default(${mapColumnDefault(unescapeSingleQuotes(defaultValue, true), isExpression)})` + : ''; + } + + if (lowered.startsWith('jsonb')) { + const def = typeof defaultValue !== 'undefined' + ? defaultValue.replace(/::(.*?)(?, + typeSchema: string, casing: Casing, defaultValue?: any, internals?: PgKitInternals, ) => { - const lowered = type.toLowerCase(); + const isExpression = internals?.tables[tableName]?.columns[name]?.isDefaultAnExpression ?? false; + const lowered = type.toLowerCase().replace('[]', ''); + + if (enumTypes.has(`${typeSchema}.${type.replace('[]', '')}`)) { + let out = `${withCasing(name, casing)}: ${withCasing(paramNameFor(type.replace('[]', ''), typeSchema), casing)}(${ + dbColumnName({ name, casing }) + })`; + return out; + } + if (lowered.startsWith('serial')) { - return `${withCasing(name, casing)}: serial("${name}")`; + return `${withCasing(name, casing)}: serial(${dbColumnName({ name, casing })})`; } if (lowered.startsWith('smallserial')) { - return `${withCasing(name, casing)}: smallserial("${name}")`; + return `${withCasing(name, casing)}: smallserial(${dbColumnName({ name, casing })})`; } if (lowered.startsWith('bigserial')) { - return `${ - withCasing( - name, - casing, - ) - }: bigserial("${name}", { mode: "bigint" })`; + return `${withCasing(name, casing)}: bigserial(${ + dbColumnName({ name, casing, withMode: true }) + }{ mode: "bigint" })`; } if (lowered.startsWith('integer')) { - let out = `${withCasing(name, casing)}: integer("${name}")`; - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + let out = `${withCasing(name, casing)}: integer(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('smallint')) { - let out = `${withCasing(name, casing)}: smallint("${name}")`; - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + let out = `${withCasing(name, casing)}: smallint(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('bigint')) { let out = `// You can use { mode: "bigint" } if numbers are exceeding js number limitations\n\t`; - out += `${withCasing(name, casing)}: bigint("${name}", { mode: "number" })`; - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + out += `${withCasing(name, casing)}: bigint(${dbColumnName({ name, casing, withMode: true })}{ mode: "number" })`; return out; } if (lowered.startsWith('boolean')) { - let out = `${withCasing(name, casing)}: boolean("${name}")`; - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + let out = `${withCasing(name, casing)}: boolean(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('double precision')) { - let out = `${withCasing(name, casing)}: doublePrecision("${name}")`; - out += defaultValue ? `.default(${defaultValue})` : ''; + let out = `${withCasing(name, casing)}: doublePrecision(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('real')) { - let out = `${withCasing(name, casing)}: real("${name}")`; - out += defaultValue ? `.default(${defaultValue})` : ''; + let out = `${withCasing(name, casing)}: real(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('uuid')) { - let out = `${withCasing(name, casing)}: uuid("${name}")`; + let out = `${withCasing(name, casing)}: uuid(${dbColumnName({ name, casing })})`; - out += defaultValue === 'gen_random_uuid()' - ? '.defaultRandom()' - : defaultValue - ? `.default(sql\`${defaultValue}\`)` - : ''; return out; } if (lowered.startsWith('numeric')) { - let params: - | { precision: string | undefined; scale: string | undefined } - | undefined; + let params: { precision: string | undefined; scale: string | undefined } | undefined; if (lowered.length > 7) { - const [precision, scale] = lowered - .slice(8, lowered.length - 1) - .split(','); + const [precision, scale] = lowered.slice(8, lowered.length - 1).split(','); params = { precision, scale }; } let out = params - ? `${withCasing(name, casing)}: numeric("${name}", ${timeConfig(params)})` - : `${withCasing(name, casing)}: numeric("${name}")`; - - defaultValue = defaultValue - ? defaultValue.startsWith(`'`) && defaultValue.endsWith(`'`) - ? defaultValue.substring(1, defaultValue.length - 1) - : defaultValue - : undefined; - out += defaultValue ? `.default('${defaultValue}')` : ''; + ? `${withCasing(name, casing)}: numeric(${dbColumnName({ name, casing, withMode: true })}${timeConfig(params)})` + : `${withCasing(name, casing)}: numeric(${dbColumnName({ name, casing })})`; return out; } @@ -564,11 +925,7 @@ const column = ( const withTimezone = lowered.includes('with time zone'); // const split = lowered.split(" "); let precision = lowered.startsWith('timestamp(') - ? Number( - lowered - .split(' ')[0] - .substring('timestamp('.length, lowered.split(' ')[0].length - 1), - ) + ? Number(lowered.split(' ')[0].substring('timestamp('.length, lowered.split(' ')[0].length - 1)) : null; precision = precision ? precision : null; @@ -579,24 +936,9 @@ const column = ( }); let out = params - ? `${withCasing(name, casing)}: timestamp("${name}", ${params})` - : `${withCasing(name, casing)}: timestamp("${name}")`; - - // defaultValue = defaultValue?.endsWith("::timestamp without time zone") - // ? defaultValue.substring(0, defaultValue.length - 29) - // : defaultValue; - - // defaultValue = defaultValue?.endsWith("::timestamp with time zone") - // ? defaultValue.substring(0, defaultValue.length - 26) - // : defaultValue; - - defaultValue = defaultValue === 'now()' || defaultValue === 'CURRENT_TIMESTAMP' - ? '.defaultNow()' - : defaultValue - ? `.default(${defaultValue})` - : ''; + ? `${withCasing(name, casing)}: timestamp(${dbColumnName({ name, casing, withMode: true })}${params})` + : `${withCasing(name, casing)}: timestamp(${dbColumnName({ name, casing })})`; - out += defaultValue; return out; } @@ -604,27 +946,16 @@ const column = ( const withTimezone = lowered.includes('with time zone'); let precision = lowered.startsWith('time(') - ? Number( - lowered - .split(' ')[0] - .substring('time('.length, lowered.split(' ')[0].length - 1), - ) + ? Number(lowered.split(' ')[0].substring('time('.length, lowered.split(' ')[0].length - 1)) : null; precision = precision ? precision : null; const params = timeConfig({ precision, withTimezone }); let out = params - ? `${withCasing(name, casing)}: time("${name}", ${params})` - : `${withCasing(name, casing)}: time("${name}")`; + ? `${withCasing(name, casing)}: time(${dbColumnName({ name, casing, withMode: true })}${params})` + : `${withCasing(name, casing)}: time(${dbColumnName({ name, casing })})`; - defaultValue = defaultValue === 'now()' - ? '.defaultNow()' - : defaultValue - ? `.default(${defaultValue})` - : ''; - - out += defaultValue; return out; } @@ -637,144 +968,73 @@ const column = ( const params = intervalConfig(lowered); let out = params - ? `${withCasing(name, casing)}: interval("${name}", ${params})` - : `${withCasing(name, casing)}: interval("${name}")`; + ? `${withCasing(name, casing)}: interval(${dbColumnName({ name, casing, withMode: true })}${params})` + : `${withCasing(name, casing)}: interval(${dbColumnName({ name, casing })})`; - out += defaultValue ? `.default(${defaultValue})` : ''; return out; } if (lowered === 'date') { - let out = `${withCasing(name, casing)}: date("${name}")`; + let out = `${withCasing(name, casing)}: date(${dbColumnName({ name, casing })})`; - defaultValue = defaultValue === 'now()' - ? '.defaultNow()' - : defaultValue === 'CURRENT_DATE' - ? `.default(sql\`${defaultValue}\`)` - : defaultValue - ? `.default(${defaultValue})` - : ''; - - out += defaultValue; return out; } if (lowered.startsWith('text')) { - let out = `${withCasing(name, casing)}: text("${name}")`; - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + let out = `${withCasing(name, casing)}: text(${dbColumnName({ name, casing })})`; return out; } - if (lowered === 'json') { - let out = `${withCasing(name, casing)}: json("${name}")`; - // defaultValue = defaultValue?.replace("::json", ""); - - defaultValue = defaultValue?.endsWith('::json') - ? defaultValue.substring(1, defaultValue.length - 7) - : defaultValue; - // const def = defaultValue ? objToStatement(JSON.parse(defaultValue)) : null; - const def = defaultValue ? defaultValue : null; - - out += typeof defaultValue !== 'undefined' ? `.default(${def})` : ''; + if (lowered.startsWith('jsonb')) { + let out = `${withCasing(name, casing)}: jsonb(${dbColumnName({ name, casing })})`; return out; } - if (lowered === 'jsonb') { - let out = `${withCasing(name, casing)}: jsonb("${name}")`; - - defaultValue = defaultValue?.endsWith('::jsonb') - ? defaultValue.substring(1, defaultValue.length - 8) - : defaultValue; - // const def = defaultValue ? objToStatement(JSON.parse(defaultValue)) : null; - const def = typeof defaultValue !== 'undefined' ? defaultValue : null; - - out += defaultValue ? `.default(${def})` : ''; + if (lowered.startsWith('json')) { + let out = `${withCasing(name, casing)}: json(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('inet')) { - let out = `${withCasing(name, casing)}: inet("${name}")`; - - // defaultValue = defaultValue?.endsWith("::inet") - // ? defaultValue.substring(0, defaultValue.length - 6) - // : defaultValue; - - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + let out = `${withCasing(name, casing)}: inet(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('cidr')) { - let out = `${withCasing(name, casing)}: cidr("${name}")`; - - // defaultValue = defaultValue?.endsWith("::cidr") - // ? defaultValue.substring(0, defaultValue.length - 6) - // : defaultValue; - - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + let out = `${withCasing(name, casing)}: cidr(${dbColumnName({ name, casing })})`; return out; } - if (lowered.startsWith('macaddr')) { - let out = `${withCasing(name, casing)}: macaddr("${name}")`; - - // defaultValue = defaultValue?.endsWith("::macaddr") - // ? defaultValue.substring(0, defaultValue.length - 9) - // : defaultValue; - - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + if (lowered.startsWith('macaddr8')) { + let out = `${withCasing(name, casing)}: macaddr8(${dbColumnName({ name, casing })})`; return out; } - if (lowered.startsWith('macaddr8')) { - let out = `${withCasing(name, casing)}: macaddr8("${name}")`; - - // defaultValue = defaultValue?.endsWith("::macaddr8") - // ? defaultValue.substring(0, defaultValue.length - 10) - // : defaultValue; - - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + if (lowered.startsWith('macaddr')) { + let out = `${withCasing(name, casing)}: macaddr(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('varchar')) { - const split = lowered.split(' '); - let out: string; if (lowered.length !== 7) { - out = `${ - withCasing( - name, - casing, - ) - }: varchar("${name}", { length: ${ - lowered.substring( - 8, - lowered.length - 1, - ) + out = `${withCasing(name, casing)}: varchar(${dbColumnName({ name, casing, withMode: true })}{ length: ${ + lowered.substring(8, lowered.length - 1) } })`; } else { - out = `${withCasing(name, casing)}: varchar("${name}")`; + out = `${withCasing(name, casing)}: varchar(${dbColumnName({ name, casing })})`; } - // defaultValue = defaultValue?.endsWith("::character varying") - // ? defaultValue.substring(0, defaultValue.length - 19) - // : defaultValue; - - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; return out; } if (lowered.startsWith('point')) { - let out: string = `${withCasing(name, casing)}: point("${name}")`; - - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + let out: string = `${withCasing(name, casing)}: point(${dbColumnName({ name, casing })})`; return out; } if (lowered.startsWith('line')) { - let out: string = `${withCasing(name, casing)}: point("${name}")`; - - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; + let out: string = `${withCasing(name, casing)}: point(${dbColumnName({ name, casing })})`; return out; } @@ -786,20 +1046,20 @@ const column = ( if (lowered.length !== 8) { const geometryOptions = lowered.slice(9, -1).split(','); if (geometryOptions.length === 1 && geometryOptions[0] !== '') { - out = `${withCasing(name, casing)}: geometry("${name}", { type: "${geometryOptions[0]}" })`; + out = `${withCasing(name, casing)}: geometry(${dbColumnName({ name, casing, withMode: true })}{ type: "${ + geometryOptions[0] + }" })`; } else if (geometryOptions.length === 2) { - out = `${withCasing(name, casing)}: geometry("${name}", { type: "${geometryOptions[0]}", srid: ${ - geometryOptions[1] - } })`; + out = `${withCasing(name, casing)}: geometry(${dbColumnName({ name, casing, withMode: true })}{ type: "${ + geometryOptions[0] + }", srid: ${geometryOptions[1]} })`; } else { isGeoUnknown = true; } } else { - out = `${withCasing(name, casing)}: geometry("${name}")`; + out = `${withCasing(name, casing)}: geometry(${dbColumnName({ name, casing })})`; } - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; - if (isGeoUnknown) { let unknown = `// TODO: failed to parse geometry type because found more than 2 options inside geometry function '${type}'\n// Introspect is currently supporting only type and srid options\n`; @@ -810,85 +1070,28 @@ const column = ( } if (lowered.startsWith('vector')) { - const split = lowered.split(' '); - let out: string; if (lowered.length !== 6) { - out = `${ - withCasing( - name, - casing, - ) - }: vector("${name}", { dimensions: ${ - lowered.substring( - 7, - lowered.length - 1, - ) + out = `${withCasing(name, casing)}: vector(${dbColumnName({ name, casing, withMode: true })}{ dimensions: ${ + lowered.substring(7, lowered.length - 1) } })`; } else { - out = `${withCasing(name, casing)}: vector("${name}")`; + out = `${withCasing(name, casing)}: vector(${dbColumnName({ name, casing })})`; } - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; return out; } if (lowered.startsWith('char')) { - // const split = lowered.split(" "); - let out: string; if (lowered.length !== 4) { - out = `${ - withCasing( - name, - casing, - ) - }: char("${name}", { length: ${ - lowered.substring( - 5, - lowered.length - 1, - ) + out = `${withCasing(name, casing)}: char(${dbColumnName({ name, casing, withMode: true })}{ length: ${ + lowered.substring(5, lowered.length - 1) } })`; } else { - out = `${withCasing(name, casing)}: char("${name}")`; - } - - // defaultValue = defaultValue?.endsWith("::bpchar") - // ? defaultValue.substring(0, defaultValue.length - 8) - // : defaultValue; - - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; - return out; - } - - // if internal has this column - use it - const columnInternals = internals?.tables[tableName]?.columns[name]; - if (typeof columnInternals !== 'undefined') { - // it means there is enum as array case - if ( - columnInternals.isArray - && columnInternals.rawType - && enumTypes.has(columnInternals.rawType) - ) { - let out = `${withCasing(columnInternals.rawType, casing)}: ${ - withCasing( - columnInternals.rawType, - casing, - ) - }("${name}")`; - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; - return out; + out = `${withCasing(name, casing)}: char(${dbColumnName({ name, casing })})`; } - } - if (enumTypes.has(type)) { - let out = `${withCasing(name, casing)}: ${ - withCasing( - type, - casing, - ) - }("${name}")`; - out += typeof defaultValue !== 'undefined' ? `.default(${defaultValue})` : ''; return out; } @@ -937,6 +1140,7 @@ const createTableColumns = ( it.type, it.name, enumTypes, + it.typeSchema ?? 'public', casing, it.default, internals, @@ -945,77 +1149,48 @@ const createTableColumns = ( statement += columnStatement; // Provide just this in column function if (internals?.tables[tableName]?.columns[it.name]?.isArray) { - statement += dimensionsInArray( - internals?.tables[tableName]?.columns[it.name]?.dimensions, - ); + statement += dimensionsInArray(internals?.tables[tableName]?.columns[it.name]?.dimensions); } + statement += mapDefault(tableName, it.type, it.name, enumTypes, it.typeSchema ?? 'public', it.default, internals); statement += it.primaryKey ? '.primaryKey()' : ''; statement += it.notNull && !it.identity ? '.notNull()' : ''; - function generateIdentityParams(identity: Column['identity']) { - let paramsObj = `{ name: "${identity!.name}"`; - if (identity?.startWith) { - paramsObj += `, startWith: ${identity.startWith}`; - } - if (identity?.increment) { - paramsObj += `, increment: ${identity.increment}`; - } - if (identity?.minValue) { - paramsObj += `, minValue: ${identity.minValue}`; - } - if (identity?.maxValue) { - paramsObj += `, maxValue: ${identity.maxValue}`; - } - if (identity?.cache) { - paramsObj += `, cache: ${identity.cache}`; - } - if (identity?.cycle) { - paramsObj += `, cycle: true`; - } - paramsObj += ' }'; - if (identity?.type === 'always') { - return `.generatedAlwaysAsIdentity(${paramsObj})`; - } - return `.generatedByDefaultAsIdentity(${paramsObj})`; - } - statement += it.identity ? generateIdentityParams(it.identity) : ''; - statement += it.generated - ? `.generatedAlwaysAs(sql\`${it.generated.as}\`)` - : ''; - - const fks = fkByColumnName[it.name]; - if (fks) { - const fksStatement = fks - .map((it) => { - const onDelete = it.onDelete && it.onDelete !== 'no action' ? it.onDelete : null; - const onUpdate = it.onUpdate && it.onUpdate !== 'no action' ? it.onUpdate : null; - const params = { onDelete, onUpdate }; - - const typeSuffix = isCyclic(it) ? ': AnyPgColumn' : ''; - - const paramsStr = objToStatement2(params); - const tableSchema = schemas[it.schemaTo || '']; - const paramName = paramNameFor(it.tableTo, tableSchema); - if (paramsStr) { - return `.references(()${typeSuffix} => ${ - withCasing( - paramName, - casing, - ) - }.${withCasing(it.columnsTo[0], casing)}, ${paramsStr} )`; - } - return `.references(()${typeSuffix} => ${ - withCasing( - paramName, - casing, - ) - }.${withCasing(it.columnsTo[0], casing)})`; - }) - .join(''); - statement += fksStatement; - } + statement += it.generated ? `.generatedAlwaysAs(sql\`${it.generated.as}\`)` : ''; + + // const fks = fkByColumnName[it.name]; + // Andrii: I switched it off until we will get a custom naem setting in references + // if (fks) { + // const fksStatement = fks + // .map((it) => { + // const onDelete = it.onDelete && it.onDelete !== 'no action' ? it.onDelete : null; + // const onUpdate = it.onUpdate && it.onUpdate !== 'no action' ? it.onUpdate : null; + // const params = { onDelete, onUpdate }; + + // const typeSuffix = isCyclic(it) ? ': AnyPgColumn' : ''; + + // const paramsStr = objToStatement2(params); + // const tableSchema = schemas[it.schemaTo || '']; + // const paramName = paramNameFor(it.tableTo, tableSchema); + // if (paramsStr) { + // return `.references(()${typeSuffix} => ${ + // withCasing( + // paramName, + // casing, + // ) + // }.${withCasing(it.columnsTo[0], casing)}, ${paramsStr} )`; + // } + // return `.references(()${typeSuffix} => ${ + // withCasing( + // paramName, + // casing, + // ) + // }.${withCasing(it.columnsTo[0], casing)})`; + // }) + // .join(''); + // statement += fksStatement; + // } statement += ',\n'; }); @@ -1023,21 +1198,13 @@ const createTableColumns = ( return statement; }; -const createTableIndexes = ( - tableName: string, - idxs: Index[], - casing: Casing, -): string => { +const createTableIndexes = (tableName: string, idxs: Index[], casing: Casing): string => { let statement = ''; idxs.forEach((it) => { // we have issue when index is called as table called - let idxKey = it.name.startsWith(tableName) && it.name !== tableName - ? it.name.slice(tableName.length + 1) - : it.name; - idxKey = idxKey.endsWith('_index') - ? idxKey.slice(0, -'_index'.length) + '_idx' - : idxKey; + let idxKey = it.name.startsWith(tableName) && it.name !== tableName ? it.name.slice(tableName.length + 1) : it.name; + idxKey = idxKey.endsWith('_index') ? idxKey.slice(0, -'_index'.length) + '_idx' : idxKey; idxKey = withCasing(idxKey, casing); @@ -1047,7 +1214,7 @@ const createTableIndexes = ( ); const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += it.isUnique ? 'uniqueIndex(' : 'index('; statement += `${escapedIndexName})`; statement += `${it.concurrently ? `.concurrently()` : ''}`; @@ -1058,8 +1225,10 @@ const createTableIndexes = ( if (it.isExpression) { return `sql\`${it.expression}\``; } else { - return `table.${withCasing(it.expression, casing)}${ - it.opclass && vectorOps.includes(it.opclass) + return `table.${withCasing(it.expression, casing)}${it.asc ? '.asc()' : '.desc()'}${ + it.nulls === 'first' ? '.nullsFirst()' : '.nullsLast()' + }${ + it.opclass ? `.op("${it.opclass}")` : '' }`; @@ -1076,16 +1245,12 @@ const createTableIndexes = ( reversedString += `${key}: "${mappedWith[key]}",`; } } - reversedString = reversedString.length > 1 - ? reversedString.slice(0, reversedString.length - 1) - : reversedString; + reversedString = reversedString.length > 1 ? reversedString.slice(0, reversedString.length - 1) : reversedString; return `${reversedString}}`; } - statement += it.with && Object.keys(it.with).length > 0 - ? `.with(${reverseLogic(it.with)})` - : ''; - statement += `,\n`; + statement += it.with && Object.keys(it.with).length > 0 ? `.with(${reverseLogic(it.with)})` : ''; + statement += `,`; }); return statement; @@ -1095,9 +1260,7 @@ const createTablePKs = (pks: PrimaryKey[], casing: Casing): string => { let statement = ''; pks.forEach((it) => { - let idxKey = withCasing(it.name, casing); - - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'primaryKey({ columns: ['; statement += `${ it.columns @@ -1107,7 +1270,36 @@ const createTablePKs = (pks: PrimaryKey[], casing: Casing): string => { .join(', ') }]${it.name ? `, name: "${it.name}"` : ''}}`; statement += ')'; - statement += `,\n`; + statement += `,`; + }); + + return statement; +}; + +// get a map of db role name to ts key +// if to by key is in this map - no quotes, otherwise - quotes + +const createTablePolicies = ( + policies: Policy[], + casing: Casing, + rolesNameToTsKey: Record = {}, +): string => { + let statement = ''; + + policies.forEach((it) => { + const idxKey = withCasing(it.name, casing); + + const mappedItTo = it.to?.map((v) => { + return rolesNameToTsKey[v] ? withCasing(rolesNameToTsKey[v], casing) : `"${v}"`; + }); + + statement += `\n\t`; + statement += 'pgPolicy('; + statement += `"${it.name}", { `; + statement += `as: "${it.as?.toLowerCase()}", for: "${it.for?.toLowerCase()}", to: [${mappedItTo?.join(', ')}]${ + it.using ? `, using: sql\`${it.using}\`` : '' + }${it.withCheck ? `, withCheck: sql\`${it.withCheck}\` ` : ''}`; + statement += ` }),`; }); return statement; @@ -1120,28 +1312,35 @@ const createTableUniques = ( let statement = ''; unqs.forEach((it) => { - const idxKey = withCasing(it.name, casing); - - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'unique('; statement += `"${it.name}")`; - statement += `.on(${ - it.columns - .map((it) => `table.${withCasing(it, casing)}`) - .join(', ') - })`; + statement += `.on(${it.columns.map((it) => `table.${withCasing(it, casing)}`).join(', ')})`; statement += it.nullsNotDistinct ? `.nullsNotDistinct()` : ''; - statement += `,\n`; + statement += `,`; }); return statement; }; -const createTableFKs = ( - fks: ForeignKey[], - schemas: Record, +const createTableChecks = ( + checkConstraints: CheckConstraint[], casing: Casing, -): string => { +) => { + let statement = ''; + + checkConstraints.forEach((it) => { + statement += `\n\t`; + statement += 'check('; + statement += `"${it.name}", `; + statement += `sql\`${it.value}\`)`; + statement += `,`; + }); + + return statement; +}; + +const createTableFKs = (fks: ForeignKey[], schemas: Record, casing: Casing): string => { let statement = ''; fks.forEach((it) => { @@ -1150,29 +1349,20 @@ const createTableFKs = ( const isSelf = it.tableTo === it.tableFrom; const tableTo = isSelf ? 'table' : `${withCasing(paramName, casing)}`; - statement += `\t\t${withCasing(it.name, casing)}: foreignKey({\n`; - statement += `\t\t\tcolumns: [${ - it.columnsFrom - .map((i) => `table.${withCasing(i, casing)}`) - .join(', ') - }],\n`; + statement += `\n\t`; + statement += `foreignKey({\n`; + statement += `\t\t\tcolumns: [${it.columnsFrom.map((i) => `table.${withCasing(i, casing)}`).join(', ')}],\n`; statement += `\t\t\tforeignColumns: [${ - it.columnsTo - .map((i) => `${tableTo}.${withCasing(i, casing)}`) - .join(', ') + it.columnsTo.map((i) => `${tableTo}.${withCasing(i, casing)}`).join(', ') }],\n`; statement += `\t\t\tname: "${it.name}"\n`; statement += `\t\t})`; - statement += it.onUpdate && it.onUpdate !== 'no action' - ? `.onUpdate("${it.onUpdate}")` - : ''; + statement += it.onUpdate && it.onUpdate !== 'no action' ? `.onUpdate("${it.onUpdate}")` : ''; - statement += it.onDelete && it.onDelete !== 'no action' - ? `.onDelete("${it.onDelete}")` - : ''; + statement += it.onDelete && it.onDelete !== 'no action' ? `.onDelete("${it.onDelete}")` : ''; - statement += `,\n`; + statement += `,`; }); return statement; diff --git a/drizzle-kit/src/introspect-singlestore.ts b/drizzle-kit/src/introspect-singlestore.ts new file mode 100644 index 000000000..ee0ae5e0d --- /dev/null +++ b/drizzle-kit/src/introspect-singlestore.ts @@ -0,0 +1,922 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +import { toCamelCase } from 'drizzle-orm/casing'; +import './@types/utils'; +import type { Casing } from './cli/validations/common'; +import { assertUnreachable } from './global'; +import { + Column, + Index, + PrimaryKey, + SingleStoreSchema, + SingleStoreSchemaInternal, + UniqueConstraint, +} from './serializer/singlestoreSchema'; +import { indexName } from './serializer/singlestoreSerializer'; + +// time precision to fsp +// {mode: "string"} for timestamp by default + +const singlestoreImportsList = new Set([ + 'singlestoreTable', + 'singlestoreEnum', + 'bigint', + 'binary', + 'boolean', + 'char', + 'date', + 'datetime', + 'decimal', + 'double', + 'float', + 'int', + 'json', + // TODO: add new type BSON + // TODO: add new type Blob + // TODO: add new type UUID + // TODO: add new type GUID + // TODO: add new type Vector + // TODO: add new type GeoPoint + 'mediumint', + 'real', + 'serial', + 'smallint', + 'text', + 'tinytext', + 'mediumtext', + 'longtext', + 'time', + 'timestamp', + 'tinyint', + 'varbinary', + 'varchar', + 'vector', + 'year', + 'enum', +]); + +const objToStatement = (json: any) => { + json = Object.fromEntries(Object.entries(json).filter((it) => it[1])); + + const keys = Object.keys(json); + if (keys.length === 0) return; + + let statement = '{ '; + statement += keys.map((it) => `"${it}": "${json[it]}"`).join(', '); + statement += ' }'; + return statement; +}; + +const objToStatement2 = (json: any) => { + json = Object.fromEntries(Object.entries(json).filter((it) => it[1])); + + const keys = Object.keys(json); + if (keys.length === 0) return; + + let statement = '{ '; + statement += keys.map((it) => `${it}: "${json[it]}"`).join(', '); // no "" for keys + statement += ' }'; + return statement; +}; + +const timeConfig = (json: any) => { + json = Object.fromEntries(Object.entries(json).filter((it) => it[1])); + + const keys = Object.keys(json); + if (keys.length === 0) return; + + let statement = '{ '; + statement += keys.map((it) => `${it}: ${json[it]}`).join(', '); + statement += ' }'; + return statement; +}; + +const binaryConfig = (json: any) => { + json = Object.fromEntries(Object.entries(json).filter((it) => it[1])); + + const keys = Object.keys(json); + if (keys.length === 0) return; + + let statement = '{ '; + statement += keys.map((it) => `${it}: ${json[it]}`).join(', '); + statement += ' }'; + return statement; +}; + +const importsPatch = { + 'double precision': 'doublePrecision', + 'timestamp without time zone': 'timestamp', +} as Record; + +const escapeColumnKey = (value: string) => { + if (/^(?![a-zA-Z_$][a-zA-Z0-9_$]*$).+$/.test(value)) { + return `"${value}"`; + } + return value; +}; + +const prepareCasing = (casing?: Casing) => (value: string) => { + if (casing === 'preserve') { + return escapeColumnKey(value); + } + if (casing === 'camel') { + return escapeColumnKey(value.camelCase()); + } + + assertUnreachable(casing); +}; + +const dbColumnName = ({ name, casing, withMode = false }: { name: string; casing: Casing; withMode?: boolean }) => { + if (casing === 'preserve') { + return ''; + } + if (casing === 'camel') { + return toCamelCase(name) === name ? '' : withMode ? `"${name}", ` : `"${name}"`; + } + + assertUnreachable(casing); +}; + +export const schemaToTypeScript = ( + schema: SingleStoreSchemaInternal, + casing: Casing, +) => { + const withCasing = prepareCasing(casing); + + const imports = Object.values(schema.tables).reduce( + (res, it) => { + const idxImports = Object.values(it.indexes).map((idx) => idx.isUnique ? 'uniqueIndex' : 'index'); + const pkImports = Object.values(it.compositePrimaryKeys).map( + (it) => 'primaryKey', + ); + const uniqueImports = Object.values(it.uniqueConstraints).map( + (it) => 'unique', + ); + + res.singlestore.push(...idxImports); + res.singlestore.push(...pkImports); + res.singlestore.push(...uniqueImports); + + const columnImports = Object.values(it.columns) + .map((col) => { + let patched = importsPatch[col.type] ?? col.type; + patched = patched.startsWith('varchar(') ? 'varchar' : patched; + patched = patched.startsWith('char(') ? 'char' : patched; + patched = patched.startsWith('binary(') ? 'binary' : patched; + patched = patched.startsWith('decimal(') ? 'decimal' : patched; + patched = patched.startsWith('smallint(') ? 'smallint' : patched; + patched = patched.startsWith('enum(') ? 'singlestoreEnum' : patched; + patched = patched.startsWith('datetime(') ? 'datetime' : patched; + patched = patched.startsWith('varbinary(') ? 'varbinary' : patched; + patched = patched.startsWith('int(') ? 'int' : patched; + patched = patched.startsWith('double(') ? 'double' : patched; + patched = patched.startsWith('float(') ? 'float' : patched; + patched = patched.startsWith('int unsigned') ? 'int' : patched; + patched = patched.startsWith('tinyint(') ? 'tinyint' : patched; + patched = patched.startsWith('mediumint(') ? 'mediumint' : patched; + patched = patched.startsWith('bigint(') ? 'bigint' : patched; + patched = patched.startsWith('tinyint unsigned') ? 'tinyint' : patched; + patched = patched.startsWith('smallint unsigned') ? 'smallint' : patched; + patched = patched.startsWith('mediumint unsigned') ? 'mediumint' : patched; + patched = patched.startsWith('bigint unsigned') ? 'bigint' : patched; + return patched; + }) + .filter((type) => { + return singlestoreImportsList.has(type); + }); + + res.singlestore.push(...columnImports); + return res; + }, + { singlestore: [] as string[] }, + ); + + /* Object.values(schema.views).forEach((it) => { + imports.singlestore.push('singlestoreView'); + + const columnImports = Object.values(it.columns) + .map((col) => { + let patched = importsPatch[col.type] ?? col.type; + patched = patched.startsWith('varchar(') ? 'varchar' : patched; + patched = patched.startsWith('char(') ? 'char' : patched; + patched = patched.startsWith('binary(') ? 'binary' : patched; + patched = patched.startsWith('decimal(') ? 'decimal' : patched; + patched = patched.startsWith('smallint(') ? 'smallint' : patched; + patched = patched.startsWith('enum(') ? 'singlestoreEnum' : patched; + patched = patched.startsWith('datetime(') ? 'datetime' : patched; + patched = patched.startsWith('varbinary(') ? 'varbinary' : patched; + patched = patched.startsWith('int(') ? 'int' : patched; + patched = patched.startsWith('double(') ? 'double' : patched; + patched = patched.startsWith('float(') ? 'float' : patched; + patched = patched.startsWith('int unsigned') ? 'int' : patched; + patched = patched.startsWith('tinyint(') ? 'tinyint' : patched; + patched = patched.startsWith('mediumint(') ? 'mediumint' : patched; + patched = patched.startsWith('bigint(') ? 'bigint' : patched; + patched = patched.startsWith('tinyint unsigned') ? 'tinyint' : patched; + patched = patched.startsWith('smallint unsigned') ? 'smallint' : patched; + patched = patched.startsWith('mediumint unsigned') ? 'mediumint' : patched; + patched = patched.startsWith('bigint unsigned') ? 'bigint' : patched; + return patched; + }) + .filter((type) => { + return singlestoreImportsList.has(type); + }); + + imports.singlestore.push(...columnImports); + }); */ + + const tableStatements = Object.values(schema.tables).map((table) => { + const func = 'singlestoreTable'; + let statement = ''; + if (imports.singlestore.includes(withCasing(table.name))) { + statement = `// Table name is in conflict with ${ + withCasing( + table.name, + ) + } import.\n// Please change to any other name, that is not in imports list\n`; + } + statement += `export const ${withCasing(table.name)} = ${func}("${table.name}", {\n`; + statement += createTableColumns( + Object.values(table.columns), + withCasing, + casing, + table.name, + schema, + ); + statement += '}'; + + if ( + Object.keys(table.indexes).length > 0 + || Object.keys(table.compositePrimaryKeys).length > 0 + || Object.keys(table.uniqueConstraints).length > 0 + ) { + statement += ',\n'; + statement += '(table) => ['; + statement += createTableIndexes( + table.name, + Object.values(table.indexes), + withCasing, + ); + statement += createTablePKs( + Object.values(table.compositePrimaryKeys), + withCasing, + ); + statement += createTableUniques( + Object.values(table.uniqueConstraints), + withCasing, + ); + statement += '\n]'; + } + + statement += ');'; + return statement; + }); + + /* const viewsStatements = Object.values(schema.views).map((view) => { + const { columns, name, algorithm, definition, sqlSecurity, withCheckOption } = view; + const func = 'singlestoreView'; + let statement = ''; + + if (imports.singlestore.includes(withCasing(name))) { + statement = `// Table name is in conflict with ${ + withCasing( + view.name, + ) + } import.\n// Please change to any other name, that is not in imports list\n`; + } + statement += `export const ${withCasing(name)} = ${func}("${name}", {\n`; + statement += createTableColumns( + Object.values(columns), + withCasing, + casing, + name, + schema, + ); + statement += '})'; + + statement += algorithm ? `.algorithm("${algorithm}")` : ''; + statement += sqlSecurity ? `.sqlSecurity("${sqlSecurity}")` : ''; + statement += withCheckOption ? `.withCheckOption("${withCheckOption}")` : ''; + statement += `.as(sql\`${definition?.replaceAll('`', '\\`')}\`);`; + + return statement; + }); */ + + const uniqueSingleStoreImports = [ + 'singlestoreTable', + 'singlestoreSchema', + 'AnySingleStoreColumn', + ...new Set(imports.singlestore), + ]; + const importsTs = `import { ${ + uniqueSingleStoreImports.join( + ', ', + ) + } } from "drizzle-orm/singlestore-core"\nimport { sql } from "drizzle-orm"\n\n`; + + let decalrations = ''; + decalrations += tableStatements.join('\n\n'); + decalrations += '\n'; + /* decalrations += viewsStatements.join('\n\n'); */ + + const file = importsTs + decalrations; + + const schemaEntry = ` + { + ${ + Object.values(schema.tables) + .map((it) => withCasing(it.name)) + .join(',') + } + } + `; + + return { + file, // backward compatible, print to file + imports: importsTs, + decalrations, + schemaEntry, + }; +}; + +const mapColumnDefault = (defaultValue: any, isExpression?: boolean) => { + if (isExpression) { + return `sql\`${defaultValue}\``; + } + + return defaultValue; +}; + +const mapColumnDefaultForJson = (defaultValue: any) => { + if ( + typeof defaultValue === 'string' + && defaultValue.startsWith("('") + && defaultValue.endsWith("')") + ) { + return defaultValue.substring(2, defaultValue.length - 2); + } + + return defaultValue; +}; + +const column = ( + type: string, + name: string, + casing: (value: string) => string, + rawCasing: Casing, + defaultValue?: any, + autoincrement?: boolean, + onUpdate?: boolean, + isExpression?: boolean, +) => { + let lowered = type; + if (!type.startsWith('enum(')) { + lowered = type.toLowerCase(); + } + + if (lowered === 'serial') { + return `${casing(name)}: serial(${dbColumnName({ name, casing: rawCasing })})`; + } + + if (lowered.startsWith('int')) { + const isUnsigned = lowered.includes('unsigned'); + const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); + let out = `${casing(name)}: int(${columnName}${ + isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' + })`; + out += autoincrement ? `.autoincrement()` : ''; + out += typeof defaultValue !== 'undefined' + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('tinyint')) { + const isUnsigned = lowered.includes('unsigned'); + const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); + // let out = `${name.camelCase()}: tinyint("${name}")`; + let out: string = `${casing(name)}: tinyint(${columnName}${ + isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' + })`; + out += autoincrement ? `.autoincrement()` : ''; + out += typeof defaultValue !== 'undefined' + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('smallint')) { + const isUnsigned = lowered.includes('unsigned'); + const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); + let out = `${casing(name)}: smallint(${columnName}${ + isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' + })`; + out += autoincrement ? `.autoincrement()` : ''; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('mediumint')) { + const isUnsigned = lowered.includes('unsigned'); + const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); + let out = `${casing(name)}: mediumint(${columnName}${ + isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' + })`; + out += autoincrement ? `.autoincrement()` : ''; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('bigint')) { + const isUnsigned = lowered.includes('unsigned'); + let out = `${casing(name)}: bigint(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ mode: "number"${ + isUnsigned ? ', unsigned: true' : '' + } })`; + out += autoincrement ? `.autoincrement()` : ''; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered === 'boolean') { + let out = `${casing(name)}: boolean(${dbColumnName({ name, casing: rawCasing })})`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('double')) { + let params: + | { precision?: string; scale?: string; unsigned?: boolean } + | undefined; + + if (lowered.length > (lowered.includes('unsigned') ? 15 : 6)) { + const [precision, scale] = lowered + .slice(7, lowered.length - (1 + (lowered.includes('unsigned') ? 9 : 0))) + .split(','); + params = { precision, scale }; + } + + if (lowered.includes('unsigned')) { + params = { ...(params ?? {}), unsigned: true }; + } + + const timeConfigParams = params ? timeConfig(params) : undefined; + + let out = params + ? `${casing(name)}: double(${ + dbColumnName({ name, casing: rawCasing, withMode: timeConfigParams !== undefined }) + }${timeConfig(params)})` + : `${casing(name)}: double(${dbColumnName({ name, casing: rawCasing })})`; + + // let out = `${name.camelCase()}: double("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('float')) { + let params: + | { precision?: string; scale?: string; unsigned?: boolean } + | undefined; + + if (lowered.length > (lowered.includes('unsigned') ? 14 : 5)) { + const [precision, scale] = lowered + .slice(6, lowered.length - (1 + (lowered.includes('unsigned') ? 9 : 0))) + .split(','); + params = { precision, scale }; + } + + if (lowered.includes('unsigned')) { + params = { ...(params ?? {}), unsigned: true }; + } + + let out = `${casing(name)}: float(${dbColumnName({ name, casing: rawCasing })}${params ? timeConfig(params) : ''})`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered === 'real') { + let out = `${casing(name)}: real(${dbColumnName({ name, casing: rawCasing })})`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('timestamp')) { + const keyLength = 'timestamp'.length + 1; + let fsp = lowered.length > keyLength + ? Number(lowered.substring(keyLength, lowered.length - 1)) + : null; + fsp = fsp ? fsp : null; + + const params = timeConfig({ fsp, mode: "'string'" }); + + let out = params + ? `${casing(name)}: timestamp(${ + dbColumnName({ name, casing: rawCasing, withMode: params !== undefined }) + }${params})` + : `${casing(name)}: timestamp(${dbColumnName({ name, casing: rawCasing })})`; + + // singlestore has only CURRENT_TIMESTAMP, as I found from docs. But will leave now() for just a case + defaultValue = defaultValue === 'now()' || defaultValue === 'CURRENT_TIMESTAMP' + ? '.defaultNow()' + : defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + + let onUpdateNow = onUpdate ? '.onUpdateNow()' : ''; + out += onUpdateNow; + + return out; + } + + if (lowered.startsWith('time')) { + const keyLength = 'time'.length + 1; + let fsp = lowered.length > keyLength + ? Number(lowered.substring(keyLength, lowered.length - 1)) + : null; + fsp = fsp ? fsp : null; + + const params = timeConfig({ fsp }); + + let out = params + ? `${casing(name)}: time(${dbColumnName({ name, casing: rawCasing, withMode: params !== undefined })}${params})` + : `${casing(name)}: time(${dbColumnName({ name, casing: rawCasing })})`; + + defaultValue = defaultValue === 'now()' + ? '.defaultNow()' + : defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered === 'date') { + let out = `// you can use { mode: 'date' }, if you want to have Date as type for this column\n\t${ + casing( + name, + ) + }: date(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ mode: 'string' })`; + + defaultValue = defaultValue === 'now()' + ? '.defaultNow()' + : defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + // in singlestore text can't have default value. Will leave it in case smth ;) + if (lowered === 'text') { + let out = `${casing(name)}: text(${dbColumnName({ name, casing: rawCasing })})`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + // in singlestore text can't have default value. Will leave it in case smth ;) + if (lowered === 'tinytext') { + let out = `${casing(name)}: tinytext(${dbColumnName({ name, casing: rawCasing })})`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + // in singlestore text can't have default value. Will leave it in case smth ;) + if (lowered === 'mediumtext') { + let out = `${casing(name)}: mediumtext(${dbColumnName({ name, casing: rawCasing })})`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + // in singlestore text can't have default value. Will leave it in case smth ;) + if (lowered === 'longtext') { + let out = `${casing(name)}: longtext(${dbColumnName({ name, casing: rawCasing })})`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered === 'year') { + let out = `${casing(name)}: year(${dbColumnName({ name, casing: rawCasing })})`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + // in singlestore json can't have default value. Will leave it in case smth ;) + if (lowered === 'json') { + let out = `${casing(name)}: json(${dbColumnName({ name, casing: rawCasing })})`; + + out += defaultValue + ? `.default(${mapColumnDefaultForJson(defaultValue)})` + : ''; + + return out; + } + + if (lowered.startsWith('varchar')) { + let out: string = `${ + casing( + name, + ) + }: varchar(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ length: ${ + lowered.substring( + 'varchar'.length + 1, + lowered.length - 1, + ) + } })`; + + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('char')) { + let out: string = `${ + casing( + name, + ) + }: char(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ length: ${ + lowered.substring( + 'char'.length + 1, + lowered.length - 1, + ) + } })`; + + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('datetime')) { + let out = `// you can use { mode: 'date' }, if you want to have Date as type for this column\n\t`; + + const fsp = lowered.startsWith('datetime(') + ? lowered.substring('datetime'.length + 1, lowered.length - 1) + : undefined; + + out = fsp + ? `${ + casing( + name, + ) + }: datetime(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ mode: 'string', fsp: ${ + lowered.substring( + 'datetime'.length + 1, + lowered.length - 1, + ) + } })` + : `${casing(name)}: datetime(${dbColumnName({ name, casing: rawCasing, withMode: true })}{ mode: 'string'})`; + + defaultValue = defaultValue === 'now()' + ? '.defaultNow()' + : defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered.startsWith('decimal')) { + let params: + | { precision?: string; scale?: string; unsigned?: boolean } + | undefined; + + if (lowered.length > (lowered.includes('unsigned') ? 16 : 7)) { + const [precision, scale] = lowered + .slice(8, lowered.length - (1 + (lowered.includes('unsigned') ? 9 : 0))) + .split(','); + params = { precision, scale }; + } + + if (lowered.includes('unsigned')) { + params = { ...(params ?? {}), unsigned: true }; + } + + const timeConfigParams = params ? timeConfig(params) : undefined; + + let out = params + ? `${casing(name)}: decimal(${ + dbColumnName({ name, casing: rawCasing, withMode: timeConfigParams !== undefined }) + }${timeConfigParams})` + : `${casing(name)}: decimal(${dbColumnName({ name, casing: rawCasing })})`; + + defaultValue = typeof defaultValue !== 'undefined' + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered.startsWith('binary')) { + const keyLength = 'binary'.length + 1; + let length = lowered.length > keyLength + ? Number(lowered.substring(keyLength, lowered.length - 1)) + : null; + length = length ? length : null; + + const params = binaryConfig({ length }); + + let out = params + ? `${casing(name)}: binary(${dbColumnName({ name, casing: rawCasing, withMode: params !== undefined })}${params})` + : `${casing(name)}: binary(${dbColumnName({ name, casing: rawCasing })})`; + + defaultValue = defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered.startsWith('enum')) { + const values = lowered.substring('enum'.length + 1, lowered.length - 1); + let out = `${casing(name)}: singlestoreEnum(${ + dbColumnName({ name, casing: rawCasing, withMode: true }) + }[${values}])`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('varbinary')) { + const keyLength = 'varbinary'.length + 1; + let length = lowered.length > keyLength + ? Number(lowered.substring(keyLength, lowered.length - 1)) + : null; + length = length ? length : null; + + const params = binaryConfig({ length }); + + let out = params + ? `${casing(name)}: varbinary(${ + dbColumnName({ name, casing: rawCasing, withMode: params !== undefined }) + }${params})` + : `${casing(name)}: varbinary(${dbColumnName({ name, casing: rawCasing })})`; + + defaultValue = defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered.startsWith('vector')) { + const [dimensions, elementType] = lowered.substring('vector'.length + 1, lowered.length - 1).split(','); + let out = `${casing(name)}: vector(${ + dbColumnName({ name, casing: rawCasing, withMode: true }) + }{ dimensions: ${dimensions}, elementType: ${elementType} })`; + + out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` : ''; + return out; + } + + console.log('uknown', type); + return `// Warning: Can't parse ${type} from database\n\t// ${type}Type: ${type}("${name}")`; +}; + +const createTableColumns = ( + columns: Column[], + casing: (val: string) => string, + rawCasing: Casing, + tableName: string, + schema: SingleStoreSchemaInternal, +): string => { + let statement = ''; + + columns.forEach((it) => { + statement += '\t'; + statement += column( + it.type, + it.name, + casing, + rawCasing, + it.default, + it.autoincrement, + it.onUpdate, + schema.internal?.tables![tableName]?.columns[it.name] + ?.isDefaultAnExpression ?? false, + ); + statement += it.primaryKey ? '.primaryKey()' : ''; + statement += it.notNull ? '.notNull()' : ''; + + statement += it.generated + ? `.generatedAlwaysAs(sql\`${ + it.generated.as.replace( + /`/g, + '\\`', + ) + }\`, { mode: "${it.generated.type}" })` + : ''; + + statement += ',\n'; + }); + + return statement; +}; + +const createTableIndexes = ( + tableName: string, + idxs: Index[], + casing: (value: string) => string, +): string => { + let statement = ''; + + idxs.forEach((it) => { + let idxKey = it.name.startsWith(tableName) && it.name !== tableName + ? it.name.slice(tableName.length + 1) + : it.name; + idxKey = idxKey.endsWith('_index') + ? idxKey.slice(0, -'_index'.length) + '_idx' + : idxKey; + + idxKey = casing(idxKey); + + const indexGeneratedName = indexName(tableName, it.columns); + const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; + + statement += `\n\t`; + statement += it.isUnique ? 'uniqueIndex(' : 'index('; + statement += `${escapedIndexName})`; + statement += `.on(${ + it.columns + .map((it) => `table.${casing(it)}`) + .join(', ') + }),`; + }); + + return statement; +}; + +const createTableUniques = ( + unqs: UniqueConstraint[], + casing: (value: string) => string, +): string => { + let statement = ''; + + unqs.forEach((it) => { + statement += `\n\t`; + statement += 'unique('; + statement += `"${it.name}")`; + statement += `.on(${ + it.columns + .map((it) => `table.${casing(it)}`) + .join(', ') + }),`; + }); + + return statement; +}; + +const createTablePKs = ( + pks: PrimaryKey[], + casing: (value: string) => string, +): string => { + let statement = ''; + + pks.forEach((it) => { + let idxKey = casing(it.name); + + statement += `\n\t`; + statement += 'primaryKey({ columns: ['; + statement += `${ + it.columns + .map((c) => { + return `table.${casing(c)}`; + }) + .join(', ') + }]${it.name ? `, name: "${it.name}"` : ''}}`; + statement += '),'; + }); + + return statement; +}; diff --git a/drizzle-kit/src/introspect-sqlite.ts b/drizzle-kit/src/introspect-sqlite.ts index 9ff119ce6..d3aac6f04 100644 --- a/drizzle-kit/src/introspect-sqlite.ts +++ b/drizzle-kit/src/introspect-sqlite.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ +import { toCamelCase } from 'drizzle-orm/casing'; import './@types/utils'; import type { Casing } from './cli/validations/common'; +import { assertUnreachable } from './global'; +import { CheckConstraint } from './serializer/mysqlSchema'; import type { Column, ForeignKey, @@ -38,17 +41,35 @@ const objToStatement2 = (json: any) => { const relations = new Set(); +const escapeColumnKey = (value: string) => { + if (/^(?![a-zA-Z_$][a-zA-Z0-9_$]*$).+$/.test(value)) { + return `"${value}"`; + } + return value; +}; + const withCasing = (value: string, casing?: Casing) => { - if (typeof casing === 'undefined') { - return value; + if (casing === 'preserve') { + return escapeColumnKey(value); } if (casing === 'camel') { - return value.camelCase(); + return escapeColumnKey(value.camelCase()); } return value; }; +const dbColumnName = ({ name, casing, withMode = false }: { name: string; casing: Casing; withMode?: boolean }) => { + if (casing === 'preserve') { + return ''; + } + if (casing === 'camel') { + return toCamelCase(name) === name ? '' : withMode ? `"${name}", ` : `"${name}"`; + } + + assertUnreachable(casing); +}; + export const schemaToTypeScript = ( schema: SQLiteSchemaInternal, casing: Casing, @@ -71,11 +92,15 @@ export const schemaToTypeScript = ( const uniqueImports = Object.values(it.uniqueConstraints).map( (it) => 'unique', ); + const checkImports = Object.values(it.checkConstraints).map( + (it) => 'check', + ); res.sqlite.push(...idxImports); res.sqlite.push(...fkImpots); res.sqlite.push(...pkImports); res.sqlite.push(...uniqueImports); + res.sqlite.push(...checkImports); const columnImports = Object.values(it.columns) .map((col) => { @@ -91,6 +116,20 @@ export const schemaToTypeScript = ( { sqlite: [] as string[] }, ); + Object.values(schema.views).forEach((it) => { + imports.sqlite.push('sqliteView'); + + const columnImports = Object.values(it.columns) + .map((col) => { + return col.type; + }) + .filter((type) => { + return sqliteImportsList.has(type); + }); + + imports.sqlite.push(...columnImports); + }); + const tableStatements = Object.values(schema.tables).map((table) => { const func = 'sqliteTable'; let statement = ''; @@ -120,10 +159,10 @@ export const schemaToTypeScript = ( || filteredFKs.length > 0 || Object.keys(table.compositePrimaryKeys).length > 0 || Object.keys(table.uniqueConstraints).length > 0 + || Object.keys(table.checkConstraints).length > 0 ) { statement += ',\n'; - statement += '(table) => {\n'; - statement += '\treturn {\n'; + statement += '(table) => ['; statement += createTableIndexes( table.name, Object.values(table.indexes), @@ -138,14 +177,41 @@ export const schemaToTypeScript = ( Object.values(table.uniqueConstraints), casing, ); - statement += '\t}\n'; - statement += '}'; + statement += createTableChecks( + Object.values(table.checkConstraints), + casing, + ); + statement += '\n]'; } statement += ');'; return statement; }); + const viewsStatements = Object.values(schema.views).map((view) => { + const func = 'sqliteView'; + + let statement = ''; + if (imports.sqlite.includes(withCasing(view.name, casing))) { + statement = `// Table name is in conflict with ${ + withCasing( + view.name, + casing, + ) + } import.\n// Please change to any other name, that is not in imports list\n`; + } + statement += `export const ${withCasing(view.name, casing)} = ${func}("${view.name}", {\n`; + statement += createTableColumns( + Object.values(view.columns), + [], + casing, + ); + statement += '})'; + statement += `.as(sql\`${view.definition?.replaceAll('`', '\\`')}\`);`; + + return statement; + }); + const uniqueSqliteImports = [ 'sqliteTable', 'AnySQLiteColumn', @@ -159,7 +225,9 @@ export const schemaToTypeScript = ( } } from "drizzle-orm/sqlite-core" import { sql } from "drizzle-orm"\n\n`; - const decalrations = tableStatements.join('\n\n'); + let decalrations = tableStatements.join('\n\n'); + decalrations += '\n\n'; + decalrations += viewsStatements.join('\n\n'); const file = importsTs + decalrations; @@ -202,10 +270,8 @@ const mapColumnDefault = (defaultValue: any) => { if ( typeof defaultValue === 'string' - && defaultValue.startsWith("'") - && defaultValue.endsWith("'") ) { - return defaultValue.substring(1, defaultValue.length - 1); + return defaultValue.substring(1, defaultValue.length - 1).replaceAll('"', '\\"').replaceAll("''", "'"); } return defaultValue; @@ -219,9 +285,10 @@ const column = ( casing?: Casing, ) => { let lowered = type; + casing = casing!; if (lowered === 'integer') { - let out = `${withCasing(name, casing)}: integer("${name}")`; + let out = `${withCasing(name, casing)}: integer(${dbColumnName({ name, casing })})`; // out += autoincrement ? `.autoincrement()` : ""; out += typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue)})` @@ -230,7 +297,7 @@ const column = ( } if (lowered === 'real') { - let out = `${withCasing(name, casing)}: real("${name}")`; + let out = `${withCasing(name, casing)}: real(${dbColumnName({ name, casing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue)})` : ''; return out; } @@ -240,9 +307,11 @@ const column = ( let out: string; if (match) { - out = `${withCasing(name, casing)}: text("${name}", { length: ${match[0]} })`; + out = `${withCasing(name, casing)}: text(${dbColumnName({ name, casing, withMode: true })}{ length: ${ + match[0] + } })`; } else { - out = `${withCasing(name, casing)}: text("${name}")`; + out = `${withCasing(name, casing)}: text(${dbColumnName({ name, casing })})`; } out += defaultValue ? `.default("${mapColumnDefault(defaultValue)}")` : ''; @@ -250,13 +319,13 @@ const column = ( } if (lowered === 'blob') { - let out = `${withCasing(name, casing)}: blob("${name}")`; + let out = `${withCasing(name, casing)}: blob(${dbColumnName({ name, casing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue)})` : ''; return out; } if (lowered === 'numeric') { - let out = `${withCasing(name, casing)}: numeric("${name}")`; + let out = `${withCasing(name, casing)}: numeric(${dbColumnName({ name, casing })})`; out += defaultValue ? `.default(${mapColumnDefault(defaultValue)})` : ''; return out; } @@ -358,7 +427,7 @@ const createTableIndexes = ( const indexGeneratedName = indexName(tableName, it.columns); const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += it.isUnique ? 'uniqueIndex(' : 'index('; statement += `${escapedIndexName})`; statement += `.on(${ @@ -366,7 +435,6 @@ const createTableIndexes = ( .map((it) => `table.${withCasing(it, casing)}`) .join(', ') }),`; - statement += `\n`; }); return statement; @@ -381,7 +449,7 @@ const createTableUniques = ( unqs.forEach((it) => { const idxKey = withCasing(it.name, casing); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'unique('; statement += `"${it.name}")`; statement += `.on(${ @@ -389,7 +457,22 @@ const createTableUniques = ( .map((it) => `table.${withCasing(it, casing)}`) .join(', ') }),`; - statement += `\n`; + }); + + return statement; +}; +const createTableChecks = ( + checks: CheckConstraint[], + casing: Casing, +): string => { + let statement = ''; + + checks.forEach((it) => { + statement += `\n\t`; + statement += 'check('; + statement += `"${it.name}", `; + statement += `sql\`${it.value}\`)`; + statement += `,`; }); return statement; @@ -399,7 +482,7 @@ const createTablePKs = (pks: PrimaryKey[], casing: Casing): string => { let statement = ''; pks.forEach((it, i) => { - statement += `\t\tpk${i}: `; + statement += `\n\t`; statement += 'primaryKey({ columns: ['; statement += `${ it.columns @@ -409,7 +492,6 @@ const createTablePKs = (pks: PrimaryKey[], casing: Casing): string => { .join(', ') }]${it.name ? `, name: "${it.name}"` : ''}}`; statement += ')'; - statement += `\n`; }); return statement; @@ -421,7 +503,8 @@ const createTableFKs = (fks: ForeignKey[], casing: Casing): string => { fks.forEach((it) => { const isSelf = it.tableTo === it.tableFrom; const tableTo = isSelf ? 'table' : `${withCasing(it.tableTo, casing)}`; - statement += `\t\t${withCasing(it.name, casing)}: foreignKey(() => ({\n`; + statement += `\n\t`; + statement += `foreignKey(() => ({\n`; statement += `\t\t\tcolumns: [${ it.columnsFrom .map((i) => `table.${withCasing(i, casing)}`) @@ -443,7 +526,7 @@ const createTableFKs = (fks: ForeignKey[], casing: Casing): string => { ? `.onDelete("${it.onDelete}")` : ''; - statement += `,\n`; + statement += `,`; }); return statement; diff --git a/drizzle-kit/src/jsonDiffer.js b/drizzle-kit/src/jsonDiffer.js index 113d7e0a4..da8284979 100644 --- a/drizzle-kit/src/jsonDiffer.js +++ b/drizzle-kit/src/jsonDiffer.js @@ -103,6 +103,22 @@ export function diffSchemasOrTables(left, right) { return { added, deleted }; } +export function diffIndPolicies(left, right) { + left = JSON.parse(JSON.stringify(left)); + right = JSON.parse(JSON.stringify(right)); + + const result = Object.entries(diff(left, right) ?? {}); + + const added = result + .filter((it) => it[0].endsWith('__added')) + .map((it) => it[1]); + const deleted = result + .filter((it) => it[0].endsWith('__deleted')) + .map((it) => it[1]); + + return { added, deleted }; +} + export function diffColumns(left, right) { left = JSON.parse(JSON.stringify(left)); right = JSON.parse(JSON.stringify(right)); @@ -146,6 +162,49 @@ export function diffColumns(left, right) { return alteredTables; } +export function diffPolicies(left, right) { + left = JSON.parse(JSON.stringify(left)); + right = JSON.parse(JSON.stringify(right)); + const result = diff(left, right) ?? {}; + + const alteredTables = Object.fromEntries( + Object.entries(result) + .filter((it) => { + return !(it[0].includes('__added') || it[0].includes('__deleted')); + }) + .map((tableEntry) => { + // const entry = { name: it, ...result[it] } + const deletedPolicies = Object.entries(tableEntry[1].policies ?? {}) + .filter((it) => { + return it[0].endsWith('__deleted'); + }) + .map((it) => { + return it[1]; + }); + + const addedPolicies = Object.entries(tableEntry[1].policies ?? {}) + .filter((it) => { + return it[0].endsWith('__added'); + }) + .map((it) => { + return it[1]; + }); + + tableEntry[1].policies = { + added: addedPolicies, + deleted: deletedPolicies, + }; + const table = left[tableEntry[0]]; + return [ + tableEntry[0], + { name: table.name, schema: table.schema, ...tableEntry[1] }, + ]; + }), + ); + + return alteredTables; +} + export function applyJsonDiff(json1, json2) { json1 = JSON.parse(JSON.stringify(json1)); json2 = JSON.parse(JSON.stringify(json2)); @@ -158,6 +217,9 @@ export function applyJsonDiff(json1, json2) { difference.tables = difference.tables || {}; difference.enums = difference.enums || {}; difference.sequences = difference.sequences || {}; + difference.roles = difference.roles || {}; + difference.policies = difference.policies || {}; + difference.views = difference.views || {}; // remove added/deleted schemas const schemaKeys = Object.keys(difference.schemas); @@ -239,6 +301,99 @@ export function applyJsonDiff(json1, json2) { return json2.sequences[it[0]]; }); + const rolesEntries = Object.entries(difference.roles); + const alteredRoles = rolesEntries + .filter((it) => !(it[0].includes('__added') || it[0].includes('__deleted'))) + .map((it) => { + return json2.roles[it[0]]; + }); + + const policiesEntries = Object.entries(difference.policies); + const alteredPolicies = policiesEntries + .filter((it) => !(it[0].includes('__added') || it[0].includes('__deleted'))) + .map((it) => { + return json2.policies[it[0]]; + }); + + const viewsEntries = Object.entries(difference.views); + + const alteredViews = viewsEntries.filter((it) => !(it[0].includes('__added') || it[0].includes('__deleted'))).map( + ([nameWithSchema, view]) => { + const deletedWithOption = view.with__deleted; + + const addedWithOption = view.with__added; + + const deletedWith = Object.fromEntries( + Object.entries(view.with || {}).filter((it) => it[0].endsWith('__deleted')).map(([key, value]) => { + return [key.replace('__deleted', ''), value]; + }), + ); + + const addedWith = Object.fromEntries( + Object.entries(view.with || {}).filter((it) => it[0].endsWith('__added')).map(([key, value]) => { + return [key.replace('__added', ''), value]; + }), + ); + + const alterWith = Object.fromEntries( + Object.entries(view.with || {}).filter((it) => + typeof it[1].__old !== 'undefined' && typeof it[1].__new !== 'undefined' + ).map( + (it) => { + return [it[0], it[1].__new]; + }, + ), + ); + + const alteredSchema = view.schema; + + const alteredDefinition = view.definition; + + const alteredExisting = view.isExisting; + + const addedTablespace = view.tablespace__added; + const droppedTablespace = view.tablespace__deleted; + const alterTablespaceTo = view.tablespace; + + let alteredTablespace; + if (addedTablespace) alteredTablespace = { __new: addedTablespace, __old: 'pg_default' }; + if (droppedTablespace) alteredTablespace = { __new: 'pg_default', __old: droppedTablespace }; + if (alterTablespaceTo) alteredTablespace = alterTablespaceTo; + + const addedUsing = view.using__added; + const droppedUsing = view.using__deleted; + const alterUsingTo = view.using; + + let alteredUsing; + if (addedUsing) alteredUsing = { __new: addedUsing, __old: 'heap' }; + if (droppedUsing) alteredUsing = { __new: 'heap', __old: droppedUsing }; + if (alterUsingTo) alteredUsing = alterUsingTo; + + const alteredMeta = view.meta; + + return Object.fromEntries( + Object.entries({ + name: json2.views[nameWithSchema].name, + schema: json2.views[nameWithSchema].schema, + // pg + deletedWithOption: deletedWithOption, + addedWithOption: addedWithOption, + deletedWith: Object.keys(deletedWith).length ? deletedWith : undefined, + addedWith: Object.keys(addedWith).length ? addedWith : undefined, + alteredWith: Object.keys(alterWith).length ? alterWith : undefined, + alteredSchema, + alteredTablespace, + alteredUsing, + // mysql + alteredMeta, + // common + alteredDefinition, + alteredExisting, + }).filter(([_, value]) => value !== undefined), + ); + }, + ); + const alteredTablesWithColumns = Object.values(difference.tables).map( (table) => { return findAlternationsInTable(table); @@ -249,6 +404,9 @@ export function applyJsonDiff(json1, json2) { alteredTablesWithColumns, alteredEnums, alteredSequences, + alteredRoles, + alteredViews, + alteredPolicies, }; } @@ -286,6 +444,28 @@ const findAlternationsInTable = (table) => { }), ); + const deletedPolicies = Object.fromEntries( + Object.entries(table.policies__deleted || {}) + .concat( + Object.entries(table.policies || {}).filter((it) => it[0].includes('__deleted')), + ) + .map((entry) => [entry[0].replace('__deleted', ''), entry[1]]), + ); + + const addedPolicies = Object.fromEntries( + Object.entries(table.policies__added || {}) + .concat( + Object.entries(table.policies || {}).filter((it) => it[0].includes('__added')), + ) + .map((entry) => [entry[0].replace('__added', ''), entry[1]]), + ); + + const alteredPolicies = Object.fromEntries( + Object.entries(table.policies || {}).filter((it) => { + return !it[0].endsWith('__deleted') && !it[0].endsWith('__added'); + }), + ); + const deletedForeignKeys = Object.fromEntries( Object.entries(table.foreignKeys__deleted || {}) .concat( @@ -346,6 +526,24 @@ const findAlternationsInTable = (table) => { }), ); + const addedCheckConstraints = Object.fromEntries( + Object.entries(table.checkConstraints || {}).filter((it) => { + return it[0].endsWith('__added'); + }), + ); + + const deletedCheckConstraints = Object.fromEntries( + Object.entries(table.checkConstraints || {}).filter((it) => { + return it[0].endsWith('__deleted'); + }), + ); + + const alteredCheckConstraints = Object.fromEntries( + Object.entries(table.checkConstraints || {}).filter((it) => { + return !it[0].endsWith('__deleted') && !it[0].endsWith('__added'); + }), + ); + const mappedAltered = altered.map((it) => alternationsInColumn(it)).filter(Boolean); return { @@ -364,11 +562,18 @@ const findAlternationsInTable = (table) => { addedUniqueConstraints, deletedUniqueConstraints, alteredUniqueConstraints, + deletedPolicies, + addedPolicies, + alteredPolicies, + addedCheckConstraints, + deletedCheckConstraints, + alteredCheckConstraints, }; }; const alternationsInColumn = (column) => { const altered = [column]; + const result = altered .filter((it) => { if ('type' in it && it.type.__old.replace(' (', '(') === it.type.__new.replace(' (', '(')) { @@ -632,6 +837,33 @@ const alternationsInColumn = (column) => { } return it; }) + .map((it) => { + if ('' in it) { + return { + ...it, + autoincrement: { + type: 'changed', + old: it.autoincrement.__old, + new: it.autoincrement.__new, + }, + }; + } + if ('autoincrement__added' in it) { + const { autoincrement__added, ...others } = it; + return { + ...others, + autoincrement: { type: 'added', value: it.autoincrement__added }, + }; + } + if ('autoincrement__deleted' in it) { + const { autoincrement__deleted, ...others } = it; + return { + ...others, + autoincrement: { type: 'deleted', value: it.autoincrement__deleted }, + }; + } + return it; + }) .filter(Boolean); return result[0]; diff --git a/drizzle-kit/src/jsonStatements.ts b/drizzle-kit/src/jsonStatements.ts index ad2afea7f..f64020f5a 100644 --- a/drizzle-kit/src/jsonStatements.ts +++ b/drizzle-kit/src/jsonStatements.ts @@ -1,10 +1,26 @@ import chalk from 'chalk'; -import { table } from 'console'; +import { getNewTableName } from './cli/commands/sqlitePushUtils'; import { warning } from './cli/views'; -import { CommonSquashedSchema, Dialect } from './schemaValidator'; -import { MySqlKitInternals, MySqlSchema, MySqlSquasher } from './serializer/mysqlSchema'; -import { Index, PgSchema, PgSquasher } from './serializer/pgSchema'; -import { SQLiteKitInternals, SQLiteSquasher } from './serializer/sqliteSchema'; +import { CommonSquashedSchema } from './schemaValidator'; +import { MySqlKitInternals, MySqlSchema, MySqlSquasher, View as MySqlView } from './serializer/mysqlSchema'; +import { + Index, + MatViewWithOption, + PgSchema, + PgSquasher, + Policy, + Role, + View as PgView, + ViewWithOption, +} from './serializer/pgSchema'; +import { SingleStoreKitInternals, SingleStoreSchema, SingleStoreSquasher } from './serializer/singlestoreSchema'; +import { + SQLiteKitInternals, + SQLiteSchemaInternal, + SQLiteSchemaSquashed, + SQLiteSquasher, + View as SqliteView, +} from './serializer/sqliteSchema'; import { AlteredColumn, Column, Sequence, Table } from './snapshotsDiffer'; export interface JsonSqliteCreateTableStatement { @@ -22,6 +38,7 @@ export interface JsonSqliteCreateTableStatement { }[]; compositePKs: string[][]; uniqueConstraints?: string[]; + checkConstraints?: string[]; } export interface JsonCreateTableStatement { @@ -32,13 +49,35 @@ export interface JsonCreateTableStatement { compositePKs: string[]; compositePkName?: string; uniqueConstraints?: string[]; - internals?: MySqlKitInternals; + policies?: string[]; + checkConstraints?: string[]; + internals?: MySqlKitInternals | SingleStoreKitInternals; + isRLSEnabled?: boolean; +} + +export interface JsonRecreateTableStatement { + type: 'recreate_table'; + tableName: string; + columns: Column[]; + referenceData: { + name: string; + tableFrom: string; + columnsFrom: string[]; + tableTo: string; + columnsTo: string[]; + onUpdate?: string | undefined; + onDelete?: string | undefined; + }[]; + compositePKs: string[][]; + uniqueConstraints?: string[]; + checkConstraints: string[]; } export interface JsonDropTableStatement { type: 'drop_table'; tableName: string; schema: string; + policies?: string[]; } export interface JsonRenameTableStatement { @@ -84,6 +123,49 @@ export interface JsonAddValueToEnumStatement { before: string; } +////// + +export interface JsonCreateRoleStatement { + type: 'create_role'; + name: string; + values: { + inherit?: boolean; + createDb?: boolean; + createRole?: boolean; + }; +} + +export interface JsonDropRoleStatement { + type: 'drop_role'; + name: string; +} +export interface JsonRenameRoleStatement { + type: 'rename_role'; + nameFrom: string; + nameTo: string; +} + +export interface JsonAlterRoleStatement { + type: 'alter_role'; + name: string; + values: { + inherit?: boolean; + createDb?: boolean; + createRole?: boolean; + }; +} + +////// + +export interface JsonDropValueFromEnumStatement { + type: 'alter_type_drop_value'; + name: string; + schema: string; + deletedValues: string[]; + newValues: string[]; + columnsWithEnum: { schema: string; table: string; column: string }[]; +} + export interface JsonCreateSequenceStatement { type: 'create_sequence'; name: string; @@ -153,12 +235,79 @@ export interface JsonSqliteAddColumnStatement { referenceData?: string; } +export interface JsonCreatePolicyStatement { + type: 'create_policy'; + tableName: string; + data: Policy; + schema: string; +} + +export interface JsonCreateIndPolicyStatement { + type: 'create_ind_policy'; + tableName: string; + data: Policy; +} + +export interface JsonDropPolicyStatement { + type: 'drop_policy'; + tableName: string; + data: Policy; + schema: string; +} + +export interface JsonDropIndPolicyStatement { + type: 'drop_ind_policy'; + tableName: string; + data: Policy; +} + +export interface JsonRenamePolicyStatement { + type: 'rename_policy'; + tableName: string; + oldName: string; + newName: string; + schema: string; +} + +export interface JsonIndRenamePolicyStatement { + type: 'rename_ind_policy'; + tableKey: string; + oldName: string; + newName: string; +} + +export interface JsonEnableRLSStatement { + type: 'enable_rls'; + tableName: string; + schema: string; +} + +export interface JsonDisableRLSStatement { + type: 'disable_rls'; + tableName: string; + schema: string; +} + +export interface JsonAlterPolicyStatement { + type: 'alter_policy'; + tableName: string; + oldData: string; + newData: string; + schema: string; +} + +export interface JsonAlterIndPolicyStatement { + type: 'alter_ind_policy'; + oldData: Policy; + newData: Policy; +} + export interface JsonCreateIndexStatement { type: 'create_index'; tableName: string; data: string; schema: string; - internal?: MySqlKitInternals | SQLiteKitInternals; + internal?: MySqlKitInternals | SQLiteKitInternals | SingleStoreKitInternals; } export interface JsonPgCreateIndexStatement { @@ -173,6 +322,10 @@ export interface JsonReferenceStatement { data: string; schema: string; tableName: string; + isMulticolumn?: boolean; + columnNotNull?: boolean; + columnDefault?: string; + columnType?: string; // fromTable: string; // fromColumns: string[]; // toTable: string; @@ -208,6 +361,20 @@ export interface JsonAlterUniqueConstraint { newConstraintName?: string; } +export interface JsonCreateCheckConstraint { + type: 'create_check_constraint'; + tableName: string; + data: string; + schema?: string; +} + +export interface JsonDeleteCheckConstraint { + type: 'delete_check_constraint'; + tableName: string; + constraintName: string; + schema?: string; +} + export interface JsonCreateCompositePK { type: 'create_composite_pk'; tableName: string; @@ -498,6 +665,114 @@ export interface JsonRenameSchema { to: string; } +export type JsonCreatePgViewStatement = { + type: 'create_view'; +} & Omit; + +export type JsonCreateMySqlViewStatement = { + type: 'mysql_create_view'; + replace: boolean; +} & Omit; + +/* export type JsonCreateSingleStoreViewStatement = { + type: 'singlestore_create_view'; + replace: boolean; +} & Omit; */ + +export type JsonCreateSqliteViewStatement = { + type: 'sqlite_create_view'; +} & Omit; + +export interface JsonDropViewStatement { + type: 'drop_view'; + name: string; + schema?: string; + materialized?: boolean; +} + +export interface JsonRenameViewStatement { + type: 'rename_view'; + nameTo: string; + nameFrom: string; + schema: string; + materialized?: boolean; +} + +export interface JsonRenameMySqlViewStatement { + type: 'rename_view'; + nameTo: string; + nameFrom: string; + schema: string; + materialized?: boolean; +} + +export interface JsonAlterViewAlterSchemaStatement { + type: 'alter_view_alter_schema'; + fromSchema: string; + toSchema: string; + name: string; + materialized?: boolean; +} + +export type JsonAlterViewAddWithOptionStatement = + & { + type: 'alter_view_add_with_option'; + schema: string; + name: string; + } + & ({ + materialized: true; + with: MatViewWithOption; + } | { + materialized: false; + with: ViewWithOption; + }); + +export type JsonAlterViewDropWithOptionStatement = + & { + type: 'alter_view_drop_with_option'; + schema: string; + name: string; + } + & ({ + materialized: true; + with: MatViewWithOption; + } | { + materialized: false; + with: ViewWithOption; + }); + +export interface JsonAlterViewAlterTablespaceStatement { + type: 'alter_view_alter_tablespace'; + toTablespace: string; + name: string; + schema: string; + materialized: true; +} + +export interface JsonAlterViewAlterUsingStatement { + type: 'alter_view_alter_using'; + toUsing: string; + name: string; + schema: string; + materialized: true; +} + +export type JsonAlterMySqlViewStatement = { + type: 'alter_mysql_view'; +} & Omit; + +/* export type JsonAlterSingleStoreViewStatement = { + type: 'alter_singlestore_view'; +} & Omit; */ + +export type JsonAlterViewStatement = + | JsonAlterViewAlterSchemaStatement + | JsonAlterViewAddWithOptionStatement + | JsonAlterViewDropWithOptionStatement + | JsonAlterViewAlterTablespaceStatement + | JsonAlterViewAlterUsingStatement; + export type JsonAlterColumnStatement = | JsonRenameColumnStatement | JsonAlterColumnTypeStatement @@ -519,6 +794,7 @@ export type JsonAlterColumnStatement = | JsonAlterColumnDropIdentityStatement; export type JsonStatement = + | JsonRecreateTableStatement | JsonAlterColumnStatement | JsonCreateTableStatement | JsonDropTableStatement @@ -555,14 +831,41 @@ export type JsonStatement = | JsonDropSequenceStatement | JsonCreateSequenceStatement | JsonMoveSequenceStatement - | JsonRenameSequenceStatement; + | JsonRenameSequenceStatement + | JsonDropPolicyStatement + | JsonCreatePolicyStatement + | JsonAlterPolicyStatement + | JsonRenamePolicyStatement + | JsonEnableRLSStatement + | JsonDisableRLSStatement + | JsonRenameRoleStatement + | JsonCreateRoleStatement + | JsonDropRoleStatement + | JsonAlterRoleStatement + | JsonCreatePgViewStatement + | JsonDropViewStatement + | JsonRenameViewStatement + | JsonAlterViewStatement + | JsonCreateMySqlViewStatement + | JsonAlterMySqlViewStatement + /* | JsonCreateSingleStoreViewStatement + | JsonAlterSingleStoreViewStatement */ + | JsonCreateSqliteViewStatement + | JsonCreateCheckConstraint + | JsonDeleteCheckConstraint + | JsonDropValueFromEnumStatement + | JsonIndRenamePolicyStatement + | JsonDropIndPolicyStatement + | JsonCreateIndPolicyStatement + | JsonAlterIndPolicyStatement; export const preparePgCreateTableJson = ( table: Table, // TODO: remove? json2: PgSchema, ): JsonCreateTableStatement => { - const { name, schema, columns, compositePrimaryKeys, uniqueConstraints } = table; + const { name, schema, columns, compositePrimaryKeys, uniqueConstraints, checkConstraints, policies, isRLSEnabled } = + table; const tableKey = `${schema || 'public'}.${name}`; // TODO: @AndriiSherman. We need this, will add test cases @@ -580,6 +883,9 @@ export const preparePgCreateTableJson = ( compositePKs: Object.values(compositePrimaryKeys), compositePkName: compositePkName, uniqueConstraints: Object.values(uniqueConstraints), + policies: Object.values(policies), + checkConstraints: Object.values(checkConstraints), + isRLSEnabled: isRLSEnabled ?? false, }; }; @@ -592,7 +898,7 @@ export const prepareMySqlCreateTableJson = ( // if previously it was an expression or column internals: MySqlKitInternals, ): JsonCreateTableStatement => { - const { name, schema, columns, compositePrimaryKeys, uniqueConstraints } = table; + const { name, schema, columns, compositePrimaryKeys, uniqueConstraints, checkConstraints } = table; return { type: 'create_table', @@ -608,6 +914,35 @@ export const prepareMySqlCreateTableJson = ( : '', uniqueConstraints: Object.values(uniqueConstraints), internals, + checkConstraints: Object.values(checkConstraints), + }; +}; + +export const prepareSingleStoreCreateTableJson = ( + table: Table, + // TODO: remove? + json2: SingleStoreSchema, + // we need it to know if some of the indexes(and in future other parts) are expressions or columns + // didn't change singlestoreserialaizer, because it will break snapshots and diffs and it's hard to detect + // if previously it was an expression or column + internals: SingleStoreKitInternals, +): JsonCreateTableStatement => { + const { name, schema, columns, compositePrimaryKeys, uniqueConstraints } = table; + + return { + type: 'create_table', + tableName: name, + schema, + columns: Object.values(columns), + compositePKs: Object.values(compositePrimaryKeys), + compositePkName: Object.values(compositePrimaryKeys).length > 0 + ? json2.tables[name].compositePrimaryKeys[ + SingleStoreSquasher.unsquashPK(Object.values(compositePrimaryKeys)[0]) + .name + ].name + : '', + uniqueConstraints: Object.values(uniqueConstraints), + internals, }; }; @@ -615,7 +950,7 @@ export const prepareSQLiteCreateTable = ( table: Table, action?: 'push' | undefined, ): JsonSqliteCreateTableStatement => { - const { name, columns, uniqueConstraints } = table; + const { name, columns, uniqueConstraints, checkConstraints } = table; const references: string[] = Object.values(table.foreignKeys); @@ -636,6 +971,7 @@ export const prepareSQLiteCreateTable = ( referenceData: fks, compositePKs: composites, uniqueConstraints: Object.values(uniqueConstraints), + checkConstraints: Object.values(checkConstraints), }; }; @@ -644,6 +980,7 @@ export const prepareDropTableJson = (table: Table): JsonDropTableStatement => { type: 'drop_table', tableName: table.name, schema: table.schema, + policies: table.policies ? Object.values(table.policies) : [], }; }; @@ -690,6 +1027,36 @@ export const prepareAddValuesToEnumJson = ( }); }; +export const prepareDropEnumValues = ( + name: string, + schema: string, + removedValues: string[], + json2: PgSchema, +): JsonDropValueFromEnumStatement[] => { + if (!removedValues.length) return []; + + const affectedColumns: { schema: string; table: string; column: string }[] = []; + + for (const tableKey in json2.tables) { + const table = json2.tables[tableKey]; + for (const columnKey in table.columns) { + const column = table.columns[columnKey]; + if (column.type === name && column.typeSchema === schema) { + affectedColumns.push({ schema: table.schema || 'public', table: table.name, column: column.name }); + } + } + } + + return [{ + type: 'alter_type_drop_value', + name: name, + schema: schema, + deletedValues: removedValues, + newValues: json2.enums[`${schema}.${name}`].values, + columnsWithEnum: affectedColumns, + }]; +}; + export const prepareDropEnumJson = ( name: string, schema: string, @@ -794,6 +1161,56 @@ export const prepareRenameSequenceJson = ( //////////// +export const prepareCreateRoleJson = ( + role: Role, +): JsonCreateRoleStatement => { + return { + type: 'create_role', + name: role.name, + values: { + createDb: role.createDb, + createRole: role.createRole, + inherit: role.inherit, + }, + }; +}; + +export const prepareAlterRoleJson = ( + role: Role, +): JsonAlterRoleStatement => { + return { + type: 'alter_role', + name: role.name, + values: { + createDb: role.createDb, + createRole: role.createRole, + inherit: role.inherit, + }, + }; +}; + +export const prepareDropRoleJson = ( + name: string, +): JsonDropRoleStatement => { + return { + type: 'drop_role', + name: name, + }; +}; + +export const prepareRenameRoleJson = ( + nameFrom: string, + nameTo: string, +): JsonRenameRoleStatement => { + return { + type: 'rename_role', + nameFrom, + nameTo, + }; +}; + +////////// + export const prepareCreateSchemasJson = ( values: string[], ): JsonCreateSchema[] => { @@ -830,7 +1247,7 @@ export const prepareDeleteSchemasJson = ( export const prepareRenameColumns = ( tableName: string, - // TODO: split for pg and mysql+sqlite without schema + // TODO: split for pg and mysql+sqlite and singlestore without schema schema: string, pairs: { from: Column; to: Column }[], ): JsonRenameColumnStatement[] => { @@ -893,19 +1310,374 @@ export const _prepareSqliteAddColumns = ( return undefined; } - return { - type: 'sqlite_alter_table_add_column', - tableName: tableName, - column: it, - referenceData: columnsWithReference - ? SQLiteSquasher.squashFK(columnsWithReference) - : undefined, - }; - }) - .filter(Boolean) as JsonSqliteAddColumnStatement[]; + return { + type: 'sqlite_alter_table_add_column', + tableName: tableName, + column: it, + referenceData: columnsWithReference + ? SQLiteSquasher.squashFK(columnsWithReference) + : undefined, + }; + }) + .filter(Boolean) as JsonSqliteAddColumnStatement[]; +}; + +export const prepareAlterColumnsMysql = ( + tableName: string, + schema: string, + columns: AlteredColumn[], + // TODO: remove? + json1: CommonSquashedSchema, + json2: CommonSquashedSchema, + action?: 'push' | undefined, +): JsonAlterColumnStatement[] => { + let statements: JsonAlterColumnStatement[] = []; + let dropPkStatements: JsonAlterColumnDropPrimaryKeyStatement[] = []; + let setPkStatements: JsonAlterColumnSetPrimaryKeyStatement[] = []; + + for (const column of columns) { + const columnName = typeof column.name !== 'string' ? column.name.new : column.name; + + const table = json2.tables[tableName]; + const snapshotColumn = table.columns[columnName]; + + const columnType = snapshotColumn.type; + const columnDefault = snapshotColumn.default; + const columnOnUpdate = 'onUpdate' in snapshotColumn ? snapshotColumn.onUpdate : undefined; + const columnNotNull = table.columns[columnName].notNull; + + const columnAutoIncrement = 'autoincrement' in snapshotColumn + ? snapshotColumn.autoincrement ?? false + : false; + + const columnPk = table.columns[columnName].primaryKey; + + if (column.autoincrement?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_autoincrement', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.autoincrement?.type === 'changed') { + const type = column.autoincrement.new + ? 'alter_table_alter_column_set_autoincrement' + : 'alter_table_alter_column_drop_autoincrement'; + + statements.push({ + type, + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.autoincrement?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_autoincrement', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + } + + for (const column of columns) { + const columnName = typeof column.name !== 'string' ? column.name.new : column.name; + + // I used any, because those fields are available only for mysql dialect + // For other dialects it will become undefined, that is fine for json statements + const columnType = json2.tables[tableName].columns[columnName].type; + const columnDefault = json2.tables[tableName].columns[columnName].default; + const columnGenerated = json2.tables[tableName].columns[columnName].generated; + const columnOnUpdate = (json2.tables[tableName].columns[columnName] as any) + .onUpdate; + const columnNotNull = json2.tables[tableName].columns[columnName].notNull; + const columnAutoIncrement = ( + json2.tables[tableName].columns[columnName] as any + ).autoincrement; + const columnPk = (json2.tables[tableName].columns[columnName] as any) + .primaryKey; + + const compositePk = json2.tables[tableName].compositePrimaryKeys[ + `${tableName}_${columnName}` + ]; + + if (typeof column.name !== 'string') { + statements.push({ + type: 'alter_table_rename_column', + tableName, + oldColumnName: column.name.old, + newColumnName: column.name.new, + schema, + }); + } + + if (column.type?.type === 'changed') { + statements.push({ + type: 'alter_table_alter_column_set_type', + tableName, + columnName, + newDataType: column.type.new, + oldDataType: column.type.old, + schema, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + columnGenerated, + }); + } + + if ( + column.primaryKey?.type === 'deleted' + || (column.primaryKey?.type === 'changed' + && !column.primaryKey.new + && typeof compositePk === 'undefined') + ) { + dropPkStatements.push({ + //// + type: 'alter_table_alter_column_drop_pk', + tableName, + columnName, + schema, + }); + } + + if (column.default?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_default', + tableName, + columnName, + newDefaultValue: column.default.value, + schema, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + newDataType: columnType, + columnPk, + }); + } + + if (column.default?.type === 'changed') { + statements.push({ + type: 'alter_table_alter_column_set_default', + tableName, + columnName, + newDefaultValue: column.default.new, + oldDefaultValue: column.default.old, + schema, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + newDataType: columnType, + columnPk, + }); + } + + if (column.default?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_default', + tableName, + columnName, + schema, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + newDataType: columnType, + columnPk, + }); + } + + if (column.notNull?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_notnull', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.notNull?.type === 'changed') { + const type = column.notNull.new + ? 'alter_table_alter_column_set_notnull' + : 'alter_table_alter_column_drop_notnull'; + statements.push({ + type: type, + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.notNull?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_notnull', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.generated?.type === 'added') { + if (columnGenerated?.type === 'virtual') { + warning( + `You are trying to add virtual generated constraint to ${ + chalk.blue( + columnName, + ) + } column. As MySQL docs mention: "Nongenerated columns can be altered to stored but not virtual generated columns". We will drop an existing column and add it with a virtual generated statement. This means that the data previously stored in this column will be wiped, and new data will be generated on each read for this column\n`, + ); + } + statements.push({ + type: 'alter_table_alter_column_set_generated', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + columnGenerated, + }); + } + + if (column.generated?.type === 'changed' && action !== 'push') { + statements.push({ + type: 'alter_table_alter_column_alter_generated', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + columnGenerated, + }); + } + + if (column.generated?.type === 'deleted') { + if (columnGenerated?.type === 'virtual') { + warning( + `You are trying to remove virtual generated constraint from ${ + chalk.blue( + columnName, + ) + } column. As MySQL docs mention: "Stored but not virtual generated columns can be altered to nongenerated columns. The stored generated values become the values of the nongenerated column". We will drop an existing column and add it without a virtual generated statement. This means that this column will have no data after migration\n`, + ); + } + statements.push({ + type: 'alter_table_alter_column_drop_generated', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + columnGenerated, + oldColumn: json1.tables[tableName].columns[columnName], + }); + } + + if ( + column.primaryKey?.type === 'added' + || (column.primaryKey?.type === 'changed' && column.primaryKey.new) + ) { + const wasAutoincrement = statements.filter( + (it) => it.type === 'alter_table_alter_column_set_autoincrement', + ); + if (wasAutoincrement.length === 0) { + setPkStatements.push({ + type: 'alter_table_alter_column_set_pk', + tableName, + schema, + columnName, + }); + } + } + + if (column.onUpdate?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_on_update', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.onUpdate?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_on_update', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + } + + return [...dropPkStatements, ...setPkStatements, ...statements]; }; -export const prepareAlterColumnsMysql = ( +export const prepareAlterColumnsSingleStore = ( tableName: string, schema: string, columns: AlteredColumn[], @@ -988,7 +1760,7 @@ export const prepareAlterColumnsMysql = ( for (const column of columns) { const columnName = typeof column.name !== 'string' ? column.name.new : column.name; - // I used any, because those fields are available only for mysql dialect + // I used any, because those fields are available only for mysql and singlestore dialect // For other dialects it will become undefined, that is fine for json statements const columnType = json2.tables[tableName].columns[columnName].type; const columnDefault = json2.tables[tableName].columns[columnName].default; @@ -1144,6 +1916,7 @@ export const prepareAlterColumnsMysql = ( if (column.generated?.type === 'added') { if (columnGenerated?.type === 'virtual') { + // TODO: Change warning message according to SingleStore docs warning( `You are trying to add virtual generated constraint to ${ chalk.blue( @@ -1185,6 +1958,7 @@ export const prepareAlterColumnsMysql = ( if (column.generated?.type === 'deleted') { if (columnGenerated?.type === 'virtual') { + // TODO: Change warning message according to SingleStore docs warning( `You are trying to remove virtual generated constraint from ${ chalk.blue( @@ -1637,6 +2411,55 @@ export const prepareSqliteAlterColumns = ( `${tableName}_${columnName}` ]; + if (column.autoincrement?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_autoincrement', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.autoincrement?.type === 'changed') { + const type = column.autoincrement.new + ? 'alter_table_alter_column_set_autoincrement' + : 'alter_table_alter_column_drop_autoincrement'; + + statements.push({ + type, + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.autoincrement?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_autoincrement', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + if (typeof column.name !== 'string') { statements.push({ type: 'alter_table_rename_column', @@ -1883,6 +2706,121 @@ export const prepareSqliteAlterColumns = ( return [...dropPkStatements, ...setPkStatements, ...statements]; }; +export const prepareRenamePolicyJsons = ( + tableName: string, + schema: string, + renames: { + from: Policy; + to: Policy; + }[], +): JsonRenamePolicyStatement[] => { + return renames.map((it) => { + return { + type: 'rename_policy', + tableName: tableName, + oldName: it.from.name, + newName: it.to.name, + schema, + }; + }); +}; + +export const prepareRenameIndPolicyJsons = ( + renames: { + from: Policy; + to: Policy; + }[], +): JsonIndRenamePolicyStatement[] => { + return renames.map((it) => { + return { + type: 'rename_ind_policy', + tableKey: it.from.on!, + oldName: it.from.name, + newName: it.to.name, + }; + }); +}; + +export const prepareCreatePolicyJsons = ( + tableName: string, + schema: string, + policies: Policy[], +): JsonCreatePolicyStatement[] => { + return policies.map((it) => { + return { + type: 'create_policy', + tableName, + data: it, + schema, + }; + }); +}; + +export const prepareCreateIndPolicyJsons = ( + policies: Policy[], +): JsonCreateIndPolicyStatement[] => { + return policies.map((it) => { + return { + type: 'create_ind_policy', + tableName: it.on!, + data: it, + }; + }); +}; + +export const prepareDropPolicyJsons = ( + tableName: string, + schema: string, + policies: Policy[], +): JsonDropPolicyStatement[] => { + return policies.map((it) => { + return { + type: 'drop_policy', + tableName, + data: it, + schema, + }; + }); +}; + +export const prepareDropIndPolicyJsons = ( + policies: Policy[], +): JsonDropIndPolicyStatement[] => { + return policies.map((it) => { + return { + type: 'drop_ind_policy', + tableName: it.on!, + data: it, + }; + }); +}; + +export const prepareAlterPolicyJson = ( + tableName: string, + schema: string, + oldPolicy: string, + newPolicy: string, +): JsonAlterPolicyStatement => { + return { + type: 'alter_policy', + tableName, + oldData: oldPolicy, + newData: newPolicy, + schema, + }; +}; + +export const prepareAlterIndPolicyJson = ( + oldPolicy: Policy, + newPolicy: Policy, +): JsonAlterIndPolicyStatement => { + return { + type: 'alter_ind_policy', + oldData: oldPolicy, + newData: newPolicy, + }; +}; + export const preparePgCreateIndexesJson = ( tableName: string, schema: string, @@ -1944,6 +2882,54 @@ export const prepareCreateReferencesJson = ( }; }); }; +export const prepareLibSQLCreateReferencesJson = ( + tableName: string, + schema: string, + foreignKeys: Record, + json2: SQLiteSchemaSquashed, + action?: 'push', +): JsonCreateReferenceStatement[] => { + return Object.values(foreignKeys).map((fkData) => { + const { columnsFrom, tableFrom, columnsTo } = action === 'push' + ? SQLiteSquasher.unsquashPushFK(fkData) + : SQLiteSquasher.unsquashFK(fkData); + + // When trying to alter table in lib sql it is necessary to pass all config for column like "NOT NULL", "DEFAULT", etc. + // If it is multicolumn reference it is not possible to pass this data for all columns + // Pass multicolumn flag for sql statements to not generate migration + let isMulticolumn = false; + + if (columnsFrom.length > 1 || columnsTo.length > 1) { + isMulticolumn = true; + + return { + type: 'create_reference', + tableName, + data: fkData, + schema, + isMulticolumn, + }; + } + + const columnFrom = columnsFrom[0]; + + const { + notNull: columnNotNull, + default: columnDefault, + type: columnType, + } = json2.tables[tableFrom].columns[columnFrom]; + + return { + type: 'create_reference', + tableName, + data: fkData, + schema, + columnNotNull, + columnDefault, + columnType, + }; + }); +}; export const prepareDropReferencesJson = ( tableName: string, @@ -1959,6 +2945,77 @@ export const prepareDropReferencesJson = ( }; }); }; +export const prepareLibSQLDropReferencesJson = ( + tableName: string, + schema: string, + foreignKeys: Record, + json2: SQLiteSchemaSquashed, + meta: SQLiteSchemaInternal['_meta'], + action?: 'push', +): JsonDeleteReferenceStatement[] => { + const statements = Object.values(foreignKeys).map((fkData) => { + const { columnsFrom, tableFrom, columnsTo, name, tableTo, onDelete, onUpdate } = action === 'push' + ? SQLiteSquasher.unsquashPushFK(fkData) + : SQLiteSquasher.unsquashFK(fkData); + + // If all columns from where were references were deleted -> skip this logic + // Drop columns will cover this scenario + const keys = Object.keys(json2.tables[tableName].columns); + const filtered = columnsFrom.filter((it) => keys.includes(it)); + const fullDrop = filtered.length === 0; + if (fullDrop) return; + + // When trying to alter table in lib sql it is necessary to pass all config for column like "NOT NULL", "DEFAULT", etc. + // If it is multicolumn reference it is not possible to pass this data for all columns + // Pass multicolumn flag for sql statements to not generate migration + let isMulticolumn = false; + + if (columnsFrom.length > 1 || columnsTo.length > 1) { + isMulticolumn = true; + + return { + type: 'delete_reference', + tableName, + data: fkData, + schema, + isMulticolumn, + }; + } + + const columnFrom = columnsFrom[0]; + const newTableName = getNewTableName(tableFrom, meta); + + const { + notNull: columnNotNull, + default: columnDefault, + type: columnType, + } = json2.tables[newTableName].columns[columnFrom]; + + const fkToSquash = { + columnsFrom, + columnsTo, + name, + tableFrom: newTableName, + tableTo, + onDelete, + onUpdate, + }; + const foreignKey = action === 'push' + ? SQLiteSquasher.squashPushFK(fkToSquash) + : SQLiteSquasher.squashFK(fkToSquash); + return { + type: 'delete_reference', + tableName, + data: foreignKey, + schema, + columnNotNull, + columnDefault, + columnType, + }; + }); + + return statements.filter((it) => it) as JsonDeleteReferenceStatement[]; +}; // alter should create 2 statements. It's important to make only 1 sql per statement(for breakpoints) export const prepareAlterReferencesJson = ( @@ -2054,9 +3111,7 @@ export const prepareAddCompositePrimaryKeyPg = ( tableName, data: it, schema, - constraintName: json2.tables[`${schema || 'public'}.${tableName}`].compositePrimaryKeys[ - unsquashed.name - ].name, + constraintName: PgSquasher.unsquashPK(it).name, } as JsonCreateCompositePK; }); }; @@ -2074,9 +3129,7 @@ export const prepareDeleteCompositePrimaryKeyPg = ( tableName, data: it, schema, - constraintName: json1.tables[`${schema || 'public'}.${tableName}`].compositePrimaryKeys[ - PgSquasher.unsquashPK(it).name - ].name, + constraintName: PgSquasher.unsquashPK(it).name, } as JsonDeleteCompositePK; }); }; @@ -2096,12 +3149,8 @@ export const prepareAlterCompositePrimaryKeyPg = ( old: it.__old, new: it.__new, schema, - oldConstraintName: json1.tables[`${schema || 'public'}.${tableName}`].compositePrimaryKeys[ - PgSquasher.unsquashPK(it.__old).name - ].name, - newConstraintName: json2.tables[`${schema || 'public'}.${tableName}`].compositePrimaryKeys[ - PgSquasher.unsquashPK(it.__new).name - ].name, + oldConstraintName: PgSquasher.unsquashPK(it.__old).name, + newConstraintName: PgSquasher.unsquashPK(it.__new).name, } as JsonAlterCompositePK; }); }; @@ -2136,6 +3185,36 @@ export const prepareDeleteUniqueConstraintPg = ( }); }; +export const prepareAddCheckConstraint = ( + tableName: string, + schema: string, + check: Record, +): JsonCreateCheckConstraint[] => { + return Object.values(check).map((it) => { + return { + type: 'create_check_constraint', + tableName, + data: it, + schema, + } as JsonCreateCheckConstraint; + }); +}; + +export const prepareDeleteCheckConstraint = ( + tableName: string, + schema: string, + check: Record, +): JsonDeleteCheckConstraint[] => { + return Object.values(check).map((it) => { + return { + type: 'delete_check_constraint', + tableName, + constraintName: PgSquasher.unsquashCheck(it).name, + schema, + } as JsonDeleteCheckConstraint; + }); +}; + // add create table changes // add handler to make drop and add and not alter(looking at __old and __new) // add serializer for mysql and sqlite + types @@ -2184,7 +3263,7 @@ export const prepareAddCompositePrimaryKeyMySql = ( type: 'create_composite_pk', tableName, data: it, - constraintName: json2.tables[tableName].compositePrimaryKeys[unsquashed.name].name, + constraintName: unsquashed.name, } as JsonCreateCompositePK); } return res; @@ -2197,13 +3276,11 @@ export const prepareDeleteCompositePrimaryKeyMySql = ( json1: MySqlSchema, ): JsonDeleteCompositePK[] => { return Object.values(pks).map((it) => { + const unsquashed = MySqlSquasher.unsquashPK(it); return { type: 'delete_composite_pk', tableName, data: it, - constraintName: json1.tables[tableName].compositePrimaryKeys[ - MySqlSquasher.unsquashPK(it).name - ].name, } as JsonDeleteCompositePK; }); }; @@ -2230,3 +3307,194 @@ export const prepareAlterCompositePrimaryKeyMySql = ( } as JsonAlterCompositePK; }); }; + +export const preparePgCreateViewJson = ( + name: string, + schema: string, + definition: string, + materialized: boolean, + withNoData: boolean = false, + withOption?: any, + using?: string, + tablespace?: string, +): JsonCreatePgViewStatement => { + return { + type: 'create_view', + name: name, + schema: schema, + definition: definition, + with: withOption, + materialized: materialized, + withNoData, + using, + tablespace, + }; +}; + +export const prepareMySqlCreateViewJson = ( + name: string, + definition: string, + meta: string, + replace: boolean = false, +): JsonCreateMySqlViewStatement => { + const { algorithm, sqlSecurity, withCheckOption } = MySqlSquasher.unsquashView(meta); + return { + type: 'mysql_create_view', + name: name, + definition: definition, + algorithm, + sqlSecurity, + withCheckOption, + replace, + }; +}; + +/* export const prepareSingleStoreCreateViewJson = ( + name: string, + definition: string, + meta: string, + replace: boolean = false, +): JsonCreateSingleStoreViewStatement => { + const { algorithm, sqlSecurity, withCheckOption } = SingleStoreSquasher.unsquashView(meta); + return { + type: 'singlestore_create_view', + name: name, + definition: definition, + algorithm, + sqlSecurity, + withCheckOption, + replace, + }; +}; */ + +export const prepareSqliteCreateViewJson = ( + name: string, + definition: string, +): JsonCreateSqliteViewStatement => { + return { + type: 'sqlite_create_view', + name: name, + definition: definition, + }; +}; + +export const prepareDropViewJson = ( + name: string, + schema?: string, + materialized?: boolean, +): JsonDropViewStatement => { + const resObject: JsonDropViewStatement = { name, type: 'drop_view' }; + + if (schema) resObject['schema'] = schema; + + if (materialized) resObject['materialized'] = materialized; + + return resObject; +}; + +export const prepareRenameViewJson = ( + to: string, + from: string, + schema?: string, + materialized?: boolean, +): JsonRenameViewStatement => { + const resObject: JsonRenameViewStatement = { + type: 'rename_view', + nameTo: to, + nameFrom: from, + }; + + if (schema) resObject['schema'] = schema; + if (materialized) resObject['materialized'] = materialized; + + return resObject; +}; + +export const preparePgAlterViewAlterSchemaJson = ( + to: string, + from: string, + name: string, + materialized?: boolean, +): JsonAlterViewAlterSchemaStatement => { + const returnObject: JsonAlterViewAlterSchemaStatement = { + type: 'alter_view_alter_schema', + fromSchema: from, + toSchema: to, + name, + }; + + if (materialized) returnObject['materialized'] = materialized; + return returnObject; +}; + +export const preparePgAlterViewAddWithOptionJson = ( + name: string, + schema: string, + materialized: boolean, + withOption: MatViewWithOption | ViewWithOption, +): JsonAlterViewAddWithOptionStatement => { + return { + type: 'alter_view_add_with_option', + name, + schema, + materialized: materialized, + with: withOption, + } as JsonAlterViewAddWithOptionStatement; +}; + +export const preparePgAlterViewDropWithOptionJson = ( + name: string, + schema: string, + materialized: boolean, + withOption: MatViewWithOption | ViewWithOption, +): JsonAlterViewDropWithOptionStatement => { + return { + type: 'alter_view_drop_with_option', + name, + schema, + materialized: materialized, + with: withOption, + } as JsonAlterViewDropWithOptionStatement; +}; + +export const preparePgAlterViewAlterTablespaceJson = ( + name: string, + schema: string, + materialized: boolean, + to: string, +): JsonAlterViewAlterTablespaceStatement => { + return { + type: 'alter_view_alter_tablespace', + name, + schema, + materialized: materialized, + toTablespace: to, + } as JsonAlterViewAlterTablespaceStatement; +}; + +export const preparePgAlterViewAlterUsingJson = ( + name: string, + schema: string, + materialized: boolean, + to: string, +): JsonAlterViewAlterUsingStatement => { + return { + type: 'alter_view_alter_using', + name, + schema, + materialized: materialized, + toUsing: to, + } as JsonAlterViewAlterUsingStatement; +}; + +export const prepareMySqlAlterView = ( + view: Omit, +): JsonAlterMySqlViewStatement => { + return { type: 'alter_mysql_view', ...view }; +}; + +/* export const prepareSingleStoreAlterView = ( + view: Omit, +): JsonAlterSingleStoreViewStatement => { + return { type: 'alter_singlestore_view', ...view }; +}; */ diff --git a/drizzle-kit/src/migrationPreparator.ts b/drizzle-kit/src/migrationPreparator.ts index 687cfdb7c..4e67e8174 100644 --- a/drizzle-kit/src/migrationPreparator.ts +++ b/drizzle-kit/src/migrationPreparator.ts @@ -1,15 +1,18 @@ import { randomUUID } from 'crypto'; import fs from 'fs'; -import { serializeMySql, serializePg, serializeSQLite } from './serializer'; +import { CasingType } from './cli/validations/common'; +import { serializeMySql, serializePg, serializeSingleStore, serializeSQLite } from './serializer'; import { dryMySql, MySqlSchema, mysqlSchema } from './serializer/mysqlSchema'; import { dryPg, PgSchema, pgSchema, PgSchemaInternal } from './serializer/pgSchema'; +import { drySingleStore, SingleStoreSchema, singlestoreSchema } from './serializer/singlestoreSchema'; import { drySQLite, SQLiteSchema, sqliteSchema } from './serializer/sqliteSchema'; export const prepareMySqlDbPushSnapshot = async ( prev: MySqlSchema, schemaPath: string | string[], + casing: CasingType | undefined, ): Promise<{ prev: MySqlSchema; cur: MySqlSchema }> => { - const serialized = await serializeMySql(schemaPath); + const serialized = await serializeMySql(schemaPath, casing); const id = randomUUID(); const idPrev = prev.id; @@ -20,11 +23,28 @@ export const prepareMySqlDbPushSnapshot = async ( return { prev, cur: result }; }; +export const prepareSingleStoreDbPushSnapshot = async ( + prev: SingleStoreSchema, + schemaPath: string | string[], + casing: CasingType | undefined, +): Promise<{ prev: SingleStoreSchema; cur: SingleStoreSchema }> => { + const serialized = await serializeSingleStore(schemaPath, casing); + + const id = randomUUID(); + const idPrev = prev.id; + + const { version, dialect, ...rest } = serialized; + const result: SingleStoreSchema = { version, dialect, id, prevId: idPrev, ...rest }; + + return { prev, cur: result }; +}; + export const prepareSQLiteDbPushSnapshot = async ( prev: SQLiteSchema, schemaPath: string | string[], + casing: CasingType | undefined, ): Promise<{ prev: SQLiteSchema; cur: SQLiteSchema }> => { - const serialized = await serializeSQLite(schemaPath); + const serialized = await serializeSQLite(schemaPath, casing); const id = randomUUID(); const idPrev = prev.id; @@ -44,9 +64,10 @@ export const prepareSQLiteDbPushSnapshot = async ( export const preparePgDbPushSnapshot = async ( prev: PgSchema, schemaPath: string | string[], + casing: CasingType | undefined, schemaFilter: string[] = ['public'], ): Promise<{ prev: PgSchema; cur: PgSchema }> => { - const serialized = await serializePg(schemaPath, schemaFilter); + const serialized = await serializePg(schemaPath, casing, schemaFilter); const id = randomUUID(); const idPrev = prev.id; @@ -60,11 +81,12 @@ export const preparePgDbPushSnapshot = async ( export const prepareMySqlMigrationSnapshot = async ( migrationFolders: string[], schemaPath: string | string[], + casing: CasingType | undefined, ): Promise<{ prev: MySqlSchema; cur: MySqlSchema; custom: MySqlSchema }> => { const prevSnapshot = mysqlSchema.parse( preparePrevSnapshot(migrationFolders, dryMySql), ); - const serialized = await serializeMySql(schemaPath); + const serialized = await serializeMySql(schemaPath, casing); const id = randomUUID(); const idPrev = prevSnapshot.id; @@ -84,14 +106,43 @@ export const prepareMySqlMigrationSnapshot = async ( return { prev: prevSnapshot, cur: result, custom }; }; +export const prepareSingleStoreMigrationSnapshot = async ( + migrationFolders: string[], + schemaPath: string | string[], + casing: CasingType | undefined, +): Promise<{ prev: SingleStoreSchema; cur: SingleStoreSchema; custom: SingleStoreSchema }> => { + const prevSnapshot = singlestoreSchema.parse( + preparePrevSnapshot(migrationFolders, drySingleStore), + ); + const serialized = await serializeSingleStore(schemaPath, casing); + + const id = randomUUID(); + const idPrev = prevSnapshot.id; + + const { version, dialect, ...rest } = serialized; + const result: SingleStoreSchema = { version, dialect, id, prevId: idPrev, ...rest }; + + const { id: _ignoredId, prevId: _ignoredPrevId, ...prevRest } = prevSnapshot; + + // that's for custom migrations, when we need new IDs, but old snapshot + const custom: SingleStoreSchema = { + id, + prevId: idPrev, + ...prevRest, + }; + + return { prev: prevSnapshot, cur: result, custom }; +}; + export const prepareSqliteMigrationSnapshot = async ( snapshots: string[], schemaPath: string | string[], + casing: CasingType | undefined, ): Promise<{ prev: SQLiteSchema; cur: SQLiteSchema; custom: SQLiteSchema }> => { const prevSnapshot = sqliteSchema.parse( preparePrevSnapshot(snapshots, drySQLite), ); - const serialized = await serializeSQLite(schemaPath); + const serialized = await serializeSQLite(schemaPath, casing); const id = randomUUID(); const idPrev = prevSnapshot.id; @@ -133,9 +184,10 @@ export const fillPgSnapshot = ({ export const preparePgMigrationSnapshot = async ( snapshots: string[], schemaPath: string | string[], + casing: CasingType | undefined, ): Promise<{ prev: PgSchema; cur: PgSchema; custom: PgSchema }> => { const prevSnapshot = pgSchema.parse(preparePrevSnapshot(snapshots, dryPg)); - const serialized = await serializePg(schemaPath); + const serialized = await serializePg(schemaPath, casing); const id = randomUUID(); const idPrev = prevSnapshot.id; diff --git a/drizzle-kit/src/schemaValidator.ts b/drizzle-kit/src/schemaValidator.ts index 9c1f4dcfc..e91b5ab11 100644 --- a/drizzle-kit/src/schemaValidator.ts +++ b/drizzle-kit/src/schemaValidator.ts @@ -1,9 +1,10 @@ import { enum as enumType, TypeOf, union } from 'zod'; import { mysqlSchema, mysqlSchemaSquashed } from './serializer/mysqlSchema'; import { pgSchema, pgSchemaSquashed } from './serializer/pgSchema'; +import { singlestoreSchema, singlestoreSchemaSquashed } from './serializer/singlestoreSchema'; import { sqliteSchema, SQLiteSchemaSquashed } from './serializer/sqliteSchema'; -export const dialects = ['postgresql', 'mysql', 'sqlite'] as const; +export const dialects = ['postgresql', 'mysql', 'sqlite', 'turso', 'singlestore'] as const; export const dialect = enumType(dialects); export type Dialect = (typeof dialects)[number]; @@ -13,9 +14,10 @@ const commonSquashedSchema = union([ pgSchemaSquashed, mysqlSchemaSquashed, SQLiteSchemaSquashed, + singlestoreSchemaSquashed, ]); -const commonSchema = union([pgSchema, mysqlSchema, sqliteSchema]); +const commonSchema = union([pgSchema, mysqlSchema, sqliteSchema, singlestoreSchema]); export type CommonSquashedSchema = TypeOf; export type CommonSchema = TypeOf; diff --git a/drizzle-kit/src/serializer/index.ts b/drizzle-kit/src/serializer/index.ts index 214ca38c7..d24afbab0 100644 --- a/drizzle-kit/src/serializer/index.ts +++ b/drizzle-kit/src/serializer/index.ts @@ -1,43 +1,17 @@ import chalk from 'chalk'; -import type { SQL } from 'drizzle-orm'; import fs from 'fs'; import * as glob from 'glob'; import Path from 'path'; +import { CasingType } from 'src/cli/validations/common'; import { error } from '../cli/views'; import type { MySqlSchemaInternal } from './mysqlSchema'; import type { PgSchemaInternal } from './pgSchema'; +import { SingleStoreSchemaInternal } from './singlestoreSchema'; import type { SQLiteSchemaInternal } from './sqliteSchema'; -export const sqlToStr = (sql: SQL) => { - return sql.toQuery({ - escapeName: () => { - throw new Error("we don't support params for `sql` default values"); - }, - escapeParam: () => { - throw new Error("we don't support params for `sql` default values"); - }, - escapeString: () => { - throw new Error("we don't support params for `sql` default values"); - }, - }).sql; -}; - -export const sqlToStrGenerated = (sql: SQL) => { - return sql.toQuery({ - escapeName: () => { - throw new Error("we don't support params for `sql` default values"); - }, - escapeParam: () => { - throw new Error("we don't support params for `sql` default values"); - }, - escapeString: () => { - throw new Error("we don't support params for `sql` default values"); - }, - }).sql; -}; - export const serializeMySql = async ( path: string | string[], + casing: CasingType | undefined, ): Promise => { const filenames = prepareFilenames(path); @@ -46,13 +20,14 @@ export const serializeMySql = async ( const { prepareFromMySqlImports } = await import('./mysqlImports'); const { generateMySqlSnapshot } = await import('./mysqlSerializer'); - const { tables } = await prepareFromMySqlImports(filenames); + const { tables, views } = await prepareFromMySqlImports(filenames); - return generateMySqlSnapshot(tables); + return generateMySqlSnapshot(tables, views, casing); }; export const serializePg = async ( path: string | string[], + casing: CasingType | undefined, schemaFilter?: string[], ): Promise => { const filenames = prepareFilenames(path); @@ -60,22 +35,39 @@ export const serializePg = async ( const { prepareFromPgImports } = await import('./pgImports'); const { generatePgSnapshot } = await import('./pgSerializer'); - const { tables, enums, schemas, sequences } = await prepareFromPgImports( + const { tables, enums, schemas, sequences, views, matViews, roles, policies } = await prepareFromPgImports( filenames, ); - return generatePgSnapshot(tables, enums, schemas, sequences, schemaFilter); + return generatePgSnapshot(tables, enums, schemas, sequences, roles, policies, views, matViews, casing, schemaFilter); }; export const serializeSQLite = async ( path: string | string[], + casing: CasingType | undefined, ): Promise => { const filenames = prepareFilenames(path); const { prepareFromSqliteImports } = await import('./sqliteImports'); const { generateSqliteSnapshot } = await import('./sqliteSerializer'); - const { tables } = await prepareFromSqliteImports(filenames); - return generateSqliteSnapshot(tables); + const { tables, views } = await prepareFromSqliteImports(filenames); + return generateSqliteSnapshot(tables, views, casing); +}; + +export const serializeSingleStore = async ( + path: string | string[], + casing: CasingType | undefined, +): Promise => { + const filenames = prepareFilenames(path); + + console.log(chalk.gray(`Reading schema files:\n${filenames.join('\n')}\n`)); + + const { prepareFromSingleStoreImports } = await import('./singlestoreImports'); + const { generateSingleStoreSnapshot } = await import('./singlestoreSerializer'); + + const { tables /* views */ } = await prepareFromSingleStoreImports(filenames); + + return generateSingleStoreSnapshot(tables, /* views, */ casing); }; export const prepareFilenames = (path: string | string[]) => { diff --git a/drizzle-kit/src/serializer/mysqlImports.ts b/drizzle-kit/src/serializer/mysqlImports.ts index d9899026b..a8e8ead39 100644 --- a/drizzle-kit/src/serializer/mysqlImports.ts +++ b/drizzle-kit/src/serializer/mysqlImports.ts @@ -1,22 +1,28 @@ import { is } from 'drizzle-orm'; -import { AnyMySqlTable, MySqlTable } from 'drizzle-orm/mysql-core'; +import { AnyMySqlTable, MySqlTable, MySqlView } from 'drizzle-orm/mysql-core'; import { safeRegister } from '../cli/commands/utils'; export const prepareFromExports = (exports: Record) => { const tables: AnyMySqlTable[] = []; + const views: MySqlView[] = []; const i0values = Object.values(exports); i0values.forEach((t) => { if (is(t, MySqlTable)) { tables.push(t); } + + if (is(t, MySqlView)) { + views.push(t); + } }); - return { tables }; + return { tables, views }; }; export const prepareFromMySqlImports = async (imports: string[]) => { const tables: AnyMySqlTable[] = []; + const views: MySqlView[] = []; const { unregister } = await safeRegister(); for (let i = 0; i < imports.length; i++) { @@ -25,7 +31,8 @@ export const prepareFromMySqlImports = async (imports: string[]) => { const prepared = prepareFromExports(i0); tables.push(...prepared.tables); + views.push(...prepared.views); } unregister(); - return { tables: Array.from(new Set(tables)) }; + return { tables: Array.from(new Set(tables)), views }; }; diff --git a/drizzle-kit/src/serializer/mysqlSchema.ts b/drizzle-kit/src/serializer/mysqlSchema.ts index 5bc62ab2f..3a6fb9179 100644 --- a/drizzle-kit/src/serializer/mysqlSchema.ts +++ b/drizzle-kit/src/serializer/mysqlSchema.ts @@ -1,5 +1,5 @@ import { any, boolean, enum as enumType, literal, object, record, string, TypeOf, union } from 'zod'; -import { mapValues, originUUID, snapshotVersion } from '../global'; +import { mapValues, originUUID } from '../global'; // ------- V3 -------- const index = object({ @@ -52,6 +52,11 @@ const uniqueConstraint = object({ columns: string().array(), }).strict(); +const checkConstraint = object({ + name: string(), + value: string(), +}).strict(); + const tableV4 = object({ name: string(), schema: string().optional(), @@ -67,8 +72,23 @@ const table = object({ foreignKeys: record(string(), fk), compositePrimaryKeys: record(string(), compositePK), uniqueConstraints: record(string(), uniqueConstraint).default({}), + checkConstraint: record(string(), checkConstraint).default({}), }).strict(); +const viewMeta = object({ + algorithm: enumType(['undefined', 'merge', 'temptable']), + sqlSecurity: enumType(['definer', 'invoker']), + withCheckOption: enumType(['local', 'cascaded']).optional(), +}).strict(); + +export const view = object({ + name: string(), + columns: record(string(), column), + definition: string().optional(), + isExisting: boolean(), +}).strict().merge(viewMeta); +type SquasherViewMeta = Omit, 'definer'>; + export const kitInternals = object({ tables: record( string(), @@ -128,6 +148,7 @@ export const schemaInternal = object({ version: literal('5'), dialect: dialect, tables: record(string(), table), + views: record(string(), view).default({}), _meta: object({ tables: record(string(), string()), columns: record(string(), string()), @@ -155,12 +176,20 @@ const tableSquashed = object({ foreignKeys: record(string(), string()), compositePrimaryKeys: record(string(), string()), uniqueConstraints: record(string(), string()).default({}), + checkConstraints: record(string(), string()).default({}), }).strict(); +const viewSquashed = view.omit({ + algorithm: true, + sqlSecurity: true, + withCheckOption: true, +}).extend({ meta: string() }); + export const schemaSquashed = object({ version: literal('5'), dialect: dialect, tables: record(string(), tableSquashed), + views: record(string(), viewSquashed), }).strict(); export const schemaSquashedV4 = object({ @@ -186,6 +215,9 @@ export type Index = TypeOf; export type ForeignKey = TypeOf; export type PrimaryKey = TypeOf; export type UniqueConstraint = TypeOf; +export type CheckConstraint = TypeOf; +export type View = TypeOf; +export type ViewSquashed = TypeOf; export const MySqlSquasher = { squashIdx: (idx: Index) => { @@ -247,6 +279,27 @@ export const MySqlSquasher = { }); return result; }, + squashCheck: (input: CheckConstraint): string => { + return `${input.name};${input.value}`; + }, + unsquashCheck: (input: string): CheckConstraint => { + const [name, value] = input.split(';'); + + return { name, value }; + }, + squashView: (view: View): string => { + return `${view.algorithm};${view.sqlSecurity};${view.withCheckOption}`; + }, + unsquashView: (meta: string): SquasherViewMeta => { + const [algorithm, sqlSecurity, withCheckOption] = meta.split(';'); + const toReturn = { + algorithm: algorithm, + sqlSecurity: sqlSecurity, + withCheckOption: withCheckOption !== 'undefined' ? withCheckOption : undefined, + }; + + return viewMeta.parse(toReturn); + }, }; export const squashMysqlSchemeV4 = ( @@ -304,6 +357,10 @@ export const squashMysqlScheme = (json: MySqlSchema): MySqlSchemaSquashed => { }, ); + const squashedCheckConstraints = mapValues(it[1].checkConstraint, (check) => { + return MySqlSquasher.squashCheck(check); + }); + return [ it[0], { @@ -313,14 +370,31 @@ export const squashMysqlScheme = (json: MySqlSchema): MySqlSchemaSquashed => { foreignKeys: squashedFKs, compositePrimaryKeys: squashedPKs, uniqueConstraints: squashedUniqueConstraints, + checkConstraints: squashedCheckConstraints, }, ]; }), ); + + const mappedViews = Object.fromEntries( + Object.entries(json.views).map(([key, value]) => { + const meta = MySqlSquasher.squashView(value); + + return [key, { + name: value.name, + isExisting: value.isExisting, + columns: value.columns, + definition: value.definition, + meta, + }]; + }), + ); + return { version: '5', dialect: json.dialect, tables: mappedTables, + views: mappedViews, }; }; @@ -340,6 +414,7 @@ export const dryMySql = mysqlSchema.parse({ prevId: '', tables: {}, schemas: {}, + views: {}, _meta: { schemas: {}, tables: {}, diff --git a/drizzle-kit/src/serializer/mysqlSerializer.ts b/drizzle-kit/src/serializer/mysqlSerializer.ts index eb18743fb..aaa1acb82 100644 --- a/drizzle-kit/src/serializer/mysqlSerializer.ts +++ b/drizzle-kit/src/serializer/mysqlSerializer.ts @@ -1,12 +1,21 @@ import chalk from 'chalk'; -import { getTableName, is } from 'drizzle-orm'; -import { SQL } from 'drizzle-orm'; -import { AnyMySqlTable, MySqlDialect, type PrimaryKey as PrimaryKeyORM, uniqueKeyName } from 'drizzle-orm/mysql-core'; -import { getTableConfig } from 'drizzle-orm/mysql-core'; +import { getTableName, is, SQL } from 'drizzle-orm'; +import { + AnyMySqlTable, + getTableConfig, + getViewConfig, + MySqlColumn, + MySqlDialect, + MySqlView, + type PrimaryKey as PrimaryKeyORM, + uniqueKeyName, +} from 'drizzle-orm/mysql-core'; import { RowDataPacket } from 'mysql2/promise'; +import { CasingType } from 'src/cli/validations/common'; import { withStyle } from '../cli/validations/outputs'; import { IntrospectStage, IntrospectStatus } from '../cli/views'; import { + CheckConstraint, Column, ForeignKey, Index, @@ -15,23 +24,32 @@ import { PrimaryKey, Table, UniqueConstraint, + View, } from '../serializer/mysqlSchema'; -import type { DB } from '../utils'; -import { sqlToStr } from '.'; -// import { MySqlColumnWithAutoIncrement } from "drizzle-orm/mysql-core"; -// import { MySqlDateBaseColumn } from "drizzle-orm/mysql-core"; - -const dialect = new MySqlDialect(); +import { type DB, escapeSingleQuotes } from '../utils'; +import { getColumnCasing, sqlToStr } from './utils'; export const indexName = (tableName: string, columns: string[]) => { return `${tableName}_${columns.join('_')}_index`; }; +const handleEnumType = (type: string) => { + let str = type.split('(')[1]; + str = str.substring(0, str.length - 1); + const values = str.split(',').map((v) => `'${escapeSingleQuotes(v.substring(1, v.length - 1))}'`); + return `enum(${values.join(',')})`; +}; + export const generateMySqlSnapshot = ( tables: AnyMySqlTable[], + views: MySqlView[], + casing: CasingType | undefined, ): MySqlSchemaInternal => { + const dialect = new MySqlDialect({ casing }); const result: Record = {}; + const resultViews: Record = {}; const internal: MySqlKitInternals = { tables: {}, indexes: {} }; + for (const table of tables) { const { name: tableName, @@ -39,18 +57,26 @@ export const generateMySqlSnapshot = ( indexes, foreignKeys, schema, + checks, primaryKeys, uniqueConstraints, } = getTableConfig(table); + const columnsObject: Record = {}; const indexesObject: Record = {}; const foreignKeysObject: Record = {}; const primaryKeysObject: Record = {}; const uniqueConstraintObject: Record = {}; + const checkConstraintObject: Record = {}; + + // this object will help to identify same check names + let checksInTable: Record = {}; columns.forEach((column) => { + const name = getColumnCasing(column, casing); const notNull: boolean = column.notNull; - const sqlTypeLowered = column.getSQLType().toLowerCase(); + const sqlType = column.getSQLType(); + const sqlTypeLowered = sqlType.toLowerCase(); const autoIncrement = typeof (column as any).autoIncrement === 'undefined' ? false : (column as any).autoIncrement; @@ -58,8 +84,8 @@ export const generateMySqlSnapshot = ( const generated = column.generated; const columnToSet: Column = { - name: column.name, - type: column.getSQLType(), + name, + type: sqlType.startsWith('enum') ? handleEnumType(sqlType) : sqlType, primaryKey: false, // If field is autoincrement it's notNull by default // notNull: autoIncrement ? true : notNull, @@ -79,9 +105,9 @@ export const generateMySqlSnapshot = ( }; if (column.primary) { - primaryKeysObject[`${tableName}_${column.name}`] = { - name: `${tableName}_${column.name}`, - columns: [column.name], + primaryKeysObject[`${tableName}_${name}`] = { + name: `${tableName}_${name}`, + columns: [name], }; } @@ -101,7 +127,7 @@ export const generateMySqlSnapshot = ( ) } on the ${ chalk.underline.blue( - column.name, + name, ) } column is confilcting with a unique constraint name already defined for ${ chalk.underline.blue( @@ -120,10 +146,10 @@ export const generateMySqlSnapshot = ( if (column.default !== undefined) { if (is(column.default, SQL)) { - columnToSet.default = sqlToStr(column.default); + columnToSet.default = sqlToStr(column.default, casing); } else { if (typeof column.default === 'string') { - columnToSet.default = `'${column.default}'`; + columnToSet.default = `'${escapeSingleQuotes(column.default)}'`; } else { if (sqlTypeLowered === 'json') { columnToSet.default = `'${JSON.stringify(column.default)}'`; @@ -150,24 +176,33 @@ export const generateMySqlSnapshot = ( } } } - columnsObject[column.name] = columnToSet; + columnsObject[name] = columnToSet; }); primaryKeys.map((pk: PrimaryKeyORM) => { - const columnNames = pk.columns.map((c: any) => c.name); - primaryKeysObject[pk.getName()] = { - name: pk.getName(), + const originalColumnNames = pk.columns.map((c) => c.name); + const columnNames = pk.columns.map((c: any) => getColumnCasing(c, casing)); + + let name = pk.getName(); + if (casing !== undefined) { + for (let i = 0; i < originalColumnNames.length; i++) { + name = name.replace(originalColumnNames[i], columnNames[i]); + } + } + + primaryKeysObject[name] = { + name, columns: columnNames, }; // all composite pk's should be treated as notNull for (const column of pk.columns) { - columnsObject[column.name].notNull = true; + columnsObject[getColumnCasing(column, casing)].notNull = true; } }); uniqueConstraints?.map((unq) => { - const columnNames = unq.columns.map((c) => c.name); + const columnNames = unq.columns.map((c) => getColumnCasing(c, casing)); const name = unq.name ?? uniqueKeyName(table, columnNames); @@ -206,7 +241,6 @@ export const generateMySqlSnapshot = ( }); const fks: ForeignKey[] = foreignKeys.map((fk) => { - const name = fk.getName(); const tableFrom = tableName; const onDelete = fk.onDelete ?? 'no action'; const onUpdate = fk.onUpdate ?? 'no action'; @@ -216,8 +250,22 @@ export const generateMySqlSnapshot = ( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const tableTo = getTableName(referenceFT); - const columnsFrom = reference.columns.map((it) => it.name); - const columnsTo = reference.foreignColumns.map((it) => it.name); + + const originalColumnsFrom = reference.columns.map((it) => it.name); + const columnsFrom = reference.columns.map((it) => getColumnCasing(it, casing)); + const originalColumnsTo = reference.foreignColumns.map((it) => it.name); + const columnsTo = reference.foreignColumns.map((it) => getColumnCasing(it, casing)); + + let name = fk.getName(); + if (casing !== undefined) { + for (let i = 0; i < originalColumnsFrom.length; i++) { + name = name.replace(originalColumnsFrom[i], columnsFrom[i]); + } + for (let i = 0; i < originalColumnsTo.length; i++) { + name = name.replace(originalColumnsTo[i], columnsTo[i]); + } + } + return { name, tableFrom, @@ -259,7 +307,7 @@ export const generateMySqlSnapshot = ( } return sql; } else { - return `${it.name}`; + return `${getColumnCasing(it, casing)}`; } }); @@ -322,6 +370,39 @@ export const generateMySqlSnapshot = ( }; }); + checks.forEach((check) => { + check; + const checkName = check.name; + if (typeof checksInTable[tableName] !== 'undefined') { + if (checksInTable[tableName].includes(check.name)) { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated check constraint name in ${ + chalk.underline.blue( + tableName, + ) + }. Please rename your check constraint in the ${ + chalk.underline.blue( + tableName, + ) + } table`, + ) + }`, + ); + process.exit(1); + } + checksInTable[tableName].push(checkName); + } else { + checksInTable[tableName] = [check.name]; + } + + checkConstraintObject[checkName] = { + name: checkName, + value: dialect.sqlToQuery(check.value).sql, + }; + }); + // only handle tables without schemas if (!schema) { result[tableName] = { @@ -331,14 +412,126 @@ export const generateMySqlSnapshot = ( foreignKeys: foreignKeysObject, compositePrimaryKeys: primaryKeysObject, uniqueConstraints: uniqueConstraintObject, + checkConstraint: checkConstraintObject, }; } } + for (const view of views) { + const { + isExisting, + name, + query, + schema, + selectedFields, + algorithm, + sqlSecurity, + withCheckOption, + } = getViewConfig(view); + + const columnsObject: Record = {}; + + const existingView = resultViews[name]; + if (typeof existingView !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated view name across ${ + chalk.underline.blue( + schema ?? 'public', + ) + } schema. Please rename your view`, + ) + }`, + ); + process.exit(1); + } + + for (const key in selectedFields) { + if (is(selectedFields[key], MySqlColumn)) { + const column = selectedFields[key]; + + const notNull: boolean = column.notNull; + const sqlTypeLowered = column.getSQLType().toLowerCase(); + const autoIncrement = typeof (column as any).autoIncrement === 'undefined' + ? false + : (column as any).autoIncrement; + + const generated = column.generated; + + const columnToSet: Column = { + name: column.name, + type: column.getSQLType(), + primaryKey: false, + // If field is autoincrement it's notNull by default + // notNull: autoIncrement ? true : notNull, + notNull, + autoincrement: autoIncrement, + onUpdate: (column as any).hasOnUpdateNow, + generated: generated + ? { + as: is(generated.as, SQL) + ? dialect.sqlToQuery(generated.as as SQL).sql + : typeof generated.as === 'function' + ? dialect.sqlToQuery(generated.as() as SQL).sql + : (generated.as as any), + type: generated.mode ?? 'stored', + } + : undefined, + }; + + if (column.default !== undefined) { + if (is(column.default, SQL)) { + columnToSet.default = sqlToStr(column.default, casing); + } else { + if (typeof column.default === 'string') { + columnToSet.default = `'${column.default}'`; + } else { + if (sqlTypeLowered === 'json') { + columnToSet.default = `'${JSON.stringify(column.default)}'`; + } else if (column.default instanceof Date) { + if (sqlTypeLowered === 'date') { + columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; + } else if ( + sqlTypeLowered.startsWith('datetime') + || sqlTypeLowered.startsWith('timestamp') + ) { + columnToSet.default = `'${ + column.default + .toISOString() + .replace('T', ' ') + .slice(0, 23) + }'`; + } + } else { + columnToSet.default = column.default; + } + } + if (['blob', 'text', 'json'].includes(column.getSQLType())) { + columnToSet.default = `(${columnToSet.default})`; + } + } + } + columnsObject[column.name] = columnToSet; + } + } + + resultViews[name] = { + columns: columnsObject, + name, + isExisting, + definition: isExisting ? undefined : dialect.sqlToQuery(query!).sql, + withCheckOption, + algorithm: algorithm ?? 'undefined', // set default values + sqlSecurity: sqlSecurity ?? 'definer', // set default values + }; + } + return { version: '5', dialect: 'mysql', tables: result, + views: resultViews, _meta: { tables: {}, columns: {}, @@ -359,9 +552,9 @@ function clearDefaults(defaultValue: any, collate: string) { .substring(collate.length, defaultValue.length) .replace(/\\/g, ''); if (resultDefault.startsWith("'") && resultDefault.endsWith("'")) { - return `('${resultDefault.substring(1, resultDefault.length - 1)}')`; + return `('${escapeSingleQuotes(resultDefault.substring(1, resultDefault.length - 1))}')`; } else { - return `'${resultDefault}'`; + return `'${escapeSingleQuotes(resultDefault.substring(1, resultDefault.length - 1))}'`; } } else { return `(${resultDefault})`; @@ -393,6 +586,8 @@ export const fromDatabase = async ( let tablesCount = new Set(); let indexesCount = 0; let foreignKeysCount = 0; + let checksCount = 0; + let viewsCount = 0; const idxs = await db.query( `select * from INFORMATION_SCHEMA.STATISTICS @@ -464,8 +659,8 @@ export const fromDatabase = async ( } } - if (columnType.startsWith('tinyint')) { - changedType = 'tinyint'; + if (columnType.includes('decimal(10,0)')) { + changedType = columnType.replace('decimal(10,0)', 'decimal'); } let onUpdate: boolean | undefined = undefined; @@ -478,14 +673,14 @@ export const fromDatabase = async ( } const newColumn: Column = { - default: columnDefault === null + default: columnDefault === null || columnDefault === undefined ? undefined : /^-?[\d.]+(?:e-?\d+)?$/.test(columnDefault) - && !columnType.startsWith('decimal') + && !['decimal', 'char', 'varchar'].some((type) => columnType.startsWith(type)) ? Number(columnDefault) : isDefaultAnExpression ? clearDefaults(columnDefault, collation) - : `'${columnDefault}'`, + : `'${escapeSingleQuotes(columnDefault)}'`, autoincrement: isAutoincrement, name: columnName, type: changedType, @@ -536,6 +731,7 @@ export const fromDatabase = async ( indexes: {}, foreignKeys: {}, uniqueConstraints: {}, + checkConstraint: {}, }; } else { result[tableName]!.columns[columnName] = newColumn; @@ -709,16 +905,91 @@ export const fromDatabase = async ( } } + const views = await db.query( + `select * from INFORMATION_SCHEMA.VIEWS WHERE table_schema = '${inputSchema}';`, + ); + + const resultViews: Record = {}; + + viewsCount = views.length; + if (progressCallback) { + progressCallback('views', viewsCount, 'fetching'); + } + for await (const view of views) { + const viewName = view['TABLE_NAME']; + const definition = view['VIEW_DEFINITION']; + + const withCheckOption = view['CHECK_OPTION'] === 'NONE' ? undefined : view['CHECK_OPTION'].toLowerCase(); + const sqlSecurity = view['SECURITY_TYPE'].toLowerCase(); + + const [createSqlStatement] = await db.query(`SHOW CREATE VIEW \`${viewName}\`;`); + const algorithmMatch = createSqlStatement['Create View'].match(/ALGORITHM=([^ ]+)/); + const algorithm = algorithmMatch ? algorithmMatch[1].toLowerCase() : undefined; + + const columns = result[viewName].columns; + delete result[viewName]; + + resultViews[viewName] = { + columns: columns, + isExisting: false, + name: viewName, + algorithm, + definition, + sqlSecurity, + withCheckOption, + }; + } + if (progressCallback) { progressCallback('indexes', indexesCount, 'done'); // progressCallback("enums", 0, "fetching"); progressCallback('enums', 0, 'done'); + progressCallback('views', viewsCount, 'done'); + } + + const checkConstraints = await db.query( + `SELECT + tc.table_name, + tc.constraint_name, + cc.check_clause +FROM + information_schema.table_constraints tc +JOIN + information_schema.check_constraints cc + ON tc.constraint_name = cc.constraint_name +WHERE + tc.constraint_schema = '${inputSchema}' +AND + tc.constraint_type = 'CHECK';`, + ); + + checksCount += checkConstraints.length; + if (progressCallback) { + progressCallback('checks', checksCount, 'fetching'); + } + for (const checkConstraintRow of checkConstraints) { + const constraintName = checkConstraintRow['CONSTRAINT_NAME']; + const constraintValue = checkConstraintRow['CHECK_CLAUSE']; + const tableName = checkConstraintRow['TABLE_NAME']; + + const tableInResult = result[tableName]; + // if (typeof tableInResult === 'undefined') continue; + + tableInResult.checkConstraint[constraintName] = { + name: constraintName, + value: constraintValue, + }; + } + + if (progressCallback) { + progressCallback('checks', checksCount, 'done'); } return { version: '5', dialect: 'mysql', tables: result, + views: resultViews, _meta: { tables: {}, columns: {}, diff --git a/drizzle-kit/src/serializer/pgImports.ts b/drizzle-kit/src/serializer/pgImports.ts index ffedd084c..40e54616a 100644 --- a/drizzle-kit/src/serializer/pgImports.ts +++ b/drizzle-kit/src/serializer/pgImports.ts @@ -1,5 +1,19 @@ import { is } from 'drizzle-orm'; -import { AnyPgTable, isPgEnum, isPgSequence, PgEnum, PgSchema, PgSequence, PgTable } from 'drizzle-orm/pg-core'; +import { + AnyPgTable, + isPgEnum, + isPgMaterializedView, + isPgSequence, + isPgView, + PgEnum, + PgMaterializedView, + PgPolicy, + PgRole, + PgSchema, + PgSequence, + PgTable, + PgView, +} from 'drizzle-orm/pg-core'; import { safeRegister } from '../cli/commands/utils'; export const prepareFromExports = (exports: Record) => { @@ -7,6 +21,10 @@ export const prepareFromExports = (exports: Record) => { const enums: PgEnum[] = []; const schemas: PgSchema[] = []; const sequences: PgSequence[] = []; + const roles: PgRole[] = []; + const policies: PgPolicy[] = []; + const views: PgView[] = []; + const matViews: PgMaterializedView[] = []; const i0values = Object.values(exports); i0values.forEach((t) => { @@ -22,19 +40,39 @@ export const prepareFromExports = (exports: Record) => { schemas.push(t); } + if (isPgView(t)) { + views.push(t); + } + + if (isPgMaterializedView(t)) { + matViews.push(t); + } + if (isPgSequence(t)) { sequences.push(t); } + + if (is(t, PgRole)) { + roles.push(t); + } + + if (is(t, PgPolicy)) { + policies.push(t); + } }); - return { tables, enums, schemas, sequences }; + return { tables, enums, schemas, sequences, views, matViews, roles, policies }; }; export const prepareFromPgImports = async (imports: string[]) => { - let tables: AnyPgTable[] = []; - let enums: PgEnum[] = []; - let schemas: PgSchema[] = []; - let sequences: PgSequence[] = []; + const tables: AnyPgTable[] = []; + const enums: PgEnum[] = []; + const schemas: PgSchema[] = []; + const sequences: PgSequence[] = []; + const views: PgView[] = []; + const roles: PgRole[] = []; + const policies: PgPolicy[] = []; + const matViews: PgMaterializedView[] = []; const { unregister } = await safeRegister(); for (let i = 0; i < imports.length; i++) { @@ -47,8 +85,12 @@ export const prepareFromPgImports = async (imports: string[]) => { enums.push(...prepared.enums); schemas.push(...prepared.schemas); sequences.push(...prepared.sequences); + views.push(...prepared.views); + matViews.push(...prepared.matViews); + roles.push(...prepared.roles); + policies.push(...prepared.policies); } unregister(); - return { tables: Array.from(new Set(tables)), enums, schemas, sequences }; + return { tables: Array.from(new Set(tables)), enums, schemas, sequences, views, matViews, roles, policies }; }; diff --git a/drizzle-kit/src/serializer/pgSchema.ts b/drizzle-kit/src/serializer/pgSchema.ts index 7e4a55afd..d7604d645 100644 --- a/drizzle-kit/src/serializer/pgSchema.ts +++ b/drizzle-kit/src/serializer/pgSchema.ts @@ -147,6 +147,13 @@ export const sequenceSchema = object({ schema: string(), }).strict(); +export const roleSchema = object({ + name: string(), + createDb: boolean().optional(), + createRole: boolean().optional(), + inherit: boolean().optional(), +}).strict(); + export const sequenceSquashed = object({ name: string(), schema: string(), @@ -184,6 +191,11 @@ const column = object({ .optional(), }).strict(); +const checkConstraint = object({ + name: string(), + value: string(), +}).strict(); + const columnSquashed = object({ name: string(), type: string(), @@ -219,6 +231,64 @@ const uniqueConstraint = object({ nullsNotDistinct: boolean(), }).strict(); +export const policy = object({ + name: string(), + as: enumType(['PERMISSIVE', 'RESTRICTIVE']).optional(), + for: enumType(['ALL', 'SELECT', 'INSERT', 'UPDATE', 'DELETE']).optional(), + to: string().array().optional(), + using: string().optional(), + withCheck: string().optional(), + on: string().optional(), + schema: string().optional(), +}).strict(); + +export const policySquashed = object({ + name: string(), + values: string(), +}).strict(); + +const viewWithOption = object({ + checkOption: enumType(['local', 'cascaded']).optional(), + securityBarrier: boolean().optional(), + securityInvoker: boolean().optional(), +}).strict(); + +const matViewWithOption = object({ + fillfactor: number().optional(), + toastTupleTarget: number().optional(), + parallelWorkers: number().optional(), + autovacuumEnabled: boolean().optional(), + vacuumIndexCleanup: enumType(['auto', 'off', 'on']).optional(), + vacuumTruncate: boolean().optional(), + autovacuumVacuumThreshold: number().optional(), + autovacuumVacuumScaleFactor: number().optional(), + autovacuumVacuumCostDelay: number().optional(), + autovacuumVacuumCostLimit: number().optional(), + autovacuumFreezeMinAge: number().optional(), + autovacuumFreezeMaxAge: number().optional(), + autovacuumFreezeTableAge: number().optional(), + autovacuumMultixactFreezeMinAge: number().optional(), + autovacuumMultixactFreezeMaxAge: number().optional(), + autovacuumMultixactFreezeTableAge: number().optional(), + logAutovacuumMinDuration: number().optional(), + userCatalogTable: boolean().optional(), +}).strict(); + +export const mergedViewWithOption = viewWithOption.merge(matViewWithOption).strict(); + +export const view = object({ + name: string(), + schema: string(), + columns: record(string(), column), + definition: string().optional(), + materialized: boolean(), + with: mergedViewWithOption.optional(), + isExisting: boolean(), + withNoData: boolean().optional(), + using: string().optional(), + tablespace: string().optional(), +}).strict(); + const tableV4 = object({ name: string(), schema: string(), @@ -265,6 +335,9 @@ const table = object({ foreignKeys: record(string(), fk), compositePrimaryKeys: record(string(), compositePK), uniqueConstraints: record(string(), uniqueConstraint).default({}), + policies: record(string(), policy).default({}), + checkConstraints: record(string(), checkConstraint).default({}), + isRLSEnabled: boolean().default(false), }).strict(); const schemaHash = object({ @@ -282,6 +355,7 @@ export const kitInternals = object({ isArray: boolean().optional(), dimensions: number().optional(), rawType: string().optional(), + isDefaultAnExpression: boolean().optional(), }).optional(), ), }).optional(), @@ -366,7 +440,10 @@ export const pgSchemaInternal = object({ tables: record(string(), table), enums: record(string(), enumSchema), schemas: record(string(), string()), + views: record(string(), view).default({}), sequences: record(string(), sequenceSchema).default({}), + roles: record(string(), roleSchema).default({}), + policies: record(string(), policy).default({}), _meta: object({ schemas: record(string(), string()), tables: record(string(), string()), @@ -383,6 +460,9 @@ const tableSquashed = object({ foreignKeys: record(string(), string()), compositePrimaryKeys: record(string(), string()), uniqueConstraints: record(string(), string()), + policies: record(string(), string()), + checkConstraints: record(string(), string()), + isRLSEnabled: boolean().default(false), }).strict(); const tableSquashedV4 = object({ @@ -415,7 +495,10 @@ export const pgSchemaSquashed = object({ tables: record(string(), tableSquashed), enums: record(string(), enumSchema), schemas: record(string(), string()), + views: record(string(), view), sequences: record(string(), sequenceSquashed), + roles: record(string(), roleSchema).default({}), + policies: record(string(), policySquashed).default({}), }).strict(); export const pgSchemaV3 = pgSchemaInternalV3.merge(schemaHash); @@ -427,6 +510,7 @@ export const pgSchema = pgSchemaInternal.merge(schemaHash); export type Enum = TypeOf; export type Sequence = TypeOf; +export type Role = TypeOf; export type Column = TypeOf; export type TableV3 = TypeOf; export type TableV4 = TypeOf; @@ -443,7 +527,13 @@ export type Index = TypeOf; export type ForeignKey = TypeOf; export type PrimaryKey = TypeOf; export type UniqueConstraint = TypeOf; +export type Policy = TypeOf; +export type View = TypeOf; +export type MatViewWithOption = TypeOf; +export type ViewWithOption = TypeOf; + export type PgKitInternals = TypeOf; +export type CheckConstraint = TypeOf; export type PgSchemaV1 = TypeOf; export type PgSchemaV2 = TypeOf; @@ -464,7 +554,7 @@ export const PgSquasher = { return `${idx.name};${ idx.columns .map( - (c) => `${c.expression}--${c.isExpression}--${c.asc}--${c.nulls}--${c.opclass}`, + (c) => `${c.expression}--${c.isExpression}--${c.asc}--${c.nulls}--${c.opclass ? c.opclass : ''}`, ) .join(',,') };${idx.isUnique};${idx.concurrently};${idx.method};${idx.where};${JSON.stringify(idx.with)}`; @@ -544,6 +634,36 @@ export const PgSquasher = { fk.onUpdate ?? '' };${fk.onDelete ?? ''};${fk.schemaTo || 'public'}`; }, + squashPolicy: (policy: Policy) => { + return `${policy.name}--${policy.as}--${policy.for}--${ + policy.to?.join(',') + }--${policy.using}--${policy.withCheck}--${policy.on}`; + }, + unsquashPolicy: (policy: string): Policy => { + const splitted = policy.split('--'); + return { + name: splitted[0], + as: splitted[1] as Policy['as'], + for: splitted[2] as Policy['for'], + to: splitted[3].split(','), + using: splitted[4] !== 'undefined' ? splitted[4] : undefined, + withCheck: splitted[5] !== 'undefined' ? splitted[5] : undefined, + on: splitted[6] !== 'undefined' ? splitted[6] : undefined, + }; + }, + squashPolicyPush: (policy: Policy) => { + return `${policy.name}--${policy.as}--${policy.for}--${policy.to?.join(',')}--${policy.on}`; + }, + unsquashPolicyPush: (policy: string): Policy => { + const splitted = policy.split('--'); + return { + name: splitted[0], + as: splitted[1] as Policy['as'], + for: splitted[2] as Policy['for'], + to: splitted[3].split(','), + on: splitted[4] !== 'undefined' ? splitted[4] : undefined, + }; + }, squashPK: (pk: PrimaryKey) => { return `${pk.columns.join(',')};${pk.name}`; }, @@ -622,6 +742,17 @@ export const PgSquasher = { cycle: splitted[7] === 'true', }; }, + squashCheck: (check: CheckConstraint) => { + return `${check.name};${check.value}`; + }, + unsquashCheck: (input: string): CheckConstraint => { + const [ + name, + value, + ] = input.split(';'); + + return { name, value }; + }, }; export const squashPgScheme = ( @@ -666,6 +797,18 @@ export const squashPgScheme = ( }, ); + const squashedPolicies = mapValues(it[1].policies, (policy) => { + return action === 'push' + ? PgSquasher.squashPolicyPush(policy) + : PgSquasher.squashPolicy(policy); + }); + const squashedChecksContraints = mapValues( + it[1].checkConstraints, + (check) => { + return PgSquasher.squashCheck(check); + }, + ); + return [ it[0], { @@ -676,6 +819,9 @@ export const squashPgScheme = ( foreignKeys: squashedFKs, compositePrimaryKeys: squashedPKs, uniqueConstraints: squashedUniqueConstraints, + policies: squashedPolicies, + checkConstraints: squashedChecksContraints, + isRLSEnabled: it[1].isRLSEnabled ?? false, }, ]; }), @@ -694,13 +840,30 @@ export const squashPgScheme = ( }), ); + const mappedPolicies = Object.fromEntries( + Object.entries(json.policies).map((it) => { + return [ + it[0], + { + name: it[1].name, + values: action === 'push' + ? PgSquasher.squashPolicyPush(it[1]) + : PgSquasher.squashPolicy(it[1]), + }, + ]; + }), + ); + return { version: '7', dialect: json.dialect, tables: mappedTables, enums: json.enums, schemas: json.schemas, + views: json.views, + policies: mappedPolicies, sequences: mappedSequences, + roles: json.roles, }; }; @@ -712,6 +875,8 @@ export const dryPg = pgSchema.parse({ tables: {}, enums: {}, schemas: {}, + policies: {}, + roles: {}, sequences: {}, _meta: { schemas: {}, diff --git a/drizzle-kit/src/serializer/pgSerializer.ts b/drizzle-kit/src/serializer/pgSerializer.ts index 4ab37a0ae..b0faa5ea8 100644 --- a/drizzle-kit/src/serializer/pgSerializer.ts +++ b/drizzle-kit/src/serializer/pgSerializer.ts @@ -2,67 +2,60 @@ import chalk from 'chalk'; import { getTableName, is, SQL } from 'drizzle-orm'; import { AnyPgTable, - ExtraConfigColumn, + getMaterializedViewConfig, + getTableConfig, + getViewConfig, IndexedColumn, PgColumn, PgDialect, PgEnum, PgEnumColumn, - PgInteger, + PgMaterializedView, + PgPolicy, + PgRole, PgSchema, PgSequence, + PgView, uniqueKeyName, } from 'drizzle-orm/pg-core'; -import { getTableConfig } from 'drizzle-orm/pg-core'; +import { CasingType } from 'src/cli/validations/common'; import { vectorOps } from 'src/extensions/vector'; import { withStyle } from '../cli/validations/outputs'; import type { IntrospectStage, IntrospectStatus } from '../cli/views'; import type { - Column as Column, + CheckConstraint, + Column, Enum, ForeignKey, Index, IndexColumnType, PgKitInternals, PgSchemaInternal, + Policy, PrimaryKey, + Role, Sequence, Table, UniqueConstraint, + View, } from '../serializer/pgSchema'; -import type { DB } from '../utils'; -import { sqlToStr } from '.'; - -const dialect = new PgDialect(); +import { type DB, escapeSingleQuotes, isPgArrayType } from '../utils'; +import { getColumnCasing, sqlToStr } from './utils'; export const indexName = (tableName: string, columns: string[]) => { return `${tableName}_${columns.join('_')}_index`; }; -function stringFromIdentityProperty( - field: string | number | undefined, -): string | undefined { - return typeof field === 'string' - ? (field as string) - : typeof field === 'undefined' - ? undefined - : String(field); +function stringFromIdentityProperty(field: string | number | undefined): string | undefined { + return typeof field === 'string' ? (field as string) : typeof field === 'undefined' ? undefined : String(field); } function maxRangeForIdentityBasedOn(columnType: string) { - return columnType === 'integer' - ? '2147483647' - : columnType === 'bigint' - ? '9223372036854775807' - : '32767'; + return columnType === 'integer' ? '2147483647' : columnType === 'bigint' ? '9223372036854775807' : '32767'; } function minRangeForIdentityBasedOn(columnType: string) { - return columnType === 'integer' - ? '-2147483648' - : columnType === 'bitint' - ? '-9223372036854775808' - : '-32768'; + return columnType === 'integer' ? '-2147483648' : columnType === 'bigint' ? '-9223372036854775808' : '-32768'; } function stringFromDatabaseIdentityProperty(field: any): string | undefined { @@ -75,21 +68,65 @@ function stringFromDatabaseIdentityProperty(field: any): string | undefined { : String(field); } +export function buildArrayString(array: any[], sqlType: string): string { + sqlType = sqlType.split('[')[0]; + const values = array + .map((value) => { + if (typeof value === 'number' || typeof value === 'bigint') { + return value.toString(); + } else if (typeof value === 'boolean') { + return value ? 'true' : 'false'; + } else if (Array.isArray(value)) { + return buildArrayString(value, sqlType); + } else if (value instanceof Date) { + if (sqlType === 'date') { + return `"${value.toISOString().split('T')[0]}"`; + } else if (sqlType === 'timestamp') { + return `"${value.toISOString().replace('T', ' ').slice(0, 23)}"`; + } else { + return `"${value.toISOString()}"`; + } + } else if (typeof value === 'object') { + return `"${JSON.stringify(value).replaceAll('"', '\\"')}"`; + } + + return `"${value}"`; + }) + .join(','); + + return `{${values}}`; +} + export const generatePgSnapshot = ( tables: AnyPgTable[], enums: PgEnum[], schemas: PgSchema[], sequences: PgSequence[], + roles: PgRole[], + policies: PgPolicy[], + views: PgView[], + matViews: PgMaterializedView[], + casing: CasingType | undefined, schemaFilter?: string[], ): PgSchemaInternal => { + const dialect = new PgDialect({ casing }); const result: Record = {}; + const resultViews: Record = {}; const sequencesToReturn: Record = {}; + const rolesToReturn: Record = {}; + // this policies are a separate objects that were linked to a table outside of it + const policiesToReturn: Record = {}; // This object stores unique names for indexes and will be used to detect if you have the same names for indexes // within the same PostgreSQL schema + const indexesInSchema: Record = {}; for (const table of tables) { + // This object stores unique names for checks and will be used to detect if you have the same names for checks + // within the same PostgreSQL table + const checksInTable: Record = {}; + const { name: tableName, columns, @@ -99,6 +136,8 @@ export const generatePgSnapshot = ( schema, primaryKeys, uniqueConstraints, + policies, + enableRLS, } = getTableConfig(table); if (schemaFilter && !schemaFilter.includes(schema ?? 'public')) { @@ -107,36 +146,33 @@ export const generatePgSnapshot = ( const columnsObject: Record = {}; const indexesObject: Record = {}; + const checksObject: Record = {}; const foreignKeysObject: Record = {}; const primaryKeysObject: Record = {}; const uniqueConstraintObject: Record = {}; + const policiesObject: Record = {}; columns.forEach((column) => { + const name = getColumnCasing(column, casing); const notNull: boolean = column.notNull; const primaryKey: boolean = column.primary; const sqlTypeLowered = column.getSQLType().toLowerCase(); - const typeSchema = is(column, PgEnumColumn) - ? column.enum.schema || 'public' - : undefined; + const typeSchema = is(column, PgEnumColumn) ? column.enum.schema || 'public' : undefined; const generated = column.generated; const identity = column.generatedIdentity; const increment = stringFromIdentityProperty(identity?.sequenceOptions?.increment) ?? '1'; const minValue = stringFromIdentityProperty(identity?.sequenceOptions?.minValue) - ?? (parseFloat(increment) < 0 - ? minRangeForIdentityBasedOn(column.columnType) - : '1'); + ?? (parseFloat(increment) < 0 ? minRangeForIdentityBasedOn(column.columnType) : '1'); const maxValue = stringFromIdentityProperty(identity?.sequenceOptions?.maxValue) - ?? (parseFloat(increment) < 0 - ? '-1' - : maxRangeForIdentityBasedOn(column.getSQLType())); + ?? (parseFloat(increment) < 0 ? '-1' : maxRangeForIdentityBasedOn(column.getSQLType())); const startWith = stringFromIdentityProperty(identity?.sequenceOptions?.startWith) ?? (parseFloat(increment) < 0 ? maxValue : minValue); const cache = stringFromIdentityProperty(identity?.sequenceOptions?.cache) ?? '1'; const columnToSet: Column = { - name: column.name, + name, type: column.getSQLType(), typeSchema: typeSchema, primaryKey, @@ -154,7 +190,7 @@ export const generatePgSnapshot = ( identity: identity ? { type: identity.type, - name: identity.sequenceName ?? `${tableName}_${column.name}_seq`, + name: identity.sequenceName ?? `${tableName}_${name}_seq`, schema: schema ?? 'public', increment, startWith, @@ -182,9 +218,9 @@ export const generatePgSnapshot = ( ) } on the ${ chalk.underline.blue( - column.name, + name, ) - } column is confilcting with a unique constraint name already defined for ${ + } column is conflicting with a unique constraint name already defined for ${ chalk.underline.blue( existingUnique.columns.join(','), ) @@ -202,30 +238,23 @@ export const generatePgSnapshot = ( if (column.default !== undefined) { if (is(column.default, SQL)) { - columnToSet.default = sqlToStr(column.default); + columnToSet.default = sqlToStr(column.default, casing); } else { if (typeof column.default === 'string') { - columnToSet.default = `'${column.default}'`; + columnToSet.default = `'${escapeSingleQuotes(column.default)}'`; } else { if (sqlTypeLowered === 'jsonb' || sqlTypeLowered === 'json') { - columnToSet.default = `'${ - JSON.stringify( - column.default, - ) - }'::${sqlTypeLowered}`; + columnToSet.default = `'${JSON.stringify(column.default)}'::${sqlTypeLowered}`; } else if (column.default instanceof Date) { if (sqlTypeLowered === 'date') { columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; } else if (sqlTypeLowered === 'timestamp') { - columnToSet.default = `'${ - column.default - .toISOString() - .replace('T', ' ') - .slice(0, 23) - }'`; + columnToSet.default = `'${column.default.toISOString().replace('T', ' ').slice(0, 23)}'`; } else { columnToSet.default = `'${column.default.toISOString()}'`; } + } else if (isPgArrayType(sqlTypeLowered) && Array.isArray(column.default)) { + columnToSet.default = `'${buildArrayString(column.default, sqlTypeLowered)}'`; } else { // Should do for all types // columnToSet.default = `'${column.default}'::${sqlTypeLowered}`; @@ -234,19 +263,28 @@ export const generatePgSnapshot = ( } } } - columnsObject[column.name] = columnToSet; + columnsObject[name] = columnToSet; }); primaryKeys.map((pk) => { - const columnNames = pk.columns.map((c) => c.name); - primaryKeysObject[pk.getName()] = { - name: pk.getName(), + const originalColumnNames = pk.columns.map((c) => c.name); + const columnNames = pk.columns.map((c) => getColumnCasing(c, casing)); + + let name = pk.getName(); + if (casing !== undefined) { + for (let i = 0; i < originalColumnNames.length; i++) { + name = name.replace(originalColumnNames[i], columnNames[i]); + } + } + + primaryKeysObject[name] = { + name, columns: columnNames, }; }); uniqueConstraints?.map((unq) => { - const columnNames = unq.columns.map((c) => c.name); + const columnNames = unq.columns.map((c) => getColumnCasing(c, casing)); const name = unq.name ?? uniqueKeyName(table, columnNames); @@ -254,24 +292,16 @@ export const generatePgSnapshot = ( if (typeof existingUnique !== 'undefined') { console.log( `\n${ - withStyle.errorWarning(`We\'ve found duplicated unique constraint names in ${ - chalk.underline.blue( - tableName, - ) - } table. - The unique constraint ${ - chalk.underline.blue( - name, - ) - } on the ${ - chalk.underline.blue( - columnNames.join(','), - ) - } columns is confilcting with a unique constraint name already defined for ${ - chalk.underline.blue( - existingUnique.columns.join(','), - ) - } columns\n`) + withStyle.errorWarning( + `We\'ve found duplicated unique constraint names in ${chalk.underline.blue(tableName)} table. + The unique constraint ${chalk.underline.blue(name)} on the ${ + chalk.underline.blue( + columnNames.join(','), + ) + } columns is confilcting with a unique constraint name already defined for ${ + chalk.underline.blue(existingUnique.columns.join(',')) + } columns\n`, + ) }`, ); process.exit(1); @@ -285,7 +315,6 @@ export const generatePgSnapshot = ( }); const fks: ForeignKey[] = foreignKeys.map((fk) => { - const name = fk.getName(); const tableFrom = tableName; const onDelete = fk.onDelete; const onUpdate = fk.onUpdate; @@ -296,8 +325,20 @@ export const generatePgSnapshot = ( // getTableConfig(reference.foreignTable).schema || "public"; const schemaTo = getTableConfig(reference.foreignTable).schema; - const columnsFrom = reference.columns.map((it) => it.name); - const columnsTo = reference.foreignColumns.map((it) => it.name); + const originalColumnsFrom = reference.columns.map((it) => it.name); + const columnsFrom = reference.columns.map((it) => getColumnCasing(it, casing)); + const originalColumnsTo = reference.foreignColumns.map((it) => it.name); + const columnsTo = reference.foreignColumns.map((it) => getColumnCasing(it, casing)); + + let name = fk.getName(); + if (casing !== undefined) { + for (let i = 0; i < originalColumnsFrom.length; i++) { + name = name.replace(originalColumnsFrom[i], columnsFrom[i]); + } + for (let i = 0; i < originalColumnsTo.length; i++) { + name = name.replace(originalColumnsTo[i], columnsTo[i]); + } + } return { name, @@ -325,11 +366,7 @@ export const generatePgSnapshot = ( console.log( `\n${ withStyle.errorWarning( - `Please specify an index name in ${ - getTableName( - value.config.table, - ) - } table that has "${ + `Please specify an index name in ${getTableName(value.config.table)} table that has "${ dialect.sqlToQuery(it).sql }" expression. We can generate index names for indexes on columns only; for expressions in indexes, you need to specify the name yourself.`, ) @@ -339,6 +376,7 @@ export const generatePgSnapshot = ( } } it = it as IndexedColumn; + const name = getColumnCasing(it as IndexedColumn, casing); if ( !is(it, SQL) && it.type! === 'PgVector' @@ -349,7 +387,7 @@ export const generatePgSnapshot = ( withStyle.errorWarning( `You are specifying an index on the ${ chalk.blueBright( - it.name, + name, ) } column inside the ${ chalk.blueBright( @@ -362,12 +400,10 @@ export const generatePgSnapshot = ( } type without specifying an operator class. Vector extension doesn't have a default operator class, so you need to specify one of the available options. Here is a list of available op classes for the vector extension: [${ vectorOps .map((it) => `${chalk.underline(`${it}`)}`) - .join( - ', ', - ) + .join(', ') }].\n\nYou can specify it using current syntax: ${ chalk.underline( - `index("${value.config.name}").using("${value.config.method}", table.${it.name}.op("${ + `index("${value.config.name}").using("${value.config.method}", table.${name}.op("${ vectorOps[0] }"))`, ) @@ -377,12 +413,10 @@ export const generatePgSnapshot = ( ); process.exit(1); } - indexColumnNames.push((it as ExtraConfigColumn).name); + indexColumnNames.push(name); }); - const name = value.config.name - ? value.config.name - : indexName(tableName, indexColumnNames); + const name = value.config.name ? value.config.name : indexName(tableName, indexColumnNames); let indexColumns: IndexColumnType[] = columns.map( (it): IndexColumnType => { @@ -396,7 +430,7 @@ export const generatePgSnapshot = ( } else { it = it as IndexedColumn; return { - expression: it.name!, + expression: getColumnCasing(it as IndexedColumn, casing), isExpression: false, asc: it.indexConfig?.order === 'asc', nulls: it.indexConfig?.nulls @@ -417,9 +451,7 @@ export const generatePgSnapshot = ( `\n${ withStyle.errorWarning( `We\'ve found duplicated index name across ${ - chalk.underline.blue( - schema ?? 'public', - ) + chalk.underline.blue(schema ?? 'public') } schema. Please rename your index in either the ${ chalk.underline.blue( tableName, @@ -439,15 +471,98 @@ export const generatePgSnapshot = ( name, columns: indexColumns, isUnique: value.config.unique ?? false, - where: value.config.where - ? dialect.sqlToQuery(value.config.where).sql - : undefined, + where: value.config.where ? dialect.sqlToQuery(value.config.where).sql : undefined, concurrently: value.config.concurrently ?? false, method: value.config.method ?? 'btree', with: value.config.with ?? {}, }; }); + policies.forEach((policy) => { + const mappedTo = []; + + if (!policy.to) { + mappedTo.push('public'); + } else { + if (policy.to && typeof policy.to === 'string') { + mappedTo.push(policy.to); + } else if (policy.to && is(policy.to, PgRole)) { + mappedTo.push(policy.to.name); + } else if (policy.to && Array.isArray(policy.to)) { + policy.to.forEach((it) => { + if (typeof it === 'string') { + mappedTo.push(it); + } else if (is(it, PgRole)) { + mappedTo.push(it.name); + } + }); + } + } + + if (policiesObject[policy.name] !== undefined) { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated policy name across ${ + chalk.underline.blue(tableKey) + } table. Please rename one of the policies with ${ + chalk.underline.blue( + policy.name, + ) + } name`, + ) + }`, + ); + process.exit(1); + } + + policiesObject[policy.name] = { + name: policy.name, + as: policy.as?.toUpperCase() as Policy['as'] ?? 'PERMISSIVE', + for: policy.for?.toUpperCase() as Policy['for'] ?? 'ALL', + to: mappedTo.sort(), + using: is(policy.using, SQL) ? dialect.sqlToQuery(policy.using).sql : undefined, + withCheck: is(policy.withCheck, SQL) ? dialect.sqlToQuery(policy.withCheck).sql : undefined, + }; + }); + + checks.forEach((check) => { + const checkName = check.name; + + if (typeof checksInTable[`"${schema ?? 'public'}"."${tableName}"`] !== 'undefined') { + if (checksInTable[`"${schema ?? 'public'}"."${tableName}"`].includes(check.name)) { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated check constraint name across ${ + chalk.underline.blue( + schema ?? 'public', + ) + } schema in ${ + chalk.underline.blue( + tableName, + ) + }. Please rename your check constraint in either the ${ + chalk.underline.blue( + tableName, + ) + } table or the table with the duplicated check contraint name`, + ) + }`, + ); + process.exit(1); + } + checksInTable[`"${schema ?? 'public'}"."${tableName}"`].push(checkName); + } else { + checksInTable[`"${schema ?? 'public'}"."${tableName}"`] = [check.name]; + } + + checksObject[checkName] = { + name: checkName, + value: dialect.sqlToQuery(check.value).sql, + }; + }); + const tableKey = `${schema ?? 'public'}.${tableName}`; result[tableKey] = { @@ -458,15 +573,94 @@ export const generatePgSnapshot = ( foreignKeys: foreignKeysObject, compositePrimaryKeys: primaryKeysObject, uniqueConstraints: uniqueConstraintObject, + policies: policiesObject, + checkConstraints: checksObject, + isRLSEnabled: enableRLS, }; } + for (const policy of policies) { + // @ts-ignore + if (!policy._linkedTable) { + console.log( + `\n${ + withStyle.errorWarning( + `"Policy ${policy.name} was skipped because it was not linked to any table. You should either include the policy in a table or use .link() on the policy to link it to any table you have. For more information, please check:`, + ) + }`, + ); + continue; + } + + // @ts-ignore + const tableConfig = getTableConfig(policy._linkedTable); + + const tableKey = `${tableConfig.schema ?? 'public'}.${tableConfig.name}`; + + const mappedTo = []; + + if (!policy.to) { + mappedTo.push('public'); + } else { + if (policy.to && typeof policy.to === 'string') { + mappedTo.push(policy.to); + } else if (policy.to && is(policy.to, PgRole)) { + mappedTo.push(policy.to.name); + } else if (policy.to && Array.isArray(policy.to)) { + policy.to.forEach((it) => { + if (typeof it === 'string') { + mappedTo.push(it); + } else if (is(it, PgRole)) { + mappedTo.push(it.name); + } + }); + } + } + + // add separate policies object, that will be only responsible for policy creation + // but we would need to track if a policy was enabled for a specific table or not + // enable only if jsonStatements for enable rls was not already there + filter it + + if (result[tableKey]?.policies[policy.name] !== undefined || policiesToReturn[policy.name] !== undefined) { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated policy name across ${ + chalk.underline.blue(tableKey) + } table. Please rename one of the policies with ${ + chalk.underline.blue( + policy.name, + ) + } name`, + ) + }`, + ); + process.exit(1); + } + + const mappedPolicy = { + name: policy.name, + as: policy.as?.toUpperCase() as Policy['as'] ?? 'PERMISSIVE', + for: policy.for?.toUpperCase() as Policy['for'] ?? 'ALL', + to: mappedTo.sort(), + using: is(policy.using, SQL) ? dialect.sqlToQuery(policy.using).sql : undefined, + withCheck: is(policy.withCheck, SQL) ? dialect.sqlToQuery(policy.withCheck).sql : undefined, + }; + + if (result[tableKey]) { + result[tableKey].policies[policy.name] = mappedPolicy; + } else { + policiesToReturn[policy.name] = { + ...mappedPolicy, + schema: tableConfig.schema ?? 'public', + on: `"${tableConfig.schema ?? 'public'}"."${tableConfig.name}"`, + }; + } + } + for (const sequence of sequences) { const name = sequence.seqName!; - if ( - typeof sequencesToReturn[`${sequence.schema ?? 'public'}.${name}`] - === 'undefined' - ) { + if (typeof sequencesToReturn[`${sequence.schema ?? 'public'}.${name}`] === 'undefined') { const increment = stringFromIdentityProperty(sequence?.seqOptions?.increment) ?? '1'; const minValue = stringFromIdentityProperty(sequence?.seqOptions?.minValue) ?? (parseFloat(increment) < 0 ? '-9223372036854775808' : '1'); @@ -491,6 +685,182 @@ export const generatePgSnapshot = ( } } + for (const role of roles) { + if (!(role as any)._existing) { + rolesToReturn[role.name] = { + name: role.name, + createDb: (role as any).createDb === undefined ? false : (role as any).createDb, + createRole: (role as any).createRole === undefined ? false : (role as any).createRole, + inherit: (role as any).inherit === undefined ? true : (role as any).inherit, + }; + } + } + const combinedViews = [...views, ...matViews]; + for (const view of combinedViews) { + let viewName; + let schema; + let query; + let selectedFields; + let isExisting; + let withOption; + let tablespace; + let using; + let withNoData; + let materialized: boolean = false; + + if (is(view, PgView)) { + ({ name: viewName, schema, query, selectedFields, isExisting, with: withOption } = getViewConfig(view)); + } else { + ({ name: viewName, schema, query, selectedFields, isExisting, with: withOption, tablespace, using, withNoData } = + getMaterializedViewConfig(view)); + + materialized = true; + } + + const viewSchema = schema ?? 'public'; + + const viewKey = `${viewSchema}.${viewName}`; + + const columnsObject: Record = {}; + const uniqueConstraintObject: Record = {}; + + const existingView = resultViews[viewKey]; + if (typeof existingView !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated view name across ${ + chalk.underline.blue(schema ?? 'public') + } schema. Please rename your view`, + ) + }`, + ); + process.exit(1); + } + + for (const key in selectedFields) { + if (is(selectedFields[key], PgColumn)) { + const column = selectedFields[key]; + + const notNull: boolean = column.notNull; + const primaryKey: boolean = column.primary; + const sqlTypeLowered = column.getSQLType().toLowerCase(); + + const typeSchema = is(column, PgEnumColumn) ? column.enum.schema || 'public' : undefined; + const generated = column.generated; + const identity = column.generatedIdentity; + + const increment = stringFromIdentityProperty(identity?.sequenceOptions?.increment) ?? '1'; + const minValue = stringFromIdentityProperty(identity?.sequenceOptions?.minValue) + ?? (parseFloat(increment) < 0 ? minRangeForIdentityBasedOn(column.columnType) : '1'); + const maxValue = stringFromIdentityProperty(identity?.sequenceOptions?.maxValue) + ?? (parseFloat(increment) < 0 ? '-1' : maxRangeForIdentityBasedOn(column.getSQLType())); + const startWith = stringFromIdentityProperty(identity?.sequenceOptions?.startWith) + ?? (parseFloat(increment) < 0 ? maxValue : minValue); + const cache = stringFromIdentityProperty(identity?.sequenceOptions?.cache) ?? '1'; + + const columnToSet: Column = { + name: column.name, + type: column.getSQLType(), + typeSchema: typeSchema, + primaryKey, + notNull, + generated: generated + ? { + as: is(generated.as, SQL) + ? dialect.sqlToQuery(generated.as as SQL).sql + : typeof generated.as === 'function' + ? dialect.sqlToQuery(generated.as() as SQL).sql + : (generated.as as any), + type: 'stored', + } + : undefined, + identity: identity + ? { + type: identity.type, + name: identity.sequenceName ?? `${viewName}_${column.name}_seq`, + schema: schema ?? 'public', + increment, + startWith, + minValue, + maxValue, + cache, + cycle: identity?.sequenceOptions?.cycle ?? false, + } + : undefined, + }; + + if (column.isUnique) { + const existingUnique = uniqueConstraintObject[column.uniqueName!]; + if (typeof existingUnique !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated unique constraint names in ${chalk.underline.blue(viewName)} table. + The unique constraint ${chalk.underline.blue(column.uniqueName)} on the ${ + chalk.underline.blue( + column.name, + ) + } column is confilcting with a unique constraint name already defined for ${ + chalk.underline.blue(existingUnique.columns.join(',')) + } columns\n`, + ) + }`, + ); + process.exit(1); + } + uniqueConstraintObject[column.uniqueName!] = { + name: column.uniqueName!, + nullsNotDistinct: column.uniqueType === 'not distinct', + columns: [columnToSet.name], + }; + } + + if (column.default !== undefined) { + if (is(column.default, SQL)) { + columnToSet.default = sqlToStr(column.default, casing); + } else { + if (typeof column.default === 'string') { + columnToSet.default = `'${column.default}'`; + } else { + if (sqlTypeLowered === 'jsonb' || sqlTypeLowered === 'json') { + columnToSet.default = `'${JSON.stringify(column.default)}'::${sqlTypeLowered}`; + } else if (column.default instanceof Date) { + if (sqlTypeLowered === 'date') { + columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; + } else if (sqlTypeLowered === 'timestamp') { + columnToSet.default = `'${column.default.toISOString().replace('T', ' ').slice(0, 23)}'`; + } else { + columnToSet.default = `'${column.default.toISOString()}'`; + } + } else if (isPgArrayType(sqlTypeLowered) && Array.isArray(column.default)) { + columnToSet.default = `'${buildArrayString(column.default, sqlTypeLowered)}'`; + } else { + // Should do for all types + // columnToSet.default = `'${column.default}'::${sqlTypeLowered}`; + columnToSet.default = column.default; + } + } + } + } + columnsObject[column.name] = columnToSet; + } + } + + resultViews[viewKey] = { + columns: columnsObject, + definition: isExisting ? undefined : dialect.sqlToQuery(query!).sql, + name: viewName, + schema: viewSchema, + isExisting, + with: withOption, + withNoData, + materialized, + tablespace, + using, + }; + } + const enumsToReturn: Record = enums.reduce<{ [key: string]: Enum; }>((map, obj) => { @@ -508,9 +878,7 @@ export const generatePgSnapshot = ( schemas .filter((it) => { if (schemaFilter) { - return ( - schemaFilter.includes(it.schemaName) && it.schemaName !== 'public' - ); + return schemaFilter.includes(it.schemaName) && it.schemaName !== 'public'; } else { return it.schemaName !== 'public'; } @@ -525,6 +893,9 @@ export const generatePgSnapshot = ( enums: enumsToReturn, schemas: schemasObject, sequences: sequencesToReturn, + roles: rolesToReturn, + policies: policiesToReturn, + views: resultViews, _meta: { schemas: {}, tables: {}, @@ -541,28 +912,93 @@ const trimChar = (str: string, char: string) => { while (end > start && str[end - 1] === char) --end; // this.toString() due to ava deep equal issue with String { "value" } - return start > 0 || end < str.length - ? str.substring(start, end) - : str.toString(); + return start > 0 || end < str.length ? str.substring(start, end) : str.toString(); }; +function prepareRoles(entities?: { + roles: boolean | { + provider?: string | undefined; + include?: string[] | undefined; + exclude?: string[] | undefined; + }; +}) { + let useRoles: boolean = false; + const includeRoles: string[] = []; + const excludeRoles: string[] = []; + + if (entities && entities.roles) { + if (typeof entities.roles === 'object') { + if (entities.roles.provider) { + if (entities.roles.provider === 'supabase') { + excludeRoles.push(...[ + 'anon', + 'authenticator', + 'authenticated', + 'service_role', + 'supabase_auth_admin', + 'supabase_storage_admin', + 'dashboard_user', + 'supabase_admin', + ]); + } else if (entities.roles.provider === 'neon') { + excludeRoles.push(...['authenticated', 'anonymous']); + } + } + if (entities.roles.include) { + includeRoles.push(...entities.roles.include); + } + if (entities.roles.exclude) { + excludeRoles.push(...entities.roles.exclude); + } + } else { + useRoles = entities.roles; + } + } + return { useRoles, includeRoles, excludeRoles }; +} + export const fromDatabase = async ( db: DB, tablesFilter: (table: string) => boolean = () => true, schemaFilters: string[], + entities?: { + roles: boolean | { + provider?: string | undefined; + include?: string[] | undefined; + exclude?: string[] | undefined; + }; + }, progressCallback?: ( stage: IntrospectStage, count: number, status: IntrospectStatus, ) => void, + tsSchema?: PgSchemaInternal, ): Promise => { const result: Record = {}; + const views: Record = {}; + const policies: Record = {}; const internals: PgKitInternals = { tables: {} }; - const where = schemaFilters.map((t) => `table_schema = '${t}'`).join(' or '); - - const allTables = await db.query( - `SELECT table_schema, table_name FROM information_schema.tables${where === '' ? '' : ` WHERE ${where}`};`, + const where = schemaFilters.map((t) => `n.nspname = '${t}'`).join(' or '); + + const allTables = await db.query<{ table_schema: string; table_name: string; type: string; rls_enabled: boolean }>( + `SELECT + n.nspname AS table_schema, + c.relname AS table_name, + CASE + WHEN c.relkind = 'r' THEN 'table' + WHEN c.relkind = 'v' THEN 'view' + WHEN c.relkind = 'm' THEN 'materialized_view' + END AS type, + c.relrowsecurity AS rls_enabled +FROM + pg_catalog.pg_class c +JOIN + pg_catalog.pg_namespace n ON n.oid = c.relnamespace +WHERE + c.relkind IN ('r', 'v', 'm') + ${where === '' ? '' : ` AND ${where}`};`, ); const schemas = new Set(allTables.map((it) => it.table_schema)); @@ -588,6 +1024,8 @@ export const fromDatabase = async ( let indexesCount = 0; let foreignKeysCount = 0; let tableCount = 0; + let checksCount = 0; + let viewsCount = 0; const sequencesToReturn: Record = {}; @@ -622,9 +1060,7 @@ export const fromDatabase = async ( }; } - const whereEnums = schemaFilters - .map((t) => `n.nspname = '${t}'`) - .join(' or '); + const whereEnums = schemaFilters.map((t) => `n.nspname = '${t}'`).join(' or '); const allEnums = await db.query( `select n.nspname as enum_schema, @@ -660,308 +1096,442 @@ export const fromDatabase = async ( progressCallback('enums', Object.keys(enumsToReturn).length, 'done'); } - const sequencesInColumns: string[] = []; + const allRoles = await db.query< + { rolname: string; rolinherit: boolean; rolcreatedb: boolean; rolcreaterole: boolean } + >( + `SELECT rolname, rolinherit, rolcreatedb, rolcreaterole FROM pg_roles;`, + ); - const all = allTables.map((row) => { - return new Promise(async (res, rej) => { - const tableName = row.table_name as string; - if (!tablesFilter(tableName)) return res(''); - tableCount += 1; - const tableSchema = row.table_schema; - - try { - const columnToReturn: Record = {}; - const indexToReturn: Record = {}; - const foreignKeysToReturn: Record = {}; - const primaryKeys: Record = {}; - const uniqueConstrains: Record = {}; - - const tableResponse = await db.query( - `SELECT a.attrelid::regclass::text, a.attname, is_nullable, a.attndims as array_dimensions - , CASE WHEN a.atttypid = ANY ('{int,int8,int2}'::regtype[]) - AND EXISTS ( - SELECT FROM pg_attrdef ad - WHERE ad.adrelid = a.attrelid - AND ad.adnum = a.attnum - AND pg_get_expr(ad.adbin, ad.adrelid) - = 'nextval(''' - || (pg_get_serial_sequence (a.attrelid::regclass::text - , a.attname))::regclass - || '''::regclass)' - ) - THEN CASE a.atttypid - WHEN 'int'::regtype THEN 'serial' - WHEN 'int8'::regtype THEN 'bigserial' - WHEN 'int2'::regtype THEN 'smallserial' - END - ELSE format_type(a.atttypid, a.atttypmod) - END AS data_type, INFORMATION_SCHEMA.COLUMNS.table_name, - pg_get_serial_sequence('"${tableSchema}"."${tableName}"', a.attname)::regclass as seq_name, INFORMATION_SCHEMA.COLUMNS.column_name, - INFORMATION_SCHEMA.COLUMNS.column_default, INFORMATION_SCHEMA.COLUMNS.data_type as additional_dt, - INFORMATION_SCHEMA.COLUMNS.udt_name as enum_name, - INFORMATION_SCHEMA.COLUMNS.is_generated, generation_expression, - INFORMATION_SCHEMA.COLUMNS.is_identity,INFORMATION_SCHEMA.COLUMNS.identity_generation, - INFORMATION_SCHEMA.COLUMNS.identity_start, INFORMATION_SCHEMA.COLUMNS.identity_increment, - INFORMATION_SCHEMA.COLUMNS.identity_maximum, INFORMATION_SCHEMA.COLUMNS.identity_minimum, - INFORMATION_SCHEMA.COLUMNS.identity_cycle - FROM pg_attribute a - JOIN INFORMATION_SCHEMA.COLUMNS ON INFORMATION_SCHEMA.COLUMNS.column_name = a.attname - WHERE a.attrelid = '"${tableSchema}"."${tableName}"'::regclass and INFORMATION_SCHEMA.COLUMNS.table_name = '${tableName}' and INFORMATION_SCHEMA.COLUMNS.table_schema = '${tableSchema}' - AND a.attnum > 0 - AND NOT a.attisdropped - ORDER BY a.attnum;`, - ); + const rolesToReturn: Record = {}; + + const preparedRoles = prepareRoles(entities); + + if ( + preparedRoles.useRoles || !(preparedRoles.includeRoles.length === 0 && preparedRoles.excludeRoles.length === 0) + ) { + for (const dbRole of allRoles) { + if ( + preparedRoles.useRoles + ) { + rolesToReturn[dbRole.rolname] = { + createDb: dbRole.rolcreatedb, + createRole: dbRole.rolcreatedb, + inherit: dbRole.rolinherit, + name: dbRole.rolname, + }; + } else { + if (preparedRoles.includeRoles.length === 0 && preparedRoles.excludeRoles.length === 0) continue; + if ( + preparedRoles.includeRoles.includes(dbRole.rolname) && preparedRoles.excludeRoles.includes(dbRole.rolname) + ) continue; + if (preparedRoles.excludeRoles.includes(dbRole.rolname)) continue; + if (!preparedRoles.includeRoles.includes(dbRole.rolname)) continue; + + rolesToReturn[dbRole.rolname] = { + createDb: dbRole.rolcreatedb, + createRole: dbRole.rolcreaterole, + inherit: dbRole.rolinherit, + name: dbRole.rolname, + }; + } + } + } + + const schemasForLinkedPoliciesInSchema = Object.values(tsSchema?.policies ?? {}).map((it) => it.schema!); + + const wherePolicies = [...schemaFilters, ...schemasForLinkedPoliciesInSchema] + .map((t) => `schemaname = '${t}'`) + .join(' or '); + + const policiesByTable: Record> = {}; + + const allPolicies = await db.query< + { + schemaname: string; + tablename: string; + name: string; + as: string; + to: string; + for: string; + using: string; + withCheck: string; + } + >(`SELECT schemaname, tablename, policyname as name, permissive as "as", roles as to, cmd as for, qual as using, with_check as "withCheck" FROM pg_policies${ + wherePolicies === '' ? '' : ` WHERE ${wherePolicies}` + };`); + + for (const dbPolicy of allPolicies) { + const { tablename, schemaname, to, withCheck, using, ...rest } = dbPolicy; + const tableForPolicy = policiesByTable[`${schemaname}.${tablename}`]; + + const parsedTo = typeof to === 'string' ? to.slice(1, -1).split(',') : to; + + const parsedWithCheck = withCheck === null ? undefined : withCheck; + const parsedUsing = using === null ? undefined : using; + + if (tableForPolicy) { + tableForPolicy[dbPolicy.name] = { ...rest, to: parsedTo } as Policy; + } else { + policiesByTable[`${schemaname}.${tablename}`] = { + [dbPolicy.name]: { ...rest, to: parsedTo, withCheck: parsedWithCheck, using: parsedUsing } as Policy, + }; + } + + if (tsSchema?.policies[dbPolicy.name]) { + policies[dbPolicy.name] = { + ...rest, + to: parsedTo, + withCheck: parsedWithCheck, + using: parsedUsing, + on: tsSchema?.policies[dbPolicy.name].on, + } as Policy; + } + } + + if (progressCallback) { + progressCallback( + 'policies', + Object.values(policiesByTable).reduce((total, innerRecord) => { + return total + Object.keys(innerRecord).length; + }, 0), + 'done', + ); + } + + const sequencesInColumns: string[] = []; - const tableConstraints = await db.query( - `SELECT c.column_name, c.data_type, constraint_type, constraint_name, constraint_schema + const all = allTables + .filter((it) => it.type === 'table') + .map((row) => { + return new Promise(async (res, rej) => { + const tableName = row.table_name as string; + if (!tablesFilter(tableName)) return res(''); + tableCount += 1; + const tableSchema = row.table_schema; + + try { + const columnToReturn: Record = {}; + const indexToReturn: Record = {}; + const foreignKeysToReturn: Record = {}; + const primaryKeys: Record = {}; + const uniqueConstrains: Record = {}; + const checkConstraints: Record = {}; + + const tableResponse = await getColumnsInfoQuery({ schema: tableSchema, table: tableName, db }); + + const tableConstraints = await db.query( + `SELECT c.column_name, c.data_type, constraint_type, constraint_name, constraint_schema FROM information_schema.table_constraints tc JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name) JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema AND tc.table_name = c.table_name AND ccu.column_name = c.column_name WHERE tc.table_name = '${tableName}' and constraint_schema = '${tableSchema}';`, - ); + ); - columnsCount += tableResponse.length; - if (progressCallback) { - progressCallback('columns', columnsCount, 'fetching'); - } + const tableChecks = await db.query(`SELECT + tc.constraint_name, + tc.constraint_type, + pg_get_constraintdef(con.oid) AS constraint_definition + FROM + information_schema.table_constraints AS tc + JOIN pg_constraint AS con + ON tc.constraint_name = con.conname + AND con.conrelid = ( + SELECT oid + FROM pg_class + WHERE relname = tc.table_name + AND relnamespace = ( + SELECT oid + FROM pg_namespace + WHERE nspname = tc.constraint_schema + ) + ) + WHERE + tc.table_name = '${tableName}' + AND tc.constraint_schema = '${tableSchema}' + AND tc.constraint_type = 'CHECK';`); + + columnsCount += tableResponse.length; + if (progressCallback) { + progressCallback('columns', columnsCount, 'fetching'); + } - const tableForeignKeys = await db.query( - `SELECT - tc.table_schema, - tc.constraint_name, - tc.table_name, - kcu.column_name, - ( - SELECT ccu.table_schema - FROM information_schema.constraint_column_usage ccu - WHERE ccu.constraint_name = tc.constraint_name - LIMIT 1 - ) AS foreign_table_schema, - ccu.table_name AS foreign_table_name, - ccu.column_name AS foreign_column_name, - rc.delete_rule, - rc.update_rule - FROM - information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema - JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name - JOIN information_schema.referential_constraints AS rc - ON ccu.constraint_name = rc.constraint_name - WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name='${tableName}' and tc.table_schema='${tableSchema}';`, - ); + const tableForeignKeys = await db.query( + `SELECT + con.contype AS constraint_type, + nsp.nspname AS constraint_schema, + con.conname AS constraint_name, + rel.relname AS table_name, + att.attname AS column_name, + fnsp.nspname AS foreign_table_schema, + frel.relname AS foreign_table_name, + fatt.attname AS foreign_column_name, + CASE con.confupdtype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'n' THEN 'SET NULL' + WHEN 'c' THEN 'CASCADE' + WHEN 'd' THEN 'SET DEFAULT' + END AS update_rule, + CASE con.confdeltype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'n' THEN 'SET NULL' + WHEN 'c' THEN 'CASCADE' + WHEN 'd' THEN 'SET DEFAULT' + END AS delete_rule + FROM + pg_catalog.pg_constraint con + JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid + JOIN pg_catalog.pg_namespace nsp ON nsp.oid = con.connamespace + LEFT JOIN pg_catalog.pg_attribute att ON att.attnum = ANY (con.conkey) + AND att.attrelid = con.conrelid + LEFT JOIN pg_catalog.pg_class frel ON frel.oid = con.confrelid + LEFT JOIN pg_catalog.pg_namespace fnsp ON fnsp.oid = frel.relnamespace + LEFT JOIN pg_catalog.pg_attribute fatt ON fatt.attnum = ANY (con.confkey) + AND fatt.attrelid = con.confrelid + WHERE + nsp.nspname = '${tableSchema}' + AND rel.relname = '${tableName}' + AND con.contype IN ('f');`, + ); - foreignKeysCount += tableForeignKeys.length; - if (progressCallback) { - progressCallback('fks', foreignKeysCount, 'fetching'); - } - for (const fk of tableForeignKeys) { - // const tableFrom = fk.table_name; - const columnFrom: string = fk.column_name; - const tableTo = fk.foreign_table_name; - const columnTo: string = fk.foreign_column_name; - const schemaTo: string = fk.foreign_table_schema; - const foreignKeyName = fk.constraint_name; - const onUpdate = fk.update_rule.toLowerCase(); - const onDelete = fk.delete_rule.toLowerCase(); - - if (typeof foreignKeysToReturn[foreignKeyName] !== 'undefined') { - foreignKeysToReturn[foreignKeyName].columnsFrom.push(columnFrom); - foreignKeysToReturn[foreignKeyName].columnsTo.push(columnTo); - } else { - foreignKeysToReturn[foreignKeyName] = { - name: foreignKeyName, - tableFrom: tableName, - tableTo, - schemaTo, - columnsFrom: [columnFrom], - columnsTo: [columnTo], - onDelete, - onUpdate, - }; + foreignKeysCount += tableForeignKeys.length; + if (progressCallback) { + progressCallback('fks', foreignKeysCount, 'fetching'); } + for (const fk of tableForeignKeys) { + // const tableFrom = fk.table_name; + const columnFrom: string = fk.column_name; + const tableTo = fk.foreign_table_name; + const columnTo: string = fk.foreign_column_name; + const schemaTo: string = fk.foreign_table_schema; + const foreignKeyName = fk.constraint_name; + const onUpdate = fk.update_rule?.toLowerCase(); + const onDelete = fk.delete_rule?.toLowerCase(); + + if (typeof foreignKeysToReturn[foreignKeyName] !== 'undefined') { + foreignKeysToReturn[foreignKeyName].columnsFrom.push(columnFrom); + foreignKeysToReturn[foreignKeyName].columnsTo.push(columnTo); + } else { + foreignKeysToReturn[foreignKeyName] = { + name: foreignKeyName, + tableFrom: tableName, + tableTo, + schemaTo, + columnsFrom: [columnFrom], + columnsTo: [columnTo], + onDelete, + onUpdate, + }; + } - foreignKeysToReturn[foreignKeyName].columnsFrom = [ - ...new Set(foreignKeysToReturn[foreignKeyName].columnsFrom), - ]; + foreignKeysToReturn[foreignKeyName].columnsFrom = [ + ...new Set(foreignKeysToReturn[foreignKeyName].columnsFrom), + ]; - foreignKeysToReturn[foreignKeyName].columnsTo = [ - ...new Set(foreignKeysToReturn[foreignKeyName].columnsTo), - ]; - } + foreignKeysToReturn[foreignKeyName].columnsTo = [...new Set(foreignKeysToReturn[foreignKeyName].columnsTo)]; + } - const uniqueConstrainsRows = tableConstraints.filter( - (mapRow) => mapRow.constraint_type === 'UNIQUE', - ); + const uniqueConstrainsRows = tableConstraints.filter((mapRow) => mapRow.constraint_type === 'UNIQUE'); - for (const unqs of uniqueConstrainsRows) { - // const tableFrom = fk.table_name; - const columnName: string = unqs.column_name; - const constraintName: string = unqs.constraint_name; + for (const unqs of uniqueConstrainsRows) { + // const tableFrom = fk.table_name; + const columnName: string = unqs.column_name; + const constraintName: string = unqs.constraint_name; - if (typeof uniqueConstrains[constraintName] !== 'undefined') { - uniqueConstrains[constraintName].columns.push(columnName); - } else { - uniqueConstrains[constraintName] = { - columns: [columnName], - nullsNotDistinct: false, + if (typeof uniqueConstrains[constraintName] !== 'undefined') { + uniqueConstrains[constraintName].columns.push(columnName); + } else { + uniqueConstrains[constraintName] = { + columns: [columnName], + nullsNotDistinct: false, + name: constraintName, + }; + } + } + + checksCount += tableChecks.length; + if (progressCallback) { + progressCallback('checks', checksCount, 'fetching'); + } + for (const checks of tableChecks) { + // CHECK (((email)::text <> 'test@gmail.com'::text)) + // Where (email) is column in table + let checkValue: string = checks.constraint_definition; + const constraintName: string = checks.constraint_name; + + checkValue = checkValue.replace(/^CHECK\s*\(\(/, '').replace(/\)\)\s*$/, ''); + + checkConstraints[constraintName] = { name: constraintName, + value: checkValue, }; } - } - for (const columnResponse of tableResponse) { - const columnName = columnResponse.attname; - const columnAdditionalDT = columnResponse.additional_dt; - const columnDimensions = columnResponse.array_dimensions; - const enumType: string = columnResponse.enum_name; - let columnType: string = columnResponse.data_type; - - const isGenerated = columnResponse.is_generated === 'ALWAYS'; - const generationExpression = columnResponse.generation_expression; - const isIdentity = columnResponse.is_identity === 'YES'; - const identityGeneration = columnResponse.identity_generation === 'ALWAYS' - ? 'always' - : 'byDefault'; - const identityStart = columnResponse.identity_start; - const identityIncrement = columnResponse.identity_increment; - const identityMaximum = columnResponse.identity_maximum; - const identityMinimum = columnResponse.identity_minimum; - const identityCycle = columnResponse.identity_cycle === 'YES'; - const identityName = columnResponse.seq_name; - - const primaryKey = tableConstraints.filter( - (mapRow) => - columnName === mapRow.column_name - && mapRow.constraint_type === 'PRIMARY KEY', - ); + for (const columnResponse of tableResponse) { + const columnName = columnResponse.column_name; + const columnAdditionalDT = columnResponse.additional_dt; + const columnDimensions = columnResponse.array_dimensions; + const enumType: string = columnResponse.enum_name; + let columnType: string = columnResponse.data_type; + const typeSchema = columnResponse.type_schema; + const defaultValueRes: string = columnResponse.column_default; + + const isGenerated = columnResponse.is_generated === 'ALWAYS'; + const generationExpression = columnResponse.generation_expression; + const isIdentity = columnResponse.is_identity === 'YES'; + const identityGeneration = columnResponse.identity_generation === 'ALWAYS' ? 'always' : 'byDefault'; + const identityStart = columnResponse.identity_start; + const identityIncrement = columnResponse.identity_increment; + const identityMaximum = columnResponse.identity_maximum; + const identityMinimum = columnResponse.identity_minimum; + const identityCycle = columnResponse.identity_cycle === 'YES'; + const identityName = columnResponse.seq_name; + + const primaryKey = tableConstraints.filter((mapRow) => + columnName === mapRow.column_name && mapRow.constraint_type === 'PRIMARY KEY' + ); - const cprimaryKey = tableConstraints.filter( - (mapRow) => mapRow.constraint_type === 'PRIMARY KEY', - ); + const cprimaryKey = tableConstraints.filter((mapRow) => mapRow.constraint_type === 'PRIMARY KEY'); - if (cprimaryKey.length > 1) { - const tableCompositePkName = await db.query( - `SELECT conname AS primary_key + if (cprimaryKey.length > 1) { + const tableCompositePkName = await db.query( + `SELECT conname AS primary_key FROM pg_constraint join pg_class on (pg_class.oid = conrelid) WHERE contype = 'p' AND connamespace = $1::regnamespace AND pg_class.relname = $2;`, - [tableSchema, tableName], - ); - primaryKeys[tableCompositePkName[0].primary_key] = { - name: tableCompositePkName[0].primary_key, - columns: cprimaryKey.map((c: any) => c.column_name), - }; - } - - const defaultValue = defaultForColumn(columnResponse); - - const isSerial = columnType === 'serial'; - - let columnTypeMapped = columnType; - - if (columnTypeMapped.startsWith('numeric(')) { - columnTypeMapped = columnTypeMapped.replace(',', ', '); - } + [tableSchema, tableName], + ); + primaryKeys[tableCompositePkName[0].primary_key] = { + name: tableCompositePkName[0].primary_key, + columns: cprimaryKey.map((c: any) => c.column_name), + }; + } - // Set default to internal object - if (columnAdditionalDT === 'ARRAY') { - if (typeof internals.tables[tableName] === 'undefined') { - internals.tables[tableName] = { - columns: { - [columnName]: { + let columnTypeMapped = columnType; + + // Set default to internal object + if (columnAdditionalDT === 'ARRAY') { + if (typeof internals.tables[tableName] === 'undefined') { + internals.tables[tableName] = { + columns: { + [columnName]: { + isArray: true, + dimensions: columnDimensions, + rawType: columnTypeMapped.substring(0, columnTypeMapped.length - 2), + }, + }, + }; + } else { + if (typeof internals.tables[tableName]!.columns[columnName] === 'undefined') { + internals.tables[tableName]!.columns[columnName] = { isArray: true, dimensions: columnDimensions, - rawType: columnTypeMapped.substring( - 0, - columnTypeMapped.length - 2, - ), + rawType: columnTypeMapped.substring(0, columnTypeMapped.length - 2), + }; + } + } + } + + const defaultValue = defaultForColumn(columnResponse, internals, tableName); + if ( + defaultValue === 'NULL' + || (defaultValueRes && defaultValueRes.startsWith('(') && defaultValueRes.endsWith(')')) + ) { + if (typeof internals!.tables![tableName] === 'undefined') { + internals!.tables![tableName] = { + columns: { + [columnName]: { + isDefaultAnExpression: true, + }, }, - }, - }; - } else { - if ( - typeof internals.tables[tableName]!.columns[columnName] - === 'undefined' - ) { - internals.tables[tableName]!.columns[columnName] = { - isArray: true, - dimensions: columnDimensions, - rawType: columnTypeMapped.substring( - 0, - columnTypeMapped.length - 2, - ), }; + } else { + if (typeof internals!.tables![tableName]!.columns[columnName] === 'undefined') { + internals!.tables![tableName]!.columns[columnName] = { + isDefaultAnExpression: true, + }; + } else { + internals!.tables![tableName]!.columns[columnName]!.isDefaultAnExpression = true; + } } } - } - if (columnAdditionalDT === 'ARRAY') { - for (let i = 1; i < Number(columnDimensions); i++) { - columnTypeMapped += '[]'; + const isSerial = columnType === 'serial'; + + if (columnTypeMapped.startsWith('numeric(')) { + columnTypeMapped = columnTypeMapped.replace(',', ', '); } - } - columnTypeMapped = columnTypeMapped - .replace('character varying', 'varchar') - .replace(' without time zone', '') - // .replace("timestamp without time zone", "timestamp") - .replace('character', 'char'); - - columnTypeMapped = trimChar(columnTypeMapped, '"'); - - columnToReturn[columnName] = { - name: columnName, - type: - // filter vectors, but in future we should filter any extension that was installed by user - columnAdditionalDT === 'USER-DEFINED' - && !['vector', 'geometry'].includes(enumType) - ? enumType - : columnTypeMapped, - typeSchema: enumsToReturn[`${tableSchema}.${enumType}`] !== undefined - ? enumsToReturn[`${tableSchema}.${enumType}`].schema - : undefined, - primaryKey: primaryKey.length === 1 && cprimaryKey.length < 2, - // default: isSerial ? undefined : defaultValue, - notNull: columnResponse.is_nullable === 'NO', - generated: isGenerated - ? { as: generationExpression, type: 'stored' } - : undefined, - identity: isIdentity - ? { - type: identityGeneration, - name: identityName, - increment: stringFromDatabaseIdentityProperty(identityIncrement), - minValue: stringFromDatabaseIdentityProperty(identityMinimum), - maxValue: stringFromDatabaseIdentityProperty(identityMaximum), - startWith: stringFromDatabaseIdentityProperty(identityStart), - cache: sequencesToReturn[identityName]?.cache - ? sequencesToReturn[identityName]?.cache - : sequencesToReturn[`${tableSchema}.${identityName}`]?.cache - ? sequencesToReturn[`${tableSchema}.${identityName}`]?.cache - : undefined, - cycle: identityCycle, - schema: tableSchema, + if (columnAdditionalDT === 'ARRAY') { + for (let i = 1; i < Number(columnDimensions); i++) { + columnTypeMapped += '[]'; } - : undefined, - }; + } - if (identityName) { - delete sequencesToReturn[`${tableSchema}.${identityName}`]; - delete sequencesToReturn[identityName]; - } + columnTypeMapped = columnTypeMapped + .replace('character varying', 'varchar') + .replace(' without time zone', '') + // .replace("timestamp without time zone", "timestamp") + .replace('character', 'char'); + + columnTypeMapped = trimChar(columnTypeMapped, '"'); + + columnToReturn[columnName] = { + name: columnName, + type: + // filter vectors, but in future we should filter any extension that was installed by user + columnAdditionalDT === 'USER-DEFINED' + && !['vector', 'geometry'].includes(enumType) + ? enumType + : columnTypeMapped, + typeSchema: enumsToReturn[`${typeSchema}.${enumType}`] !== undefined + ? enumsToReturn[`${typeSchema}.${enumType}`].schema + : undefined, + primaryKey: primaryKey.length === 1 && cprimaryKey.length < 2, + // default: isSerial ? undefined : defaultValue, + notNull: columnResponse.is_nullable === 'NO', + generated: isGenerated + ? { as: generationExpression, type: 'stored' } + : undefined, + identity: isIdentity + ? { + type: identityGeneration, + name: identityName, + increment: stringFromDatabaseIdentityProperty(identityIncrement), + minValue: stringFromDatabaseIdentityProperty(identityMinimum), + maxValue: stringFromDatabaseIdentityProperty(identityMaximum), + startWith: stringFromDatabaseIdentityProperty(identityStart), + cache: sequencesToReturn[identityName]?.cache + ? sequencesToReturn[identityName]?.cache + : sequencesToReturn[`${tableSchema}.${identityName}`]?.cache + ? sequencesToReturn[`${tableSchema}.${identityName}`]?.cache + : undefined, + cycle: identityCycle, + schema: tableSchema, + } + : undefined, + }; - if (!isSerial && typeof defaultValue !== 'undefined') { - columnToReturn[columnName].default = defaultValue; + if (identityName && typeof identityName === 'string') { + // remove "" from sequence name + delete sequencesToReturn[ + `${tableSchema}.${ + identityName.startsWith('"') && identityName.endsWith('"') ? identityName.slice(1, -1) : identityName + }` + ]; + delete sequencesToReturn[identityName]; + } + + if (!isSerial && typeof defaultValue !== 'undefined') { + columnToReturn[columnName].default = defaultValue; + } } - } - const dbIndexes = await db.query( - `SELECT DISTINCT ON (t.relname, ic.relname, k.i) t.relname as table_name, ic.relname AS indexname, + const dbIndexes = await db.query( + `SELECT DISTINCT ON (t.relname, ic.relname, k.i) t.relname as table_name, ic.relname AS indexname, k.i AS index_order, i.indisunique as is_unique, am.amname as method, @@ -997,10 +1567,10 @@ export const fromDatabase = async ( WHERE c.nspname = '${tableSchema}' AND t.relname = '${tableName}';`, - ); + ); - const dbIndexFromConstraint = await db.query( - `SELECT + const dbIndexFromConstraint = await db.query( + `SELECT idx.indexrelname AS index_name, idx.relname AS table_name, schemaname, @@ -1011,89 +1581,92 @@ export const fromDatabase = async ( pg_constraint con ON con.conindid = idx.indexrelid WHERE idx.relname = '${tableName}' and schemaname = '${tableSchema}' group by index_name, table_name,schemaname, generated_by_constraint;`, - ); + ); - const idxsInConsteraint = dbIndexFromConstraint - .filter((it) => it.generated_by_constraint === 1) - .map((it) => it.index_name); - - for (const dbIndex of dbIndexes) { - const indexName: string = dbIndex.indexname; - const indexColumnName: string = dbIndex.column_name; - const indexIsUnique = dbIndex.is_unique; - const indexMethod = dbIndex.method; - const indexWith: string[] = dbIndex.with; - const indexWhere: string = dbIndex.where; - const opclass: string = dbIndex.opcname; - const isExpression = dbIndex.is_expression === 1; - - const desc: boolean = dbIndex.descending; - const nullsFirst: boolean = dbIndex.nulls_first; - - const mappedWith: Record = {}; - - if (indexWith !== null) { - indexWith - // .slice(1, indexWith.length - 1) - // .split(",") - .forEach((it) => { - const splitted = it.split('='); - mappedWith[splitted[0]] = splitted[1]; - }); - } + const idxsInConsteraint = dbIndexFromConstraint.filter((it) => it.generated_by_constraint === 1).map((it) => + it.index_name + ); - if (idxsInConsteraint.includes(indexName)) continue; + for (const dbIndex of dbIndexes) { + const indexName: string = dbIndex.indexname; + const indexColumnName: string = dbIndex.column_name; + const indexIsUnique = dbIndex.is_unique; + const indexMethod = dbIndex.method; + const indexWith: string[] = dbIndex.with; + const indexWhere: string = dbIndex.where; + const opclass: string = dbIndex.opcname; + const isExpression = dbIndex.is_expression === 1; + + const desc: boolean = dbIndex.descending; + const nullsFirst: boolean = dbIndex.nulls_first; + + const mappedWith: Record = {}; + + if (indexWith !== null) { + indexWith + // .slice(1, indexWith.length - 1) + // .split(",") + .forEach((it) => { + const splitted = it.split('='); + mappedWith[splitted[0]] = splitted[1]; + }); + } - if (typeof indexToReturn[indexName] !== 'undefined') { - indexToReturn[indexName].columns.push({ - expression: indexColumnName, - asc: !desc, - nulls: nullsFirst ? 'first' : 'last', - opclass, - isExpression, - }); - } else { - indexToReturn[indexName] = { - name: indexName, - columns: [ - { - expression: indexColumnName, - asc: !desc, - nulls: nullsFirst ? 'first' : 'last', - opclass, - isExpression, - }, - ], - isUnique: indexIsUnique, - // should not be a part of diff detecs - concurrently: false, - method: indexMethod, - where: indexWhere === null ? undefined : indexWhere, - with: mappedWith, - }; + if (idxsInConsteraint.includes(indexName)) continue; + + if (typeof indexToReturn[indexName] !== 'undefined') { + indexToReturn[indexName].columns.push({ + expression: indexColumnName, + asc: !desc, + nulls: nullsFirst ? 'first' : 'last', + opclass, + isExpression, + }); + } else { + indexToReturn[indexName] = { + name: indexName, + columns: [ + { + expression: indexColumnName, + asc: !desc, + nulls: nullsFirst ? 'first' : 'last', + opclass, + isExpression, + }, + ], + isUnique: indexIsUnique, + // should not be a part of diff detects + concurrently: false, + method: indexMethod, + where: indexWhere === null ? undefined : indexWhere, + with: mappedWith, + }; + } } - } - indexesCount += Object.keys(indexToReturn).length; - if (progressCallback) { - progressCallback('indexes', indexesCount, 'fetching'); + indexesCount += Object.keys(indexToReturn).length; + if (progressCallback) { + progressCallback('indexes', indexesCount, 'fetching'); + } + result[`${tableSchema}.${tableName}`] = { + name: tableName, + schema: tableSchema !== 'public' ? tableSchema : '', + columns: columnToReturn, + indexes: indexToReturn, + foreignKeys: foreignKeysToReturn, + compositePrimaryKeys: primaryKeys, + uniqueConstraints: uniqueConstrains, + checkConstraints: checkConstraints, + policies: policiesByTable[`${tableSchema}.${tableName}`] ?? {}, + isRLSEnabled: row.rls_enabled, + }; + } catch (e) { + rej(e); + return; } - result[`${tableSchema}.${tableName}`] = { - name: tableName, - schema: tableSchema !== 'public' ? tableSchema : '', - columns: columnToReturn, - indexes: indexToReturn, - foreignKeys: foreignKeysToReturn, - compositePrimaryKeys: primaryKeys, - uniqueConstraints: uniqueConstrains, - }; - } catch (e) { - rej(e); - return; - } - res(''); + res(''); + }); }); - }); if (progressCallback) { progressCallback('tables', tableCount, 'done'); @@ -1102,10 +1675,241 @@ export const fromDatabase = async ( for await (const _ of all) { } + const allViews = allTables + .filter((it) => it.type === 'view' || it.type === 'materialized_view') + .map((row) => { + return new Promise(async (res, rej) => { + const viewName = row.table_name as string; + if (!tablesFilter(viewName)) return res(''); + tableCount += 1; + const viewSchema = row.table_schema; + + try { + const columnToReturn: Record = {}; + + const viewResponses = await getColumnsInfoQuery({ schema: viewSchema, table: viewName, db }); + + for (const viewResponse of viewResponses) { + const columnName = viewResponse.column_name; + const columnAdditionalDT = viewResponse.additional_dt; + const columnDimensions = viewResponse.array_dimensions; + const enumType: string = viewResponse.enum_name; + let columnType: string = viewResponse.data_type; + const typeSchema = viewResponse.type_schema; + // const defaultValueRes: string = viewResponse.column_default; + + const isGenerated = viewResponse.is_generated === 'ALWAYS'; + const generationExpression = viewResponse.generation_expression; + const isIdentity = viewResponse.is_identity === 'YES'; + const identityGeneration = viewResponse.identity_generation === 'ALWAYS' ? 'always' : 'byDefault'; + const identityStart = viewResponse.identity_start; + const identityIncrement = viewResponse.identity_increment; + const identityMaximum = viewResponse.identity_maximum; + const identityMinimum = viewResponse.identity_minimum; + const identityCycle = viewResponse.identity_cycle === 'YES'; + const identityName = viewResponse.seq_name; + const defaultValueRes = viewResponse.column_default; + + const primaryKey = viewResponse.constraint_type === 'PRIMARY KEY'; + + let columnTypeMapped = columnType; + + // Set default to internal object + if (columnAdditionalDT === 'ARRAY') { + if (typeof internals.tables[viewName] === 'undefined') { + internals.tables[viewName] = { + columns: { + [columnName]: { + isArray: true, + dimensions: columnDimensions, + rawType: columnTypeMapped.substring(0, columnTypeMapped.length - 2), + }, + }, + }; + } else { + if (typeof internals.tables[viewName]!.columns[columnName] === 'undefined') { + internals.tables[viewName]!.columns[columnName] = { + isArray: true, + dimensions: columnDimensions, + rawType: columnTypeMapped.substring(0, columnTypeMapped.length - 2), + }; + } + } + } + + const defaultValue = defaultForColumn(viewResponse, internals, viewName); + if ( + defaultValue === 'NULL' + || (defaultValueRes && defaultValueRes.startsWith('(') && defaultValueRes.endsWith(')')) + ) { + if (typeof internals!.tables![viewName] === 'undefined') { + internals!.tables![viewName] = { + columns: { + [columnName]: { + isDefaultAnExpression: true, + }, + }, + }; + } else { + if (typeof internals!.tables![viewName]!.columns[columnName] === 'undefined') { + internals!.tables![viewName]!.columns[columnName] = { + isDefaultAnExpression: true, + }; + } else { + internals!.tables![viewName]!.columns[columnName]!.isDefaultAnExpression = true; + } + } + } + + const isSerial = columnType === 'serial'; + + if (columnTypeMapped.startsWith('numeric(')) { + columnTypeMapped = columnTypeMapped.replace(',', ', '); + } + + if (columnAdditionalDT === 'ARRAY') { + for (let i = 1; i < Number(columnDimensions); i++) { + columnTypeMapped += '[]'; + } + } + + columnTypeMapped = columnTypeMapped + .replace('character varying', 'varchar') + .replace(' without time zone', '') + // .replace("timestamp without time zone", "timestamp") + .replace('character', 'char'); + + columnTypeMapped = trimChar(columnTypeMapped, '"'); + + columnToReturn[columnName] = { + name: columnName, + type: + // filter vectors, but in future we should filter any extension that was installed by user + columnAdditionalDT === 'USER-DEFINED' && !['vector', 'geometry'].includes(enumType) + ? enumType + : columnTypeMapped, + typeSchema: enumsToReturn[`${typeSchema}.${enumType}`] !== undefined + ? enumsToReturn[`${typeSchema}.${enumType}`].schema + : undefined, + primaryKey: primaryKey, + notNull: viewResponse.is_nullable === 'NO', + generated: isGenerated ? { as: generationExpression, type: 'stored' } : undefined, + identity: isIdentity + ? { + type: identityGeneration, + name: identityName, + increment: stringFromDatabaseIdentityProperty(identityIncrement), + minValue: stringFromDatabaseIdentityProperty(identityMinimum), + maxValue: stringFromDatabaseIdentityProperty(identityMaximum), + startWith: stringFromDatabaseIdentityProperty(identityStart), + cache: sequencesToReturn[identityName]?.cache + ? sequencesToReturn[identityName]?.cache + : sequencesToReturn[`${viewSchema}.${identityName}`]?.cache + ? sequencesToReturn[`${viewSchema}.${identityName}`]?.cache + : undefined, + cycle: identityCycle, + schema: viewSchema, + } + : undefined, + }; + + if (identityName) { + // remove "" from sequence name + delete sequencesToReturn[ + `${viewSchema}.${ + identityName.startsWith('"') && identityName.endsWith('"') ? identityName.slice(1, -1) : identityName + }` + ]; + delete sequencesToReturn[identityName]; + } + + if (!isSerial && typeof defaultValue !== 'undefined') { + columnToReturn[columnName].default = defaultValue; + } + } + + const [viewInfo] = await db.query<{ + view_name: string; + schema_name: string; + definition: string; + tablespace_name: string | null; + options: string[] | null; + location: string | null; + }>(` + SELECT + c.relname AS view_name, + n.nspname AS schema_name, + pg_get_viewdef(c.oid, true) AS definition, + ts.spcname AS tablespace_name, + c.reloptions AS options, + pg_tablespace_location(ts.oid) AS location +FROM + pg_class c +JOIN + pg_namespace n ON c.relnamespace = n.oid +LEFT JOIN + pg_tablespace ts ON c.reltablespace = ts.oid +WHERE + (c.relkind = 'm' OR c.relkind = 'v') + AND n.nspname = '${viewSchema}' + AND c.relname = '${viewName}';`); + + const resultWith: { [key: string]: string | boolean | number } = {}; + if (viewInfo.options) { + viewInfo.options.forEach((pair) => { + const splitted = pair.split('='); + const key = splitted[0]; + const value = splitted[1]; + + if (value === 'true') { + resultWith[key] = true; + } else if (value === 'false') { + resultWith[key] = false; + } else if (!isNaN(Number(value))) { + resultWith[key] = Number(value); + } else { + resultWith[key] = value; + } + }); + } + + const definition = viewInfo.definition.replace(/\s+/g, ' ').replace(';', '').trim(); + // { "check_option":"cascaded","security_barrier":true} -> // { "checkOption":"cascaded","securityBarrier":true} + const withOption = Object.values(resultWith).length + ? Object.fromEntries(Object.entries(resultWith).map(([key, value]) => [key.camelCase(), value])) + : undefined; + + const materialized = row.type === 'materialized_view'; + + views[`${viewSchema}.${viewName}`] = { + name: viewName, + schema: viewSchema, + columns: columnToReturn, + isExisting: false, + definition: definition, + materialized: materialized, + with: withOption, + tablespace: viewInfo.tablespace_name ?? undefined, + }; + } catch (e) { + rej(e); + return; + } + res(''); + }); + }); + + viewsCount = allViews.length; + + for await (const _ of allViews) { + } + if (progressCallback) { progressCallback('columns', columnsCount, 'done'); progressCallback('indexes', indexesCount, 'done'); progressCallback('fks', foreignKeysCount, 'done'); + progressCallback('checks', checksCount, 'done'); + progressCallback('views', viewsCount, 'done'); } const schemasObject = Object.fromEntries([...schemas].map((it) => [it, it])); @@ -1117,6 +1921,9 @@ export const fromDatabase = async ( enums: enumsToReturn, schemas: schemasObject, sequences: sequencesToReturn, + roles: rolesToReturn, + policies, + views: views, _meta: { schemas: {}, tables: {}, @@ -1126,89 +1933,162 @@ export const fromDatabase = async ( }; }; -const columnToDefault: Record = { - 'numeric(': '::numeric', - // text: "::text", - // "character varying": "::character varying", - // "double precision": "::double precision", - // "time with time zone": "::time with time zone", - 'time without time zone': '::time without time zone', - // "timestamp with time zone": "::timestamp with time zone", - 'timestamp without time zone': '::timestamp without time zone', - 'timestamp(': '::timestamp without time zone', - // date: "::date", - // interval: "::interval", - // character: "::bpchar", - // macaddr8: "::macaddr8", - // macaddr: "::macaddr", - // inet: "::inet", - // cidr: "::cidr", - // jsonb: "::jsonb", - // json: "::json", - 'character(': '::bpchar', -}; - -const defaultForColumn = (column: any) => { - if (column.column_default === null) { - return undefined; - } +const defaultForColumn = (column: any, internals: PgKitInternals, tableName: string) => { + const columnName = column.column_name; + const isArray = internals?.tables[tableName]?.columns[columnName]?.isArray ?? false; if ( - column.data_type === 'serial' + column.column_default === null + || column.column_default === undefined + || column.data_type === 'serial' || column.data_type === 'smallserial' || column.data_type === 'bigserial' ) { return undefined; } - const hasDifferentDefaultCast = Object.keys(columnToDefault).find((it) => column.data_type.startsWith(it)); + if (column.column_default.endsWith('[]')) { + column.column_default = column.column_default.slice(0, -2); + } + + // if ( + // !['integer', 'smallint', 'bigint', 'double precision', 'real'].includes(column.data_type) + // ) { + column.column_default = column.column_default.replace(/::(.*?)(? { + if (['integer', 'smallint', 'bigint', 'double precision', 'real'].includes(column.data_type.slice(0, -2))) { + return value; + } else if (column.data_type.startsWith('timestamp')) { + return `${value}`; + } else if (column.data_type.slice(0, -2) === 'interval') { + return value.replaceAll('"', `\"`); + } else if (column.data_type.slice(0, -2) === 'boolean') { + return value === 't' ? 'true' : 'false'; + } else if (['json', 'jsonb'].includes(column.data_type.slice(0, -2))) { + return JSON.stringify(JSON.stringify(JSON.parse(JSON.parse(value)), null, 0)); + } else { + return `\"${value}\"`; + } + }) + .join(',') + }}'`; + } + + if (['integer', 'smallint', 'bigint', 'double precision', 'real'].includes(column.data_type)) { + if (/^-?[\d.]+(?:e-?\d+)?$/.test(columnDefaultAsString)) { return Number(columnDefaultAsString); - } else if (column.data_type === 'boolean') { - return column.column_default === 'true'; } else { - return `${columnDefaultAsString}`; + if (typeof internals!.tables![tableName] === 'undefined') { + internals!.tables![tableName] = { + columns: { + [columnName]: { + isDefaultAnExpression: true, + }, + }, + }; + } else { + if (typeof internals!.tables![tableName]!.columns[columnName] === 'undefined') { + internals!.tables![tableName]!.columns[columnName] = { + isDefaultAnExpression: true, + }; + } else { + internals!.tables![tableName]!.columns[columnName]!.isDefaultAnExpression = true; + } + } + return columnDefaultAsString; } + } else if (column.data_type.includes('numeric')) { + // if numeric(1,1) and used '99' -> psql stores like '99'::numeric + return columnDefaultAsString.includes("'") ? columnDefaultAsString : `'${columnDefaultAsString}'`; + } else if (column.data_type === 'json' || column.data_type === 'jsonb') { + const jsonWithoutSpaces = JSON.stringify(JSON.parse(columnDefaultAsString.slice(1, -1))); + return `'${jsonWithoutSpaces}'::${column.data_type}`; + } else if (column.data_type === 'boolean') { + return column.column_default === 'true'; + } else if (columnDefaultAsString === 'NULL') { + return `NULL`; + } else if (columnDefaultAsString.startsWith("'") && columnDefaultAsString.endsWith("'")) { + return columnDefaultAsString; + } else { + return `${columnDefaultAsString.replace(/\\/g, '`\\')}`; } }; + +const getColumnsInfoQuery = ({ schema, table, db }: { schema: string; table: string; db: DB }) => { + return db.query( + `SELECT + a.attrelid::regclass::text AS table_name, -- Table, view, or materialized view name + a.attname AS column_name, -- Column name + CASE + WHEN NOT a.attisdropped THEN + CASE + WHEN a.attnotnull THEN 'NO' + ELSE 'YES' + END + ELSE NULL + END AS is_nullable, -- NULL or NOT NULL constraint + a.attndims AS array_dimensions, -- Array dimensions + CASE + WHEN a.atttypid = ANY ('{int,int8,int2}'::regtype[]) + AND EXISTS ( + SELECT FROM pg_attrdef ad + WHERE ad.adrelid = a.attrelid + AND ad.adnum = a.attnum + AND pg_get_expr(ad.adbin, ad.adrelid) = 'nextval(''' + || pg_get_serial_sequence(a.attrelid::regclass::text, a.attname)::regclass || '''::regclass)' + ) + THEN CASE a.atttypid + WHEN 'int'::regtype THEN 'serial' + WHEN 'int8'::regtype THEN 'bigserial' + WHEN 'int2'::regtype THEN 'smallserial' + END + ELSE format_type(a.atttypid, a.atttypmod) + END AS data_type, -- Column data type +-- ns.nspname AS type_schema, -- Schema name + pg_get_serial_sequence('"${schema}"."${table}"', a.attname)::regclass AS seq_name, -- Serial sequence (if any) + c.column_default, -- Column default value + c.data_type AS additional_dt, -- Data type from information_schema + c.udt_name AS enum_name, -- Enum type (if applicable) + c.is_generated, -- Is it a generated column? + c.generation_expression, -- Generation expression (if generated) + c.is_identity, -- Is it an identity column? + c.identity_generation, -- Identity generation strategy (ALWAYS or BY DEFAULT) + c.identity_start, -- Start value of identity column + c.identity_increment, -- Increment for identity column + c.identity_maximum, -- Maximum value for identity column + c.identity_minimum, -- Minimum value for identity column + c.identity_cycle, -- Does the identity column cycle? + enum_ns.nspname AS type_schema -- Schema of the enum type +FROM + pg_attribute a +JOIN + pg_class cls ON cls.oid = a.attrelid -- Join pg_class to get table/view/materialized view info +JOIN + pg_namespace ns ON ns.oid = cls.relnamespace -- Join namespace to get schema info +LEFT JOIN + information_schema.columns c ON c.column_name = a.attname + AND c.table_schema = ns.nspname + AND c.table_name = cls.relname -- Match schema and table/view name +LEFT JOIN + pg_type enum_t ON enum_t.oid = a.atttypid -- Join to get the type info +LEFT JOIN + pg_namespace enum_ns ON enum_ns.oid = enum_t.typnamespace -- Join to get the enum schema +WHERE + a.attnum > 0 -- Valid column numbers only + AND NOT a.attisdropped -- Skip dropped columns + AND cls.relkind IN ('r', 'v', 'm') -- Include regular tables ('r'), views ('v'), and materialized views ('m') + AND ns.nspname = '${schema}' -- Filter by schema + AND cls.relname = '${table}' -- Filter by table name +ORDER BY + a.attnum; -- Order by column number`, + ); +}; diff --git a/drizzle-kit/src/serializer/singlestoreImports.ts b/drizzle-kit/src/serializer/singlestoreImports.ts new file mode 100644 index 000000000..23c2d66a9 --- /dev/null +++ b/drizzle-kit/src/serializer/singlestoreImports.ts @@ -0,0 +1,38 @@ +import { is } from 'drizzle-orm'; +import { AnySingleStoreTable, SingleStoreTable } from 'drizzle-orm/singlestore-core'; +import { safeRegister } from '../cli/commands/utils'; + +export const prepareFromExports = (exports: Record) => { + const tables: AnySingleStoreTable[] = []; + /* const views: SingleStoreView[] = []; */ + + const i0values = Object.values(exports); + i0values.forEach((t) => { + if (is(t, SingleStoreTable)) { + tables.push(t); + } + + /* if (is(t, SingleStoreView)) { + views.push(t); + } */ + }); + + return { tables /* views */ }; +}; + +export const prepareFromSingleStoreImports = async (imports: string[]) => { + const tables: AnySingleStoreTable[] = []; + /* const views: SingleStoreView[] = []; */ + + const { unregister } = await safeRegister(); + for (let i = 0; i < imports.length; i++) { + const it = imports[i]; + const i0: Record = require(`${it}`); + const prepared = prepareFromExports(i0); + + tables.push(...prepared.tables); + /* views.push(...prepared.views); */ + } + unregister(); + return { tables: Array.from(new Set(tables)) /* , views */ }; +}; diff --git a/drizzle-kit/src/serializer/singlestoreSchema.ts b/drizzle-kit/src/serializer/singlestoreSchema.ts new file mode 100644 index 000000000..9ff45ef5a --- /dev/null +++ b/drizzle-kit/src/serializer/singlestoreSchema.ts @@ -0,0 +1,257 @@ +import { any, boolean, enum as enumType, literal, object, record, string, TypeOf, union } from 'zod'; +import { mapValues, originUUID, snapshotVersion } from '../global'; + +// ------- V3 -------- +const index = object({ + name: string(), + columns: string().array(), + isUnique: boolean(), + using: enumType(['btree', 'hash']).optional(), + algorithm: enumType(['default', 'inplace', 'copy']).optional(), + lock: enumType(['default', 'none', 'shared', 'exclusive']).optional(), +}).strict(); + +const column = object({ + name: string(), + type: string(), + primaryKey: boolean(), + notNull: boolean(), + autoincrement: boolean().optional(), + default: any().optional(), + onUpdate: any().optional(), + generated: object({ + type: enumType(['stored', 'virtual']), + as: string(), + }).optional(), +}).strict(); + +const compositePK = object({ + name: string(), + columns: string().array(), +}).strict(); + +const uniqueConstraint = object({ + name: string(), + columns: string().array(), +}).strict(); + +const table = object({ + name: string(), + columns: record(string(), column), + indexes: record(string(), index), + compositePrimaryKeys: record(string(), compositePK), + uniqueConstraints: record(string(), uniqueConstraint).default({}), +}).strict(); + +const viewMeta = object({ + algorithm: enumType(['undefined', 'merge', 'temptable']), + sqlSecurity: enumType(['definer', 'invoker']), + withCheckOption: enumType(['local', 'cascaded']).optional(), +}).strict(); + +/* export const view = object({ + name: string(), + columns: record(string(), column), + definition: string().optional(), + isExisting: boolean(), +}).strict().merge(viewMeta); +type SquasherViewMeta = Omit, 'definer'>; */ + +export const kitInternals = object({ + tables: record( + string(), + object({ + columns: record( + string(), + object({ isDefaultAnExpression: boolean().optional() }).optional(), + ), + }).optional(), + ).optional(), + indexes: record( + string(), + object({ + columns: record( + string(), + object({ isExpression: boolean().optional() }).optional(), + ), + }).optional(), + ).optional(), +}).optional(); + +// use main dialect +const dialect = literal('singlestore'); + +const schemaHash = object({ + id: string(), + prevId: string(), +}); + +export const schemaInternal = object({ + version: literal('1'), + dialect: dialect, + tables: record(string(), table), + /* views: record(string(), view).default({}), */ + _meta: object({ + tables: record(string(), string()), + columns: record(string(), string()), + }), + internal: kitInternals, +}).strict(); + +export const schema = schemaInternal.merge(schemaHash); + +const tableSquashed = object({ + name: string(), + columns: record(string(), column), + indexes: record(string(), string()), + compositePrimaryKeys: record(string(), string()), + uniqueConstraints: record(string(), string()).default({}), +}).strict(); + +/* const viewSquashed = view.omit({ + algorithm: true, + sqlSecurity: true, + withCheckOption: true, +}).extend({ meta: string() }); */ + +export const schemaSquashed = object({ + version: literal('1'), + dialect: dialect, + tables: record(string(), tableSquashed), + /* views: record(string(), viewSquashed), */ +}).strict(); + +export type Dialect = TypeOf; +export type Column = TypeOf; +export type Table = TypeOf; +export type SingleStoreSchema = TypeOf; +export type SingleStoreSchemaInternal = TypeOf; +export type SingleStoreKitInternals = TypeOf; +export type SingleStoreSchemaSquashed = TypeOf; +export type Index = TypeOf; +export type PrimaryKey = TypeOf; +export type UniqueConstraint = TypeOf; +/* export type View = TypeOf; */ +/* export type ViewSquashed = TypeOf; */ + +export const SingleStoreSquasher = { + squashIdx: (idx: Index) => { + index.parse(idx); + return `${idx.name};${idx.columns.join(',')};${idx.isUnique};${idx.using ?? ''};${idx.algorithm ?? ''};${ + idx.lock ?? '' + }`; + }, + unsquashIdx: (input: string): Index => { + const [name, columnsString, isUnique, using, algorithm, lock] = input.split(';'); + const destructed = { + name, + columns: columnsString.split(','), + isUnique: isUnique === 'true', + using: using ? using : undefined, + algorithm: algorithm ? algorithm : undefined, + lock: lock ? lock : undefined, + }; + return index.parse(destructed); + }, + squashPK: (pk: PrimaryKey) => { + return `${pk.name};${pk.columns.join(',')}`; + }, + unsquashPK: (pk: string): PrimaryKey => { + const splitted = pk.split(';'); + return { name: splitted[0], columns: splitted[1].split(',') }; + }, + squashUnique: (unq: UniqueConstraint) => { + return `${unq.name};${unq.columns.join(',')}`; + }, + unsquashUnique: (unq: string): UniqueConstraint => { + const [name, columns] = unq.split(';'); + return { name, columns: columns.split(',') }; + }, + /* squashView: (view: View): string => { + return `${view.algorithm};${view.sqlSecurity};${view.withCheckOption}`; + }, + unsquashView: (meta: string): SquasherViewMeta => { + const [algorithm, sqlSecurity, withCheckOption] = meta.split(';'); + const toReturn = { + algorithm: algorithm, + sqlSecurity: sqlSecurity, + withCheckOption: withCheckOption !== 'undefined' ? withCheckOption : undefined, + }; + + return viewMeta.parse(toReturn); + }, */ +}; + +export const squashSingleStoreScheme = (json: SingleStoreSchema): SingleStoreSchemaSquashed => { + const mappedTables = Object.fromEntries( + Object.entries(json.tables).map((it) => { + const squashedIndexes = mapValues(it[1].indexes, (index) => { + return SingleStoreSquasher.squashIdx(index); + }); + + const squashedPKs = mapValues(it[1].compositePrimaryKeys, (pk) => { + return SingleStoreSquasher.squashPK(pk); + }); + + const squashedUniqueConstraints = mapValues( + it[1].uniqueConstraints, + (unq) => { + return SingleStoreSquasher.squashUnique(unq); + }, + ); + + return [ + it[0], + { + name: it[1].name, + columns: it[1].columns, + indexes: squashedIndexes, + compositePrimaryKeys: squashedPKs, + uniqueConstraints: squashedUniqueConstraints, + }, + ]; + }), + ); + + /* const mappedViews = Object.fromEntries( + Object.entries(json.views).map(([key, value]) => { + const meta = SingleStoreSquasher.squashView(value); + + return [key, { + name: value.name, + isExisting: value.isExisting, + columns: value.columns, + definition: value.definition, + meta, + }]; + }), + ); */ + + return { + version: '1', + dialect: json.dialect, + tables: mappedTables, + /* views: mappedViews, */ + }; +}; + +export const singlestoreSchema = schema; +export const singlestoreSchemaSquashed = schemaSquashed; + +// no prev version +export const backwardCompatibleSingleStoreSchema = union([singlestoreSchema, schema]); + +export const drySingleStore = singlestoreSchema.parse({ + version: '1', + dialect: 'singlestore', + id: originUUID, + prevId: '', + tables: {}, + schemas: {}, + /* views: {}, */ + _meta: { + schemas: {}, + tables: {}, + columns: {}, + }, +}); diff --git a/drizzle-kit/src/serializer/singlestoreSerializer.ts b/drizzle-kit/src/serializer/singlestoreSerializer.ts new file mode 100644 index 000000000..e65f53d25 --- /dev/null +++ b/drizzle-kit/src/serializer/singlestoreSerializer.ts @@ -0,0 +1,767 @@ +import chalk from 'chalk'; +import { is, SQL } from 'drizzle-orm'; +import { + AnySingleStoreTable, + getTableConfig, + type PrimaryKey as PrimaryKeyORM, + SingleStoreDialect, + uniqueKeyName, +} from 'drizzle-orm/singlestore-core'; +import { RowDataPacket } from 'mysql2/promise'; +import { withStyle } from '../cli/validations/outputs'; +import { IntrospectStage, IntrospectStatus } from '../cli/views'; + +import { CasingType } from 'src/cli/validations/common'; +import type { DB } from '../utils'; +import { + Column, + Index, + PrimaryKey, + SingleStoreKitInternals, + SingleStoreSchemaInternal, + Table, + UniqueConstraint, +} from './singlestoreSchema'; +import { sqlToStr } from './utils'; + +const dialect = new SingleStoreDialect(); + +export const indexName = (tableName: string, columns: string[]) => { + return `${tableName}_${columns.join('_')}_index`; +}; + +export const generateSingleStoreSnapshot = ( + tables: AnySingleStoreTable[], + /* views: SingleStoreView[], */ + casing: CasingType | undefined, +): SingleStoreSchemaInternal => { + const dialect = new SingleStoreDialect({ casing }); + const result: Record = {}; + /* const resultViews: Record = {}; */ + const internal: SingleStoreKitInternals = { tables: {}, indexes: {} }; + for (const table of tables) { + const { + name: tableName, + columns, + indexes, + schema, + primaryKeys, + uniqueConstraints, + } = getTableConfig(table); + const columnsObject: Record = {}; + const indexesObject: Record = {}; + const primaryKeysObject: Record = {}; + const uniqueConstraintObject: Record = {}; + + columns.forEach((column) => { + const notNull: boolean = column.notNull; + const sqlTypeLowered = column.getSQLType().toLowerCase(); + const autoIncrement = typeof (column as any).autoIncrement === 'undefined' + ? false + : (column as any).autoIncrement; + + const generated = column.generated; + + const columnToSet: Column = { + name: column.name, + type: column.getSQLType(), + primaryKey: false, + // If field is autoincrement it's notNull by default + // notNull: autoIncrement ? true : notNull, + notNull, + autoincrement: autoIncrement, + onUpdate: (column as any).hasOnUpdateNow, + generated: generated + ? { + as: is(generated.as, SQL) + ? dialect.sqlToQuery(generated.as as SQL).sql + : typeof generated.as === 'function' + ? dialect.sqlToQuery(generated.as() as SQL).sql + : (generated.as as any), + type: generated.mode ?? 'stored', + } + : undefined, + }; + + if (column.primary) { + primaryKeysObject[`${tableName}_${column.name}`] = { + name: `${tableName}_${column.name}`, + columns: [column.name], + }; + } + + if (column.isUnique) { + const existingUnique = uniqueConstraintObject[column.uniqueName!]; + if (typeof existingUnique !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning(`We\'ve found duplicated unique constraint names in ${ + chalk.underline.blue( + tableName, + ) + } table. + The unique constraint ${ + chalk.underline.blue( + column.uniqueName, + ) + } on the ${ + chalk.underline.blue( + column.name, + ) + } column is confilcting with a unique constraint name already defined for ${ + chalk.underline.blue( + existingUnique.columns.join(','), + ) + } columns\n`) + }`, + ); + process.exit(1); + } + uniqueConstraintObject[column.uniqueName!] = { + name: column.uniqueName!, + columns: [columnToSet.name], + }; + } + + if (column.default !== undefined) { + if (is(column.default, SQL)) { + columnToSet.default = sqlToStr(column.default, casing); + } else { + if (typeof column.default === 'string') { + columnToSet.default = `'${column.default}'`; + } else { + if (sqlTypeLowered === 'json' || Array.isArray(column.default)) { + columnToSet.default = `'${JSON.stringify(column.default)}'`; + } else if (column.default instanceof Date) { + if (sqlTypeLowered === 'date') { + columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; + } else if ( + sqlTypeLowered.startsWith('datetime') + || sqlTypeLowered.startsWith('timestamp') + ) { + columnToSet.default = `'${ + column.default + .toISOString() + .replace('T', ' ') + .slice(0, 23) + }'`; + } + } else { + columnToSet.default = column.default; + } + } + // if (['blob', 'text', 'json'].includes(column.getSQLType())) { + // columnToSet.default = `(${columnToSet.default})`; + // } + } + } + columnsObject[column.name] = columnToSet; + }); + + primaryKeys.map((pk: PrimaryKeyORM) => { + const columnNames = pk.columns.map((c: any) => c.name); + primaryKeysObject[pk.getName()] = { + name: pk.getName(), + columns: columnNames, + }; + + // all composite pk's should be treated as notNull + for (const column of pk.columns) { + columnsObject[column.name].notNull = true; + } + }); + + uniqueConstraints?.map((unq) => { + const columnNames = unq.columns.map((c) => c.name); + + const name = unq.name ?? uniqueKeyName(table, columnNames); + + const existingUnique = uniqueConstraintObject[name]; + if (typeof existingUnique !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated unique constraint names in ${ + chalk.underline.blue( + tableName, + ) + } table. \nThe unique constraint ${ + chalk.underline.blue( + name, + ) + } on the ${ + chalk.underline.blue( + columnNames.join(','), + ) + } columns is confilcting with a unique constraint name already defined for ${ + chalk.underline.blue( + existingUnique.columns.join(','), + ) + } columns\n`, + ) + }`, + ); + process.exit(1); + } + + uniqueConstraintObject[name] = { + name: unq.name!, + columns: columnNames, + }; + }); + + indexes.forEach((value) => { + const columns = value.config.columns; + const name = value.config.name; + + let indexColumns = columns.map((it) => { + if (is(it, SQL)) { + const sql = dialect.sqlToQuery(it, 'indexes').sql; + if (typeof internal!.indexes![name] === 'undefined') { + internal!.indexes![name] = { + columns: { + [sql]: { + isExpression: true, + }, + }, + }; + } else { + if (typeof internal!.indexes![name]?.columns[sql] === 'undefined') { + internal!.indexes![name]!.columns[sql] = { + isExpression: true, + }; + } else { + internal!.indexes![name]!.columns[sql]!.isExpression = true; + } + } + return sql; + } else { + return `${it.name}`; + } + }); + + if (value.config.unique) { + if (typeof uniqueConstraintObject[name] !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated unique constraint names in ${ + chalk.underline.blue( + tableName, + ) + } table. \nThe unique index ${ + chalk.underline.blue( + name, + ) + } on the ${ + chalk.underline.blue( + indexColumns.join(','), + ) + } columns is confilcting with a unique constraint name already defined for ${ + chalk.underline.blue( + uniqueConstraintObject[name].columns.join(','), + ) + } columns\n`, + ) + }`, + ); + process.exit(1); + } + } + + indexesObject[name] = { + name, + columns: indexColumns, + isUnique: value.config.unique ?? false, + using: value.config.using, + algorithm: value.config.algorythm, + lock: value.config.lock, + }; + }); + + // only handle tables without schemas + if (!schema) { + result[tableName] = { + name: tableName, + columns: columnsObject, + indexes: indexesObject, + compositePrimaryKeys: primaryKeysObject, + uniqueConstraints: uniqueConstraintObject, + }; + } + } + + /* for (const view of views) { + const { + isExisting, + name, + query, + schema, + selectedFields, + algorithm, + sqlSecurity, + withCheckOption, + } = getViewConfig(view); + + const columnsObject: Record = {}; + + const existingView = resultViews[name]; + if (typeof existingView !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated view name across ${ + chalk.underline.blue( + schema ?? 'public', + ) + } schema. Please rename your view`, + ) + }`, + ); + process.exit(1); + } + for (const key in selectedFields) { + if (is(selectedFields[key], SingleStoreColumn)) { + const column = selectedFields[key]; + + const notNull: boolean = column.notNull; + const sqlTypeLowered = column.getSQLType().toLowerCase(); + const autoIncrement = typeof (column as any).autoIncrement === 'undefined' + ? false + : (column as any).autoIncrement; + + const generated = column.generated; + + const columnToSet: Column = { + name: column.name, + type: column.getSQLType(), + primaryKey: false, + // If field is autoincrement it's notNull by default + // notNull: autoIncrement ? true : notNull, + notNull, + autoincrement: autoIncrement, + onUpdate: (column as any).hasOnUpdateNow, + generated: generated + ? { + as: is(generated.as, SQL) + ? dialect.sqlToQuery(generated.as as SQL).sql + : typeof generated.as === 'function' + ? dialect.sqlToQuery(generated.as() as SQL).sql + : (generated.as as any), + type: generated.mode ?? 'stored', + } + : undefined, + }; + + if (column.default !== undefined) { + if (is(column.default, SQL)) { + columnToSet.default = sqlToStr(column.default, casing); + } else { + if (typeof column.default === 'string') { + columnToSet.default = `'${column.default}'`; + } else { + if (sqlTypeLowered === 'json') { + columnToSet.default = `'${JSON.stringify(column.default)}'`; + } else if (column.default instanceof Date) { + if (sqlTypeLowered === 'date') { + columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; + } else if ( + sqlTypeLowered.startsWith('datetime') + || sqlTypeLowered.startsWith('timestamp') + ) { + columnToSet.default = `'${ + column.default + .toISOString() + .replace('T', ' ') + .slice(0, 23) + }'`; + } + } else { + columnToSet.default = column.default; + } + } + } + } + columnsObject[column.name] = columnToSet; + } + } + + resultViews[name] = { + columns: columnsObject, + name, + isExisting, + definition: isExisting ? undefined : dialect.sqlToQuery(query!).sql, + withCheckOption, + algorithm: algorithm ?? 'undefined', // set default values + sqlSecurity: sqlSecurity ?? 'definer', // set default values + }; + } */ + + return { + version: '1', + dialect: 'singlestore', + tables: result, + /* views: resultViews, */ + _meta: { + tables: {}, + columns: {}, + }, + internal, + }; +}; + +function clearDefaults(defaultValue: any, collate: string) { + if (typeof collate === 'undefined' || collate === null) { + collate = `utf8mb4`; + } + + let resultDefault = defaultValue; + collate = `_${collate}`; + if (defaultValue.startsWith(collate)) { + resultDefault = resultDefault + .substring(collate.length, defaultValue.length) + .replace(/\\/g, ''); + if (resultDefault.startsWith("'") && resultDefault.endsWith("'")) { + return `('${resultDefault.substring(1, resultDefault.length - 1)}')`; + } else { + return `'${resultDefault}'`; + } + } else { + return `(${resultDefault})`; + } +} + +export const fromDatabase = async ( + db: DB, + inputSchema: string, + tablesFilter: (table: string) => boolean = (table) => true, + progressCallback?: ( + stage: IntrospectStage, + count: number, + status: IntrospectStatus, + ) => void, +): Promise => { + const result: Record = {}; + const internals: SingleStoreKitInternals = { tables: {}, indexes: {} }; + + const columns = await db.query(`select * from information_schema.columns + where table_schema = '${inputSchema}' and table_name != '__drizzle_migrations' + order by table_name, ordinal_position;`); + + const response = columns as RowDataPacket[]; + + const schemas: string[] = []; + + let columnsCount = 0; + let tablesCount = new Set(); + let indexesCount = 0; + /* let viewsCount = 0; */ + + const idxs = await db.query( + `select * from INFORMATION_SCHEMA.STATISTICS + WHERE INFORMATION_SCHEMA.STATISTICS.TABLE_SCHEMA = '${inputSchema}' and INFORMATION_SCHEMA.STATISTICS.INDEX_NAME != 'PRIMARY';`, + ); + + const idxRows = idxs as RowDataPacket[]; + + for (const column of response) { + if (!tablesFilter(column['TABLE_NAME'] as string)) continue; + + columnsCount += 1; + if (progressCallback) { + progressCallback('columns', columnsCount, 'fetching'); + } + const schema: string = column['TABLE_SCHEMA']; + const tableName = column['TABLE_NAME']; + + tablesCount.add(`${schema}.${tableName}`); + if (progressCallback) { + progressCallback('columns', tablesCount.size, 'fetching'); + } + const columnName: string = column['COLUMN_NAME']; + const isNullable = column['IS_NULLABLE'] === 'YES'; // 'YES', 'NO' + const dataType = column['DATA_TYPE']; // varchar + const columnType = column['COLUMN_TYPE']; // varchar(256) + // const columnType = column["DATA_TYPE"]; + const isPrimary = column['COLUMN_KEY'] === 'PRI'; // 'PRI', '' + let columnDefault: string | null = column['COLUMN_DEFAULT']; + const collation: string = column['CHARACTER_SET_NAME']; + const geenratedExpression: string = column['GENERATION_EXPRESSION']; + + let columnExtra = column['EXTRA']; + let isAutoincrement = false; // 'auto_increment', '' + let isDefaultAnExpression = false; // 'auto_increment', '' + + if (typeof column['EXTRA'] !== 'undefined') { + columnExtra = column['EXTRA']; + isAutoincrement = column['EXTRA'] === 'auto_increment'; // 'auto_increment', '' + isDefaultAnExpression = column['EXTRA'].includes('DEFAULT_GENERATED'); // 'auto_increment', '' + } + + // if (isPrimary) { + // if (typeof tableToPk[tableName] === "undefined") { + // tableToPk[tableName] = [columnName]; + // } else { + // tableToPk[tableName].push(columnName); + // } + // } + + if (schema !== inputSchema) { + schemas.push(schema); + } + + const table = result[tableName]; + + // let changedType = columnType.replace("bigint unsigned", "serial") + let changedType = columnType; + + if (columnType === 'bigint unsigned' && !isNullable && isAutoincrement) { + // check unique here + const uniqueIdx = idxRows.filter( + (it) => + it['COLUMN_NAME'] === columnName + && it['TABLE_NAME'] === tableName + && it['NON_UNIQUE'] === 0, + ); + if (uniqueIdx && uniqueIdx.length === 1) { + changedType = columnType.replace('bigint unsigned', 'serial'); + } + } + + if ( + columnType.startsWith('bigint(') + || columnType.startsWith('tinyint(') + || columnType.startsWith('date(') + || columnType.startsWith('int(') + || columnType.startsWith('mediumint(') + || columnType.startsWith('smallint(') + || columnType.startsWith('text(') + || columnType.startsWith('time(') + || columnType.startsWith('year(') + ) { + changedType = columnType.replace(/\(\s*[^)]*\)$/, ''); + } + + if (columnType.includes('decimal(10,0)')) { + changedType = columnType.replace('decimal(10,0)', 'decimal'); + } + + if (columnDefault?.endsWith('.')) { + columnDefault = columnDefault.slice(0, -1); + } + + let onUpdate: boolean | undefined = undefined; + if ( + columnType.startsWith('timestamp') + && typeof columnExtra !== 'undefined' + && columnExtra.includes('on update CURRENT_TIMESTAMP') + ) { + onUpdate = true; + } + + const newColumn: Column = { + default: columnDefault === null + ? undefined + : /^-?[\d.]+(?:e-?\d+)?$/.test(columnDefault) + && !['decimal', 'char', 'varchar'].some((type) => columnType.startsWith(type)) + ? Number(columnDefault) + : isDefaultAnExpression + ? clearDefaults(columnDefault, collation) + : columnDefault.startsWith('CURRENT_TIMESTAMP') + ? 'CURRENT_TIMESTAMP' + : `'${columnDefault}'`, + autoincrement: isAutoincrement, + name: columnName, + type: changedType, + primaryKey: false, + notNull: !isNullable, + onUpdate, + generated: geenratedExpression + ? { + as: geenratedExpression, + type: columnExtra === 'VIRTUAL GENERATED' ? 'virtual' : 'stored', + } + : undefined, + }; + + // Set default to internal object + if (isDefaultAnExpression) { + if (typeof internals!.tables![tableName] === 'undefined') { + internals!.tables![tableName] = { + columns: { + [columnName]: { + isDefaultAnExpression: true, + }, + }, + }; + } else { + if ( + typeof internals!.tables![tableName]!.columns[columnName] + === 'undefined' + ) { + internals!.tables![tableName]!.columns[columnName] = { + isDefaultAnExpression: true, + }; + } else { + internals!.tables![tableName]!.columns[ + columnName + ]!.isDefaultAnExpression = true; + } + } + } + + if (!table) { + result[tableName] = { + name: tableName, + columns: { + [columnName]: newColumn, + }, + compositePrimaryKeys: {}, + indexes: {}, + uniqueConstraints: {}, + }; + } else { + result[tableName]!.columns[columnName] = newColumn; + } + } + + const tablePks = await db.query( + `SELECT table_name, column_name, ordinal_position + FROM information_schema.table_constraints t + LEFT JOIN information_schema.key_column_usage k + USING(constraint_name,table_schema,table_name) + WHERE t.constraint_type='UNIQUE' + and table_name != '__drizzle_migrations' + AND t.table_schema = '${inputSchema}' + ORDER BY ordinal_position`, + ); + + const tableToPk: { [tname: string]: string[] } = {}; + + const tableToPkRows = tablePks as RowDataPacket[]; + for (const tableToPkRow of tableToPkRows) { + const tableName: string = tableToPkRow['table_name']; + const columnName: string = tableToPkRow['column_name']; + const position: string = tableToPkRow['ordinal_position']; + + if (typeof result[tableName] === 'undefined') { + continue; + } + + if (typeof tableToPk[tableName] === 'undefined') { + tableToPk[tableName] = [columnName]; + } else { + tableToPk[tableName].push(columnName); + } + } + + for (const [key, value] of Object.entries(tableToPk)) { + // if (value.length > 1) { + result[key].compositePrimaryKeys = { + [`${key}_${value.join('_')}`]: { + name: `${key}_${value.join('_')}`, + columns: value, + }, + }; + // } else if (value.length === 1) { + // result[key].columns[value[0]].primaryKey = true; + // } else { + // } + } + if (progressCallback) { + progressCallback('columns', columnsCount, 'done'); + progressCallback('tables', tablesCount.size, 'done'); + } + + for (const idxRow of idxRows) { + const tableSchema = idxRow['TABLE_SCHEMA']; + const tableName = idxRow['TABLE_NAME']; + const constraintName = idxRow['INDEX_NAME']; + const columnName: string = idxRow['COLUMN_NAME']; + const isUnique = idxRow['NON_UNIQUE'] === 0; + + const tableInResult = result[tableName]; + if (typeof tableInResult === 'undefined') continue; + + // if (tableInResult.columns[columnName].type === "serial") continue; + + indexesCount += 1; + if (progressCallback) { + progressCallback('indexes', indexesCount, 'fetching'); + } + + if (isUnique) { + if ( + typeof tableInResult.uniqueConstraints[constraintName] !== 'undefined' + ) { + tableInResult.uniqueConstraints[constraintName]!.columns.push( + columnName, + ); + } else { + tableInResult.uniqueConstraints[constraintName] = { + name: constraintName, + columns: [columnName], + }; + } + } + } + + /* const views = await db.query( + `select * from INFORMATION_SCHEMA.VIEWS WHERE table_schema = '${inputSchema}';`, + ); */ + + /* const resultViews: Record = {}; */ + + /* viewsCount = views.length; + if (progressCallback) { + progressCallback('views', viewsCount, 'fetching'); + } + for await (const view of views) { + const viewName = view['TABLE_NAME']; + const definition = view['VIEW_DEFINITION']; + + const withCheckOption = view['CHECK_OPTION'] === 'NONE' + ? undefined + : view['CHECK_OPTION'].toLowerCase(); + const sqlSecurity = view['SECURITY_TYPE'].toLowerCase(); + + const [createSqlStatement] = await db.query( + `SHOW CREATE VIEW \`${viewName}\`;`, + ); + const algorithmMatch = createSqlStatement['Create View'].match(/ALGORITHM=([^ ]+)/); + const algorithm = algorithmMatch + ? algorithmMatch[1].toLowerCase() + : undefined; + + const columns = result[viewName].columns; + delete result[viewName]; + + resultViews[viewName] = { + columns: columns, + isExisting: false, + name: viewName, + algorithm, + definition, + sqlSecurity, + withCheckOption, + }; + } */ + + if (progressCallback) { + progressCallback('indexes', indexesCount, 'done'); + // progressCallback("enums", 0, "fetching"); + progressCallback('enums', 0, 'done'); + } + + return { + version: '1', + dialect: 'singlestore', + tables: result, + /* views: resultViews, */ + _meta: { + tables: {}, + columns: {}, + }, + internal: internals, + }; +}; diff --git a/drizzle-kit/src/serializer/sqliteImports.ts b/drizzle-kit/src/serializer/sqliteImports.ts index 534427e47..0164604d1 100644 --- a/drizzle-kit/src/serializer/sqliteImports.ts +++ b/drizzle-kit/src/serializer/sqliteImports.ts @@ -1,21 +1,28 @@ import { is } from 'drizzle-orm'; -import { AnySQLiteTable, SQLiteTable } from 'drizzle-orm/sqlite-core'; +import { AnySQLiteTable, SQLiteTable, SQLiteView } from 'drizzle-orm/sqlite-core'; import { safeRegister } from '../cli/commands/utils'; export const prepareFromExports = (exports: Record) => { const tables: AnySQLiteTable[] = []; + const views: SQLiteView[] = []; + const i0values = Object.values(exports); i0values.forEach((t) => { if (is(t, SQLiteTable)) { tables.push(t); } + + if (is(t, SQLiteView)) { + views.push(t); + } }); - return { tables }; + return { tables, views }; }; export const prepareFromSqliteImports = async (imports: string[]) => { const tables: AnySQLiteTable[] = []; + const views: SQLiteView[] = []; const { unregister } = await safeRegister(); for (let i = 0; i < imports.length; i++) { @@ -25,9 +32,10 @@ export const prepareFromSqliteImports = async (imports: string[]) => { const prepared = prepareFromExports(i0); tables.push(...prepared.tables); + views.push(...prepared.views); } unregister(); - return { tables: Array.from(new Set(tables)) }; + return { tables: Array.from(new Set(tables)), views }; }; diff --git a/drizzle-kit/src/serializer/sqliteSchema.ts b/drizzle-kit/src/serializer/sqliteSchema.ts index a8114e3a8..8fd98d99d 100644 --- a/drizzle-kit/src/serializer/sqliteSchema.ts +++ b/drizzle-kit/src/serializer/sqliteSchema.ts @@ -1,5 +1,5 @@ import { any, boolean, enum as enumType, literal, object, record, string, TypeOf, union } from 'zod'; -import { customMapEntries, mapEntries, mapValues, originUUID } from '../global'; +import { customMapEntries, mapValues, originUUID } from '../global'; // ------- V3 -------- const index = object({ @@ -49,6 +49,11 @@ const uniqueConstraint = object({ columns: string().array(), }).strict(); +const checkConstraint = object({ + name: string(), + value: string(), +}).strict(); + const table = object({ name: string(), columns: record(string(), column), @@ -56,6 +61,14 @@ const table = object({ foreignKeys: record(string(), fk), compositePrimaryKeys: record(string(), compositePK), uniqueConstraints: record(string(), uniqueConstraint).default({}), + checkConstraints: record(string(), checkConstraint).default({}), +}).strict(); + +export const view = object({ + name: string(), + columns: record(string(), column), + definition: string().optional(), + isExisting: boolean(), }).strict(); // use main dialect @@ -77,6 +90,7 @@ export const schemaInternalV4 = object({ version: literal('4'), dialect: dialect, tables: record(string(), table), + views: record(string(), view).default({}), enums: object({}), }).strict(); @@ -108,6 +122,7 @@ export const schemaInternal = object({ version: latestVersion, dialect: dialect, tables: record(string(), table), + views: record(string(), view).default({}), enums: object({}), _meta: object({ tables: record(string(), string()), @@ -128,12 +143,14 @@ const tableSquashed = object({ foreignKeys: record(string(), string()), compositePrimaryKeys: record(string(), string()), uniqueConstraints: record(string(), string()).default({}), + checkConstraints: record(string(), string()).default({}), }).strict(); export const schemaSquashed = object({ version: latestVersion, dialect: dialect, tables: record(string(), tableSquashed), + views: record(string(), view), enums: any(), }).strict(); @@ -150,6 +167,8 @@ export type Index = TypeOf; export type ForeignKey = TypeOf; export type PrimaryKey = TypeOf; export type UniqueConstraint = TypeOf; +export type CheckConstraint = TypeOf; +export type View = TypeOf; export const SQLiteSquasher = { squashIdx: (idx: Index) => { @@ -233,6 +252,17 @@ export const SQLiteSquasher = { unsquashPK: (pk: string) => { return pk.split(','); }, + squashCheck: (check: CheckConstraint) => { + return `${check.name};${check.value}`; + }, + unsquashCheck: (input: string): CheckConstraint => { + const [ + name, + value, + ] = input.split(';'); + + return { name, value }; + }, }; export const squashSqliteScheme = ( @@ -268,6 +298,13 @@ export const squashSqliteScheme = ( }, ); + const squashedCheckConstraints = mapValues( + it[1].checkConstraints, + (check) => { + return SQLiteSquasher.squashCheck(check); + }, + ); + return [ it[0], { @@ -277,6 +314,7 @@ export const squashSqliteScheme = ( foreignKeys: squashedFKs, compositePrimaryKeys: squashedPKs, uniqueConstraints: squashedUniqueConstraints, + checkConstraints: squashedCheckConstraints, }, ]; }), @@ -286,6 +324,7 @@ export const squashSqliteScheme = ( version: '6', dialect: json.dialect, tables: mappedTables, + views: json.views, enums: json.enums, }; }; @@ -296,6 +335,7 @@ export const drySQLite = schema.parse({ id: originUUID, prevId: '', tables: {}, + views: {}, enums: {}, _meta: { tables: {}, diff --git a/drizzle-kit/src/serializer/sqliteSerializer.ts b/drizzle-kit/src/serializer/sqliteSerializer.ts index c673daafb..107a1b292 100644 --- a/drizzle-kit/src/serializer/sqliteSerializer.ts +++ b/drizzle-kit/src/serializer/sqliteSerializer.ts @@ -1,16 +1,20 @@ import chalk from 'chalk'; import { getTableName, is, SQL } from 'drizzle-orm'; import { - // AnySQLiteColumnBuilder, AnySQLiteTable, getTableConfig, + getViewConfig, SQLiteBaseInteger, + SQLiteColumn, SQLiteSyncDialect, + SQLiteView, uniqueKeyName, } from 'drizzle-orm/sqlite-core'; +import { CasingType } from 'src/cli/validations/common'; import { withStyle } from '../cli/validations/outputs'; import type { IntrospectStage, IntrospectStatus } from '../cli/views'; import type { + CheckConstraint, Column, ForeignKey, Index, @@ -19,16 +23,20 @@ import type { SQLiteSchemaInternal, Table, UniqueConstraint, + View, } from '../serializer/sqliteSchema'; -import type { SQLiteDB } from '../utils'; -import { sqlToStr } from '.'; - -const dialect = new SQLiteSyncDialect(); +import { escapeSingleQuotes, type SQLiteDB } from '../utils'; +import { getColumnCasing, sqlToStr } from './utils'; export const generateSqliteSnapshot = ( tables: AnySQLiteTable[], + views: SQLiteView[], + casing: CasingType | undefined, ): SQLiteSchemaInternal => { + const dialect = new SQLiteSyncDialect({ casing }); const result: Record = {}; + const resultViews: Record = {}; + const internal: SQLiteKitInternals = { indexes: {} }; for (const table of tables) { // const tableName = getTableName(table); @@ -37,23 +45,28 @@ export const generateSqliteSnapshot = ( const foreignKeysObject: Record = {}; const primaryKeysObject: Record = {}; const uniqueConstraintObject: Record = {}; + const checkConstraintObject: Record = {}; + + const checksInTable: Record = {}; const { name: tableName, columns, indexes, + checks, foreignKeys: tableForeignKeys, primaryKeys, uniqueConstraints, } = getTableConfig(table); columns.forEach((column) => { + const name = getColumnCasing(column, casing); const notNull: boolean = column.notNull; const primaryKey: boolean = column.primary; const generated = column.generated; const columnToSet: Column = { - name: column.name, + name, type: column.getSQLType(), primaryKey, notNull, @@ -74,17 +87,17 @@ export const generateSqliteSnapshot = ( if (column.default !== undefined) { if (is(column.default, SQL)) { - columnToSet.default = sqlToStr(column.default); + columnToSet.default = sqlToStr(column.default, casing); } else { columnToSet.default = typeof column.default === 'string' - ? `'${column.default}'` + ? `'${escapeSingleQuotes(column.default)}'` : typeof column.default === 'object' || Array.isArray(column.default) ? `'${JSON.stringify(column.default)}'` : column.default; } } - columnsObject[column.name] = columnToSet; + columnsObject[name] = columnToSet; if (column.isUnique) { const existingUnique = indexesObject[column.uniqueName!]; @@ -102,7 +115,7 @@ export const generateSqliteSnapshot = ( ) } on the ${ chalk.underline.blue( - column.name, + name, ) } column is confilcting with a unique constraint name already defined for ${ chalk.underline.blue( @@ -122,7 +135,6 @@ export const generateSqliteSnapshot = ( }); const foreignKeys: ForeignKey[] = tableForeignKeys.map((fk) => { - const name = fk.getName(); const tableFrom = tableName; const onDelete = fk.onDelete ?? 'no action'; const onUpdate = fk.onUpdate ?? 'no action'; @@ -132,8 +144,22 @@ export const generateSqliteSnapshot = ( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const tableTo = getTableName(referenceFT); - const columnsFrom = reference.columns.map((it) => it.name); - const columnsTo = reference.foreignColumns.map((it) => it.name); + + const originalColumnsFrom = reference.columns.map((it) => it.name); + const columnsFrom = reference.columns.map((it) => getColumnCasing(it, casing)); + const originalColumnsTo = reference.foreignColumns.map((it) => it.name); + const columnsTo = reference.foreignColumns.map((it) => getColumnCasing(it, casing)); + + let name = fk.getName(); + if (casing !== undefined) { + for (let i = 0; i < originalColumnsFrom.length; i++) { + name = name.replace(originalColumnsFrom[i], columnsFrom[i]); + } + for (let i = 0; i < originalColumnsTo.length; i++) { + name = name.replace(originalColumnsTo[i], columnsTo[i]); + } + } + return { name, tableFrom, @@ -175,7 +201,7 @@ export const generateSqliteSnapshot = ( } return sql; } else { - return it.name; + return getColumnCasing(it, casing); } }); @@ -195,7 +221,7 @@ export const generateSqliteSnapshot = ( }); uniqueConstraints?.map((unq) => { - const columnNames = unq.columns.map((c) => c.name); + const columnNames = unq.columns.map((c) => getColumnCasing(c, casing)); const name = unq.name ?? uniqueKeyName(table, columnNames); @@ -236,15 +262,57 @@ export const generateSqliteSnapshot = ( primaryKeys.forEach((it) => { if (it.columns.length > 1) { - primaryKeysObject[it.getName()] = { - columns: it.columns.map((it) => it.name).sort(), - name: it.getName(), + const originalColumnNames = it.columns.map((c) => c.name); + const columnNames = it.columns.map((c) => getColumnCasing(c, casing)); + + let name = it.getName(); + if (casing !== undefined) { + for (let i = 0; i < originalColumnNames.length; i++) { + name = name.replace(originalColumnNames[i], columnNames[i]); + } + } + + primaryKeysObject[name] = { + columns: columnNames, + name, }; } else { - columnsObject[it.columns[0].name].primaryKey = true; + columnsObject[getColumnCasing(it.columns[0], casing)].primaryKey = true; } }); + checks.forEach((check) => { + const checkName = check.name; + if (typeof checksInTable[tableName] !== 'undefined') { + if (checksInTable[tableName].includes(check.name)) { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated check constraint name in ${ + chalk.underline.blue( + tableName, + ) + }. Please rename your check constraint in the ${ + chalk.underline.blue( + tableName, + ) + } table`, + ) + }`, + ); + process.exit(1); + } + checksInTable[tableName].push(checkName); + } else { + checksInTable[tableName] = [check.name]; + } + + checkConstraintObject[checkName] = { + name: checkName, + value: dialect.sqlToQuery(check.value).sql, + }; + }); + result[tableName] = { name: tableName, columns: columnsObject, @@ -252,6 +320,79 @@ export const generateSqliteSnapshot = ( foreignKeys: foreignKeysObject, compositePrimaryKeys: primaryKeysObject, uniqueConstraints: uniqueConstraintObject, + checkConstraints: checkConstraintObject, + }; + } + + for (const view of views) { + const { name, isExisting, selectedFields, query, schema } = getViewConfig(view); + + const columnsObject: Record = {}; + + const existingView = resultViews[name]; + if (typeof existingView !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated view name across ${ + chalk.underline.blue( + schema ?? 'public', + ) + } schema. Please rename your view`, + ) + }`, + ); + process.exit(1); + } + + for (const key in selectedFields) { + if (is(selectedFields[key], SQLiteColumn)) { + const column = selectedFields[key]; + const notNull: boolean = column.notNull; + const primaryKey: boolean = column.primary; + const generated = column.generated; + + const columnToSet: Column = { + name: column.name, + type: column.getSQLType(), + primaryKey, + notNull, + autoincrement: is(column, SQLiteBaseInteger) + ? column.autoIncrement + : false, + generated: generated + ? { + as: is(generated.as, SQL) + ? `(${dialect.sqlToQuery(generated.as as SQL, 'indexes').sql})` + : typeof generated.as === 'function' + ? `(${dialect.sqlToQuery(generated.as() as SQL, 'indexes').sql})` + : `(${generated.as as any})`, + type: generated.mode ?? 'virtual', + } + : undefined, + }; + + if (column.default !== undefined) { + if (is(column.default, SQL)) { + columnToSet.default = sqlToStr(column.default, casing); + } else { + columnToSet.default = typeof column.default === 'string' + ? `'${column.default}'` + : typeof column.default === 'object' + || Array.isArray(column.default) + ? `'${JSON.stringify(column.default)}'` + : column.default; + } + } + columnsObject[column.name] = columnToSet; + } + } + + resultViews[name] = { + columns: columnsObject, + name, + isExisting, + definition: isExisting ? undefined : dialect.sqlToQuery(query!).sql, }; } @@ -259,6 +400,7 @@ export const generateSqliteSnapshot = ( version: '6', dialect: 'sqlite', tables: result, + views: resultViews, enums: {}, _meta: { tables: {}, @@ -363,6 +505,7 @@ export const fromDatabase = async ( ) => void, ): Promise => { const result: Record = {}; + const resultViews: Record = {}; const columns = await db.query<{ tableName: string; @@ -374,11 +517,12 @@ export const fromDatabase = async ( seq: number; hidden: number; sql: string; + type: 'view' | 'table'; }>( `SELECT - m.name as "tableName", p.name as "columnName", p.type as "columnType", p."notnull" as "notNull", p.dflt_value as "defaultValue", p.pk as pk, p.hidden as hidden, m.sql + m.name as "tableName", p.name as "columnName", p.type as "columnType", p."notnull" as "notNull", p.dflt_value as "defaultValue", p.pk as pk, p.hidden as hidden, m.sql, m.type as type FROM sqlite_master AS m JOIN pragma_table_xinfo(m.name) AS p - WHERE m.type = 'table' + WHERE (m.type = 'table' OR m.type = 'view') and m.tbl_name != 'sqlite_sequence' and m.tbl_name != 'sqlite_stat1' and m.tbl_name != '_litestream_seq' @@ -410,6 +554,8 @@ export const fromDatabase = async ( let tablesCount = new Set(); let indexesCount = 0; let foreignKeysCount = 0; + let checksCount = 0; + let viewsCount = 0; // append primaryKeys by table const tableToPk: { [tname: string]: string[] } = {}; @@ -422,7 +568,10 @@ export const fromDatabase = async ( for (const column of columns) { if (!tablesFilter(column.tableName)) continue; - columnsCount += 1; + // TODO + if (column.type !== 'view') { + columnsCount += 1; + } if (progressCallback) { progressCallback('columns', columnsCount, 'fetching'); } @@ -501,6 +650,7 @@ export const fromDatabase = async ( indexes: {}, foreignKeys: {}, uniqueConstraints: {}, + checkConstraints: {}, }; } else { result[tableName]!.columns[columnName] = newColumn; @@ -509,7 +659,6 @@ export const fromDatabase = async ( for (const [key, value] of Object.entries(tableToPk)) { if (value.length > 1) { - value.sort(); result[key].compositePrimaryKeys = { [`${key}_${value.join('_')}_pk`]: { columns: value, @@ -672,10 +821,107 @@ WHERE progressCallback('enums', 0, 'done'); } + const views = await db.query( + `SELECT name AS view_name, sql AS sql FROM sqlite_master WHERE type = 'view';`, + ); + + viewsCount = views.length; + + if (progressCallback) { + progressCallback('views', viewsCount, 'fetching'); + } + for (const view of views) { + const viewName = view['view_name']; + const sql = view['sql']; + + const regex = new RegExp(`\\bAS\\b\\s+(SELECT.+)$`, 'i'); + const match = sql.match(regex); + + if (!match) { + console.log('Could not process view'); + process.exit(1); + } + + const viewDefinition = match[1] as string; + + const columns = result[viewName].columns; + delete result[viewName]; + + resultViews[viewName] = { + columns: columns, + isExisting: false, + name: viewName, + definition: viewDefinition, + }; + } + if (progressCallback) { + progressCallback('views', viewsCount, 'done'); + } + + const namedCheckPattern = /CONSTRAINT\s*["']?(\w+)["']?\s*CHECK\s*\((.*?)\)/gi; + const unnamedCheckPattern = /CHECK\s*\((.*?)\)/gi; + let checkCounter = 0; + const checkConstraints: Record = {}; + const checks = await db.query<{ tableName: string; sql: string }>(`SELECT name as "tableName", sql as "sql" + FROM sqlite_master + WHERE type = 'table' AND name != 'sqlite_sequence';`); + for (const check of checks) { + if (!tablesFilter(check.tableName)) continue; + + const { tableName, sql } = check; + + // Find named CHECK constraints + let namedChecks = [...sql.matchAll(namedCheckPattern)]; + if (namedChecks.length > 0) { + namedChecks.forEach(([_, checkName, checkValue]) => { + checkConstraints[checkName] = { + name: checkName, + value: checkValue.trim(), + }; + }); + } else { + // If no named constraints, find unnamed CHECK constraints and assign names + let unnamedChecks = [...sql.matchAll(unnamedCheckPattern)]; + unnamedChecks.forEach(([_, checkValue]) => { + let checkName = `${tableName}_check_${++checkCounter}`; + checkConstraints[checkName] = { + name: checkName, + value: checkValue.trim(), + }; + }); + } + + checksCount += Object.values(checkConstraints).length; + if (progressCallback) { + progressCallback('checks', checksCount, 'fetching'); + } + + const table = result[tableName]; + + if (!table) { + result[tableName] = { + name: tableName, + columns: {}, + compositePrimaryKeys: {}, + indexes: {}, + foreignKeys: {}, + uniqueConstraints: {}, + checkConstraints: checkConstraints, + }; + } else { + result[tableName]!.checkConstraints = checkConstraints; + } + } + + if (progressCallback) { + progressCallback('checks', checksCount, 'done'); + } + return { version: '6', dialect: 'sqlite', tables: result, + views: resultViews, enums: {}, _meta: { tables: {}, diff --git a/drizzle-kit/src/serializer/studio.ts b/drizzle-kit/src/serializer/studio.ts index 4b7b12c1e..bbd811627 100644 --- a/drizzle-kit/src/serializer/studio.ts +++ b/drizzle-kit/src/serializer/studio.ts @@ -15,17 +15,25 @@ import { } from 'drizzle-orm'; import { AnyMySqlTable, getTableConfig as mysqlTableConfig, MySqlTable } from 'drizzle-orm/mysql-core'; import { AnyPgTable, getTableConfig as pgTableConfig, PgTable } from 'drizzle-orm/pg-core'; +import { + AnySingleStoreTable, + getTableConfig as singlestoreTableConfig, + SingleStoreTable, +} from 'drizzle-orm/singlestore-core'; import { AnySQLiteTable, getTableConfig as sqliteTableConfig, SQLiteTable } from 'drizzle-orm/sqlite-core'; import fs from 'fs'; import { Hono } from 'hono'; +import { compress } from 'hono/compress'; import { cors } from 'hono/cors'; import { createServer } from 'node:https'; +import { LibSQLCredentials } from 'src/cli/validations/libsql'; import { assertUnreachable } from 'src/global'; import superjson from 'superjson'; import { z } from 'zod'; import { safeRegister } from '../cli/commands/utils'; import type { MysqlCredentials } from '../cli/validations/mysql'; import type { PostgresCredentials } from '../cli/validations/postgres'; +import type { SingleStoreCredentials } from '../cli/validations/singlestore'; import type { SqliteCredentials } from '../cli/validations/sqlite'; import { prepareFilenames } from '.'; @@ -43,8 +51,8 @@ type SchemaFile = { export type Setup = { dbHash: string; - dialect: 'postgresql' | 'mysql' | 'sqlite'; - driver?: 'aws-data-api' | 'd1-http' | 'turso'; + dialect: 'postgresql' | 'mysql' | 'sqlite' | 'singlestore'; + driver?: 'aws-data-api' | 'd1-http' | 'turso' | 'pglite'; proxy: (params: ProxyParams) => Promise; customDefaults: CustomDefault[]; schema: Record>>; @@ -170,6 +178,43 @@ export const prepareSQLiteSchema = async (path: string | string[]) => { return { schema: sqliteSchema, relations, files }; }; +export const prepareSingleStoreSchema = async (path: string | string[]) => { + const imports = prepareFilenames(path); + const singlestoreSchema: Record> = { + public: {}, + }; + const relations: Record = {}; + + // files content as string + const files = imports.map((it, index) => ({ + // get the file name from the path + name: it.split('/').pop() || `schema${index}.ts`, + content: fs.readFileSync(it, 'utf-8'), + })); + + const { unregister } = await safeRegister(); + for (let i = 0; i < imports.length; i++) { + const it = imports[i]; + + const i0: Record = require(`${it}`); + const i0values = Object.entries(i0); + + i0values.forEach(([k, t]) => { + if (is(t, SingleStoreTable)) { + const schema = singlestoreTableConfig(t).schema || 'public'; + singlestoreSchema[schema][k] = t; + } + + if (is(t, Relations)) { + relations[k] = t; + } + }); + } + unregister(); + + return { schema: singlestoreSchema, relations, files }; +}; + const getCustomDefaults = >( schema: Record>, ): CustomDefault[] => { @@ -185,8 +230,10 @@ const getCustomDefaults = >( tableConfig = pgTableConfig(table); } else if (is(table, MySqlTable)) { tableConfig = mysqlTableConfig(table); - } else { + } else if (is(table, SQLiteTable)) { tableConfig = sqliteTableConfig(table); + } else { + tableConfig = singlestoreTableConfig(table); } tableConfig.columns.map((column) => { @@ -218,11 +265,13 @@ export const drizzleForPostgres = async ( let dbUrl: string; if ('driver' in credentials) { - // aws-data-api - if (credentials.driver === 'aws-data-api') { + const { driver } = credentials; + if (driver === 'aws-data-api') { dbUrl = `aws-data-api://${credentials.database}/${credentials.secretArn}/${credentials.resourceArn}`; + } else if (driver === 'pglite') { + dbUrl = credentials.url; } else { - assertUnreachable(credentials.driver); + assertUnreachable(driver); } } else if ('url' in credentials) { dbUrl = credentials.url; @@ -295,8 +344,6 @@ export const drizzleForSQLite = async ( const { driver } = credentials; if (driver === 'd1-http') { dbUrl = `d1-http://${credentials.accountId}/${credentials.databaseId}/${credentials.token}`; - } else if (driver === 'turso') { - dbUrl = `turso://${credentials.url}/${credentials.authToken}`; } else { assertUnreachable(driver); } @@ -317,6 +364,65 @@ export const drizzleForSQLite = async ( schemaFiles, }; }; +export const drizzleForLibSQL = async ( + credentials: LibSQLCredentials, + sqliteSchema: Record>, + relations: Record, + schemaFiles?: SchemaFile[], +): Promise => { + const { connectToLibSQL } = await import('../cli/connections'); + + const sqliteDB = await connectToLibSQL(credentials); + const customDefaults = getCustomDefaults(sqliteSchema); + + let dbUrl: string = `turso://${credentials.url}/${credentials.authToken}`; + + const dbHash = createHash('sha256').update(dbUrl).digest('hex'); + + return { + dbHash, + dialect: 'sqlite', + driver: undefined, + proxy: sqliteDB.proxy, + customDefaults, + schema: sqliteSchema, + relations, + schemaFiles, + }; +}; + +export const drizzleForSingleStore = async ( + credentials: SingleStoreCredentials, + singlestoreSchema: Record>, + relations: Record, + schemaFiles?: SchemaFile[], +): Promise => { + const { connectToSingleStore } = await import('../cli/connections'); + const { proxy } = await connectToSingleStore(credentials); + + const customDefaults = getCustomDefaults(singlestoreSchema); + + let dbUrl: string; + + if ('url' in credentials) { + dbUrl = credentials.url; + } else { + dbUrl = + `singlestore://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}/${credentials.database}`; + } + + const dbHash = createHash('sha256').update(dbUrl).digest('hex'); + + return { + dbHash, + dialect: 'singlestore', + proxy, + customDefaults, + schema: singlestoreSchema, + relations, + schemaFiles, + }; +}; export const extractRelations = (tablesConfig: { tables: TablesRelationalConfig; @@ -343,6 +449,8 @@ export const extractRelations = (tablesConfig: { refSchema = mysqlTableConfig(refTable).schema; } else if (is(refTable, SQLiteTable)) { refSchema = undefined; + } else if (is(refTable, SingleStoreTable)) { + refSchema = singlestoreTableConfig(refTable).schema; } else { throw new Error('unsupported dialect'); } @@ -465,12 +573,14 @@ export const prepareServer = async ( ): Promise => { app = app !== undefined ? app : new Hono(); - app.use(cors()); + app.use(compress()); app.use(async (ctx, next) => { await next(); // * https://wicg.github.io/private-network-access/#headers + // * https://github.com/drizzle-team/drizzle-orm/issues/1857#issuecomment-2395724232 ctx.header('Access-Control-Allow-Private-Network', 'true'); }); + app.use(cors()); app.onError((err, ctx) => { console.error(err); return ctx.json({ diff --git a/drizzle-kit/src/serializer/utils.ts b/drizzle-kit/src/serializer/utils.ts new file mode 100644 index 000000000..18d5bb9ad --- /dev/null +++ b/drizzle-kit/src/serializer/utils.ts @@ -0,0 +1,45 @@ +import { SQL } from 'drizzle-orm'; +import { CasingCache, toCamelCase, toSnakeCase } from 'drizzle-orm/casing'; +import { CasingType } from '../cli/validations/common'; + +export function getColumnCasing( + column: { keyAsName: boolean; name: string | undefined }, + casing: CasingType | undefined, +) { + if (!column.name) return ''; + return !column.keyAsName || casing === undefined + ? column.name + : casing === 'camelCase' + ? toCamelCase(column.name) + : toSnakeCase(column.name); +} + +export const sqlToStr = (sql: SQL, casing: CasingType | undefined) => { + return sql.toQuery({ + escapeName: () => { + throw new Error("we don't support params for `sql` default values"); + }, + escapeParam: () => { + throw new Error("we don't support params for `sql` default values"); + }, + escapeString: () => { + throw new Error("we don't support params for `sql` default values"); + }, + casing: new CasingCache(casing), + }).sql; +}; + +export const sqlToStrGenerated = (sql: SQL, casing: CasingType | undefined) => { + return sql.toQuery({ + escapeName: () => { + throw new Error("we don't support params for `sql` default values"); + }, + escapeParam: () => { + throw new Error("we don't support params for `sql` default values"); + }, + escapeString: () => { + throw new Error("we don't support params for `sql` default values"); + }, + casing: new CasingCache(casing), + }).sql; +}; diff --git a/drizzle-kit/src/snapshotsDiffer.ts b/drizzle-kit/src/snapshotsDiffer.ts index 9ad2d9e32..2db4ad02c 100644 --- a/drizzle-kit/src/snapshotsDiffer.ts +++ b/drizzle-kit/src/snapshotsDiffer.ts @@ -5,7 +5,6 @@ import { enum as enumType, literal, never, - number, object, record, string, @@ -13,7 +12,7 @@ import { union, ZodTypeAny, } from 'zod'; -import { applyJsonDiff, diffColumns, diffSchemasOrTables } from './jsonDiffer'; +import { applyJsonDiff, diffColumns, diffIndPolicies, diffPolicies, diffSchemasOrTables } from './jsonDiffer'; import { fromJson } from './sqlgenerator'; import { @@ -22,18 +21,39 @@ import { _prepareSqliteAddColumns, JsonAddColumnStatement, JsonAlterCompositePK, + JsonAlterIndPolicyStatement, + JsonAlterMySqlViewStatement, + JsonAlterPolicyStatement, JsonAlterTableSetSchema, JsonAlterUniqueConstraint, + JsonAlterViewStatement, + JsonCreateCheckConstraint, JsonCreateCompositePK, + JsonCreateIndPolicyStatement, + JsonCreateMySqlViewStatement, + JsonCreatePgViewStatement, + JsonCreatePolicyStatement, JsonCreateReferenceStatement, + JsonCreateSqliteViewStatement, JsonCreateUniqueConstraint, + JsonDeleteCheckConstraint, JsonDeleteCompositePK, JsonDeleteUniqueConstraint, + JsonDisableRLSStatement, JsonDropColumnStatement, + JsonDropIndPolicyStatement, + JsonDropPolicyStatement, + JsonDropViewStatement, + JsonEnableRLSStatement, + JsonIndRenamePolicyStatement, JsonReferenceStatement, JsonRenameColumnStatement, + JsonRenamePolicyStatement, + JsonRenameRoleStatement, + JsonRenameViewStatement, JsonSqliteAddColumnStatement, JsonStatement, + prepareAddCheckConstraint, prepareAddCompositePrimaryKeyMySql, prepareAddCompositePrimaryKeyPg, prepareAddCompositePrimaryKeySqlite, @@ -43,43 +63,85 @@ import { prepareAlterCompositePrimaryKeyMySql, prepareAlterCompositePrimaryKeyPg, prepareAlterCompositePrimaryKeySqlite, + prepareAlterIndPolicyJson, + prepareAlterPolicyJson, prepareAlterReferencesJson, + prepareAlterRoleJson, prepareAlterSequenceJson, prepareCreateEnumJson, prepareCreateIndexesJson, + prepareCreateIndPolicyJsons, + prepareCreatePolicyJsons, prepareCreateReferencesJson, + prepareCreateRoleJson, prepareCreateSchemasJson, prepareCreateSequenceJson, + prepareDeleteCheckConstraint, prepareDeleteCompositePrimaryKeyMySql, prepareDeleteCompositePrimaryKeyPg, prepareDeleteCompositePrimaryKeySqlite, prepareDeleteSchemasJson as prepareDropSchemasJson, prepareDeleteUniqueConstraintPg as prepareDeleteUniqueConstraint, prepareDropEnumJson, + prepareDropEnumValues, prepareDropIndexesJson, + prepareDropIndPolicyJsons, + prepareDropPolicyJsons, prepareDropReferencesJson, + prepareDropRoleJson, prepareDropSequenceJson, prepareDropTableJson, + prepareDropViewJson, + prepareLibSQLCreateReferencesJson, + prepareLibSQLDropReferencesJson, prepareMoveEnumJson, prepareMoveSequenceJson, + prepareMySqlAlterView, prepareMySqlCreateTableJson, + prepareMySqlCreateViewJson, preparePgAlterColumns, + preparePgAlterViewAddWithOptionJson, + preparePgAlterViewAlterSchemaJson, + preparePgAlterViewAlterTablespaceJson, + preparePgAlterViewAlterUsingJson, + preparePgAlterViewDropWithOptionJson, preparePgCreateIndexesJson, preparePgCreateTableJson, + preparePgCreateViewJson, prepareRenameColumns, prepareRenameEnumJson, + prepareRenameIndPolicyJsons, + prepareRenamePolicyJsons, + prepareRenameRoleJson, prepareRenameSchemasJson, prepareRenameSequenceJson, prepareRenameTableJson, + prepareRenameViewJson, + prepareSingleStoreCreateTableJson, prepareSqliteAlterColumns, prepareSQLiteCreateTable, + prepareSqliteCreateViewJson, } from './jsonStatements'; import { Named, NamedWithSchema } from './cli/commands/migrate'; import { mapEntries, mapKeys, mapValues } from './global'; -import { MySqlSchema, MySqlSchemaSquashed, MySqlSquasher } from './serializer/mysqlSchema'; -import { PgSchema, PgSchemaSquashed, PgSquasher, sequenceSchema, sequenceSquashed } from './serializer/pgSchema'; -import { SQLiteSchema, SQLiteSchemaSquashed, SQLiteSquasher } from './serializer/sqliteSchema'; +import { MySqlSchema, MySqlSchemaSquashed, MySqlSquasher, ViewSquashed } from './serializer/mysqlSchema'; +import { + mergedViewWithOption, + PgSchema, + PgSchemaSquashed, + PgSquasher, + Policy, + policy, + policySquashed, + Role, + roleSchema, + sequenceSquashed, + View, +} from './serializer/pgSchema'; +import { SingleStoreSchema, SingleStoreSchemaSquashed, SingleStoreSquasher } from './serializer/singlestoreSchema'; +import { SQLiteSchema, SQLiteSchemaSquashed, SQLiteSquasher, View as SqliteView } from './serializer/sqliteSchema'; +import { libSQLCombineStatements, sqliteCombineStatements } from './statementCombiner'; import { copy, prepareMigrationMeta } from './utils'; const makeChanged = (schema: T) => { @@ -204,6 +266,9 @@ const tableScheme = object({ foreignKeys: record(string(), string()), compositePrimaryKeys: record(string(), string()).default({}), uniqueConstraints: record(string(), string()).default({}), + policies: record(string(), string()).default({}), + checkConstraints: record(string(), string()).default({}), + isRLSEnabled: boolean().default(false), }).strict(); export const alteredTableScheme = object({ @@ -246,22 +311,100 @@ export const alteredTableScheme = object({ __old: string(), }), ), + addedPolicies: record(string(), string()), + deletedPolicies: record(string(), string()), + alteredPolicies: record( + string(), + object({ + __new: string(), + __old: string(), + }), + ), + addedCheckConstraints: record( + string(), + string(), + ), + deletedCheckConstraints: record( + string(), + string(), + ), + alteredCheckConstraints: record( + string(), + object({ + __new: string(), + __old: string(), + }), + ), }).strict(); +const alteredViewCommon = object({ + name: string(), + alteredDefinition: object({ + __old: string(), + __new: string(), + }).strict().optional(), + alteredExisting: object({ + __old: boolean(), + __new: boolean(), + }).strict().optional(), +}); + +export const alteredPgViewSchema = alteredViewCommon.merge( + object({ + schema: string(), + deletedWithOption: mergedViewWithOption.optional(), + addedWithOption: mergedViewWithOption.optional(), + addedWith: mergedViewWithOption.optional(), + deletedWith: mergedViewWithOption.optional(), + alteredWith: mergedViewWithOption.optional(), + alteredSchema: object({ + __old: string(), + __new: string(), + }).strict().optional(), + alteredTablespace: object({ + __old: string(), + __new: string(), + }).strict().optional(), + alteredUsing: object({ + __old: string(), + __new: string(), + }).strict().optional(), + }).strict(), +); + +const alteredMySqlViewSchema = alteredViewCommon.merge( + object({ + alteredMeta: object({ + __old: string(), + __new: string(), + }).strict().optional(), + }).strict(), +); + export const diffResultScheme = object({ alteredTablesWithColumns: alteredTableScheme.array(), alteredEnums: changedEnumSchema.array(), alteredSequences: sequenceSquashed.array(), + alteredRoles: roleSchema.array(), + alteredPolicies: policySquashed.array(), + alteredViews: alteredPgViewSchema.array(), }).strict(); export const diffResultSchemeMysql = object({ alteredTablesWithColumns: alteredTableScheme.array(), alteredEnums: never().array(), + alteredViews: alteredMySqlViewSchema.array(), +}); + +export const diffResultSchemeSingleStore = object({ + alteredTablesWithColumns: alteredTableScheme.array(), + alteredEnums: never().array(), }); export const diffResultSchemeSQLite = object({ alteredTablesWithColumns: alteredTableScheme.array(), alteredEnums: never().array(), + alteredViews: alteredViewCommon.array(), }); export type Column = TypeOf; @@ -272,6 +415,7 @@ export type Table = TypeOf; export type AlteredTable = TypeOf; export type DiffResult = TypeOf; export type DiffResultMysql = TypeOf; +export type DiffResultSingleStore = TypeOf; export type DiffResultSQLite = TypeOf; export interface ResolverInput { @@ -299,6 +443,43 @@ export interface ColumnsResolverInput { deleted: T[]; } +export interface TablePolicyResolverInput { + tableName: string; + schema: string; + created: T[]; + deleted: T[]; +} + +export interface TablePolicyResolverOutput { + tableName: string; + schema: string; + created: T[]; + renamed: { from: T; to: T }[]; + deleted: T[]; +} + +export interface PolicyResolverInput { + created: T[]; + deleted: T[]; +} + +export interface PolicyResolverOutput { + created: T[]; + renamed: { from: T; to: T }[]; + deleted: T[]; +} + +export interface RolesResolverInput { + created: T[]; + deleted: T[]; +} + +export interface RolesResolverOutput { + created: T[]; + renamed: { from: T; to: T }[]; + deleted: T[]; +} + export interface ColumnsResolverOutput { tableName: string; schema: string; @@ -369,6 +550,12 @@ const columnChangeFor = ( return column; }; +// resolve roles same as enums +// create new json statements +// sql generators + +// tests everything! + export const applyPgSnapshotsDiff = async ( json1: PgSchemaSquashed, json2: PgSchemaSquashed, @@ -381,12 +568,24 @@ export const applyPgSnapshotsDiff = async ( sequencesResolver: ( input: ResolverInput, ) => Promise>, + policyResolver: ( + input: TablePolicyResolverInput, + ) => Promise>, + indPolicyResolver: ( + input: PolicyResolverInput, + ) => Promise>, + roleResolver: ( + input: RolesResolverInput, + ) => Promise>, tablesResolver: ( input: ResolverInput, ) => Promise>, columnsResolver: ( input: ColumnsResolverInput, ) => Promise>, + viewsResolver: ( + input: ResolverInput, + ) => Promise>, prevFull: PgSchema, curFull: PgSchema, action?: 'push' | undefined, @@ -605,6 +804,60 @@ export const applyPgSnapshotsDiff = async ( }, ); + const rolesDiff = diffSchemasOrTables( + schemasPatchedSnap1.roles, + json2.roles, + ); + + const { + created: createdRoles, + deleted: deletedRoles, + renamed: renamedRoles, + } = await roleResolver({ + created: rolesDiff.added, + deleted: rolesDiff.deleted, + }); + + schemasPatchedSnap1.roles = mapEntries( + schemasPatchedSnap1.roles, + (_, it) => { + const { name } = nameChangeFor(it, renamedRoles); + it.name = name; + return [name, it]; + }, + ); + + const rolesChangeMap = renamedRoles.reduce( + (acc, it) => { + acc[it.from.name] = { + nameFrom: it.from.name, + nameTo: it.to.name, + }; + return acc; + }, + {} as Record< + string, + { + nameFrom: string; + nameTo: string; + } + >, + ); + + schemasPatchedSnap1.roles = mapEntries( + schemasPatchedSnap1.roles, + (roleKey, roleValue) => { + const key = roleKey; + const change = rolesChangeMap[key]; + + if (change) { + roleValue.name = change.nameTo; + } + + return [roleKey, roleValue]; + }, + ); + const tablesDiff = diffSchemasOrTables( schemasPatchedSnap1.tables as Record, json2.tables, @@ -717,11 +970,212 @@ export const applyPgSnapshotsDiff = async ( }, ); - const diffResult = applyJsonDiff(columnsPatchedSnap1, json2); + //// Policies + + const policyRes = diffPolicies(tablesPatchedSnap1.tables, json2.tables); + + const policyRenames = [] as { + table: string; + schema: string; + renames: { from: Policy; to: Policy }[]; + }[]; + + const policyCreates = [] as { + table: string; + schema: string; + columns: Policy[]; + }[]; + + const policyDeletes = [] as { + table: string; + schema: string; + columns: Policy[]; + }[]; + + for (let entry of Object.values(policyRes)) { + const { renamed, created, deleted } = await policyResolver({ + tableName: entry.name, + schema: entry.schema, + deleted: entry.policies.deleted.map( + action === 'push' ? PgSquasher.unsquashPolicyPush : PgSquasher.unsquashPolicy, + ), + created: entry.policies.added.map(action === 'push' ? PgSquasher.unsquashPolicyPush : PgSquasher.unsquashPolicy), + }); + + if (created.length > 0) { + policyCreates.push({ + table: entry.name, + schema: entry.schema, + columns: created, + }); + } + + if (deleted.length > 0) { + policyDeletes.push({ + table: entry.name, + schema: entry.schema, + columns: deleted, + }); + } + + if (renamed.length > 0) { + policyRenames.push({ + table: entry.name, + schema: entry.schema, + renames: renamed, + }); + } + } + + const policyRenamesDict = columnRenames.reduce( + (acc, it) => { + acc[`${it.schema || 'public'}.${it.table}`] = it.renames; + return acc; + }, + {} as Record< + string, + { + from: Named; + to: Named; + }[] + >, + ); + + const policyPatchedSnap1 = copy(tablesPatchedSnap1); + policyPatchedSnap1.tables = mapEntries( + policyPatchedSnap1.tables, + (tableKey, tableValue) => { + const patchedPolicies = mapKeys( + tableValue.policies, + (policyKey, policy) => { + const rens = policyRenamesDict[ + `${tableValue.schema || 'public'}.${tableValue.name}` + ] || []; + + const newName = columnChangeFor(policyKey, rens); + const unsquashedPolicy = action === 'push' + ? PgSquasher.unsquashPolicyPush(policy) + : PgSquasher.unsquashPolicy(policy); + unsquashedPolicy.name = newName; + policy = PgSquasher.squashPolicy(unsquashedPolicy); + return newName; + }, + ); + + tableValue.policies = patchedPolicies; + return [tableKey, tableValue]; + }, + ); + + //// Individual policies + + const indPolicyRes = diffIndPolicies(policyPatchedSnap1.policies, json2.policies); + + const indPolicyCreates = [] as { + policies: Policy[]; + }[]; + + const indPolicyDeletes = [] as { + policies: Policy[]; + }[]; + + const { renamed: indPolicyRenames, created, deleted } = await indPolicyResolver({ + deleted: indPolicyRes.deleted.map((t) => + action === 'push' ? PgSquasher.unsquashPolicyPush(t.values) : PgSquasher.unsquashPolicy(t.values) + ), + created: indPolicyRes.added.map((t) => + action === 'push' ? PgSquasher.unsquashPolicyPush(t.values) : PgSquasher.unsquashPolicy(t.values) + ), + }); + + if (created.length > 0) { + indPolicyCreates.push({ + policies: created, + }); + } + + if (deleted.length > 0) { + indPolicyDeletes.push({ + policies: deleted, + }); + } + + const indPolicyRenamesDict = indPolicyRenames.reduce( + (acc, it) => { + acc[it.from.name] = { + nameFrom: it.from.name, + nameTo: it.to.name, + }; + return acc; + }, + {} as Record< + string, + { + nameFrom: string; + nameTo: string; + } + >, + ); + + const indPolicyPatchedSnap1 = copy(policyPatchedSnap1); + indPolicyPatchedSnap1.policies = mapEntries( + indPolicyPatchedSnap1.policies, + (policyKey, policyValue) => { + const key = policyKey; + const change = indPolicyRenamesDict[key]; + + if (change) { + policyValue.name = change.nameTo; + } + + return [policyKey, policyValue]; + }, + ); + + //// + const viewsDiff = diffSchemasOrTables(indPolicyPatchedSnap1.views, json2.views); + + const { + created: createdViews, + deleted: deletedViews, + renamed: renamedViews, + moved: movedViews, + } = await viewsResolver({ + created: viewsDiff.added, + deleted: viewsDiff.deleted, + }); + + const renamesViewDic: Record = {}; + renamedViews.forEach((it) => { + renamesViewDic[`${it.from.schema}.${it.from.name}`] = { to: it.to.name, from: it.from.name }; + }); + + const movedViewDic: Record = {}; + movedViews.forEach((it) => { + movedViewDic[`${it.schemaFrom}.${it.name}`] = { to: it.schemaTo, from: it.schemaFrom }; + }); + + const viewsPatchedSnap1 = copy(policyPatchedSnap1); + viewsPatchedSnap1.views = mapEntries( + viewsPatchedSnap1.views, + (viewKey, viewValue) => { + const rename = renamesViewDic[`${viewValue.schema}.${viewValue.name}`]; + const moved = movedViewDic[`${viewValue.schema}.${viewValue.name}`]; + + if (rename) { + viewValue.name = rename.to; + viewKey = `${viewValue.schema}.${viewValue.name}`; + } + + if (moved) viewKey = `${moved.to}.${viewValue.name}`; + + return [viewKey, viewValue]; + }, + ); + + const diffResult = applyJsonDiff(viewsPatchedSnap1, json2); - // no diffs const typedResult: DiffResult = diffResultScheme.parse(diffResult); - // const typedResult: DiffResult = {}; const jsonStatements: JsonStatement[] = []; @@ -779,35 +1233,40 @@ export const applyPgSnapshotsDiff = async ( const jsonSetTableSchemas: JsonAlterTableSetSchema[] = []; - for (let it of movedTables) { - jsonSetTableSchemas.push({ - type: 'alter_table_set_schema', - tableName: it.name, - schemaFrom: it.schemaFrom || 'public', - schemaTo: it.schemaTo || 'public', - }); + if (movedTables) { + for (let it of movedTables) { + jsonSetTableSchemas.push({ + type: 'alter_table_set_schema', + tableName: it.name, + schemaFrom: it.schemaFrom || 'public', + schemaTo: it.schemaTo || 'public', + }); + } } + const jsonDeletedCheckConstraints: JsonDeleteCheckConstraint[] = []; + const jsonCreatedCheckConstraints: JsonCreateCheckConstraint[] = []; + for (let it of alteredTables) { // This part is needed to make sure that same columns in a table are not triggered for change // there is a case where orm and kit are responsible for pk name generation and one of them is not sorting name // We double-check that pk with same set of columns are both in added and deleted diffs - let addedColumns: string[] = []; + let addedColumns: { name: string; columns: string[] } | undefined; for (const addedPkName of Object.keys(it.addedCompositePKs)) { const addedPkColumns = it.addedCompositePKs[addedPkName]; - addedColumns = SQLiteSquasher.unsquashPK(addedPkColumns); + addedColumns = PgSquasher.unsquashPK(addedPkColumns); } - let deletedColumns: string[] = []; + let deletedColumns: { name: string; columns: string[] } | undefined; for (const deletedPkName of Object.keys(it.deletedCompositePKs)) { const deletedPkColumns = it.deletedCompositePKs[deletedPkName]; - deletedColumns = SQLiteSquasher.unsquashPK(deletedPkColumns); + deletedColumns = PgSquasher.unsquashPK(deletedPkColumns); } // Don't need to sort, but need to add tests for it // addedColumns.sort(); // deletedColumns.sort(); - const doPerformDeleteAndCreate = JSON.stringify(addedColumns) !== JSON.stringify(deletedColumns); + const doPerformDeleteAndCreate = JSON.stringify(addedColumns ?? {}) !== JSON.stringify(deletedColumns ?? {}); let addedCompositePKs: JsonCreateCompositePK[] = []; let deletedCompositePKs: JsonDeleteCompositePK[] = []; @@ -838,6 +1297,8 @@ export const applyPgSnapshotsDiff = async ( let addedUniqueConstraints: JsonCreateUniqueConstraint[] = []; let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + let createCheckConstraints: JsonCreateCheckConstraint[] = []; + let deleteCheckConstraints: JsonDeleteCheckConstraint[] = []; addedUniqueConstraints = prepareAddUniqueConstraint( it.name, @@ -864,6 +1325,28 @@ export const applyPgSnapshotsDiff = async ( ); } + createCheckConstraints = prepareAddCheckConstraint(it.name, it.schema, it.addedCheckConstraints); + deleteCheckConstraints = prepareDeleteCheckConstraint( + it.name, + it.schema, + it.deletedCheckConstraints, + ); + + if (it.alteredCheckConstraints && action !== 'push') { + const added: Record = {}; + const deleted: Record = {}; + + for (const k of Object.keys(it.alteredCheckConstraints)) { + added[k] = it.alteredCheckConstraints[k].__new; + deleted[k] = it.alteredCheckConstraints[k].__old; + } + createCheckConstraints.push(...prepareAddCheckConstraint(it.name, it.schema, added)); + deleteCheckConstraints.push(...prepareDeleteCheckConstraint(it.name, it.schema, deleted)); + } + + jsonCreatedCheckConstraints.push(...createCheckConstraints); + jsonDeletedCheckConstraints.push(...deleteCheckConstraints); + jsonAddedCompositePKs.push(...addedCompositePKs); jsonDeletedCompositePKs.push(...deletedCompositePKs); jsonAlteredCompositePKs.push(...alteredCompositePKs); @@ -916,20 +1399,236 @@ export const applyPgSnapshotsDiff = async ( }) .flat(); - alteredTables.forEach((it) => { - const droppedIndexes = Object.keys(it.alteredIndexes).reduce( - (current, item: string) => { - current[item] = it.alteredIndexes[item].__old; - return current; - }, - {} as Record, + const jsonCreatePoliciesStatements: JsonCreatePolicyStatement[] = []; + const jsonDropPoliciesStatements: JsonDropPolicyStatement[] = []; + const jsonAlterPoliciesStatements: JsonAlterPolicyStatement[] = []; + const jsonRenamePoliciesStatements: JsonRenamePolicyStatement[] = []; + + const jsonRenameIndPoliciesStatements: JsonIndRenamePolicyStatement[] = []; + const jsonCreateIndPoliciesStatements: JsonCreateIndPolicyStatement[] = []; + const jsonDropIndPoliciesStatements: JsonDropIndPolicyStatement[] = []; + const jsonAlterIndPoliciesStatements: JsonAlterIndPolicyStatement[] = []; + + const jsonEnableRLSStatements: JsonEnableRLSStatement[] = []; + const jsonDisableRLSStatements: JsonDisableRLSStatement[] = []; + + for (let it of indPolicyRenames) { + jsonRenameIndPoliciesStatements.push( + ...prepareRenameIndPolicyJsons([it]), ); - const createdIndexes = Object.keys(it.alteredIndexes).reduce( - (current, item: string) => { - current[item] = it.alteredIndexes[item].__new; - return current; - }, - {} as Record, + } + + for (const it of indPolicyCreates) { + jsonCreateIndPoliciesStatements.push( + ...prepareCreateIndPolicyJsons( + it.policies, + ), + ); + } + + for (const it of indPolicyDeletes) { + jsonDropIndPoliciesStatements.push( + ...prepareDropIndPolicyJsons( + it.policies, + ), + ); + } + + typedResult.alteredPolicies.forEach(({ values }) => { + // return prepareAlterIndPolicyJson(json1.policies[it.name], json2.policies[it.name]); + + const policy = action === 'push' ? PgSquasher.unsquashPolicyPush(values) : PgSquasher.unsquashPolicy(values); + + const newPolicy = action === 'push' + ? PgSquasher.unsquashPolicyPush(json2.policies[policy.name].values) + : PgSquasher.unsquashPolicy(json2.policies[policy.name].values); + const oldPolicy = action === 'push' + ? PgSquasher.unsquashPolicyPush(json2.policies[policy.name].values) + : PgSquasher.unsquashPolicy(json1.policies[policy.name].values); + + if (newPolicy.as !== oldPolicy.as) { + jsonDropIndPoliciesStatements.push( + ...prepareDropIndPolicyJsons( + [oldPolicy], + ), + ); + + jsonCreateIndPoliciesStatements.push( + ...prepareCreateIndPolicyJsons( + [newPolicy], + ), + ); + return; + } + + if (newPolicy.for !== oldPolicy.for) { + jsonDropIndPoliciesStatements.push( + ...prepareDropIndPolicyJsons( + [oldPolicy], + ), + ); + + jsonCreateIndPoliciesStatements.push( + ...prepareCreateIndPolicyJsons( + [newPolicy], + ), + ); + return; + } + + // alter + jsonAlterIndPoliciesStatements.push( + prepareAlterIndPolicyJson( + oldPolicy, + newPolicy, + ), + ); + }); + + for (let it of policyRenames) { + jsonRenamePoliciesStatements.push( + ...prepareRenamePolicyJsons(it.table, it.schema, it.renames), + ); + } + + for (const it of policyCreates) { + jsonCreatePoliciesStatements.push( + ...prepareCreatePolicyJsons( + it.table, + it.schema, + it.columns, + ), + ); + } + + for (const it of policyDeletes) { + jsonDropPoliciesStatements.push( + ...prepareDropPolicyJsons( + it.table, + it.schema, + it.columns, + ), + ); + } + + alteredTables.forEach((it) => { + // handle policies + Object.keys(it.alteredPolicies).forEach((policyName: string) => { + const newPolicy = action === 'push' + ? PgSquasher.unsquashPolicyPush(it.alteredPolicies[policyName].__new) + : PgSquasher.unsquashPolicy(it.alteredPolicies[policyName].__new); + const oldPolicy = action === 'push' + ? PgSquasher.unsquashPolicyPush(it.alteredPolicies[policyName].__old) + : PgSquasher.unsquashPolicy(it.alteredPolicies[policyName].__old); + + if (newPolicy.as !== oldPolicy.as) { + jsonDropPoliciesStatements.push( + ...prepareDropPolicyJsons( + it.name, + it.schema, + [oldPolicy], + ), + ); + + jsonCreatePoliciesStatements.push( + ...prepareCreatePolicyJsons( + it.name, + it.schema, + [newPolicy], + ), + ); + return; + } + + if (newPolicy.for !== oldPolicy.for) { + jsonDropPoliciesStatements.push( + ...prepareDropPolicyJsons( + it.name, + it.schema, + [oldPolicy], + ), + ); + + jsonCreatePoliciesStatements.push( + ...prepareCreatePolicyJsons( + it.name, + it.schema, + [newPolicy], + ), + ); + return; + } + + // alter + jsonAlterPoliciesStatements.push( + prepareAlterPolicyJson( + it.name, + it.schema, + it.alteredPolicies[policyName].__old, + it.alteredPolicies[policyName].__new, + ), + ); + }); + + // Handle enabling and disabling RLS + for (const table of Object.values(json2.tables)) { + const policiesInCurrentState = Object.keys(table.policies); + const tableInPreviousState = + columnsPatchedSnap1.tables[`${table.schema === '' ? 'public' : table.schema}.${table.name}`]; + const policiesInPreviousState = tableInPreviousState ? Object.keys(tableInPreviousState.policies) : []; + + // const indPoliciesInCurrentState = Object.keys(table.policies); + // const indPoliciesInPreviousState = Object.keys(columnsPatchedSnap1.policies); + + if ( + (policiesInPreviousState.length === 0 && policiesInCurrentState.length > 0) && !table.isRLSEnabled + ) { + jsonEnableRLSStatements.push({ type: 'enable_rls', tableName: table.name, schema: table.schema }); + } + + if ( + (policiesInPreviousState.length > 0 && policiesInCurrentState.length === 0) && !table.isRLSEnabled + ) { + jsonDisableRLSStatements.push({ type: 'disable_rls', tableName: table.name, schema: table.schema }); + } + + // handle table.isRLSEnabled + const wasRlsEnabled = tableInPreviousState ? tableInPreviousState.isRLSEnabled : false; + if (table.isRLSEnabled !== wasRlsEnabled) { + if (table.isRLSEnabled) { + // was force enabled + jsonEnableRLSStatements.push({ type: 'enable_rls', tableName: table.name, schema: table.schema }); + } else if ( + !table.isRLSEnabled && policiesInCurrentState.length === 0 + ) { + // was force disabled + jsonDisableRLSStatements.push({ type: 'disable_rls', tableName: table.name, schema: table.schema }); + } + } + } + + for (const table of Object.values(columnsPatchedSnap1.tables)) { + const tableInCurrentState = json2.tables[`${table.schema === '' ? 'public' : table.schema}.${table.name}`]; + + if (tableInCurrentState === undefined && !table.isRLSEnabled) { + jsonDisableRLSStatements.push({ type: 'disable_rls', tableName: table.name, schema: table.schema }); + } + } + + // handle indexes + const droppedIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__old; + return current; + }, + {} as Record, + ); + const createdIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__new; + return current; + }, + {} as Record, ); jsonCreateIndexesFoAlteredTables.push( @@ -999,30 +1698,6 @@ export const applyPgSnapshotsDiff = async ( // - create table with generated // - alter - should be not triggered, but should get warning - // TODO: - // let hasEnumValuesDeletions = false; - // let enumValuesDeletions: { name: string; schema: string; values: string[] }[] = - // []; - // for (let alteredEnum of typedResult.alteredEnums) { - // if (alteredEnum.deletedValues.length > 0) { - // hasEnumValuesDeletions = true; - // enumValuesDeletions.push({ - // name: alteredEnum.name, - // schema: alteredEnum.schema, - // values: alteredEnum.deletedValues, - // }); - // } - // } - // if (hasEnumValuesDeletions) { - // console.log(error("Deletion of enum values is prohibited in Postgres - see here")); - // for(let entry of enumValuesDeletions){ - // console.log(error(`You're trying to delete ${chalk.blue(`[${entry.values.join(", ")}]`)} values from ${chalk.blue(`${entry.schema}.${entry.name}`)}`)) - // } - // } - // if (hasEnumValuesDeletions && action === "push") { - // process.exit(1); - // } - const createEnums = createdEnums.map((it) => { return prepareCreateEnumJson(it.name, it.schema, it.values); }) ?? []; @@ -1039,14 +1714,17 @@ export const applyPgSnapshotsDiff = async ( return prepareRenameEnumJson(it.from.name, it.to.name, it.to.schema); }); - // todo: block enum rename, enum value rename and enun deletion for now const jsonAlterEnumsWithAddedValues = typedResult.alteredEnums .map((it) => { return prepareAddValuesToEnumJson(it.name, it.schema, it.addedValues); }) .flat() ?? []; - /////////// + const jsonAlterEnumsWithDroppedValues = typedResult.alteredEnums + .map((it) => { + return prepareDropEnumValues(it.name, it.schema, it.deletedValues, curFull); + }) + .flat() ?? []; const createSequences = createdSequences.map((it) => { return prepareCreateSequenceJson(it); @@ -1072,6 +1750,25 @@ export const applyPgSnapshotsDiff = async ( //////////// + const createRoles = createdRoles.map((it) => { + return prepareCreateRoleJson(it); + }) ?? []; + + const dropRoles = deletedRoles.map((it) => { + return prepareDropRoleJson(it.name); + }); + + const renameRoles = renamedRoles.map((it) => { + return prepareRenameRoleJson(it.from.name, it.to.name); + }); + + const jsonAlterRoles = typedResult.alteredRoles + .map((it) => { + return prepareAlterRoleJson(it); + }) + .flat() ?? []; + + //////////// const createSchemas = prepareCreateSchemasJson( createdSchemas.map((it) => it.name), ); @@ -1088,6 +1785,165 @@ export const applyPgSnapshotsDiff = async ( return preparePgCreateTableJson(it, curFull); }); + jsonCreatePoliciesStatements.push(...([] as JsonCreatePolicyStatement[]).concat( + ...(createdTables.map((it) => + prepareCreatePolicyJsons( + it.name, + it.schema, + Object.values(it.policies).map(action === 'push' ? PgSquasher.unsquashPolicyPush : PgSquasher.unsquashPolicy), + ) + )), + )); + const createViews: JsonCreatePgViewStatement[] = []; + const dropViews: JsonDropViewStatement[] = []; + const renameViews: JsonRenameViewStatement[] = []; + const alterViews: JsonAlterViewStatement[] = []; + + createViews.push( + ...createdViews.filter((it) => !it.isExisting).map((it) => { + return preparePgCreateViewJson( + it.name, + it.schema, + it.definition!, + it.materialized, + it.withNoData, + it.with, + it.using, + it.tablespace, + ); + }), + ); + + dropViews.push( + ...deletedViews.filter((it) => !it.isExisting).map((it) => { + return prepareDropViewJson(it.name, it.schema, it.materialized); + }), + ); + + renameViews.push( + ...renamedViews.filter((it) => !it.to.isExisting && !json1.views[`${it.from.schema}.${it.from.name}`].isExisting) + .map((it) => { + return prepareRenameViewJson(it.to.name, it.from.name, it.to.schema, it.to.materialized); + }), + ); + + alterViews.push( + ...movedViews.filter((it) => + !json2.views[`${it.schemaTo}.${it.name}`].isExisting && !json1.views[`${it.schemaFrom}.${it.name}`].isExisting + ).map((it) => { + return preparePgAlterViewAlterSchemaJson( + it.schemaTo, + it.schemaFrom, + it.name, + json2.views[`${it.schemaTo}.${it.name}`].materialized, + ); + }), + ); + + const alteredViews = typedResult.alteredViews.filter((it) => !json2.views[`${it.schema}.${it.name}`].isExisting); + + for (const alteredView of alteredViews) { + const viewKey = `${alteredView.schema}.${alteredView.name}`; + + const { materialized, with: withOption, definition, withNoData, using, tablespace } = json2.views[viewKey]; + + if (alteredView.alteredExisting || (alteredView.alteredDefinition && action !== 'push')) { + dropViews.push(prepareDropViewJson(alteredView.name, alteredView.schema, materialized)); + + createViews.push( + preparePgCreateViewJson( + alteredView.name, + alteredView.schema, + definition!, + materialized, + withNoData, + withOption, + using, + tablespace, + ), + ); + + continue; + } + + if (alteredView.addedWithOption) { + alterViews.push( + preparePgAlterViewAddWithOptionJson( + alteredView.name, + alteredView.schema, + materialized, + alteredView.addedWithOption, + ), + ); + } + + if (alteredView.deletedWithOption) { + alterViews.push( + preparePgAlterViewDropWithOptionJson( + alteredView.name, + alteredView.schema, + materialized, + alteredView.deletedWithOption, + ), + ); + } + + if (alteredView.addedWith) { + alterViews.push( + preparePgAlterViewAddWithOptionJson( + alteredView.name, + alteredView.schema, + materialized, + alteredView.addedWith, + ), + ); + } + + if (alteredView.deletedWith) { + alterViews.push( + preparePgAlterViewDropWithOptionJson( + alteredView.name, + alteredView.schema, + materialized, + alteredView.deletedWith, + ), + ); + } + + if (alteredView.alteredWith) { + alterViews.push( + preparePgAlterViewAddWithOptionJson( + alteredView.name, + alteredView.schema, + materialized, + alteredView.alteredWith, + ), + ); + } + + if (alteredView.alteredTablespace) { + alterViews.push( + preparePgAlterViewAlterTablespaceJson( + alteredView.name, + alteredView.schema, + materialized, + alteredView.alteredTablespace.__new, + ), + ); + } + + if (alteredView.alteredUsing) { + alterViews.push( + preparePgAlterViewAlterUsingJson( + alteredView.name, + alteredView.schema, + materialized, + alteredView.alteredUsing.__new, + ), + ); + } + } + jsonStatements.push(...createSchemas); jsonStatements.push(...renameSchemas); jsonStatements.push(...createEnums); @@ -1100,14 +1956,26 @@ export const applyPgSnapshotsDiff = async ( jsonStatements.push(...renameSequences); jsonStatements.push(...jsonAlterSequences); + jsonStatements.push(...renameRoles); + jsonStatements.push(...dropRoles); + jsonStatements.push(...createRoles); + jsonStatements.push(...jsonAlterRoles); + jsonStatements.push(...createTables); + jsonStatements.push(...jsonEnableRLSStatements); + jsonStatements.push(...jsonDisableRLSStatements); + jsonStatements.push(...dropViews); + jsonStatements.push(...renameViews); + jsonStatements.push(...alterViews); + jsonStatements.push(...jsonDropTables); jsonStatements.push(...jsonSetTableSchemas); jsonStatements.push(...jsonRenameTables); jsonStatements.push(...jsonRenameColumnsStatements); jsonStatements.push(...jsonDeletedUniqueConstraints); + jsonStatements.push(...jsonDeletedCheckConstraints); jsonStatements.push(...jsonDroppedReferencesForAlteredTables); @@ -1130,8 +1998,22 @@ export const applyPgSnapshotsDiff = async ( jsonStatements.push(...jsonAlteredCompositePKs); jsonStatements.push(...jsonAddedUniqueConstraints); + jsonStatements.push(...jsonCreatedCheckConstraints); jsonStatements.push(...jsonAlteredUniqueConstraints); + jsonStatements.push(...jsonAlterEnumsWithDroppedValues); + + jsonStatements.push(...createViews); + + jsonStatements.push(...jsonRenamePoliciesStatements); + jsonStatements.push(...jsonDropPoliciesStatements); + jsonStatements.push(...jsonCreatePoliciesStatements); + jsonStatements.push(...jsonAlterPoliciesStatements); + + jsonStatements.push(...jsonRenameIndPoliciesStatements); + jsonStatements.push(...jsonDropIndPoliciesStatements); + jsonStatements.push(...jsonCreateIndPoliciesStatements); + jsonStatements.push(...jsonAlterIndPoliciesStatements); jsonStatements.push(...dropEnums); jsonStatements.push(...dropSequences); @@ -1166,7 +2048,25 @@ export const applyPgSnapshotsDiff = async ( return true; }); - const sqlStatements = fromJson(filteredJsonStatements, 'postgresql'); + // enum filters + // Need to find add and drop enum values in same enum and remove add values + const filteredEnumsJsonStatements = filteredJsonStatements.filter((st) => { + if (st.type === 'alter_type_add_value') { + if ( + jsonStatements.find( + (it) => + it.type === 'alter_type_drop_value' + && it.name === st.name + && it.schema === st.schema, + ) + ) { + return false; + } + } + return true; + }); + + const sqlStatements = fromJson(filteredEnumsJsonStatements, 'postgresql', action); const uniqueSqlStatements: string[] = []; sqlStatements.forEach((ss) => { @@ -1187,7 +2087,7 @@ export const applyPgSnapshotsDiff = async ( const _meta = prepareMigrationMeta(rSchemas, rTables, rColumns); return { - statements: filteredJsonStatements, + statements: filteredEnumsJsonStatements, sqlStatements: uniqueSqlStatements, _meta, }; @@ -1202,6 +2102,9 @@ export const applyMysqlSnapshotsDiff = async ( columnsResolver: ( input: ColumnsResolverInput, ) => Promise>, + viewsResolver: ( + input: ResolverInput, + ) => Promise>, prevFull: MySqlSchema, curFull: MySqlSchema, action?: 'push' | undefined, @@ -1349,7 +2252,38 @@ export const applyMysqlSnapshotsDiff = async ( }, ); - const diffResult = applyJsonDiff(columnsPatchedSnap1, json2); + const viewsDiff = diffSchemasOrTables(json1.views, json2.views); + + const { + created: createdViews, + deleted: deletedViews, + renamed: renamedViews, // renamed or moved + } = await viewsResolver({ + created: viewsDiff.added, + deleted: viewsDiff.deleted, + }); + + const renamesViewDic: Record = {}; + renamedViews.forEach((it) => { + renamesViewDic[it.from.name] = { to: it.to.name, from: it.from.name }; + }); + + const viewsPatchedSnap1 = copy(columnsPatchedSnap1); + viewsPatchedSnap1.views = mapEntries( + viewsPatchedSnap1.views, + (viewKey, viewValue) => { + const rename = renamesViewDic[viewValue.name]; + + if (rename) { + viewValue.name = rename.to; + viewKey = rename.to; + } + + return [viewKey, viewValue]; + }, + ); + + const diffResult = applyJsonDiff(viewsPatchedSnap1, json2); const typedResult: DiffResultMysql = diffResultSchemeMysql.parse(diffResult); @@ -1384,6 +2318,9 @@ export const applyMysqlSnapshotsDiff = async ( const jsonDeletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; const jsonAlteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + const jsonCreatedCheckConstraints: JsonCreateCheckConstraint[] = []; + const jsonDeletedCheckConstraints: JsonDeleteCheckConstraint[] = []; + const jsonRenameColumnsStatements: JsonRenameColumnStatement[] = columnRenames .map((it) => prepareRenameColumns(it.table, '', it.renames)) .flat(); @@ -1445,6 +2382,9 @@ export const applyMysqlSnapshotsDiff = async ( let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + let createdCheckConstraints: JsonCreateCheckConstraint[] = []; + let deletedCheckConstraints: JsonDeleteCheckConstraint[] = []; + addedUniqueConstraints = prepareAddUniqueConstraint( it.name, it.schema, @@ -1470,6 +2410,26 @@ export const applyMysqlSnapshotsDiff = async ( ); } + createdCheckConstraints = prepareAddCheckConstraint(it.name, it.schema, it.addedCheckConstraints); + deletedCheckConstraints = prepareDeleteCheckConstraint( + it.name, + it.schema, + it.deletedCheckConstraints, + ); + + // skip for push + if (it.alteredCheckConstraints && action !== 'push') { + const added: Record = {}; + const deleted: Record = {}; + + for (const k of Object.keys(it.alteredCheckConstraints)) { + added[k] = it.alteredCheckConstraints[k].__new; + deleted[k] = it.alteredCheckConstraints[k].__old; + } + createdCheckConstraints.push(...prepareAddCheckConstraint(it.name, it.schema, added)); + deletedCheckConstraints.push(...prepareDeleteCheckConstraint(it.name, it.schema, deleted)); + } + jsonAddedCompositePKs.push(...addedCompositePKs); jsonDeletedCompositePKs.push(...deletedCompositePKs); jsonAlteredCompositePKs.push(...alteredCompositePKs); @@ -1477,6 +2437,9 @@ export const applyMysqlSnapshotsDiff = async ( jsonAddedUniqueConstraints.push(...addedUniqueConstraints); jsonDeletedUniqueConstraints.push(...deletedUniqueConstraints); jsonAlteredUniqueConstraints.push(...alteredUniqueConstraints); + + jsonCreatedCheckConstraints.push(...createdCheckConstraints); + jsonDeletedCheckConstraints.push(...deletedCheckConstraints); }); const rColumns = jsonRenameColumnsStatements.map((it) => { @@ -1590,13 +2553,85 @@ export const applyMysqlSnapshotsDiff = async ( curFull.internal, ); }); + + const createViews: JsonCreateMySqlViewStatement[] = []; + const dropViews: JsonDropViewStatement[] = []; + const renameViews: JsonRenameViewStatement[] = []; + const alterViews: JsonAlterMySqlViewStatement[] = []; + + createViews.push( + ...createdViews.filter((it) => !it.isExisting).map((it) => { + return prepareMySqlCreateViewJson( + it.name, + it.definition!, + it.meta, + ); + }), + ); + + dropViews.push( + ...deletedViews.filter((it) => !it.isExisting).map((it) => { + return prepareDropViewJson(it.name); + }), + ); + + renameViews.push( + ...renamedViews.filter((it) => !it.to.isExisting && !json1.views[it.from.name].isExisting).map((it) => { + return prepareRenameViewJson(it.to.name, it.from.name); + }), + ); + + const alteredViews = typedResult.alteredViews.filter((it) => !json2.views[it.name].isExisting); + + for (const alteredView of alteredViews) { + const { definition, meta } = json2.views[alteredView.name]; + + if (alteredView.alteredExisting) { + dropViews.push(prepareDropViewJson(alteredView.name)); + + createViews.push( + prepareMySqlCreateViewJson( + alteredView.name, + definition!, + meta, + ), + ); + + continue; + } + + if (alteredView.alteredDefinition && action !== 'push') { + createViews.push( + prepareMySqlCreateViewJson( + alteredView.name, + definition!, + meta, + true, + ), + ); + continue; + } + + if (alteredView.alteredMeta) { + const view = curFull['views'][alteredView.name]; + alterViews.push( + prepareMySqlAlterView(view), + ); + } + } + jsonStatements.push(...jsonMySqlCreateTables); jsonStatements.push(...jsonDropTables); jsonStatements.push(...jsonRenameTables); jsonStatements.push(...jsonRenameColumnsStatements); + jsonStatements.push(...dropViews); + jsonStatements.push(...renameViews); + jsonStatements.push(...alterViews); + jsonStatements.push(...jsonDeletedUniqueConstraints); + jsonStatements.push(...jsonDeletedCheckConstraints); jsonStatements.push(...jsonDroppedReferencesForAlteredTables); @@ -1607,14 +2642,14 @@ export const applyMysqlSnapshotsDiff = async ( jsonStatements.push(...jsonDeletedCompositePKs); jsonStatements.push(...jsonTableAlternations); jsonStatements.push(...jsonAddedCompositePKs); + jsonStatements.push(...jsonAddColumnsStatemets); jsonStatements.push(...jsonAddedUniqueConstraints); jsonStatements.push(...jsonDeletedUniqueConstraints); - jsonStatements.push(...jsonAddColumnsStatemets); - jsonStatements.push(...jsonCreateReferencesForCreatedTables); jsonStatements.push(...jsonCreateIndexesForCreatedTables); + jsonStatements.push(...jsonCreatedCheckConstraints); jsonStatements.push(...jsonCreatedReferencesForAlteredTables); jsonStatements.push(...jsonCreateIndexesForAllAlteredTables); @@ -1625,7 +2660,7 @@ export const applyMysqlSnapshotsDiff = async ( // jsonStatements.push(...jsonAddedCompositePKs); jsonStatements.push(...jsonAlteredCompositePKs); - jsonStatements.push(...jsonAddedUniqueConstraints); + jsonStatements.push(...createViews); jsonStatements.push(...jsonAlteredUniqueConstraints); @@ -1651,17 +2686,20 @@ export const applyMysqlSnapshotsDiff = async ( }; }; -export const applySqliteSnapshotsDiff = async ( - json1: SQLiteSchemaSquashed, - json2: SQLiteSchemaSquashed, +export const applySingleStoreSnapshotsDiff = async ( + json1: SingleStoreSchemaSquashed, + json2: SingleStoreSchemaSquashed, tablesResolver: ( input: ResolverInput
, ) => Promise>, columnsResolver: ( input: ColumnsResolverInput, ) => Promise>, - prevFull: SQLiteSchema, - curFull: SQLiteSchema, + /* viewsResolver: ( + input: ResolverInput, + ) => Promise>, */ + prevFull: SingleStoreSchema, + curFull: SingleStoreSchema, action?: 'push' | undefined, ): Promise<{ statements: JsonStatement[]; @@ -1674,12 +2712,48 @@ export const applySqliteSnapshotsDiff = async ( } | undefined; }> => { + // squash indexes and fks + + // squash uniqueIndexes and uniqueConstraint into constraints object + // it should be done for singlestore only because it has no diffs for it + + // TODO: @AndriiSherman + // Add an upgrade to v6 and move all snaphosts to this strcutre + // After that we can generate singlestore in 1 object directly(same as sqlite) + for (const tableName in json1.tables) { + const table = json1.tables[tableName]; + for (const indexName in table.indexes) { + const index = SingleStoreSquasher.unsquashIdx(table.indexes[indexName]); + if (index.isUnique) { + table.uniqueConstraints[indexName] = SingleStoreSquasher.squashUnique({ + name: index.name, + columns: index.columns, + }); + delete json1.tables[tableName].indexes[index.name]; + } + } + } + + for (const tableName in json2.tables) { + const table = json2.tables[tableName]; + for (const indexName in table.indexes) { + const index = SingleStoreSquasher.unsquashIdx(table.indexes[indexName]); + if (index.isUnique) { + table.uniqueConstraints[indexName] = SingleStoreSquasher.squashUnique({ + name: index.name, + columns: index.columns, + }); + delete json2.tables[tableName].indexes[index.name]; + } + } + } + const tablesDiff = diffSchemasOrTables(json1.tables, json2.tables); const { created: createdTables, deleted: deletedTables, - renamed: renamedTables, + renamed: renamedTables, // renamed or moved } = await tablesResolver({ created: tablesDiff.added, deleted: tablesDiff.deleted, @@ -1693,7 +2767,6 @@ export const applySqliteSnapshotsDiff = async ( }); const res = diffColumns(tablesPatchedSnap1.tables, json2.tables); - const columnRenames = [] as { table: string; renames: { from: Column; to: Column }[]; @@ -1772,9 +2845,1071 @@ export const applySqliteSnapshotsDiff = async ( }, ); - const diffResult = applyJsonDiff(columnsPatchedSnap1, json2); - - const typedResult = diffResultSchemeSQLite.parse(diffResult); + /* const viewsDiff = diffSchemasOrTables(json1.views, json2.views); + + const { + created: createdViews, + deleted: deletedViews, + renamed: renamedViews, // renamed or moved + } = await viewsResolver({ + created: viewsDiff.added, + deleted: viewsDiff.deleted, + }); + + const renamesViewDic: Record = {}; + renamedViews.forEach((it) => { + renamesViewDic[it.from.name] = { to: it.to.name, from: it.from.name }; + }); + + const viewsPatchedSnap1 = copy(columnsPatchedSnap1); + viewsPatchedSnap1.views = mapEntries( + viewsPatchedSnap1.views, + (viewKey, viewValue) => { + const rename = renamesViewDic[viewValue.name]; + + if (rename) { + viewValue.name = rename.to; + viewKey = rename.to; + } + + return [viewKey, viewValue]; + }, + ); + + */ + const diffResult = applyJsonDiff(tablesPatchedSnap1, json2); // replace tablesPatchedSnap1 with viewsPatchedSnap1 + + const typedResult: DiffResultSingleStore = diffResultSchemeSingleStore.parse(diffResult); + + const jsonStatements: JsonStatement[] = []; + + const jsonCreateIndexesForCreatedTables = createdTables + .map((it) => { + return prepareCreateIndexesJson( + it.name, + it.schema, + it.indexes, + curFull.internal, + ); + }) + .flat(); + + const jsonDropTables = deletedTables.map((it) => { + return prepareDropTableJson(it); + }); + + const jsonRenameTables = renamedTables.map((it) => { + return prepareRenameTableJson(it.from, it.to); + }); + + const alteredTables = typedResult.alteredTablesWithColumns; + + const jsonAddedCompositePKs: JsonCreateCompositePK[] = []; + + const jsonAddedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + const jsonDeletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + const jsonAlteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + + const jsonRenameColumnsStatements: JsonRenameColumnStatement[] = columnRenames + .map((it) => prepareRenameColumns(it.table, '', it.renames)) + .flat(); + + const jsonAddColumnsStatemets: JsonAddColumnStatement[] = columnCreates + .map((it) => _prepareAddColumns(it.table, '', it.columns)) + .flat(); + + const jsonDropColumnsStatemets: JsonDropColumnStatement[] = columnDeletes + .map((it) => _prepareDropColumns(it.table, '', it.columns)) + .flat(); + + alteredTables.forEach((it) => { + // This part is needed to make sure that same columns in a table are not triggered for change + // there is a case where orm and kit are responsible for pk name generation and one of them is not sorting name + // We double-check that pk with same set of columns are both in added and deleted diffs + let addedColumns: string[] = []; + for (const addedPkName of Object.keys(it.addedCompositePKs)) { + const addedPkColumns = it.addedCompositePKs[addedPkName]; + addedColumns = SingleStoreSquasher.unsquashPK(addedPkColumns).columns; + } + + let deletedColumns: string[] = []; + for (const deletedPkName of Object.keys(it.deletedCompositePKs)) { + const deletedPkColumns = it.deletedCompositePKs[deletedPkName]; + deletedColumns = SingleStoreSquasher.unsquashPK(deletedPkColumns).columns; + } + + // Don't need to sort, but need to add tests for it + // addedColumns.sort(); + // deletedColumns.sort(); + const doPerformDeleteAndCreate = JSON.stringify(addedColumns) !== JSON.stringify(deletedColumns); + + // add logic for unique constraints + let addedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + + let createdCheckConstraints: JsonCreateCheckConstraint[] = []; + let deletedCheckConstraints: JsonDeleteCheckConstraint[] = []; + + addedUniqueConstraints = prepareAddUniqueConstraint( + it.name, + it.schema, + it.addedUniqueConstraints, + ); + deletedUniqueConstraints = prepareDeleteUniqueConstraint( + it.name, + it.schema, + it.deletedUniqueConstraints, + ); + if (it.alteredUniqueConstraints) { + const added: Record = {}; + const deleted: Record = {}; + for (const k of Object.keys(it.alteredUniqueConstraints)) { + added[k] = it.alteredUniqueConstraints[k].__new; + deleted[k] = it.alteredUniqueConstraints[k].__old; + } + addedUniqueConstraints.push( + ...prepareAddUniqueConstraint(it.name, it.schema, added), + ); + deletedUniqueConstraints.push( + ...prepareDeleteUniqueConstraint(it.name, it.schema, deleted), + ); + } + + createdCheckConstraints = prepareAddCheckConstraint(it.name, it.schema, it.addedCheckConstraints); + deletedCheckConstraints = prepareDeleteCheckConstraint( + it.name, + it.schema, + it.deletedCheckConstraints, + ); + + // skip for push + if (it.alteredCheckConstraints && action !== 'push') { + const added: Record = {}; + const deleted: Record = {}; + + for (const k of Object.keys(it.alteredCheckConstraints)) { + added[k] = it.alteredCheckConstraints[k].__new; + deleted[k] = it.alteredCheckConstraints[k].__old; + } + createdCheckConstraints.push(...prepareAddCheckConstraint(it.name, it.schema, added)); + deletedCheckConstraints.push(...prepareDeleteCheckConstraint(it.name, it.schema, deleted)); + } + + jsonAddedUniqueConstraints.push(...addedUniqueConstraints); + jsonDeletedUniqueConstraints.push(...deletedUniqueConstraints); + jsonAlteredUniqueConstraints.push(...alteredUniqueConstraints); + }); + + const rColumns = jsonRenameColumnsStatements.map((it) => { + const tableName = it.tableName; + const schema = it.schema; + return { + from: { schema, table: tableName, column: it.oldColumnName }, + to: { schema, table: tableName, column: it.newColumnName }, + }; + }); + + const jsonTableAlternations = alteredTables + .map((it) => { + return prepareAlterColumnsMysql( + it.name, + it.schema, + it.altered, + json1, + json2, + action, + ); + }) + .flat(); + + const jsonCreateIndexesForAllAlteredTables = alteredTables + .map((it) => { + return prepareCreateIndexesJson( + it.name, + it.schema, + it.addedIndexes || {}, + curFull.internal, + ); + }) + .flat(); + + const jsonDropIndexesForAllAlteredTables = alteredTables + .map((it) => { + return prepareDropIndexesJson( + it.name, + it.schema, + it.deletedIndexes || {}, + ); + }) + .flat(); + + alteredTables.forEach((it) => { + const droppedIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__old; + return current; + }, + {} as Record, + ); + const createdIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__new; + return current; + }, + {} as Record, + ); + + jsonCreateIndexesForAllAlteredTables.push( + ...prepareCreateIndexesJson(it.name, it.schema, createdIndexes || {}), + ); + jsonDropIndexesForAllAlteredTables.push( + ...prepareDropIndexesJson(it.name, it.schema, droppedIndexes || {}), + ); + }); + + const jsonSingleStoreCreateTables = createdTables.map((it) => { + return prepareSingleStoreCreateTableJson( + it, + curFull as SingleStoreSchema, + curFull.internal, + ); + }); + + /* const createViews: JsonCreateSingleStoreViewStatement[] = []; + const dropViews: JsonDropViewStatement[] = []; + const renameViews: JsonRenameViewStatement[] = []; + const alterViews: JsonAlterSingleStoreViewStatement[] = []; + + createViews.push( + ...createdViews.filter((it) => !it.isExisting).map((it) => { + return prepareSingleStoreCreateViewJson( + it.name, + it.definition!, + it.meta, + ); + }), + ); + + dropViews.push( + ...deletedViews.filter((it) => !it.isExisting).map((it) => { + return prepareDropViewJson(it.name); + }), + ); + + renameViews.push( + ...renamedViews.filter((it) => !it.to.isExisting && !json1.views[it.from.name].isExisting).map((it) => { + return prepareRenameViewJson(it.to.name, it.from.name); + }), + ); + + const alteredViews = typedResult.alteredViews.filter((it) => !json2.views[it.name].isExisting); + + for (const alteredView of alteredViews) { + const { definition, meta } = json2.views[alteredView.name]; + + if (alteredView.alteredExisting) { + dropViews.push(prepareDropViewJson(alteredView.name)); + + createViews.push( + prepareSingleStoreCreateViewJson( + alteredView.name, + definition!, + meta, + ), + ); + + continue; + } + + if (alteredView.alteredDefinition && action !== 'push') { + createViews.push( + prepareSingleStoreCreateViewJson( + alteredView.name, + definition!, + meta, + true, + ), + ); + continue; + } + + if (alteredView.alteredMeta) { + const view = curFull['views'][alteredView.name]; + alterViews.push( + prepareSingleStoreAlterView(view), + ); + } + } */ + + jsonStatements.push(...jsonSingleStoreCreateTables); + + jsonStatements.push(...jsonDropTables); + jsonStatements.push(...jsonRenameTables); + jsonStatements.push(...jsonRenameColumnsStatements); + + /*jsonStatements.push(...createViews); + jsonStatements.push(...dropViews); + jsonStatements.push(...renameViews); + jsonStatements.push(...alterViews); + */ + jsonStatements.push(...jsonDeletedUniqueConstraints); + + // Will need to drop indexes before changing any columns in table + // Then should go column alternations and then index creation + jsonStatements.push(...jsonDropIndexesForAllAlteredTables); + + jsonStatements.push(...jsonTableAlternations); + jsonStatements.push(...jsonAddedCompositePKs); + + jsonStatements.push(...jsonAddedUniqueConstraints); + jsonStatements.push(...jsonDeletedUniqueConstraints); + + jsonStatements.push(...jsonAddColumnsStatemets); + + jsonStatements.push(...jsonCreateIndexesForCreatedTables); + + jsonStatements.push(...jsonCreateIndexesForAllAlteredTables); + + jsonStatements.push(...jsonDropColumnsStatemets); + + jsonStatements.push(...jsonAddedCompositePKs); + + jsonStatements.push(...jsonAlteredUniqueConstraints); + + const sqlStatements = fromJson(jsonStatements, 'singlestore'); + + const uniqueSqlStatements: string[] = []; + sqlStatements.forEach((ss) => { + if (!uniqueSqlStatements.includes(ss)) { + uniqueSqlStatements.push(ss); + } + }); + + const rTables = renamedTables.map((it) => { + return { from: it.from, to: it.to }; + }); + + const _meta = prepareMigrationMeta([], rTables, rColumns); + + return { + statements: jsonStatements, + sqlStatements: uniqueSqlStatements, + _meta, + }; +}; + +export const applySqliteSnapshotsDiff = async ( + json1: SQLiteSchemaSquashed, + json2: SQLiteSchemaSquashed, + tablesResolver: ( + input: ResolverInput
, + ) => Promise>, + columnsResolver: ( + input: ColumnsResolverInput, + ) => Promise>, + viewsResolver: ( + input: ResolverInput, + ) => Promise>, + prevFull: SQLiteSchema, + curFull: SQLiteSchema, + action?: 'push' | undefined, +): Promise<{ + statements: JsonStatement[]; + sqlStatements: string[]; + _meta: + | { + schemas: {}; + tables: {}; + columns: {}; + } + | undefined; +}> => { + const tablesDiff = diffSchemasOrTables(json1.tables, json2.tables); + + const { + created: createdTables, + deleted: deletedTables, + renamed: renamedTables, + } = await tablesResolver({ + created: tablesDiff.added, + deleted: tablesDiff.deleted, + }); + + const tablesPatchedSnap1 = copy(json1); + tablesPatchedSnap1.tables = mapEntries(tablesPatchedSnap1.tables, (_, it) => { + const { name } = nameChangeFor(it, renamedTables); + it.name = name; + return [name, it]; + }); + + const res = diffColumns(tablesPatchedSnap1.tables, json2.tables); + + const columnRenames = [] as { + table: string; + renames: { from: Column; to: Column }[]; + }[]; + + const columnCreates = [] as { + table: string; + columns: Column[]; + }[]; + + const columnDeletes = [] as { + table: string; + columns: Column[]; + }[]; + + for (let entry of Object.values(res)) { + const { renamed, created, deleted } = await columnsResolver({ + tableName: entry.name, + schema: entry.schema, + deleted: entry.columns.deleted, + created: entry.columns.added, + }); + + if (created.length > 0) { + columnCreates.push({ + table: entry.name, + columns: created, + }); + } + + if (deleted.length > 0) { + columnDeletes.push({ + table: entry.name, + columns: deleted, + }); + } + + if (renamed.length > 0) { + columnRenames.push({ + table: entry.name, + renames: renamed, + }); + } + } + + const columnRenamesDict = columnRenames.reduce( + (acc, it) => { + acc[it.table] = it.renames; + return acc; + }, + {} as Record< + string, + { + from: Named; + to: Named; + }[] + >, + ); + + const columnsPatchedSnap1 = copy(tablesPatchedSnap1); + columnsPatchedSnap1.tables = mapEntries( + columnsPatchedSnap1.tables, + (tableKey, tableValue) => { + const patchedColumns = mapKeys( + tableValue.columns, + (columnKey, column) => { + const rens = columnRenamesDict[tableValue.name] || []; + const newName = columnChangeFor(columnKey, rens); + column.name = newName; + return newName; + }, + ); + + tableValue.columns = patchedColumns; + return [tableKey, tableValue]; + }, + ); + + const viewsDiff = diffSchemasOrTables(json1.views, json2.views); + + const { + created: createdViews, + deleted: deletedViews, + renamed: renamedViews, // renamed or moved + } = await viewsResolver({ + created: viewsDiff.added, + deleted: viewsDiff.deleted, + }); + + const renamesViewDic: Record = {}; + renamedViews.forEach((it) => { + renamesViewDic[it.from.name] = { to: it.to.name, from: it.from.name }; + }); + + const viewsPatchedSnap1 = copy(columnsPatchedSnap1); + viewsPatchedSnap1.views = mapEntries( + viewsPatchedSnap1.views, + (viewKey, viewValue) => { + const rename = renamesViewDic[viewValue.name]; + + if (rename) { + viewValue.name = rename.to; + } + + return [viewKey, viewValue]; + }, + ); + + const diffResult = applyJsonDiff(viewsPatchedSnap1, json2); + + const typedResult = diffResultSchemeSQLite.parse(diffResult); + + // Map array of objects to map + const tablesMap: { + [key: string]: (typeof typedResult.alteredTablesWithColumns)[number]; + } = {}; + + typedResult.alteredTablesWithColumns.forEach((obj) => { + tablesMap[obj.name] = obj; + }); + + const jsonCreateTables = createdTables.map((it) => { + return prepareSQLiteCreateTable(it, action); + }); + + const jsonCreateIndexesForCreatedTables = createdTables + .map((it) => { + return prepareCreateIndexesJson( + it.name, + it.schema, + it.indexes, + curFull.internal, + ); + }) + .flat(); + + const jsonDropTables = deletedTables.map((it) => { + return prepareDropTableJson(it); + }); + + const jsonRenameTables = renamedTables.map((it) => { + return prepareRenameTableJson(it.from, it.to); + }); + + const jsonRenameColumnsStatements: JsonRenameColumnStatement[] = columnRenames + .map((it) => prepareRenameColumns(it.table, '', it.renames)) + .flat(); + + const jsonDropColumnsStatemets: JsonDropColumnStatement[] = columnDeletes + .map((it) => _prepareDropColumns(it.table, '', it.columns)) + .flat(); + + const jsonAddColumnsStatemets: JsonSqliteAddColumnStatement[] = columnCreates + .map((it) => { + return _prepareSqliteAddColumns( + it.table, + it.columns, + tablesMap[it.table] && tablesMap[it.table].addedForeignKeys + ? Object.values(tablesMap[it.table].addedForeignKeys) + : [], + ); + }) + .flat(); + + const allAltered = typedResult.alteredTablesWithColumns; + + const jsonAddedCompositePKs: JsonCreateCompositePK[] = []; + const jsonDeletedCompositePKs: JsonDeleteCompositePK[] = []; + const jsonAlteredCompositePKs: JsonAlterCompositePK[] = []; + + const jsonAddedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + const jsonDeletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + const jsonAlteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + + const jsonDeletedCheckConstraints: JsonDeleteCheckConstraint[] = []; + const jsonCreatedCheckConstraints: JsonCreateCheckConstraint[] = []; + + allAltered.forEach((it) => { + // This part is needed to make sure that same columns in a table are not triggered for change + // there is a case where orm and kit are responsible for pk name generation and one of them is not sorting name + // We double-check that pk with same set of columns are both in added and deleted diffs + let addedColumns: string[] = []; + for (const addedPkName of Object.keys(it.addedCompositePKs)) { + const addedPkColumns = it.addedCompositePKs[addedPkName]; + addedColumns = SQLiteSquasher.unsquashPK(addedPkColumns); + } + + let deletedColumns: string[] = []; + for (const deletedPkName of Object.keys(it.deletedCompositePKs)) { + const deletedPkColumns = it.deletedCompositePKs[deletedPkName]; + deletedColumns = SQLiteSquasher.unsquashPK(deletedPkColumns); + } + + // Don't need to sort, but need to add tests for it + // addedColumns.sort(); + // deletedColumns.sort(); + + const doPerformDeleteAndCreate = JSON.stringify(addedColumns) !== JSON.stringify(deletedColumns); + + let addedCompositePKs: JsonCreateCompositePK[] = []; + let deletedCompositePKs: JsonDeleteCompositePK[] = []; + let alteredCompositePKs: JsonAlterCompositePK[] = []; + if (doPerformDeleteAndCreate) { + addedCompositePKs = prepareAddCompositePrimaryKeySqlite( + it.name, + it.addedCompositePKs, + ); + deletedCompositePKs = prepareDeleteCompositePrimaryKeySqlite( + it.name, + it.deletedCompositePKs, + ); + } + alteredCompositePKs = prepareAlterCompositePrimaryKeySqlite( + it.name, + it.alteredCompositePKs, + ); + + // add logic for unique constraints + let addedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + + addedUniqueConstraints = prepareAddUniqueConstraint( + it.name, + it.schema, + it.addedUniqueConstraints, + ); + deletedUniqueConstraints = prepareDeleteUniqueConstraint( + it.name, + it.schema, + it.deletedUniqueConstraints, + ); + if (it.alteredUniqueConstraints) { + const added: Record = {}; + const deleted: Record = {}; + for (const k of Object.keys(it.alteredUniqueConstraints)) { + added[k] = it.alteredUniqueConstraints[k].__new; + deleted[k] = it.alteredUniqueConstraints[k].__old; + } + addedUniqueConstraints.push( + ...prepareAddUniqueConstraint(it.name, it.schema, added), + ); + deletedUniqueConstraints.push( + ...prepareDeleteUniqueConstraint(it.name, it.schema, deleted), + ); + } + + let createdCheckConstraints: JsonCreateCheckConstraint[] = []; + let deletedCheckConstraints: JsonDeleteCheckConstraint[] = []; + + addedUniqueConstraints = prepareAddUniqueConstraint( + it.name, + it.schema, + it.addedUniqueConstraints, + ); + deletedUniqueConstraints = prepareDeleteUniqueConstraint( + it.name, + it.schema, + it.deletedUniqueConstraints, + ); + if (it.alteredUniqueConstraints) { + const added: Record = {}; + const deleted: Record = {}; + for (const k of Object.keys(it.alteredUniqueConstraints)) { + added[k] = it.alteredUniqueConstraints[k].__new; + deleted[k] = it.alteredUniqueConstraints[k].__old; + } + addedUniqueConstraints.push( + ...prepareAddUniqueConstraint(it.name, it.schema, added), + ); + deletedUniqueConstraints.push( + ...prepareDeleteUniqueConstraint(it.name, it.schema, deleted), + ); + } + + createdCheckConstraints = prepareAddCheckConstraint(it.name, it.schema, it.addedCheckConstraints); + deletedCheckConstraints = prepareDeleteCheckConstraint( + it.name, + it.schema, + it.deletedCheckConstraints, + ); + + // skip for push + if (it.alteredCheckConstraints && action !== 'push') { + const added: Record = {}; + const deleted: Record = {}; + + for (const k of Object.keys(it.alteredCheckConstraints)) { + added[k] = it.alteredCheckConstraints[k].__new; + deleted[k] = it.alteredCheckConstraints[k].__old; + } + createdCheckConstraints.push(...prepareAddCheckConstraint(it.name, it.schema, added)); + deletedCheckConstraints.push(...prepareDeleteCheckConstraint(it.name, it.schema, deleted)); + } + + jsonAddedCompositePKs.push(...addedCompositePKs); + jsonDeletedCompositePKs.push(...deletedCompositePKs); + jsonAlteredCompositePKs.push(...alteredCompositePKs); + + jsonAddedUniqueConstraints.push(...addedUniqueConstraints); + jsonDeletedUniqueConstraints.push(...deletedUniqueConstraints); + jsonAlteredUniqueConstraints.push(...alteredUniqueConstraints); + + jsonCreatedCheckConstraints.push(...createdCheckConstraints); + jsonDeletedCheckConstraints.push(...deletedCheckConstraints); + }); + + const rColumns = jsonRenameColumnsStatements.map((it) => { + const tableName = it.tableName; + const schema = it.schema; + return { + from: { schema, table: tableName, column: it.oldColumnName }, + to: { schema, table: tableName, column: it.newColumnName }, + }; + }); + + const jsonTableAlternations = allAltered + .map((it) => { + return prepareSqliteAlterColumns(it.name, it.schema, it.altered, json2); + }) + .flat(); + + const jsonCreateIndexesForAllAlteredTables = allAltered + .map((it) => { + return prepareCreateIndexesJson( + it.name, + it.schema, + it.addedIndexes || {}, + curFull.internal, + ); + }) + .flat(); + + const jsonDropIndexesForAllAlteredTables = allAltered + .map((it) => { + return prepareDropIndexesJson( + it.name, + it.schema, + it.deletedIndexes || {}, + ); + }) + .flat(); + + allAltered.forEach((it) => { + const droppedIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__old; + return current; + }, + {} as Record, + ); + const createdIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__new; + return current; + }, + {} as Record, + ); + + jsonCreateIndexesForAllAlteredTables.push( + ...prepareCreateIndexesJson( + it.name, + it.schema, + createdIndexes || {}, + curFull.internal, + ), + ); + jsonDropIndexesForAllAlteredTables.push( + ...prepareDropIndexesJson(it.name, it.schema, droppedIndexes || {}), + ); + }); + + const jsonReferencesForAllAlteredTables: JsonReferenceStatement[] = allAltered + .map((it) => { + const forAdded = prepareCreateReferencesJson( + it.name, + it.schema, + it.addedForeignKeys, + ); + + const forAltered = prepareDropReferencesJson( + it.name, + it.schema, + it.deletedForeignKeys, + ); + + const alteredFKs = prepareAlterReferencesJson( + it.name, + it.schema, + it.alteredForeignKeys, + ); + + return [...forAdded, ...forAltered, ...alteredFKs]; + }) + .flat(); + + const jsonCreatedReferencesForAlteredTables = jsonReferencesForAllAlteredTables.filter( + (t) => t.type === 'create_reference', + ); + const jsonDroppedReferencesForAlteredTables = jsonReferencesForAllAlteredTables.filter( + (t) => t.type === 'delete_reference', + ); + + const createViews: JsonCreateSqliteViewStatement[] = []; + const dropViews: JsonDropViewStatement[] = []; + + createViews.push( + ...createdViews.filter((it) => !it.isExisting).map((it) => { + return prepareSqliteCreateViewJson( + it.name, + it.definition!, + ); + }), + ); + + dropViews.push( + ...deletedViews.filter((it) => !it.isExisting).map((it) => { + return prepareDropViewJson(it.name); + }), + ); + + dropViews.push( + ...renamedViews.filter((it) => !it.to.isExisting).map((it) => { + return prepareDropViewJson(it.from.name); + }), + ); + createViews.push( + ...renamedViews.filter((it) => !it.to.isExisting).map((it) => { + return prepareSqliteCreateViewJson(it.to.name, it.to.definition!); + }), + ); + + const alteredViews = typedResult.alteredViews.filter((it) => !json2.views[it.name].isExisting); + + for (const alteredView of alteredViews) { + const { definition } = json2.views[alteredView.name]; + + if (alteredView.alteredExisting || (alteredView.alteredDefinition && action !== 'push')) { + dropViews.push(prepareDropViewJson(alteredView.name)); + + createViews.push( + prepareSqliteCreateViewJson( + alteredView.name, + definition!, + ), + ); + } + } + + const jsonStatements: JsonStatement[] = []; + jsonStatements.push(...jsonCreateTables); + + jsonStatements.push(...jsonDropTables); + jsonStatements.push(...jsonRenameTables); + jsonStatements.push(...jsonRenameColumnsStatements); + + jsonStatements.push(...jsonDroppedReferencesForAlteredTables); + jsonStatements.push(...jsonDeletedCheckConstraints); + + // Will need to drop indexes before changing any columns in table + // Then should go column alternations and then index creation + jsonStatements.push(...jsonDropIndexesForAllAlteredTables); + + jsonStatements.push(...jsonDeletedCompositePKs); + jsonStatements.push(...jsonTableAlternations); + jsonStatements.push(...jsonAddedCompositePKs); + jsonStatements.push(...jsonAddColumnsStatemets); + + jsonStatements.push(...jsonCreateIndexesForCreatedTables); + jsonStatements.push(...jsonCreateIndexesForAllAlteredTables); + + jsonStatements.push(...jsonCreatedCheckConstraints); + + jsonStatements.push(...jsonCreatedReferencesForAlteredTables); + + jsonStatements.push(...jsonDropColumnsStatemets); + + // jsonStatements.push(...jsonDeletedCompositePKs); + // jsonStatements.push(...jsonAddedCompositePKs); + jsonStatements.push(...jsonAlteredCompositePKs); + + jsonStatements.push(...jsonAlteredUniqueConstraints); + + jsonStatements.push(...dropViews); + jsonStatements.push(...createViews); + + const combinedJsonStatements = sqliteCombineStatements(jsonStatements, json2, action); + const sqlStatements = fromJson(combinedJsonStatements, 'sqlite'); + + const uniqueSqlStatements: string[] = []; + sqlStatements.forEach((ss) => { + if (!uniqueSqlStatements.includes(ss)) { + uniqueSqlStatements.push(ss); + } + }); + + const rTables = renamedTables.map((it) => { + return { from: it.from, to: it.to }; + }); + + const _meta = prepareMigrationMeta([], rTables, rColumns); + + return { + statements: combinedJsonStatements, + sqlStatements: uniqueSqlStatements, + _meta, + }; +}; + +export const applyLibSQLSnapshotsDiff = async ( + json1: SQLiteSchemaSquashed, + json2: SQLiteSchemaSquashed, + tablesResolver: ( + input: ResolverInput
, + ) => Promise>, + columnsResolver: ( + input: ColumnsResolverInput, + ) => Promise>, + viewsResolver: ( + input: ResolverInput, + ) => Promise>, + prevFull: SQLiteSchema, + curFull: SQLiteSchema, + action?: 'push', +): Promise<{ + statements: JsonStatement[]; + sqlStatements: string[]; + _meta: + | { + schemas: {}; + tables: {}; + columns: {}; + } + | undefined; +}> => { + const tablesDiff = diffSchemasOrTables(json1.tables, json2.tables); + const { + created: createdTables, + deleted: deletedTables, + renamed: renamedTables, + } = await tablesResolver({ + created: tablesDiff.added, + deleted: tablesDiff.deleted, + }); + + const tablesPatchedSnap1 = copy(json1); + tablesPatchedSnap1.tables = mapEntries(tablesPatchedSnap1.tables, (_, it) => { + const { name } = nameChangeFor(it, renamedTables); + it.name = name; + return [name, it]; + }); + + const res = diffColumns(tablesPatchedSnap1.tables, json2.tables); + + const columnRenames = [] as { + table: string; + renames: { from: Column; to: Column }[]; + }[]; + + const columnCreates = [] as { + table: string; + columns: Column[]; + }[]; + + const columnDeletes = [] as { + table: string; + columns: Column[]; + }[]; + + for (let entry of Object.values(res)) { + const { renamed, created, deleted } = await columnsResolver({ + tableName: entry.name, + schema: entry.schema, + deleted: entry.columns.deleted, + created: entry.columns.added, + }); + + if (created.length > 0) { + columnCreates.push({ + table: entry.name, + columns: created, + }); + } + + if (deleted.length > 0) { + columnDeletes.push({ + table: entry.name, + columns: deleted, + }); + } + + if (renamed.length > 0) { + columnRenames.push({ + table: entry.name, + renames: renamed, + }); + } + } + + const columnRenamesDict = columnRenames.reduce( + (acc, it) => { + acc[it.table] = it.renames; + return acc; + }, + {} as Record< + string, + { + from: Named; + to: Named; + }[] + >, + ); + + const columnsPatchedSnap1 = copy(tablesPatchedSnap1); + columnsPatchedSnap1.tables = mapEntries( + columnsPatchedSnap1.tables, + (tableKey, tableValue) => { + const patchedColumns = mapKeys( + tableValue.columns, + (columnKey, column) => { + const rens = columnRenamesDict[tableValue.name] || []; + const newName = columnChangeFor(columnKey, rens); + column.name = newName; + return newName; + }, + ); + + tableValue.columns = patchedColumns; + return [tableKey, tableValue]; + }, + ); + + const viewsDiff = diffSchemasOrTables(json1.views, json2.views); + + const { + created: createdViews, + deleted: deletedViews, + renamed: renamedViews, // renamed or moved + } = await viewsResolver({ + created: viewsDiff.added, + deleted: viewsDiff.deleted, + }); + + const renamesViewDic: Record = {}; + renamedViews.forEach((it) => { + renamesViewDic[it.from.name] = { to: it.to.name, from: it.from.name }; + }); + + const viewsPatchedSnap1 = copy(columnsPatchedSnap1); + viewsPatchedSnap1.views = mapEntries( + viewsPatchedSnap1.views, + (viewKey, viewValue) => { + const rename = renamesViewDic[viewValue.name]; + + if (rename) { + viewValue.name = rename.to; + } + + return [viewKey, viewValue]; + }, + ); + + const diffResult = applyJsonDiff(viewsPatchedSnap1, json2); + + const typedResult = diffResultSchemeSQLite.parse(diffResult); // Map array of objects to map const tablesMap: { @@ -1828,6 +3963,21 @@ export const applySqliteSnapshotsDiff = async ( }) .flat(); + const rColumns = jsonRenameColumnsStatements.map((it) => { + const tableName = it.tableName; + const schema = it.schema; + return { + from: { schema, table: tableName, column: it.oldColumnName }, + to: { schema, table: tableName, column: it.newColumnName }, + }; + }); + + const rTables = renamedTables.map((it) => { + return { from: it.from, to: it.to }; + }); + + const _meta = prepareMigrationMeta([], rTables, rColumns); + const allAltered = typedResult.alteredTablesWithColumns; const jsonAddedCompositePKs: JsonCreateCompositePK[] = []; @@ -1838,6 +3988,9 @@ export const applySqliteSnapshotsDiff = async ( const jsonDeletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; const jsonAlteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + const jsonDeletedCheckConstraints: JsonDeleteCheckConstraint[] = []; + const jsonCreatedCheckConstraints: JsonCreateCheckConstraint[] = []; + allAltered.forEach((it) => { // This part is needed to make sure that same columns in a table are not triggered for change // there is a case where orm and kit are responsible for pk name generation and one of them is not sorting name @@ -1883,11 +4036,15 @@ export const applySqliteSnapshotsDiff = async ( let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + let createdCheckConstraints: JsonCreateCheckConstraint[] = []; + let deletedCheckConstraints: JsonDeleteCheckConstraint[] = []; + addedUniqueConstraints = prepareAddUniqueConstraint( it.name, it.schema, it.addedUniqueConstraints, ); + deletedUniqueConstraints = prepareDeleteUniqueConstraint( it.name, it.schema, @@ -1908,6 +4065,26 @@ export const applySqliteSnapshotsDiff = async ( ); } + createdCheckConstraints = prepareAddCheckConstraint(it.name, it.schema, it.addedCheckConstraints); + deletedCheckConstraints = prepareDeleteCheckConstraint( + it.name, + it.schema, + it.deletedCheckConstraints, + ); + + // skip for push + if (it.alteredCheckConstraints && action !== 'push') { + const added: Record = {}; + const deleted: Record = {}; + + for (const k of Object.keys(it.alteredCheckConstraints)) { + added[k] = it.alteredCheckConstraints[k].__new; + deleted[k] = it.alteredCheckConstraints[k].__old; + } + createdCheckConstraints.push(...prepareAddCheckConstraint(it.name, it.schema, added)); + deletedCheckConstraints.push(...prepareDeleteCheckConstraint(it.name, it.schema, deleted)); + } + jsonAddedCompositePKs.push(...addedCompositePKs); jsonDeletedCompositePKs.push(...deletedCompositePKs); jsonAlteredCompositePKs.push(...alteredCompositePKs); @@ -1915,15 +4092,9 @@ export const applySqliteSnapshotsDiff = async ( jsonAddedUniqueConstraints.push(...addedUniqueConstraints); jsonDeletedUniqueConstraints.push(...deletedUniqueConstraints); jsonAlteredUniqueConstraints.push(...alteredUniqueConstraints); - }); - const rColumns = jsonRenameColumnsStatements.map((it) => { - const tableName = it.tableName; - const schema = it.schema; - return { - from: { schema, table: tableName, column: it.oldColumnName }, - to: { schema, table: tableName, column: it.newColumnName }, - }; + jsonCreatedCheckConstraints.push(...createdCheckConstraints); + jsonDeletedCheckConstraints.push(...deletedCheckConstraints); }); const jsonTableAlternations = allAltered @@ -1984,23 +4155,24 @@ export const applySqliteSnapshotsDiff = async ( const jsonReferencesForAllAlteredTables: JsonReferenceStatement[] = allAltered .map((it) => { - const forAdded = prepareCreateReferencesJson( + const forAdded = prepareLibSQLCreateReferencesJson( it.name, it.schema, it.addedForeignKeys, + json2, + action, ); - const forAltered = prepareDropReferencesJson( + const forAltered = prepareLibSQLDropReferencesJson( it.name, it.schema, it.deletedForeignKeys, + json2, + _meta, + action, ); - const alteredFKs = prepareAlterReferencesJson( - it.name, - it.schema, - it.alteredForeignKeys, - ); + const alteredFKs = prepareAlterReferencesJson(it.name, it.schema, it.alteredForeignKeys); return [...forAdded, ...forAltered, ...alteredFKs]; }) @@ -2013,6 +4185,53 @@ export const applySqliteSnapshotsDiff = async ( (t) => t.type === 'delete_reference', ); + const createViews: JsonCreateSqliteViewStatement[] = []; + const dropViews: JsonDropViewStatement[] = []; + + createViews.push( + ...createdViews.filter((it) => !it.isExisting).map((it) => { + return prepareSqliteCreateViewJson( + it.name, + it.definition!, + ); + }), + ); + + dropViews.push( + ...deletedViews.filter((it) => !it.isExisting).map((it) => { + return prepareDropViewJson(it.name); + }), + ); + + // renames + dropViews.push( + ...renamedViews.filter((it) => !it.to.isExisting).map((it) => { + return prepareDropViewJson(it.from.name); + }), + ); + createViews.push( + ...renamedViews.filter((it) => !it.to.isExisting).map((it) => { + return prepareSqliteCreateViewJson(it.to.name, it.to.definition!); + }), + ); + + const alteredViews = typedResult.alteredViews.filter((it) => !json2.views[it.name].isExisting); + + for (const alteredView of alteredViews) { + const { definition } = json2.views[alteredView.name]; + + if (alteredView.alteredExisting || (alteredView.alteredDefinition && action !== 'push')) { + dropViews.push(prepareDropViewJson(alteredView.name)); + + createViews.push( + prepareSqliteCreateViewJson( + alteredView.name, + definition!, + ), + ); + } + } + const jsonStatements: JsonStatement[] = []; jsonStatements.push(...jsonCreateTables); @@ -2022,6 +4241,8 @@ export const applySqliteSnapshotsDiff = async ( jsonStatements.push(...jsonDroppedReferencesForAlteredTables); + jsonStatements.push(...jsonDeletedCheckConstraints); + // Will need to drop indexes before changing any columns in table // Then should go column alternations and then index creation jsonStatements.push(...jsonDropIndexesForAllAlteredTables); @@ -2033,18 +4254,27 @@ export const applySqliteSnapshotsDiff = async ( jsonStatements.push(...jsonCreateIndexesForCreatedTables); jsonStatements.push(...jsonCreateIndexesForAllAlteredTables); + jsonStatements.push(...jsonCreatedCheckConstraints); + + jsonStatements.push(...dropViews); + jsonStatements.push(...createViews); jsonStatements.push(...jsonCreatedReferencesForAlteredTables); jsonStatements.push(...jsonDropColumnsStatemets); - // jsonStatements.push(...jsonDeletedCompositePKs); - // jsonStatements.push(...jsonAddedCompositePKs); jsonStatements.push(...jsonAlteredCompositePKs); jsonStatements.push(...jsonAlteredUniqueConstraints); - const sqlStatements = fromJson(jsonStatements, 'sqlite'); + const combinedJsonStatements = libSQLCombineStatements(jsonStatements, json2, action); + + const sqlStatements = fromJson( + combinedJsonStatements, + 'turso', + action, + json2, + ); const uniqueSqlStatements: string[] = []; sqlStatements.forEach((ss) => { @@ -2053,14 +4283,8 @@ export const applySqliteSnapshotsDiff = async ( } }); - const rTables = renamedTables.map((it) => { - return { from: it.from, to: it.to }; - }); - - const _meta = prepareMigrationMeta([], rTables, rColumns); - return { - statements: jsonStatements, + statements: combinedJsonStatements, sqlStatements: uniqueSqlStatements, _meta, }; diff --git a/drizzle-kit/src/sqlgenerator.ts b/drizzle-kit/src/sqlgenerator.ts index 769da7c5a..6d3034b61 100644 --- a/drizzle-kit/src/sqlgenerator.ts +++ b/drizzle-kit/src/sqlgenerator.ts @@ -20,114 +20,361 @@ import { JsonAlterColumnSetPrimaryKeyStatement, JsonAlterColumnTypeStatement, JsonAlterCompositePK, + JsonAlterIndPolicyStatement, + JsonAlterMySqlViewStatement, + JsonAlterPolicyStatement, JsonAlterReferenceStatement, + JsonAlterRoleStatement, JsonAlterSequenceStatement, JsonAlterTableRemoveFromSchema, JsonAlterTableSetNewSchema, JsonAlterTableSetSchema, - JsonAlterUniqueConstraint, + JsonAlterViewAddWithOptionStatement, + JsonAlterViewAlterSchemaStatement, + JsonAlterViewAlterTablespaceStatement, + JsonAlterViewAlterUsingStatement, + JsonAlterViewDropWithOptionStatement, + JsonCreateCheckConstraint, JsonCreateCompositePK, JsonCreateEnumStatement, JsonCreateIndexStatement, + JsonCreateIndPolicyStatement, + JsonCreateMySqlViewStatement, + JsonCreatePgViewStatement, + JsonCreatePolicyStatement, JsonCreateReferenceStatement, + JsonCreateRoleStatement, JsonCreateSchema, JsonCreateSequenceStatement, + JsonCreateSqliteViewStatement, JsonCreateTableStatement, JsonCreateUniqueConstraint, + JsonDeleteCheckConstraint, JsonDeleteCompositePK, JsonDeleteReferenceStatement, JsonDeleteUniqueConstraint, + JsonDisableRLSStatement, JsonDropColumnStatement, + JsonDropEnumStatement, JsonDropIndexStatement, + JsonDropIndPolicyStatement, + JsonDropPolicyStatement, + JsonDropRoleStatement, JsonDropSequenceStatement, JsonDropTableStatement, + JsonDropValueFromEnumStatement, + JsonDropViewStatement, + JsonEnableRLSStatement, + JsonIndRenamePolicyStatement, + JsonMoveEnumStatement, JsonMoveSequenceStatement, JsonPgCreateIndexStatement, + JsonRecreateTableStatement, JsonRenameColumnStatement, + JsonRenameEnumStatement, + JsonRenamePolicyStatement, + JsonRenameRoleStatement, JsonRenameSchema, JsonRenameSequenceStatement, JsonRenameTableStatement, + JsonRenameViewStatement, JsonSqliteAddColumnStatement, JsonSqliteCreateTableStatement, JsonStatement, } from './jsonStatements'; import { Dialect } from './schemaValidator'; import { MySqlSquasher } from './serializer/mysqlSchema'; -import { PgSquasher } from './serializer/pgSchema'; -import { SQLiteSquasher } from './serializer/sqliteSchema'; - -export const pgNativeTypes = new Set([ - 'uuid', - 'smallint', - 'integer', - 'bigint', - 'boolean', - 'text', - 'varchar', - 'serial', - 'bigserial', - 'decimal', - 'numeric', - 'real', - 'json', - 'jsonb', - 'time', - 'time with time zone', - 'time without time zone', - 'time', - 'timestamp', - 'timestamp with time zone', - 'timestamp without time zone', - 'date', - 'interval', - 'bigint', - 'bigserial', - 'double precision', - 'interval year', - 'interval month', - 'interval day', - 'interval hour', - 'interval minute', - 'interval second', - 'interval year to month', - 'interval day to hour', - 'interval day to minute', - 'interval day to second', - 'interval hour to minute', - 'interval hour to second', - 'interval minute to second', -]); - -const isPgNativeType = (it: string) => { - if (pgNativeTypes.has(it)) return true; - const toCheck = it.replace(/ /g, ''); - return ( - toCheck.startsWith('varchar(') - || toCheck.startsWith('char(') - || toCheck.startsWith('numeric(') - || toCheck.startsWith('timestamp(') - || toCheck.startsWith('intervalyear(') - || toCheck.startsWith('intervalmonth(') - || toCheck.startsWith('intervalday(') - || toCheck.startsWith('intervalhour(') - || toCheck.startsWith('intervalminute(') - || toCheck.startsWith('intervalsecond(') - || toCheck.startsWith('intervalyeartomonth(') - || toCheck.startsWith('intervaldaytohour(') - || toCheck.startsWith('intervaldaytominute(') - || toCheck.startsWith('intervaldaytosecond(') - || toCheck.startsWith('intervalhourtominute(') - || toCheck.startsWith('intervalhourtosecond(') - || toCheck.startsWith('intervalminutetosecond(') - || toCheck.startsWith('vector(') - || toCheck.startsWith('geometry(') - || /^(\w+)(\[\d*])+$/.test(it) - ); +import { PgSquasher, policy } from './serializer/pgSchema'; +import { SingleStoreSquasher } from './serializer/singlestoreSchema'; +import { SQLiteSchemaSquashed, SQLiteSquasher } from './serializer/sqliteSchema'; + +import { escapeSingleQuotes } from './utils'; + +const parseType = (schemaPrefix: string, type: string) => { + const pgNativeTypes = [ + 'uuid', + 'smallint', + 'integer', + 'bigint', + 'boolean', + 'text', + 'varchar', + 'serial', + 'bigserial', + 'decimal', + 'numeric', + 'real', + 'json', + 'jsonb', + 'time', + 'time with time zone', + 'time without time zone', + 'time', + 'timestamp', + 'timestamp with time zone', + 'timestamp without time zone', + 'date', + 'interval', + 'bigint', + 'bigserial', + 'double precision', + 'interval year', + 'interval month', + 'interval day', + 'interval hour', + 'interval minute', + 'interval second', + 'interval year to month', + 'interval day to hour', + 'interval day to minute', + 'interval day to second', + 'interval hour to minute', + 'interval hour to second', + 'interval minute to second', + 'char', + 'vector', + 'geometry', + ]; + const arrayDefinitionRegex = /\[\d*(?:\[\d*\])*\]/g; + const arrayDefinition = (type.match(arrayDefinitionRegex) ?? []).join(''); + const withoutArrayDefinition = type.replace(arrayDefinitionRegex, ''); + return pgNativeTypes.some((it) => type.startsWith(it)) + ? `${withoutArrayDefinition}${arrayDefinition}` + : `${schemaPrefix}"${withoutArrayDefinition}"${arrayDefinition}`; }; abstract class Convertor { - abstract can(statement: JsonStatement, dialect: Dialect): boolean; - abstract convert(statement: JsonStatement): string | string[]; + abstract can( + statement: JsonStatement, + dialect: Dialect, + ): boolean; + abstract convert( + statement: JsonStatement, + json2?: SQLiteSchemaSquashed, + action?: 'push', + ): string | string[]; +} + +class PgCreateRoleConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_role' && dialect === 'postgresql'; + } + override convert(statement: JsonCreateRoleStatement): string | string[] { + return `CREATE ROLE "${statement.name}"${ + statement.values.createDb || statement.values.createRole || !statement.values.inherit + ? ` WITH${statement.values.createDb ? ' CREATEDB' : ''}${statement.values.createRole ? ' CREATEROLE' : ''}${ + statement.values.inherit ? '' : ' NOINHERIT' + }` + : '' + };`; + } +} + +class PgDropRoleConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_role' && dialect === 'postgresql'; + } + override convert(statement: JsonDropRoleStatement): string | string[] { + return `DROP ROLE "${statement.name}";`; + } +} + +class PgRenameRoleConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'rename_role' && dialect === 'postgresql'; + } + override convert(statement: JsonRenameRoleStatement): string | string[] { + return `ALTER ROLE "${statement.nameFrom}" RENAME TO "${statement.nameTo}";`; + } +} + +class PgAlterRoleConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_role' && dialect === 'postgresql'; + } + override convert(statement: JsonAlterRoleStatement): string | string[] { + return `ALTER ROLE "${statement.name}"${` WITH${statement.values.createDb ? ' CREATEDB' : ' NOCREATEDB'}${ + statement.values.createRole ? ' CREATEROLE' : ' NOCREATEROLE' + }${statement.values.inherit ? ' INHERIT' : ' NOINHERIT'}`};`; + } +} + +///// + +class PgCreatePolicyConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_policy' && dialect === 'postgresql'; + } + override convert(statement: JsonCreatePolicyStatement): string | string[] { + const policy = statement.data; + + const tableNameWithSchema = statement.schema + ? `"${statement.schema}"."${statement.tableName}"` + : `"${statement.tableName}"`; + + const usingPart = policy.using ? ` USING (${policy.using})` : ''; + + const withCheckPart = policy.withCheck ? ` WITH CHECK (${policy.withCheck})` : ''; + + const policyToPart = policy.to?.map((v) => + ['current_user', 'current_role', 'session_user', 'public'].includes(v) ? v : `"${v}"` + ).join(', '); + + return `CREATE POLICY "${policy.name}" ON ${tableNameWithSchema} AS ${policy.as?.toUpperCase()} FOR ${policy.for?.toUpperCase()} TO ${policyToPart}${usingPart}${withCheckPart};`; + } +} + +class PgDropPolicyConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_policy' && dialect === 'postgresql'; + } + override convert(statement: JsonDropPolicyStatement): string | string[] { + const policy = statement.data; + + const tableNameWithSchema = statement.schema + ? `"${statement.schema}"."${statement.tableName}"` + : `"${statement.tableName}"`; + + return `DROP POLICY "${policy.name}" ON ${tableNameWithSchema} CASCADE;`; + } +} + +class PgRenamePolicyConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'rename_policy' && dialect === 'postgresql'; + } + override convert(statement: JsonRenamePolicyStatement): string | string[] { + const tableNameWithSchema = statement.schema + ? `"${statement.schema}"."${statement.tableName}"` + : `"${statement.tableName}"`; + + return `ALTER POLICY "${statement.oldName}" ON ${tableNameWithSchema} RENAME TO "${statement.newName}";`; + } +} + +class PgAlterPolicyConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_policy' && dialect === 'postgresql'; + } + override convert(statement: JsonAlterPolicyStatement, _dialect: any, action?: string): string | string[] { + const newPolicy = action === 'push' + ? PgSquasher.unsquashPolicyPush(statement.newData) + : PgSquasher.unsquashPolicy(statement.newData); + const oldPolicy = action === 'push' + ? PgSquasher.unsquashPolicyPush(statement.oldData) + : PgSquasher.unsquashPolicy(statement.oldData); + + const tableNameWithSchema = statement.schema + ? `"${statement.schema}"."${statement.tableName}"` + : `"${statement.tableName}"`; + + const usingPart = newPolicy.using + ? ` USING (${newPolicy.using})` + : oldPolicy.using + ? ` USING (${oldPolicy.using})` + : ''; + + const withCheckPart = newPolicy.withCheck + ? ` WITH CHECK (${newPolicy.withCheck})` + : oldPolicy.withCheck + ? ` WITH CHECK (${oldPolicy.withCheck})` + : ''; + + return `ALTER POLICY "${oldPolicy.name}" ON ${tableNameWithSchema} TO ${newPolicy.to}${usingPart}${withCheckPart};`; + } +} + +//// + +class PgCreateIndPolicyConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_ind_policy' && dialect === 'postgresql'; + } + override convert(statement: JsonCreateIndPolicyStatement): string | string[] { + const policy = statement.data; + + const usingPart = policy.using ? ` USING (${policy.using})` : ''; + + const withCheckPart = policy.withCheck ? ` WITH CHECK (${policy.withCheck})` : ''; + + const policyToPart = policy.to?.map((v) => + ['current_user', 'current_role', 'session_user', 'public'].includes(v) ? v : `"${v}"` + ).join(', '); + + return `CREATE POLICY "${policy.name}" ON ${policy.on} AS ${policy.as?.toUpperCase()} FOR ${policy.for?.toUpperCase()} TO ${policyToPart}${usingPart}${withCheckPart};`; + } +} + +class PgDropIndPolicyConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_ind_policy' && dialect === 'postgresql'; + } + override convert(statement: JsonDropIndPolicyStatement): string | string[] { + const policy = statement.data; + + return `DROP POLICY "${policy.name}" ON ${policy.on} CASCADE;`; + } +} + +class PgRenameIndPolicyConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'rename_ind_policy' && dialect === 'postgresql'; + } + override convert(statement: JsonIndRenamePolicyStatement): string | string[] { + return `ALTER POLICY "${statement.oldName}" ON ${statement.tableKey} RENAME TO "${statement.newName}";`; + } +} + +class PgAlterIndPolicyConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_ind_policy' && dialect === 'postgresql'; + } + override convert(statement: JsonAlterIndPolicyStatement): string | string[] { + const newPolicy = statement.newData; + const oldPolicy = statement.oldData; + + const usingPart = newPolicy.using + ? ` USING (${newPolicy.using})` + : oldPolicy.using + ? ` USING (${oldPolicy.using})` + : ''; + + const withCheckPart = newPolicy.withCheck + ? ` WITH CHECK (${newPolicy.withCheck})` + : oldPolicy.withCheck + ? ` WITH CHECK (${oldPolicy.withCheck})` + : ''; + + return `ALTER POLICY "${oldPolicy.name}" ON ${oldPolicy.on} TO ${newPolicy.to}${usingPart}${withCheckPart};`; + } +} + +//// + +class PgEnableRlsConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'enable_rls' && dialect === 'postgresql'; + } + override convert(statement: JsonEnableRLSStatement): string { + const tableNameWithSchema = statement.schema + ? `"${statement.schema}"."${statement.tableName}"` + : `"${statement.tableName}"`; + + return `ALTER TABLE ${tableNameWithSchema} ENABLE ROW LEVEL SECURITY;`; + } +} + +class PgDisableRlsConvertor extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'disable_rls' && dialect === 'postgresql'; + } + override convert(statement: JsonDisableRLSStatement): string { + const tableNameWithSchema = statement.schema + ? `"${statement.schema}"."${statement.tableName}"` + : `"${statement.tableName}"`; + + return `ALTER TABLE ${tableNameWithSchema} DISABLE ROW LEVEL SECURITY;`; + } } class PgCreateTableConvertor extends Convertor { @@ -136,12 +383,13 @@ class PgCreateTableConvertor extends Convertor { } convert(st: JsonCreateTableStatement) { - const { tableName, schema, columns, compositePKs, uniqueConstraints } = st; + const { tableName, schema, columns, compositePKs, uniqueConstraints, checkConstraints, policies, isRLSEnabled } = + st; let statement = ''; const name = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`; - statement += `CREATE TABLE IF NOT EXISTS ${name} (\n`; + statement += `CREATE TABLE ${name} (\n`; for (let i = 0; i < columns.length; i++) { const column = columns[i]; @@ -157,12 +405,10 @@ class PgCreateTableConvertor extends Convertor { ? `"${column.typeSchema}".` : ''; - const type = isPgNativeType(column.type) - ? column.type - : `${schemaPrefix}"${column.type}"`; + const type = parseType(schemaPrefix, column.type); const generated = column.generated; - const generatedStatement = ` GENERATED ALWAYS AS (${generated?.as}) STORED`; + const generatedStatement = generated ? ` GENERATED ALWAYS AS (${generated?.as}) STORED` : ''; const unsquashedIdentity = column.identity ? PgSquasher.unsquashIdentity(column.identity) @@ -197,9 +443,7 @@ class PgCreateTableConvertor extends Convertor { : ''; statement += '\t' - + `"${column.name}" ${type}${primaryKeyStatement}${defaultStatement}${notNullStatement}${uniqueConstraint}${ - generated ? generatedStatement : '' - }${identity}`; + + `"${column.name}" ${type}${primaryKeyStatement}${defaultStatement}${generatedStatement}${notNullStatement}${uniqueConstraint}${identity}`; statement += i === columns.length - 1 ? '' : ',\n'; } @@ -223,10 +467,25 @@ class PgCreateTableConvertor extends Convertor { // statement += `\n`; } } + + if (typeof checkConstraints !== 'undefined' && checkConstraints.length > 0) { + for (const checkConstraint of checkConstraints) { + statement += ',\n'; + const unsquashedCheck = PgSquasher.unsquashCheck(checkConstraint); + statement += `\tCONSTRAINT "${unsquashedCheck.name}" CHECK (${unsquashedCheck.value})`; + } + } + statement += `\n);`; statement += `\n`; - return statement; + const enableRls = new PgEnableRlsConvertor().convert({ + type: 'enable_rls', + tableName, + schema, + }); + + return [statement, ...(policies && policies.length > 0 || isRLSEnabled ? [enableRls] : [])]; } } @@ -240,6 +499,7 @@ class MySqlCreateTableConvertor extends Convertor { tableName, columns, schema, + checkConstraints, compositePKs, uniqueConstraints, internals, @@ -267,7 +527,7 @@ class MySqlCreateTableConvertor extends Convertor { : ''; statement += '\t' - + `\`${column.name}\` ${column.type}${autoincrementStatement}${primaryKeyStatement}${notNullStatement}${defaultStatement}${onUpdateStatement}${generatedStatement}`; + + `\`${column.name}\` ${column.type}${autoincrementStatement}${primaryKeyStatement}${generatedStatement}${notNullStatement}${defaultStatement}${onUpdateStatement}`; statement += i === columns.length - 1 ? '' : ',\n'; } @@ -300,6 +560,90 @@ class MySqlCreateTableConvertor extends Convertor { } } + if (typeof checkConstraints !== 'undefined' && checkConstraints.length > 0) { + for (const checkConstraint of checkConstraints) { + statement += ',\n'; + const unsquashedCheck = MySqlSquasher.unsquashCheck(checkConstraint); + + statement += `\tCONSTRAINT \`${unsquashedCheck.name}\` CHECK(${unsquashedCheck.value})`; + } + } + + statement += `\n);`; + statement += `\n`; + return statement; + } +} +class SingleStoreCreateTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_table' && dialect === 'singlestore'; + } + + convert(st: JsonCreateTableStatement) { + const { + tableName, + columns, + schema, + compositePKs, + uniqueConstraints, + internals, + } = st; + + let statement = ''; + statement += `CREATE TABLE \`${tableName}\` (\n`; + for (let i = 0; i < columns.length; i++) { + const column = columns[i]; + + const primaryKeyStatement = column.primaryKey ? ' PRIMARY KEY' : ''; + const notNullStatement = column.notNull ? ' NOT NULL' : ''; + const defaultStatement = column.default !== undefined ? ` DEFAULT ${column.default}` : ''; + + const onUpdateStatement = column.onUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + + const autoincrementStatement = column.autoincrement + ? ' AUTO_INCREMENT' + : ''; + + const generatedStatement = column.generated + ? ` GENERATED ALWAYS AS (${column.generated?.as}) ${column.generated?.type.toUpperCase()}` + : ''; + + statement += '\t' + + `\`${column.name}\` ${column.type}${autoincrementStatement}${primaryKeyStatement}${notNullStatement}${defaultStatement}${onUpdateStatement}${generatedStatement}`; + statement += i === columns.length - 1 ? '' : ',\n'; + } + + if (typeof compositePKs !== 'undefined' && compositePKs.length > 0) { + statement += ',\n'; + const compositePK = SingleStoreSquasher.unsquashPK(compositePKs[0]); + statement += `\tCONSTRAINT \`${st.compositePkName}\` PRIMARY KEY(\`${compositePK.columns.join(`\`,\``)}\`)`; + } + + if ( + typeof uniqueConstraints !== 'undefined' + && uniqueConstraints.length > 0 + ) { + for (const uniqueConstraint of uniqueConstraints) { + statement += ',\n'; + const unsquashedUnique = SingleStoreSquasher.unsquashUnique(uniqueConstraint); + + const uniqueString = unsquashedUnique.columns + .map((it) => { + return internals?.indexes + ? internals?.indexes[unsquashedUnique.name]?.columns[it] + ?.isExpression + ? it + : `\`${it}\`` + : `\`${it}\``; + }) + .join(','); + + statement += `\tCONSTRAINT \`${unsquashedUnique.name}\` UNIQUE(${uniqueString})`; + } + } + statement += `\n);`; statement += `\n`; return statement; @@ -308,7 +652,7 @@ class MySqlCreateTableConvertor extends Convertor { export class SQLiteCreateTableConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'sqlite_create_table' && dialect === 'sqlite'; + return statement.type === 'sqlite_create_table' && (dialect === 'sqlite' || dialect === 'turso'); } convert(st: JsonSqliteCreateTableStatement) { @@ -318,6 +662,7 @@ export class SQLiteCreateTableConvertor extends Convertor { referenceData, compositePKs, uniqueConstraints, + checkConstraints, } = st; let statement = ''; @@ -339,7 +684,7 @@ export class SQLiteCreateTableConvertor extends Convertor { statement += '\t'; statement += - `\`${column.name}\` ${column.type}${primaryKeyStatement}${autoincrementStatement}${defaultStatement}${notNullStatement}${generatedStatement}`; + `\`${column.name}\` ${column.type}${primaryKeyStatement}${autoincrementStatement}${defaultStatement}${generatedStatement}${notNullStatement}`; statement += i === columns.length - 1 ? '' : ',\n'; } @@ -377,11 +722,22 @@ export class SQLiteCreateTableConvertor extends Convertor { ) { for (const uniqueConstraint of uniqueConstraints) { statement += ',\n'; - const unsquashedUnique = MySqlSquasher.unsquashUnique(uniqueConstraint); + const unsquashedUnique = SQLiteSquasher.unsquashUnique(uniqueConstraint); statement += `\tCONSTRAINT ${unsquashedUnique.name} UNIQUE(\`${unsquashedUnique.columns.join(`\`,\``)}\`)`; } } + if ( + typeof checkConstraints !== 'undefined' + && checkConstraints.length > 0 + ) { + for (const check of checkConstraints) { + statement += ',\n'; + const { value, name } = SQLiteSquasher.unsquashCheck(check); + statement += `\tCONSTRAINT "${name}" CHECK(${value})`; + } + } + statement += `\n`; statement += `);`; statement += `\n`; @@ -389,57 +745,307 @@ export class SQLiteCreateTableConvertor extends Convertor { } } -class PgAlterTableAlterColumnSetGenerated extends Convertor { - override can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_identity' - && dialect === 'postgresql' - ); +class PgCreateViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_view' && dialect === 'postgresql'; } - override convert( - statement: JsonAlterColumnSetIdentityStatement, - ): string | string[] { - const { identity, tableName, columnName, schema } = statement; - const tableNameWithSchema = schema - ? `"${schema}"."${tableName}"` - : `"${tableName}"`; + convert(st: JsonCreatePgViewStatement) { + const { definition, name: viewName, schema, with: withOption, materialized, withNoData, tablespace, using } = st; - const unsquashedIdentity = PgSquasher.unsquashIdentity(identity); + const name = schema ? `"${schema}"."${viewName}"` : `"${viewName}"`; - const identityWithSchema = schema - ? `"${schema}"."${unsquashedIdentity?.name}"` - : `"${unsquashedIdentity?.name}"`; + let statement = materialized ? `CREATE MATERIALIZED VIEW ${name}` : `CREATE VIEW ${name}`; - const identityStatement = unsquashedIdentity - ? ` GENERATED ${ - unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' - } AS IDENTITY (sequence name ${identityWithSchema}${ - unsquashedIdentity.increment - ? ` INCREMENT BY ${unsquashedIdentity.increment}` - : '' - }${ - unsquashedIdentity.minValue - ? ` MINVALUE ${unsquashedIdentity.minValue}` - : '' - }${ - unsquashedIdentity.maxValue - ? ` MAXVALUE ${unsquashedIdentity.maxValue}` - : '' - }${ - unsquashedIdentity.startWith - ? ` START WITH ${unsquashedIdentity.startWith}` - : '' - }${unsquashedIdentity.cache ? ` CACHE ${unsquashedIdentity.cache}` : ''}${ - unsquashedIdentity.cycle ? ` CYCLE` : '' - })` - : ''; + if (using) statement += ` USING "${using}"`; - return `ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${columnName}" ADD${identityStatement};`; - } -} + const options: string[] = []; + if (withOption) { + statement += ` WITH (`; -class PgAlterTableAlterColumnDropGenerated extends Convertor { + Object.entries(withOption).forEach(([key, value]) => { + if (typeof value === 'undefined') return; + + options.push(`${key.snake_case()} = ${value}`); + }); + + statement += options.join(', '); + + statement += `)`; + } + + if (tablespace) statement += ` TABLESPACE ${tablespace}`; + + statement += ` AS (${definition})`; + + if (withNoData) statement += ` WITH NO DATA`; + + statement += `;`; + + return statement; + } +} + +class MySqlCreateViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'mysql_create_view' && dialect === 'mysql'; + } + + convert(st: JsonCreateMySqlViewStatement) { + const { definition, name, algorithm, sqlSecurity, withCheckOption, replace } = st; + + let statement = `CREATE `; + statement += replace ? `OR REPLACE ` : ''; + statement += algorithm ? `ALGORITHM = ${algorithm}\n` : ''; + statement += sqlSecurity ? `SQL SECURITY ${sqlSecurity}\n` : ''; + statement += `VIEW \`${name}\` AS (${definition})`; + statement += withCheckOption ? `\nWITH ${withCheckOption} CHECK OPTION` : ''; + + statement += ';'; + + return statement; + } +} + +class SqliteCreateViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'sqlite_create_view' && (dialect === 'sqlite' || dialect === 'turso'); + } + + convert(st: JsonCreateSqliteViewStatement) { + const { definition, name } = st; + + return `CREATE VIEW \`${name}\` AS ${definition};`; + } +} + +class PgDropViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_view' && dialect === 'postgresql'; + } + + convert(st: JsonDropViewStatement) { + const { name: viewName, schema, materialized } = st; + + const name = schema ? `"${schema}"."${viewName}"` : `"${viewName}"`; + + return `DROP${materialized ? ' MATERIALIZED' : ''} VIEW ${name};`; + } +} + +class MySqlDropViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_view' && dialect === 'mysql'; + } + + convert(st: JsonDropViewStatement) { + const { name } = st; + + return `DROP VIEW \`${name}\`;`; + } +} + +class SqliteDropViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_view' && (dialect === 'sqlite' || dialect === 'turso'); + } + + convert(st: JsonDropViewStatement) { + const { name } = st; + + return `DROP VIEW \`${name}\`;`; + } +} + +class MySqlAlterViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_mysql_view' && dialect === 'mysql'; + } + + convert(st: JsonAlterMySqlViewStatement) { + const { name, algorithm, definition, sqlSecurity, withCheckOption } = st; + + let statement = `ALTER `; + statement += algorithm ? `ALGORITHM = ${algorithm}\n` : ''; + statement += sqlSecurity ? `SQL SECURITY ${sqlSecurity}\n` : ''; + statement += `VIEW \`${name}\` AS ${definition}`; + statement += withCheckOption ? `\nWITH ${withCheckOption} CHECK OPTION` : ''; + + statement += ';'; + + return statement; + } +} + +class PgRenameViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'rename_view' && dialect === 'postgresql'; + } + + convert(st: JsonRenameViewStatement) { + const { nameFrom: from, nameTo: to, schema, materialized } = st; + + const nameFrom = `"${schema}"."${from}"`; + + return `ALTER${materialized ? ' MATERIALIZED' : ''} VIEW ${nameFrom} RENAME TO "${to}";`; + } +} + +class MySqlRenameViewConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'rename_view' && dialect === 'mysql'; + } + + convert(st: JsonRenameViewStatement) { + const { nameFrom: from, nameTo: to } = st; + + return `RENAME TABLE \`${from}\` TO \`${to}\`;`; + } +} + +class PgAlterViewSchemaConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_view_alter_schema' && dialect === 'postgresql'; + } + + convert(st: JsonAlterViewAlterSchemaStatement) { + const { fromSchema, toSchema, name, materialized } = st; + + const statement = `ALTER${ + materialized ? ' MATERIALIZED' : '' + } VIEW "${fromSchema}"."${name}" SET SCHEMA "${toSchema}";`; + + return statement; + } +} + +class PgAlterViewAddWithOptionConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_view_add_with_option' && dialect === 'postgresql'; + } + + convert(st: JsonAlterViewAddWithOptionStatement) { + const { schema, with: withOption, name, materialized } = st; + + let statement = `ALTER${materialized ? ' MATERIALIZED' : ''} VIEW "${schema}"."${name}" SET (`; + + const options: string[] = []; + + Object.entries(withOption).forEach(([key, value]) => { + options.push(`${key.snake_case()} = ${value}`); + }); + + statement += options.join(', '); + + statement += `);`; + + return statement; + } +} + +class PgAlterViewDropWithOptionConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_view_drop_with_option' && dialect === 'postgresql'; + } + + convert(st: JsonAlterViewDropWithOptionStatement) { + const { schema, name, materialized, with: withOptions } = st; + + let statement = `ALTER${materialized ? ' MATERIALIZED' : ''} VIEW "${schema}"."${name}" RESET (`; + + const options: string[] = []; + + Object.entries(withOptions).forEach(([key, value]) => { + options.push(`${key.snake_case()}`); + }); + + statement += options.join(', '); + + statement += ');'; + + return statement; + } +} + +class PgAlterViewAlterTablespaceConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_view_alter_tablespace' && dialect === 'postgresql'; + } + + convert(st: JsonAlterViewAlterTablespaceStatement) { + const { schema, name, toTablespace } = st; + + const statement = `ALTER MATERIALIZED VIEW "${schema}"."${name}" SET TABLESPACE ${toTablespace};`; + + return statement; + } +} + +class PgAlterViewAlterUsingConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_view_alter_using' && dialect === 'postgresql'; + } + + convert(st: JsonAlterViewAlterUsingStatement) { + const { schema, name, toUsing } = st; + + const statement = `ALTER MATERIALIZED VIEW "${schema}"."${name}" SET ACCESS METHOD "${toUsing}";`; + + return statement; + } +} + +class PgAlterTableAlterColumnSetGenerated extends Convertor { + override can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_alter_column_set_identity' + && dialect === 'postgresql' + ); + } + override convert( + statement: JsonAlterColumnSetIdentityStatement, + ): string | string[] { + const { identity, tableName, columnName, schema } = statement; + + const tableNameWithSchema = schema + ? `"${schema}"."${tableName}"` + : `"${tableName}"`; + + const unsquashedIdentity = PgSquasher.unsquashIdentity(identity); + + const identityWithSchema = schema + ? `"${schema}"."${unsquashedIdentity?.name}"` + : `"${unsquashedIdentity?.name}"`; + + const identityStatement = unsquashedIdentity + ? ` GENERATED ${ + unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' + } AS IDENTITY (sequence name ${identityWithSchema}${ + unsquashedIdentity.increment + ? ` INCREMENT BY ${unsquashedIdentity.increment}` + : '' + }${ + unsquashedIdentity.minValue + ? ` MINVALUE ${unsquashedIdentity.minValue}` + : '' + }${ + unsquashedIdentity.maxValue + ? ` MAXVALUE ${unsquashedIdentity.maxValue}` + : '' + }${ + unsquashedIdentity.startWith + ? ` START WITH ${unsquashedIdentity.startWith}` + : '' + }${unsquashedIdentity.cache ? ` CACHE ${unsquashedIdentity.cache}` : ''}${ + unsquashedIdentity.cycle ? ` CYCLE` : '' + })` + : ''; + + return `ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${columnName}" ADD${identityStatement};`; + } +} + +class PgAlterTableAlterColumnDropGenerated extends Convertor { override can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_drop_identity' @@ -566,6 +1172,38 @@ class PgAlterTableDropUniqueConstraintConvertor extends Convertor { } } +class PgAlterTableAddCheckConstraintConvertor extends Convertor { + can(statement: JsonCreateCheckConstraint, dialect: Dialect): boolean { + return ( + statement.type === 'create_check_constraint' && dialect === 'postgresql' + ); + } + convert(statement: JsonCreateCheckConstraint): string { + const unsquashed = PgSquasher.unsquashCheck(statement.data); + + const tableNameWithSchema = statement.schema + ? `"${statement.schema}"."${statement.tableName}"` + : `"${statement.tableName}"`; + + return `ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${unsquashed.name}" CHECK (${unsquashed.value});`; + } +} + +class PgAlterTableDeleteCheckConstraintConvertor extends Convertor { + can(statement: JsonDeleteCheckConstraint, dialect: Dialect): boolean { + return ( + statement.type === 'delete_check_constraint' && dialect === 'postgresql' + ); + } + convert(statement: JsonDeleteCheckConstraint): string { + const tableNameWithSchema = statement.schema + ? `"${statement.schema}"."${statement.tableName}"` + : `"${statement.tableName}"`; + + return `ALTER TABLE ${tableNameWithSchema} DROP CONSTRAINT "${statement.constraintName}";`; + } +} + class MySQLAlterTableAddUniqueConstraintConvertor extends Convertor { can(statement: JsonCreateUniqueConstraint, dialect: Dialect): boolean { return statement.type === 'create_unique_constraint' && dialect === 'mysql'; @@ -590,36 +1228,54 @@ class MySQLAlterTableDropUniqueConstraintConvertor extends Convertor { } } -class SQLiteAlterTableAddUniqueConstraintConvertor extends Convertor { - can(statement: JsonCreateUniqueConstraint, dialect: Dialect): boolean { +class MySqlAlterTableAddCheckConstraintConvertor extends Convertor { + can(statement: JsonCreateCheckConstraint, dialect: Dialect): boolean { return ( - statement.type === 'create_unique_constraint' && dialect === 'sqlite' + statement.type === 'create_check_constraint' && dialect === 'mysql' ); } - convert(statement: JsonCreateUniqueConstraint): string { - return ( - '/*\n SQLite does not support "Adding unique constraint to an existing table" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/unique.php' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); + convert(statement: JsonCreateCheckConstraint): string { + const unsquashed = MySqlSquasher.unsquashCheck(statement.data); + const { tableName } = statement; + + return `ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${unsquashed.name}\` CHECK (${unsquashed.value});`; } } -class SQLiteAlterTableDropUniqueConstraintConvertor extends Convertor { +class SingleStoreAlterTableAddUniqueConstraintConvertor extends Convertor { + can(statement: JsonCreateUniqueConstraint, dialect: Dialect): boolean { + return statement.type === 'create_unique_constraint' && dialect === 'singlestore'; + } + convert(statement: JsonCreateUniqueConstraint): string { + const unsquashed = SingleStoreSquasher.unsquashUnique(statement.data); + + return `ALTER TABLE \`${statement.tableName}\` ADD CONSTRAINT \`${unsquashed.name}\` UNIQUE(\`${ + unsquashed.columns.join('`,`') + }\`);`; + } +} +class SingleStoreAlterTableDropUniqueConstraintConvertor extends Convertor { can(statement: JsonDeleteUniqueConstraint, dialect: Dialect): boolean { - return ( - statement.type === 'delete_unique_constraint' && dialect === 'sqlite' - ); + return statement.type === 'delete_unique_constraint' && dialect === 'singlestore'; } convert(statement: JsonDeleteUniqueConstraint): string { + const unsquashed = SingleStoreSquasher.unsquashUnique(statement.data); + + return `ALTER TABLE \`${statement.tableName}\` DROP INDEX \`${unsquashed.name}\`;`; + } +} + +class MySqlAlterTableDeleteCheckConstraintConvertor extends Convertor { + can(statement: JsonDeleteCheckConstraint, dialect: Dialect): boolean { return ( - '/*\n SQLite does not support "Dropping unique constraint from an existing table" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/unique.php' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' + statement.type === 'delete_check_constraint' && dialect === 'mysql' ); } + convert(statement: JsonDeleteCheckConstraint): string { + const { tableName } = statement; + + return `ALTER TABLE \`${tableName}\` DROP CONSTRAINT \`${statement.constraintName}\`;`; + } } class CreatePgSequenceConvertor extends Convertor { @@ -719,51 +1375,148 @@ class CreateTypeEnumConvertor extends Convertor { convert(st: JsonCreateEnumStatement) { const { name, values, schema } = st; - const tableNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; + const enumNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; let valuesStatement = '('; - valuesStatement += values.map((it) => `'${it}'`).join(', '); + valuesStatement += values.map((it) => `'${escapeSingleQuotes(it)}'`).join(', '); valuesStatement += ')'; - let statement = 'DO $$ BEGIN'; - statement += '\n'; - statement += ` CREATE TYPE ${tableNameWithSchema} AS ENUM${valuesStatement};`; - statement += '\n'; - statement += 'EXCEPTION'; - statement += '\n'; - statement += ' WHEN duplicate_object THEN null;'; - statement += '\n'; - statement += 'END $$;'; - statement += '\n'; + // TODO do we need this? + // let statement = 'DO $$ BEGIN'; + // statement += '\n'; + let statement = `CREATE TYPE ${enumNameWithSchema} AS ENUM${valuesStatement};`; + // statement += '\n'; + // statement += 'EXCEPTION'; + // statement += '\n'; + // statement += ' WHEN duplicate_object THEN null;'; + // statement += '\n'; + // statement += 'END $$;'; + // statement += '\n'; return statement; } } -class AlterTypeAddValueConvertor extends Convertor { +class DropTypeEnumConvertor extends Convertor { can(statement: JsonStatement): boolean { - return statement.type === 'alter_type_add_value'; + return statement.type === 'drop_type_enum'; } - convert(st: JsonAddValueToEnumStatement) { - const { name, schema, value } = st; - const schemaPrefix = schema && schema !== 'public' ? `"${schema}".` : ''; - return `ALTER TYPE ${schemaPrefix}"${name}" ADD VALUE '${value}';`; + convert(st: JsonDropEnumStatement) { + const { name, schema } = st; + + const enumNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; + + let statement = `DROP TYPE ${enumNameWithSchema};`; + + return statement; } } -class PgDropTableConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'drop_table' && dialect === 'postgresql'; +class AlterTypeAddValueConvertor extends Convertor { + can(statement: JsonStatement): boolean { + return statement.type === 'alter_type_add_value'; } - convert(statement: JsonDropTableStatement) { - const { tableName, schema } = statement; + convert(st: JsonAddValueToEnumStatement) { + const { name, schema, value, before } = st; - const tableNameWithSchema = schema - ? `"${schema}"."${tableName}"` - : `"${tableName}"`; + const enumNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; - return `DROP TABLE ${tableNameWithSchema};`; + return `ALTER TYPE ${enumNameWithSchema} ADD VALUE '${value}'${before.length ? ` BEFORE '${before}'` : ''};`; + } +} + +class AlterTypeSetSchemaConvertor extends Convertor { + can(statement: JsonStatement): boolean { + return statement.type === 'move_type_enum'; + } + + convert(st: JsonMoveEnumStatement) { + const { name, schemaFrom, schemaTo } = st; + + const enumNameWithSchema = schemaFrom ? `"${schemaFrom}"."${name}"` : `"${name}"`; + + return `ALTER TYPE ${enumNameWithSchema} SET SCHEMA "${schemaTo}";`; + } +} + +class AlterRenameTypeConvertor extends Convertor { + can(statement: JsonStatement): boolean { + return statement.type === 'rename_type_enum'; + } + + convert(st: JsonRenameEnumStatement) { + const { nameTo, nameFrom, schema } = st; + + const enumNameWithSchema = schema ? `"${schema}"."${nameFrom}"` : `"${nameFrom}"`; + + return `ALTER TYPE ${enumNameWithSchema} RENAME TO "${nameTo}";`; + } +} + +class AlterTypeDropValueConvertor extends Convertor { + can(statement: JsonStatement): boolean { + return statement.type === 'alter_type_drop_value'; + } + + convert(st: JsonDropValueFromEnumStatement) { + const { columnsWithEnum, name, newValues, schema } = st; + + const statements: string[] = []; + + for (const withEnum of columnsWithEnum) { + statements.push( + `ALTER TABLE "${withEnum.schema}"."${withEnum.table}" ALTER COLUMN "${withEnum.column}" SET DATA TYPE text;`, + ); + } + + statements.push(new DropTypeEnumConvertor().convert({ name: name, schema, type: 'drop_type_enum' })); + + statements.push(new CreateTypeEnumConvertor().convert({ + name: name, + schema: schema, + values: newValues, + type: 'create_type_enum', + })); + + for (const withEnum of columnsWithEnum) { + statements.push( + `ALTER TABLE "${withEnum.schema}"."${withEnum.table}" ALTER COLUMN "${withEnum.column}" SET DATA TYPE "${schema}"."${name}" USING "${withEnum.column}"::"${schema}"."${name}";`, + ); + } + + return statements; + } +} + +class PgDropTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_table' && dialect === 'postgresql'; + } + + convert(statement: JsonDropTableStatement, _d: any, action?: string) { + const { tableName, schema, policies } = statement; + + const tableNameWithSchema = schema + ? `"${schema}"."${tableName}"` + : `"${tableName}"`; + + const dropPolicyConvertor = new PgDropPolicyConvertor(); + const droppedPolicies = policies?.map((p) => { + return dropPolicyConvertor.convert({ + type: 'drop_policy', + tableName, + data: action === 'push' + ? PgSquasher.unsquashPolicyPush(p) + : PgSquasher.unsquashPolicy(p), + schema, + }) as string; + }) ?? []; + + return [ + ...droppedPolicies, + `DROP TABLE ${tableNameWithSchema} CASCADE;`, + ]; } } @@ -778,9 +1531,20 @@ class MySQLDropTableConvertor extends Convertor { } } +class SingleStoreDropTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_table' && dialect === 'singlestore'; + } + + convert(statement: JsonDropTableStatement) { + const { tableName } = statement; + return `DROP TABLE \`${tableName}\`;`; + } +} + export class SQLiteDropTableConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'drop_table' && dialect === 'sqlite'; + return statement.type === 'drop_table' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonDropTableStatement) { @@ -806,7 +1570,7 @@ class PgRenameTableConvertor extends Convertor { export class SqliteRenameTableConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'rename_table' && dialect === 'sqlite'; + return statement.type === 'rename_table' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonRenameTableStatement) { @@ -826,6 +1590,17 @@ class MySqlRenameTableConvertor extends Convertor { } } +class SingleStoreRenameTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'rename_table' && dialect === 'singlestore'; + } + + convert(statement: JsonRenameTableStatement) { + const { tableNameFrom, tableNameTo } = statement; + return `RENAME TABLE \`${tableNameFrom}\` TO \`${tableNameTo}\`;`; + } +} + class PgAlterTableRenameColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -857,10 +1632,10 @@ class MySqlAlterTableRenameColumnConvertor extends Convertor { } } -class SQLiteAlterTableRenameColumnConvertor extends Convertor { +class SingleStoreAlterTableRenameColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( - statement.type === 'alter_table_rename_column' && dialect === 'sqlite' + statement.type === 'alter_table_rename_column' && dialect === 'singlestore' ); } @@ -870,6 +1645,19 @@ class SQLiteAlterTableRenameColumnConvertor extends Convertor { } } +class SQLiteAlterTableRenameColumnConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_rename_column' && (dialect === 'sqlite' || dialect === 'turso') + ); + } + + convert(statement: JsonRenameColumnStatement) { + const { tableName, oldColumnName, newColumnName } = statement; + return `ALTER TABLE \`${tableName}\` RENAME COLUMN "${oldColumnName}" TO "${newColumnName}";`; + } +} + class PgAlterTableDropColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -884,7 +1672,7 @@ class PgAlterTableDropColumnConvertor extends Convertor { ? `"${schema}"."${tableName}"` : `"${tableName}"`; - return `ALTER TABLE ${tableNameWithSchema} DROP COLUMN IF EXISTS "${columnName}";`; + return `ALTER TABLE ${tableNameWithSchema} DROP COLUMN "${columnName}";`; } } @@ -899,9 +1687,20 @@ class MySqlAlterTableDropColumnConvertor extends Convertor { } } +class SingleStoreAlterTableDropColumnConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_table_drop_column' && dialect === 'singlestore'; + } + + convert(statement: JsonDropColumnStatement) { + const { tableName, columnName } = statement; + return `ALTER TABLE \`${tableName}\` DROP COLUMN \`${columnName}\`;`; + } +} + class SQLiteAlterTableDropColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'alter_table_drop_column' && dialect === 'sqlite'; + return statement.type === 'alter_table_drop_column' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonDropColumnStatement) { @@ -933,9 +1732,7 @@ class PgAlterTableAddColumnConvertor extends Convertor { ? `"${column.typeSchema}".` : ''; - const fixedType = isPgNativeType(column.type) - ? column.type - : `${schemaPrefix}"${column.type}"`; + const fixedType = parseType(schemaPrefix, column.type); const notNullStatement = `${notNull ? ' NOT NULL' : ''}`; @@ -971,11 +1768,9 @@ class PgAlterTableAddColumnConvertor extends Convertor { })` : ''; - const generatedStatement = ` GENERATED ALWAYS AS (${generated?.as}) STORED`; + const generatedStatement = generated ? ` GENERATED ALWAYS AS (${generated?.as}) STORED` : ''; - return `ALTER TABLE ${tableNameWithSchema} ADD COLUMN "${name}" ${fixedType}${primaryKeyStatement}${defaultStatement}${notNullStatement}${ - generated ? generatedStatement : '' - }${identityStatement};`; + return `ALTER TABLE ${tableNameWithSchema} ADD COLUMN "${name}" ${fixedType}${primaryKeyStatement}${defaultStatement}${generatedStatement}${notNullStatement}${identityStatement};`; } } @@ -984,6 +1779,37 @@ class MySqlAlterTableAddColumnConvertor extends Convertor { return statement.type === 'alter_table_add_column' && dialect === 'mysql'; } + convert(statement: JsonAddColumnStatement) { + const { tableName, column } = statement; + const { + name, + type, + notNull, + primaryKey, + autoincrement, + onUpdate, + generated, + } = column; + + const defaultStatement = `${column.default !== undefined ? ` DEFAULT ${column.default}` : ''}`; + const notNullStatement = `${notNull ? ' NOT NULL' : ''}`; + const primaryKeyStatement = `${primaryKey ? ' PRIMARY KEY' : ''}`; + const autoincrementStatement = `${autoincrement ? ' AUTO_INCREMENT' : ''}`; + const onUpdateStatement = `${onUpdate ? ' ON UPDATE CURRENT_TIMESTAMP' : ''}`; + + const generatedStatement = generated + ? ` GENERATED ALWAYS AS (${generated?.as}) ${generated?.type.toUpperCase()}` + : ''; + + return `ALTER TABLE \`${tableName}\` ADD \`${name}\` ${type}${primaryKeyStatement}${autoincrementStatement}${defaultStatement}${generatedStatement}${notNullStatement}${onUpdateStatement};`; + } +} + +class SingleStoreAlterTableAddColumnConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_table_add_column' && dialect === 'singlestore'; + } + convert(statement: JsonAddColumnStatement) { const { tableName, column } = statement; const { @@ -1013,7 +1839,7 @@ class MySqlAlterTableAddColumnConvertor extends Convertor { export class SQLiteAlterTableAddColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( - statement.type === 'sqlite_alter_table_add_column' && dialect === 'sqlite' + statement.type === 'sqlite_alter_table_add_column' && (dialect === 'sqlite' || dialect === 'turso') ); } @@ -1037,7 +1863,7 @@ export class SQLiteAlterTableAddColumnConvertor extends Convertor { ? ` GENERATED ALWAYS AS ${generated.as} ${generated.type.toUpperCase()}` : ''; - return `ALTER TABLE \`${tableName}\` ADD \`${name}\` ${type}${primaryKeyStatement}${defaultStatement}${notNullStatement}${generatedStatement}${referenceStatement};`; + return `ALTER TABLE \`${tableName}\` ADD \`${name}\` ${type}${primaryKeyStatement}${defaultStatement}${generatedStatement}${notNullStatement}${referenceStatement};`; } } @@ -1060,26 +1886,6 @@ class PgAlterTableAlterColumnSetTypeConvertor extends Convertor { } } -class SQLiteAlterTableAlterColumnSetTypeConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_type' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnTypeStatement) { - return ( - '/*\n SQLite does not support "Changing existing column type" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class PgAlterTableAlterColumnSetDefaultConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -1099,26 +1905,6 @@ class PgAlterTableAlterColumnSetDefaultConvertor extends Convertor { } } -class SqliteAlterTableAlterColumnSetDefaultConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_default' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnSetDefaultStatement) { - return ( - '/*\n SQLite does not support "Set default to column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class PgAlterTableAlterColumnDropDefaultConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -1258,7 +2044,7 @@ class SqliteAlterTableAlterColumnDropGeneratedConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_drop_generated' - && dialect === 'sqlite' + && (dialect === 'sqlite' || dialect === 'turso') ); } @@ -1307,7 +2093,7 @@ class SqliteAlterTableAlterColumnSetExpressionConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_set_generated' - && dialect === 'sqlite' + && (dialect === 'sqlite' || dialect === 'turso') ); } @@ -1356,7 +2142,7 @@ class SqliteAlterTableAlterColumnAlterGeneratedConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_alter_generated' - && dialect === 'sqlite' + && (dialect === 'sqlite' || dialect === 'turso') ); } @@ -1390,24 +2176,474 @@ class SqliteAlterTableAlterColumnAlterGeneratedConvertor extends Convertor { }, ); - const dropColumnStatement = new SQLiteAlterTableDropColumnConvertor().convert({ - tableName, - columnName, - schema, - type: 'alter_table_drop_column', - }); + const dropColumnStatement = new SQLiteAlterTableDropColumnConvertor().convert({ + tableName, + columnName, + schema, + type: 'alter_table_drop_column', + }); + + return [dropColumnStatement, addColumnStatement]; + } +} + +//// + +class MySqlAlterTableAlterColumnAlterrGeneratedConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_alter_column_alter_generated' + && dialect === 'mysql' + ); + } + + convert(statement: JsonAlterColumnAlterGeneratedStatement) { + const { + tableName, + columnName, + schema, + columnNotNull: notNull, + columnDefault, + columnOnUpdate, + columnAutoIncrement, + columnPk, + columnGenerated, + } = statement; + + const tableNameWithSchema = schema + ? `\`${schema}\`.\`${tableName}\`` + : `\`${tableName}\``; + + const addColumnStatement = new MySqlAlterTableAddColumnConvertor().convert({ + schema, + tableName, + column: { + name: columnName, + type: statement.newDataType, + notNull, + default: columnDefault, + onUpdate: columnOnUpdate, + autoincrement: columnAutoIncrement, + primaryKey: columnPk, + generated: columnGenerated, + }, + type: 'alter_table_add_column', + }); + + return [ + `ALTER TABLE ${tableNameWithSchema} drop column \`${columnName}\`;`, + addColumnStatement, + ]; + } +} + +class MySqlAlterTableAlterColumnSetDefaultConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_alter_column_set_default' + && dialect === 'mysql' + ); + } + + convert(statement: JsonAlterColumnSetDefaultStatement) { + const { tableName, columnName } = statement; + return `ALTER TABLE \`${tableName}\` ALTER COLUMN \`${columnName}\` SET DEFAULT ${statement.newDefaultValue};`; + } +} + +class MySqlAlterTableAlterColumnDropDefaultConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_alter_column_drop_default' + && dialect === 'mysql' + ); + } + + convert(statement: JsonAlterColumnDropDefaultStatement) { + const { tableName, columnName } = statement; + return `ALTER TABLE \`${tableName}\` ALTER COLUMN \`${columnName}\` DROP DEFAULT;`; + } +} + +class MySqlAlterTableAddPk extends Convertor { + can(statement: JsonStatement, dialect: string): boolean { + return ( + statement.type === 'alter_table_alter_column_set_pk' + && dialect === 'mysql' + ); + } + convert(statement: JsonAlterColumnSetPrimaryKeyStatement): string { + return `ALTER TABLE \`${statement.tableName}\` ADD PRIMARY KEY (\`${statement.columnName}\`);`; + } +} + +class MySqlAlterTableDropPk extends Convertor { + can(statement: JsonStatement, dialect: string): boolean { + return ( + statement.type === 'alter_table_alter_column_drop_pk' + && dialect === 'mysql' + ); + } + convert(statement: JsonAlterColumnDropPrimaryKeyStatement): string { + return `ALTER TABLE \`${statement.tableName}\` DROP PRIMARY KEY`; + } +} + +type LibSQLModifyColumnStatement = + | JsonAlterColumnTypeStatement + | JsonAlterColumnDropNotNullStatement + | JsonAlterColumnSetNotNullStatement + | JsonAlterColumnSetDefaultStatement + | JsonAlterColumnDropDefaultStatement; + +export class LibSQLModifyColumn extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + (statement.type === 'alter_table_alter_column_set_type' + || statement.type === 'alter_table_alter_column_drop_notnull' + || statement.type === 'alter_table_alter_column_set_notnull' + || statement.type === 'alter_table_alter_column_set_default' + || statement.type === 'alter_table_alter_column_drop_default' + || statement.type === 'create_check_constraint' + || statement.type === 'delete_check_constraint') + && dialect === 'turso' + ); + } + + convert(statement: LibSQLModifyColumnStatement, json2: SQLiteSchemaSquashed) { + const { tableName, columnName } = statement; + + let columnType = ``; + let columnDefault: any = ''; + let columnNotNull = ''; + + const sqlStatements: string[] = []; + + // collect index info + const indexes: { + name: string; + tableName: string; + columns: string[]; + isUnique: boolean; + where?: string | undefined; + }[] = []; + for (const table of Object.values(json2.tables)) { + for (const index of Object.values(table.indexes)) { + const unsquashed = SQLiteSquasher.unsquashIdx(index); + sqlStatements.push(`DROP INDEX "${unsquashed.name}";`); + indexes.push({ ...unsquashed, tableName: table.name }); + } + } + + switch (statement.type) { + case 'alter_table_alter_column_set_type': + columnType = ` ${statement.newDataType}`; + + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + + break; + case 'alter_table_alter_column_drop_notnull': + columnType = ` ${statement.newDataType}`; + + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + + columnNotNull = ''; + break; + case 'alter_table_alter_column_set_notnull': + columnType = ` ${statement.newDataType}`; + + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + + columnNotNull = ` NOT NULL`; + break; + case 'alter_table_alter_column_set_default': + columnType = ` ${statement.newDataType}`; + + columnDefault = ` DEFAULT ${statement.newDefaultValue}`; + + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + break; + case 'alter_table_alter_column_drop_default': + columnType = ` ${statement.newDataType}`; + + columnDefault = ''; + + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + break; + } + + // Seems like getting value from simple json2 shanpshot makes dates be dates + columnDefault = columnDefault instanceof Date + ? columnDefault.toISOString() + : columnDefault; + + sqlStatements.push( + `ALTER TABLE \`${tableName}\` ALTER COLUMN "${columnName}" TO "${columnName}"${columnType}${columnNotNull}${columnDefault};`, + ); + + for (const index of indexes) { + const indexPart = index.isUnique ? 'UNIQUE INDEX' : 'INDEX'; + const whereStatement = index.where ? ` WHERE ${index.where}` : ''; + const uniqueString = index.columns.map((it) => `\`${it}\``).join(','); + const tableName = index.tableName; + + sqlStatements.push( + `CREATE ${indexPart} \`${index.name}\` ON \`${tableName}\` (${uniqueString})${whereStatement};`, + ); + } + + return sqlStatements; + } +} + +type MySqlModifyColumnStatement = + | JsonAlterColumnDropNotNullStatement + | JsonAlterColumnSetNotNullStatement + | JsonAlterColumnTypeStatement + | JsonAlterColumnDropOnUpdateStatement + | JsonAlterColumnSetOnUpdateStatement + | JsonAlterColumnDropAutoincrementStatement + | JsonAlterColumnSetAutoincrementStatement + | JsonAlterColumnSetDefaultStatement + | JsonAlterColumnDropDefaultStatement + | JsonAlterColumnSetGeneratedStatement + | JsonAlterColumnDropGeneratedStatement; + +class MySqlModifyColumn extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + (statement.type === 'alter_table_alter_column_set_type' + || statement.type === 'alter_table_alter_column_set_notnull' + || statement.type === 'alter_table_alter_column_drop_notnull' + || statement.type === 'alter_table_alter_column_drop_on_update' + || statement.type === 'alter_table_alter_column_set_on_update' + || statement.type === 'alter_table_alter_column_set_autoincrement' + || statement.type === 'alter_table_alter_column_drop_autoincrement' + || statement.type === 'alter_table_alter_column_set_default' + || statement.type === 'alter_table_alter_column_drop_default' + || statement.type === 'alter_table_alter_column_set_generated' + || statement.type === 'alter_table_alter_column_drop_generated') + && dialect === 'mysql' + ); + } + + convert(statement: MySqlModifyColumnStatement) { + const { tableName, columnName } = statement; + let columnType = ``; + let columnDefault: any = ''; + let columnNotNull = ''; + let columnOnUpdate = ''; + let columnAutoincrement = ''; + let primaryKey = statement.columnPk ? ' PRIMARY KEY' : ''; + let columnGenerated = ''; + + if (statement.type === 'alter_table_alter_column_drop_notnull') { + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_set_notnull') { + columnNotNull = ` NOT NULL`; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_drop_on_update') { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnOnUpdate = ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_set_on_update') { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = ` ON UPDATE CURRENT_TIMESTAMP`; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if ( + statement.type === 'alter_table_alter_column_set_autoincrement' + ) { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = ' AUTO_INCREMENT'; + } else if ( + statement.type === 'alter_table_alter_column_drop_autoincrement' + ) { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = ''; + } else if (statement.type === 'alter_table_alter_column_set_default') { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = ` DEFAULT ${statement.newDefaultValue}`; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_drop_default') { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_set_generated') { + columnType = ` ${statement.newDataType}`; + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + + if (statement.columnGenerated?.type === 'virtual') { + return [ + new MySqlAlterTableDropColumnConvertor().convert({ + type: 'alter_table_drop_column', + tableName: statement.tableName, + columnName: statement.columnName, + schema: statement.schema, + }), + new MySqlAlterTableAddColumnConvertor().convert({ + tableName, + column: { + name: columnName, + type: statement.newDataType, + notNull: statement.columnNotNull, + default: statement.columnDefault, + onUpdate: statement.columnOnUpdate, + autoincrement: statement.columnAutoIncrement, + primaryKey: statement.columnPk, + generated: statement.columnGenerated, + }, + schema: statement.schema, + type: 'alter_table_add_column', + }), + ]; + } else { + columnGenerated = statement.columnGenerated + ? ` GENERATED ALWAYS AS (${statement.columnGenerated?.as}) ${statement.columnGenerated?.type.toUpperCase()}` + : ''; + } + } else if (statement.type === 'alter_table_alter_column_drop_generated') { + columnType = ` ${statement.newDataType}`; + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + + if (statement.oldColumn?.generated?.type === 'virtual') { + return [ + new MySqlAlterTableDropColumnConvertor().convert({ + type: 'alter_table_drop_column', + tableName: statement.tableName, + columnName: statement.columnName, + schema: statement.schema, + }), + new MySqlAlterTableAddColumnConvertor().convert({ + tableName, + column: { + name: columnName, + type: statement.newDataType, + notNull: statement.columnNotNull, + default: statement.columnDefault, + onUpdate: statement.columnOnUpdate, + autoincrement: statement.columnAutoIncrement, + primaryKey: statement.columnPk, + generated: statement.columnGenerated, + }, + schema: statement.schema, + type: 'alter_table_add_column', + }), + ]; + } + } else { + columnType = ` ${statement.newDataType}`; + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + columnGenerated = statement.columnGenerated + ? ` GENERATED ALWAYS AS (${statement.columnGenerated?.as}) ${statement.columnGenerated?.type.toUpperCase()}` + : ''; + } + + // Seems like getting value from simple json2 shanpshot makes dates be dates + columnDefault = columnDefault instanceof Date + ? columnDefault.toISOString() + : columnDefault; - return [dropColumnStatement, addColumnStatement]; + return `ALTER TABLE \`${tableName}\` MODIFY COLUMN \`${columnName}\`${columnType}${columnAutoincrement}${columnGenerated}${columnNotNull}${columnDefault}${columnOnUpdate};`; } } -//// - -class MySqlAlterTableAlterColumnAlterrGeneratedConvertor extends Convertor { +class SingleStoreAlterTableAlterColumnAlterrGeneratedConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_alter_generated' - && dialect === 'mysql' + && dialect === 'singlestore' ); } @@ -1428,7 +2664,7 @@ class MySqlAlterTableAlterColumnAlterrGeneratedConvertor extends Convertor { ? `\`${schema}\`.\`${tableName}\`` : `\`${tableName}\``; - const addColumnStatement = new MySqlAlterTableAddColumnConvertor().convert({ + const addColumnStatement = new SingleStoreAlterTableAddColumnConvertor().convert({ schema, tableName, column: { @@ -1451,11 +2687,11 @@ class MySqlAlterTableAlterColumnAlterrGeneratedConvertor extends Convertor { } } -class MySqlAlterTableAlterColumnSetDefaultConvertor extends Convertor { +class SingleStoreAlterTableAlterColumnSetDefaultConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_set_default' - && dialect === 'mysql' + && dialect === 'singlestore' ); } @@ -1465,11 +2701,11 @@ class MySqlAlterTableAlterColumnSetDefaultConvertor extends Convertor { } } -class MySqlAlterTableAlterColumnDropDefaultConvertor extends Convertor { +class SingleStoreAlterTableAlterColumnDropDefaultConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_drop_default' - && dialect === 'mysql' + && dialect === 'singlestore' ); } @@ -1479,11 +2715,11 @@ class MySqlAlterTableAlterColumnDropDefaultConvertor extends Convertor { } } -class MySqlAlterTableAddPk extends Convertor { +class SingleStoreAlterTableAddPk extends Convertor { can(statement: JsonStatement, dialect: string): boolean { return ( statement.type === 'alter_table_alter_column_set_pk' - && dialect === 'mysql' + && dialect === 'singlestore' ); } convert(statement: JsonAlterColumnSetPrimaryKeyStatement): string { @@ -1491,11 +2727,11 @@ class MySqlAlterTableAddPk extends Convertor { } } -class MySqlAlterTableDropPk extends Convertor { +class SingleStoreAlterTableDropPk extends Convertor { can(statement: JsonStatement, dialect: string): boolean { return ( statement.type === 'alter_table_alter_column_drop_pk' - && dialect === 'mysql' + && dialect === 'singlestore' ); } convert(statement: JsonAlterColumnDropPrimaryKeyStatement): string { @@ -1503,7 +2739,7 @@ class MySqlAlterTableDropPk extends Convertor { } } -type MySqlModifyColumnStatement = +type SingleStoreModifyColumnStatement = | JsonAlterColumnDropNotNullStatement | JsonAlterColumnSetNotNullStatement | JsonAlterColumnTypeStatement @@ -1516,7 +2752,7 @@ type MySqlModifyColumnStatement = | JsonAlterColumnSetGeneratedStatement | JsonAlterColumnDropGeneratedStatement; -class MySqlModifyColumn extends Convertor { +class SingleStoreModifyColumn extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( (statement.type === 'alter_table_alter_column_set_type' @@ -1530,11 +2766,11 @@ class MySqlModifyColumn extends Convertor { || statement.type === 'alter_table_alter_column_drop_default' || statement.type === 'alter_table_alter_column_set_generated' || statement.type === 'alter_table_alter_column_drop_generated') - && dialect === 'mysql' + && dialect === 'singlestore' ); } - convert(statement: MySqlModifyColumnStatement) { + convert(statement: SingleStoreModifyColumnStatement) { const { tableName, columnName } = statement; let columnType = ``; let columnDefault: any = ''; @@ -1647,13 +2883,13 @@ class MySqlModifyColumn extends Convertor { if (statement.columnGenerated?.type === 'virtual') { return [ - new MySqlAlterTableDropColumnConvertor().convert({ + new SingleStoreAlterTableDropColumnConvertor().convert({ type: 'alter_table_drop_column', tableName: statement.tableName, columnName: statement.columnName, schema: statement.schema, }), - new MySqlAlterTableAddColumnConvertor().convert({ + new SingleStoreAlterTableAddColumnConvertor().convert({ tableName, column: { name: columnName, @@ -1689,13 +2925,13 @@ class MySqlModifyColumn extends Convertor { if (statement.oldColumn?.generated?.type === 'virtual') { return [ - new MySqlAlterTableDropColumnConvertor().convert({ + new SingleStoreAlterTableDropColumnConvertor().convert({ type: 'alter_table_drop_column', tableName: statement.tableName, columnName: statement.columnName, schema: statement.schema, }), - new MySqlAlterTableAddColumnConvertor().convert({ + new SingleStoreAlterTableAddColumnConvertor().convert({ tableName, column: { name: columnName, @@ -1737,7 +2973,6 @@ class MySqlModifyColumn extends Convertor { return `ALTER TABLE \`${tableName}\` MODIFY COLUMN \`${columnName}\`${columnType}${columnAutoincrement}${columnNotNull}${columnDefault}${columnOnUpdate}${columnGenerated};`; } } - class SqliteAlterTableAlterColumnDropDefaultConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -1775,7 +3010,6 @@ class PgAlterTableCreateCompositePrimaryKeyConvertor extends Convertor { }");`; } } - class PgAlterTableDeleteCompositePrimaryKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return statement.type === 'delete_composite_pk' && dialect === 'postgresql'; @@ -1807,9 +3041,9 @@ class PgAlterTableAlterCompositePrimaryKeyConvertor extends Convertor { ? `"${statement.schema}"."${statement.tableName}"` : `"${statement.tableName}"`; - return `ALTER TABLE ${tableNameWithSchema} DROP CONSTRAINT ${statement.oldConstraintName};\n${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT ${statement.newConstraintName} PRIMARY KEY(${ - newColumns.join(',') - });`; + return `ALTER TABLE ${tableNameWithSchema} DROP CONSTRAINT "${statement.oldConstraintName}";\n${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newConstraintName}" PRIMARY KEY("${ + newColumns.join('","') + }");`; } } @@ -1999,66 +3233,6 @@ class PgAlterTableAlterColumnSetNotNullConvertor extends Convertor { } } -class SqliteAlterTableAlterColumnSetNotNullConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_notnull' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnSetNotNullStatement) { - return ( - '/*\n SQLite does not support "Set not null to column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - -class SqliteAlterTableAlterColumnSetAutoincrementConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_autoincrement' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnSetAutoincrementStatement) { - return ( - '/*\n SQLite does not support "Set autoincrement to a column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - -class SqliteAlterTableAlterColumnDropAutoincrementConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_drop_autoincrement' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnDropAutoincrementStatement) { - return ( - '/*\n SQLite does not support "Drop autoincrement from a column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class PgAlterTableAlterColumnDropNotNullConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -2078,26 +3252,6 @@ class PgAlterTableAlterColumnDropNotNullConvertor extends Convertor { } } -class SqliteAlterTableAlterColumnDropNotNullConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_drop_notnull' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnDropNotNullStatement) { - return ( - '/*\n SQLite does not support "Drop not null from column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - // FK class PgCreateForeignKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { @@ -2129,31 +3283,43 @@ class PgCreateForeignKeyConvertor extends Convertor { : `"${tableTo}"`; const alterStatement = - `ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${name}" FOREIGN KEY (${fromColumnsString}) REFERENCES ${tableToNameWithSchema}(${toColumnsString})${onDeleteStatement}${onUpdateStatement}`; + `ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${name}" FOREIGN KEY (${fromColumnsString}) REFERENCES ${tableToNameWithSchema}(${toColumnsString})${onDeleteStatement}${onUpdateStatement};`; - let sql = 'DO $$ BEGIN\n'; - sql += ' ' + alterStatement + ';\n'; - sql += 'EXCEPTION\n'; - sql += ' WHEN duplicate_object THEN null;\n'; - sql += 'END $$;\n'; - return sql; + return alterStatement; } } -class SqliteCreateForeignKeyConvertor extends Convertor { +class LibSQLCreateForeignKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'create_reference' && dialect === 'sqlite'; - } - - convert(statement: JsonCreateReferenceStatement): string { return ( - '/*\n SQLite does not support "Creating foreign key on existing column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' + statement.type === 'create_reference' + && dialect === 'turso' ); } + + convert( + statement: JsonCreateReferenceStatement, + json2?: SQLiteSchemaSquashed, + action?: 'push', + ): string { + const { columnsFrom, columnsTo, tableFrom, onDelete, onUpdate, tableTo } = action === 'push' + ? SQLiteSquasher.unsquashPushFK(statement.data) + : SQLiteSquasher.unsquashFK(statement.data); + const { columnDefault, columnNotNull, columnType } = statement; + + const onDeleteStatement = onDelete ? ` ON DELETE ${onDelete}` : ''; + const onUpdateStatement = onUpdate ? ` ON UPDATE ${onUpdate}` : ''; + const columnsDefaultValue = columnDefault + ? ` DEFAULT ${columnDefault}` + : ''; + const columnNotNullValue = columnNotNull ? ` NOT NULL` : ''; + const columnTypeValue = columnType ? ` ${columnType}` : ''; + + const columnFrom = columnsFrom[0]; + const columnTo = columnsTo[0]; + + return `ALTER TABLE \`${tableFrom}\` ALTER COLUMN "${columnFrom}" TO "${columnFrom}"${columnTypeValue}${columnNotNullValue}${columnsDefaultValue} REFERENCES ${tableTo}(${columnTo})${onDeleteStatement}${onUpdateStatement};`; + } } class MySqlCreateForeignKeyConvertor extends Convertor { @@ -2216,33 +3382,13 @@ class PgAlterForeignKeyConvertor extends Convertor { : `"${newFk.tableFrom}"`; const alterStatement = - `ALTER TABLE ${tableFromNameWithSchema} ADD CONSTRAINT "${newFk.name}" FOREIGN KEY (${fromColumnsString}) REFERENCES ${tableToNameWithSchema}(${toColumnsString})${onDeleteStatement}${onUpdateStatement}`; + `ALTER TABLE ${tableFromNameWithSchema} ADD CONSTRAINT "${newFk.name}" FOREIGN KEY (${fromColumnsString}) REFERENCES ${tableToNameWithSchema}(${toColumnsString})${onDeleteStatement}${onUpdateStatement};`; - sql += 'DO $$ BEGIN\n'; - sql += ' ' + alterStatement + ';\n'; - sql += 'EXCEPTION\n'; - sql += ' WHEN duplicate_object THEN null;\n'; - sql += 'END $$;\n'; + sql += alterStatement; return sql; } } -class SqliteAlterForeignKeyConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'alter_reference' && dialect === 'sqlite'; - } - - convert(statement: JsonAlterReferenceStatement): string { - return ( - '/*\n SQLite does not support "Changing existing foreign key" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class PgDeleteForeignKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return statement.type === 'delete_reference' && dialect === 'postgresql'; @@ -2260,22 +3406,6 @@ class PgDeleteForeignKeyConvertor extends Convertor { } } -class SqliteDeleteForeignKeyConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'delete_reference' && dialect === 'sqlite'; - } - - convert(statement: JsonDeleteReferenceStatement): string { - return ( - '/*\n SQLite does not support "Dropping foreign key" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class MySqlDeleteForeignKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return statement.type === 'delete_reference' && dialect === 'mysql'; @@ -2335,7 +3465,7 @@ class CreatePgIndexConvertor extends Convertor { return `CREATE ${indexPart}${ concurrently ? ' CONCURRENTLY' : '' - } IF NOT EXISTS "${name}" ON ${tableNameWithSchema} USING ${method} (${value})${ + } "${name}" ON ${tableNameWithSchema} USING ${method} (${value})${ Object.keys(withMap!).length !== 0 ? ` WITH (${reverseLogic(withMap!)})` : '' @@ -2369,9 +3499,35 @@ class CreateMySqlIndexConvertor extends Convertor { } } +class CreateSingleStoreIndexConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_index' && dialect === 'singlestore'; + } + + convert(statement: JsonCreateIndexStatement): string { + // should be changed + const { name, columns, isUnique } = SingleStoreSquasher.unsquashIdx( + statement.data, + ); + const indexPart = isUnique ? 'UNIQUE INDEX' : 'INDEX'; + + const uniqueString = columns + .map((it) => { + return statement.internal?.indexes + ? statement.internal?.indexes[name]?.columns[it]?.isExpression + ? it + : `\`${it}\`` + : `\`${it}\``; + }) + .join(','); + + return `CREATE ${indexPart} \`${name}\` ON \`${statement.tableName}\` (${uniqueString});`; + } +} + export class CreateSqliteIndexConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'create_index' && dialect === 'sqlite'; + return statement.type === 'create_index' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonCreateIndexStatement): string { @@ -2401,8 +3557,12 @@ class PgDropIndexConvertor extends Convertor { } convert(statement: JsonDropIndexStatement): string { + const { schema } = statement; const { name } = PgSquasher.unsquashIdx(statement.data); - return `DROP INDEX IF EXISTS "${name}";`; + + const indexNameWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; + + return `DROP INDEX ${indexNameWithSchema};`; } } @@ -2493,12 +3653,12 @@ class PgAlterTableRemoveFromSchemaConvertor extends Convertor { export class SqliteDropIndexConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'drop_index' && dialect === 'sqlite'; + return statement.type === 'drop_index' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonDropIndexStatement): string { const { name } = PgSquasher.unsquashIdx(statement.data); - return `DROP INDEX IF EXISTS \`${name}\`;`; + return `DROP INDEX \`${name}\`;`; } } @@ -2513,12 +3673,180 @@ class MySqlDropIndexConvertor extends Convertor { } } +class SingleStoreDropIndexConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_index' && dialect === 'singlestore'; + } + + convert(statement: JsonDropIndexStatement): string { + const { name } = SingleStoreSquasher.unsquashIdx(statement.data); + return `DROP INDEX \`${name}\` ON \`${statement.tableName}\`;`; + } +} + +class SQLiteRecreateTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'recreate_table' && dialect === 'sqlite' + ); + } + + convert(statement: JsonRecreateTableStatement): string | string[] { + const { tableName, columns, compositePKs, referenceData, checkConstraints } = statement; + + const columnNames = columns.map((it) => `"${it.name}"`).join(', '); + const newTableName = `__new_${tableName}`; + + const sqlStatements: string[] = []; + + sqlStatements.push(`PRAGMA foreign_keys=OFF;`); + + // map all possible variants + const mappedCheckConstraints: string[] = checkConstraints.map((it) => + it.replaceAll(`"${tableName}".`, `"${newTableName}".`).replaceAll(`\`${tableName}\`.`, `\`${newTableName}\`.`) + .replaceAll(`${tableName}.`, `${newTableName}.`).replaceAll(`'${tableName}'.`, `'${newTableName}'.`) + ); + + // create new table + sqlStatements.push( + new SQLiteCreateTableConvertor().convert({ + type: 'sqlite_create_table', + tableName: newTableName, + columns, + referenceData, + compositePKs, + checkConstraints: mappedCheckConstraints, + }), + ); + + // migrate data + sqlStatements.push( + `INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${columnNames} FROM \`${tableName}\`;`, + ); + + // drop table + sqlStatements.push( + new SQLiteDropTableConvertor().convert({ + type: 'drop_table', + tableName: tableName, + schema: '', + }), + ); + + // rename table + sqlStatements.push( + new SqliteRenameTableConvertor().convert({ + fromSchema: '', + tableNameFrom: newTableName, + tableNameTo: tableName, + toSchema: '', + type: 'rename_table', + }), + ); + + sqlStatements.push(`PRAGMA foreign_keys=ON;`); + + return sqlStatements; + } +} + +class LibSQLRecreateTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'recreate_table' + && dialect === 'turso' + ); + } + + convert(statement: JsonRecreateTableStatement): string[] { + const { tableName, columns, compositePKs, referenceData, checkConstraints } = statement; + + const columnNames = columns.map((it) => `"${it.name}"`).join(', '); + const newTableName = `__new_${tableName}`; + + const sqlStatements: string[] = []; + + const mappedCheckConstraints: string[] = checkConstraints.map((it) => + it.replaceAll(`"${tableName}".`, `"${newTableName}".`).replaceAll(`\`${tableName}\`.`, `\`${newTableName}\`.`) + .replaceAll(`${tableName}.`, `${newTableName}.`).replaceAll(`'${tableName}'.`, `\`${newTableName}\`.`) + ); + + sqlStatements.push(`PRAGMA foreign_keys=OFF;`); + + // create new table + sqlStatements.push( + new SQLiteCreateTableConvertor().convert({ + type: 'sqlite_create_table', + tableName: newTableName, + columns, + referenceData, + compositePKs, + checkConstraints: mappedCheckConstraints, + }), + ); + + // migrate data + sqlStatements.push( + `INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${columnNames} FROM \`${tableName}\`;`, + ); + + // drop table + sqlStatements.push( + new SQLiteDropTableConvertor().convert({ + type: 'drop_table', + tableName: tableName, + schema: '', + }), + ); + + // rename table + sqlStatements.push( + new SqliteRenameTableConvertor().convert({ + fromSchema: '', + tableNameFrom: newTableName, + tableNameTo: tableName, + toSchema: '', + type: 'rename_table', + }), + ); + + sqlStatements.push(`PRAGMA foreign_keys=ON;`); + + return sqlStatements; + } +} + const convertors: Convertor[] = []; convertors.push(new PgCreateTableConvertor()); convertors.push(new MySqlCreateTableConvertor()); +convertors.push(new SingleStoreCreateTableConvertor()); convertors.push(new SQLiteCreateTableConvertor()); +convertors.push(new SQLiteRecreateTableConvertor()); +convertors.push(new LibSQLRecreateTableConvertor()); + +convertors.push(new PgCreateViewConvertor()); +convertors.push(new PgDropViewConvertor()); +convertors.push(new PgRenameViewConvertor()); +convertors.push(new PgAlterViewSchemaConvertor()); +convertors.push(new PgAlterViewAddWithOptionConvertor()); +convertors.push(new PgAlterViewDropWithOptionConvertor()); +convertors.push(new PgAlterViewAlterTablespaceConvertor()); +convertors.push(new PgAlterViewAlterUsingConvertor()); + +convertors.push(new MySqlCreateViewConvertor()); +convertors.push(new MySqlDropViewConvertor()); +convertors.push(new MySqlRenameViewConvertor()); +convertors.push(new MySqlAlterViewConvertor()); + +convertors.push(new SqliteCreateViewConvertor()); +convertors.push(new SqliteDropViewConvertor()); convertors.push(new CreateTypeEnumConvertor()); +convertors.push(new DropTypeEnumConvertor()); +convertors.push(new AlterTypeAddValueConvertor()); +convertors.push(new AlterTypeSetSchemaConvertor()); +convertors.push(new AlterRenameTypeConvertor()); +convertors.push(new AlterTypeDropValueConvertor()); convertors.push(new CreatePgSequenceConvertor()); convertors.push(new DropPgSequenceConvertor()); @@ -2528,22 +3856,27 @@ convertors.push(new AlterPgSequenceConvertor()); convertors.push(new PgDropTableConvertor()); convertors.push(new MySQLDropTableConvertor()); +convertors.push(new SingleStoreDropTableConvertor()); convertors.push(new SQLiteDropTableConvertor()); convertors.push(new PgRenameTableConvertor()); convertors.push(new MySqlRenameTableConvertor()); +convertors.push(new SingleStoreRenameTableConvertor()); convertors.push(new SqliteRenameTableConvertor()); convertors.push(new PgAlterTableRenameColumnConvertor()); convertors.push(new MySqlAlterTableRenameColumnConvertor()); +convertors.push(new SingleStoreAlterTableRenameColumnConvertor()); convertors.push(new SQLiteAlterTableRenameColumnConvertor()); convertors.push(new PgAlterTableDropColumnConvertor()); convertors.push(new MySqlAlterTableDropColumnConvertor()); +convertors.push(new SingleStoreAlterTableDropColumnConvertor()); convertors.push(new SQLiteAlterTableDropColumnConvertor()); convertors.push(new PgAlterTableAddColumnConvertor()); convertors.push(new MySqlAlterTableAddColumnConvertor()); +convertors.push(new SingleStoreAlterTableAddColumnConvertor()); convertors.push(new SQLiteAlterTableAddColumnConvertor()); convertors.push(new PgAlterTableAlterColumnSetTypeConvertor()); @@ -2551,18 +3884,26 @@ convertors.push(new PgAlterTableAlterColumnSetTypeConvertor()); convertors.push(new PgAlterTableAddUniqueConstraintConvertor()); convertors.push(new PgAlterTableDropUniqueConstraintConvertor()); +convertors.push(new PgAlterTableAddCheckConstraintConvertor()); +convertors.push(new PgAlterTableDeleteCheckConstraintConvertor()); +convertors.push(new MySqlAlterTableAddCheckConstraintConvertor()); +convertors.push(new MySqlAlterTableDeleteCheckConstraintConvertor()); + convertors.push(new MySQLAlterTableAddUniqueConstraintConvertor()); convertors.push(new MySQLAlterTableDropUniqueConstraintConvertor()); +convertors.push(new SingleStoreAlterTableAddUniqueConstraintConvertor()); +convertors.push(new SingleStoreAlterTableDropUniqueConstraintConvertor()); + convertors.push(new CreatePgIndexConvertor()); convertors.push(new CreateMySqlIndexConvertor()); +convertors.push(new CreateSingleStoreIndexConvertor()); convertors.push(new CreateSqliteIndexConvertor()); convertors.push(new PgDropIndexConvertor()); convertors.push(new SqliteDropIndexConvertor()); convertors.push(new MySqlDropIndexConvertor()); - -convertors.push(new AlterTypeAddValueConvertor()); +convertors.push(new SingleStoreDropIndexConvertor()); convertors.push(new PgAlterTableAlterColumnSetPrimaryKeyConvertor()); convertors.push(new PgAlterTableAlterColumnDropPrimaryKeyConvertor()); @@ -2571,6 +3912,24 @@ convertors.push(new PgAlterTableAlterColumnDropNotNullConvertor()); convertors.push(new PgAlterTableAlterColumnSetDefaultConvertor()); convertors.push(new PgAlterTableAlterColumnDropDefaultConvertor()); +convertors.push(new PgAlterPolicyConvertor()); +convertors.push(new PgCreatePolicyConvertor()); +convertors.push(new PgDropPolicyConvertor()); +convertors.push(new PgRenamePolicyConvertor()); + +convertors.push(new PgAlterIndPolicyConvertor()); +convertors.push(new PgCreateIndPolicyConvertor()); +convertors.push(new PgDropIndPolicyConvertor()); +convertors.push(new PgRenameIndPolicyConvertor()); + +convertors.push(new PgEnableRlsConvertor()); +convertors.push(new PgDisableRlsConvertor()); + +convertors.push(new PgDropRoleConvertor()); +convertors.push(new PgAlterRoleConvertor()); +convertors.push(new PgCreateRoleConvertor()); +convertors.push(new PgRenameRoleConvertor()); + /// generated convertors.push(new PgAlterTableAlterColumnSetExpressionConvertor()); convertors.push(new PgAlterTableAlterColumnDropGeneratedConvertor()); @@ -2578,14 +3937,19 @@ convertors.push(new PgAlterTableAlterColumnAlterrGeneratedConvertor()); convertors.push(new MySqlAlterTableAlterColumnAlterrGeneratedConvertor()); +convertors.push(new SingleStoreAlterTableAlterColumnAlterrGeneratedConvertor()); + convertors.push(new SqliteAlterTableAlterColumnDropGeneratedConvertor()); convertors.push(new SqliteAlterTableAlterColumnAlterGeneratedConvertor()); convertors.push(new SqliteAlterTableAlterColumnSetExpressionConvertor()); convertors.push(new MySqlModifyColumn()); +convertors.push(new LibSQLModifyColumn()); // convertors.push(new MySqlAlterTableAlterColumnSetDefaultConvertor()); // convertors.push(new MySqlAlterTableAlterColumnDropDefaultConvertor()); +convertors.push(new SingleStoreModifyColumn()); + convertors.push(new PgCreateForeignKeyConvertor()); convertors.push(new MySqlCreateForeignKeyConvertor()); @@ -2601,31 +3965,12 @@ convertors.push(new PgAlterTableSetSchemaConvertor()); convertors.push(new PgAlterTableSetNewSchemaConvertor()); convertors.push(new PgAlterTableRemoveFromSchemaConvertor()); -// Unhandled sqlite queries, so they will appear last -convertors.push(new SQLiteAlterTableAlterColumnSetTypeConvertor()); -convertors.push(new SqliteAlterForeignKeyConvertor()); -convertors.push(new SqliteDeleteForeignKeyConvertor()); -convertors.push(new SqliteCreateForeignKeyConvertor()); - -convertors.push(new SQLiteAlterTableAddUniqueConstraintConvertor()); -convertors.push(new SQLiteAlterTableDropUniqueConstraintConvertor()); +convertors.push(new LibSQLCreateForeignKeyConvertor()); convertors.push(new PgAlterTableAlterColumnDropGenerated()); convertors.push(new PgAlterTableAlterColumnSetGenerated()); convertors.push(new PgAlterTableAlterColumnAlterGenerated()); -convertors.push(new SqliteAlterTableAlterColumnSetNotNullConvertor()); -convertors.push(new SqliteAlterTableAlterColumnDropNotNullConvertor()); -convertors.push(new SqliteAlterTableAlterColumnSetDefaultConvertor()); -convertors.push(new SqliteAlterTableAlterColumnDropDefaultConvertor()); - -convertors.push(new SqliteAlterTableAlterColumnSetAutoincrementConvertor()); -convertors.push(new SqliteAlterTableAlterColumnDropAutoincrementConvertor()); - -convertors.push(new SqliteAlterTableCreateCompositePrimaryKeyConvertor()); -convertors.push(new SqliteAlterTableDeleteCompositePrimaryKeyConvertor()); -convertors.push(new SqliteAlterTableAlterCompositePrimaryKeyConvertor()); - convertors.push(new PgAlterTableCreateCompositePrimaryKeyConvertor()); convertors.push(new PgAlterTableDeleteCompositePrimaryKeyConvertor()); convertors.push(new PgAlterTableAlterCompositePrimaryKeyConvertor()); @@ -2636,26 +3981,32 @@ convertors.push(new MySqlAlterTableCreateCompositePrimaryKeyConvertor()); convertors.push(new MySqlAlterTableAddPk()); convertors.push(new MySqlAlterTableAlterCompositePrimaryKeyConvertor()); -export const fromJson = (statements: JsonStatement[], dialect: Dialect) => { +convertors.push(new SingleStoreAlterTableDropPk()); +convertors.push(new SingleStoreAlterTableAddPk()); + +export function fromJson( + statements: JsonStatement[], + dialect: Dialect, + action?: 'push', + json2?: SQLiteSchemaSquashed, +) { const result = statements .flatMap((statement) => { const filtered = convertors.filter((it) => { - // console.log(statement, dialect) return it.can(statement, dialect); }); const convertor = filtered.length === 1 ? filtered[0] : undefined; if (!convertor) { - // console.log("no convertor:", statement.type, dialect); return ''; } - return convertor.convert(statement); + return convertor.convert(statement, json2, action); }) .filter((it) => it !== ''); return result; -}; +} // blog.yo1.dog/updating-enum-values-in-postgresql-the-safe-and-easy-way/ // test case for enum altering diff --git a/drizzle-kit/src/statementCombiner.ts b/drizzle-kit/src/statementCombiner.ts new file mode 100644 index 000000000..f3ca9789c --- /dev/null +++ b/drizzle-kit/src/statementCombiner.ts @@ -0,0 +1,446 @@ +import { + JsonCreateIndexStatement, + JsonRecreateTableStatement, + JsonStatement, + prepareCreateIndexesJson, +} from './jsonStatements'; +import { SQLiteSchemaSquashed, SQLiteSquasher } from './serializer/sqliteSchema'; + +export const prepareLibSQLRecreateTable = ( + table: SQLiteSchemaSquashed['tables'][keyof SQLiteSchemaSquashed['tables']], + action?: 'push', +): (JsonRecreateTableStatement | JsonCreateIndexStatement)[] => { + const { name, columns, uniqueConstraints, indexes, checkConstraints } = table; + + const composites: string[][] = Object.values(table.compositePrimaryKeys).map( + (it) => SQLiteSquasher.unsquashPK(it), + ); + + const references: string[] = Object.values(table.foreignKeys); + const fks = references.map((it) => + action === 'push' ? SQLiteSquasher.unsquashPushFK(it) : SQLiteSquasher.unsquashFK(it) + ); + + const statements: (JsonRecreateTableStatement | JsonCreateIndexStatement)[] = [ + { + type: 'recreate_table', + tableName: name, + columns: Object.values(columns), + compositePKs: composites, + referenceData: fks, + uniqueConstraints: Object.values(uniqueConstraints), + checkConstraints: Object.values(checkConstraints), + }, + ]; + + if (Object.keys(indexes).length) { + statements.push(...prepareCreateIndexesJson(name, '', indexes)); + } + return statements; +}; + +export const prepareSQLiteRecreateTable = ( + table: SQLiteSchemaSquashed['tables'][keyof SQLiteSchemaSquashed['tables']], + action?: 'push', +): JsonStatement[] => { + const { name, columns, uniqueConstraints, indexes, checkConstraints } = table; + + const composites: string[][] = Object.values(table.compositePrimaryKeys).map( + (it) => SQLiteSquasher.unsquashPK(it), + ); + + const references: string[] = Object.values(table.foreignKeys); + const fks = references.map((it) => + action === 'push' ? SQLiteSquasher.unsquashPushFK(it) : SQLiteSquasher.unsquashFK(it) + ); + + const statements: JsonStatement[] = [ + { + type: 'recreate_table', + tableName: name, + columns: Object.values(columns), + compositePKs: composites, + referenceData: fks, + uniqueConstraints: Object.values(uniqueConstraints), + checkConstraints: Object.values(checkConstraints), + }, + ]; + + if (Object.keys(indexes).length) { + statements.push(...prepareCreateIndexesJson(name, '', indexes)); + } + return statements; +}; + +export const libSQLCombineStatements = ( + statements: JsonStatement[], + json2: SQLiteSchemaSquashed, + action?: 'push', +) => { + // const tablesContext: Record = {}; + const newStatements: Record = {}; + for (const statement of statements) { + if ( + statement.type === 'alter_table_alter_column_drop_autoincrement' + || statement.type === 'alter_table_alter_column_set_autoincrement' + || statement.type === 'alter_table_alter_column_drop_pk' + || statement.type === 'alter_table_alter_column_set_pk' + || statement.type === 'create_composite_pk' + || statement.type === 'alter_composite_pk' + || statement.type === 'delete_composite_pk' + || statement.type === 'create_check_constraint' + || statement.type === 'delete_check_constraint' + ) { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if ( + statement.type === 'alter_table_alter_column_set_type' + || statement.type === 'alter_table_alter_column_drop_notnull' + || statement.type === 'alter_table_alter_column_set_notnull' + || statement.type === 'alter_table_alter_column_set_default' + || statement.type === 'alter_table_alter_column_drop_default' + ) { + const { tableName, columnName, columnPk } = statement; + + const columnIsPartOfForeignKey = Object.values( + json2.tables[tableName].foreignKeys, + ).some((it) => { + const unsquashFk = action === 'push' ? SQLiteSquasher.unsquashPushFK(it) : SQLiteSquasher.unsquashFK(it); + + return ( + unsquashFk.columnsFrom.includes(columnName) + ); + }); + + const statementsForTable = newStatements[tableName]; + + if ( + !statementsForTable && (columnIsPartOfForeignKey || columnPk) + ) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + continue; + } + + if ( + statementsForTable && (columnIsPartOfForeignKey || columnPk) + ) { + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + } + continue; + } + if ( + statementsForTable && !(columnIsPartOfForeignKey || columnPk) + ) { + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + newStatements[tableName].push(statement); + } + continue; + } + + newStatements[tableName] = [statement]; + + continue; + } + + if (statement.type === 'create_reference') { + const tableName = statement.tableName; + + const data = action === 'push' + ? SQLiteSquasher.unsquashPushFK(statement.data) + : SQLiteSquasher.unsquashFK(statement.data); + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = statement.isMulticolumn + ? prepareLibSQLRecreateTable(json2.tables[tableName], action) + : [statement]; + + continue; + } + + // if add column with reference -> skip create_reference statement + if ( + !statement.isMulticolumn + && statementsForTable.some((st) => + st.type === 'sqlite_alter_table_add_column' && st.column.name === data.columnsFrom[0] + ) + ) { + continue; + } + + if (statement.isMulticolumn) { + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + newStatements[tableName].push(statement); + } + + continue; + } + + if (statement.type === 'delete_reference') { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if (statement.type === 'sqlite_alter_table_add_column' && statement.column.primaryKey) { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + const tableName = statement.type === 'rename_table' + ? statement.tableNameTo + : (statement as { tableName: string }).tableName; + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = [statement]; + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + newStatements[tableName].push(statement); + } + } + + const combinedStatements = Object.values(newStatements).flat(); + const renamedTables = combinedStatements.filter((it) => it.type === 'rename_table'); + const renamedColumns = combinedStatements.filter((it) => it.type === 'alter_table_rename_column'); + + const rest = combinedStatements.filter((it) => it.type !== 'rename_table' && it.type !== 'alter_table_rename_column'); + + return [...renamedTables, ...renamedColumns, ...rest]; +}; + +export const sqliteCombineStatements = ( + statements: JsonStatement[], + json2: SQLiteSchemaSquashed, + action?: 'push', +) => { + // const tablesContext: Record = {}; + const newStatements: Record = {}; + for (const statement of statements) { + if ( + statement.type === 'alter_table_alter_column_set_type' + || statement.type === 'alter_table_alter_column_set_default' + || statement.type === 'alter_table_alter_column_drop_default' + || statement.type === 'alter_table_alter_column_set_notnull' + || statement.type === 'alter_table_alter_column_drop_notnull' + || statement.type === 'alter_table_alter_column_drop_autoincrement' + || statement.type === 'alter_table_alter_column_set_autoincrement' + || statement.type === 'alter_table_alter_column_drop_pk' + || statement.type === 'alter_table_alter_column_set_pk' + || statement.type === 'delete_reference' + || statement.type === 'alter_reference' + || statement.type === 'create_composite_pk' + || statement.type === 'alter_composite_pk' + || statement.type === 'delete_composite_pk' + || statement.type === 'create_unique_constraint' + || statement.type === 'delete_unique_constraint' + || statement.type === 'create_check_constraint' + || statement.type === 'delete_check_constraint' + ) { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], action); + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if (statement.type === 'sqlite_alter_table_add_column' && statement.column.primaryKey) { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], action); + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if (statement.type === 'create_reference') { + const tableName = statement.tableName; + + const data = action === 'push' + ? SQLiteSquasher.unsquashPushFK(statement.data) + : SQLiteSquasher.unsquashFK(statement.data); + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], action); + continue; + } + + // if add column with reference -> skip create_reference statement + if ( + data.columnsFrom.length === 1 + && statementsForTable.some((st) => + st.type === 'sqlite_alter_table_add_column' && st.column.name === data.columnsFrom[0] + ) + ) { + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareSQLiteRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + const tableName = statement.type === 'rename_table' + ? statement.tableNameTo + : (statement as { tableName: string }).tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = [statement]; + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + newStatements[tableName].push(statement); + } + } + + const combinedStatements = Object.values(newStatements).flat(); + + const renamedTables = combinedStatements.filter((it) => it.type === 'rename_table'); + const renamedColumns = combinedStatements.filter((it) => it.type === 'alter_table_rename_column'); + + const rest = combinedStatements.filter((it) => it.type !== 'rename_table' && it.type !== 'alter_table_rename_column'); + + return [...renamedTables, ...renamedColumns, ...rest]; +}; diff --git a/drizzle-kit/src/utils.ts b/drizzle-kit/src/utils.ts index 279520ea6..2638ca4ef 100644 --- a/drizzle-kit/src/utils.ts +++ b/drizzle-kit/src/utils.ts @@ -4,11 +4,13 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from import { join } from 'path'; import { parse } from 'url'; import type { NamedWithSchema } from './cli/commands/migrate'; +import { CasingType } from './cli/validations/common'; import { info } from './cli/views'; import { assertUnreachable, snapshotVersion } from './global'; import type { Dialect } from './schemaValidator'; import { backwardCompatibleMysqlSchema } from './serializer/mysqlSchema'; import { backwardCompatiblePgSchema } from './serializer/pgSchema'; +import { backwardCompatibleSingleStoreSchema } from './serializer/singlestoreSchema'; import { backwardCompatibleSqliteSchema } from './serializer/sqliteSchema'; import type { ProxyParams } from './serializer/studio'; @@ -25,9 +27,12 @@ export type DB = { export type SQLiteDB = { query: (sql: string, params?: any[]) => Promise; run(query: string): Promise; - batch?( - queries: { query: string; values?: any[] | undefined }[], - ): Promise; +}; + +export type LibSQLDB = { + query: (sql: string, params?: any[]) => Promise; + run(query: string): Promise; + batchWithPragma?(queries: string[]): Promise; }; export const copy = (it: T): T => { @@ -115,8 +120,12 @@ const validatorForDialect = (dialect: Dialect) => { return { validator: backwardCompatiblePgSchema, version: 7 }; case 'sqlite': return { validator: backwardCompatibleSqliteSchema, version: 6 }; + case 'turso': + return { validator: backwardCompatibleSqliteSchema, version: 6 }; case 'mysql': return { validator: backwardCompatibleMysqlSchema, version: 5 }; + case 'singlestore': + return { validator: backwardCompatibleSingleStoreSchema, version: 1 }; } }; @@ -327,3 +336,36 @@ export const normaliseSQLiteUrl = ( assertUnreachable(type); }; + +export const normalisePGliteUrl = ( + it: string, +) => { + if (it.startsWith('file:')) { + return it.substring(5); + } + + return it; +}; + +export function isPgArrayType(sqlType: string) { + return sqlType.match(/.*\[\d*\].*|.*\[\].*/g) !== null; +} + +export function findAddedAndRemoved(columnNames1: string[], columnNames2: string[]) { + const set1 = new Set(columnNames1); + const set2 = new Set(columnNames2); + + const addedColumns = columnNames2.filter((it) => !set1.has(it)); + const removedColumns = columnNames1.filter((it) => !set2.has(it)); + + return { addedColumns, removedColumns }; +} + +export function escapeSingleQuotes(str: string) { + return str.replace(/'/g, "''"); +} + +export function unescapeSingleQuotes(str: string, ignoreFirstAndLastChar: boolean) { + const regex = ignoreFirstAndLastChar ? /(?((resolve) => { + exec(command, options, (error) => { + return resolve({ exitCode: error?.code ?? 0 }); + }); + }); +} export const certs = async () => { - const res = await $`mkcert --help`.nothrow(); - - // ~/.local/share/drizzle-studio - const keyPath = join(p.data, 'localhost-key.pem'); - const certPath = join(p.data, 'localhost.pem'); + const res = await runCommand('mkcert --help'); if (res.exitCode === 0) { + const p = envPaths('drizzle-studio', { + suffix: '', + }); + + // create ~/.local/share/drizzle-studio + mkdirSync(p.data, { recursive: true }); + + // ~/.local/share/drizzle-studio + const keyPath = join(p.data, 'localhost-key.pem'); + const certPath = join(p.data, 'localhost.pem'); + try { + // check if the files exist await Promise.all([access(keyPath), access(certPath)]); } catch (e) { - await $`mkcert localhost`.nothrow(); + // if not create them + await runCommand(`mkcert localhost`, { cwd: p.data }); } const [key, cert] = await Promise.all([ readFile(keyPath, { encoding: 'utf-8' }), @@ -33,5 +42,3 @@ export const certs = async () => { } return null; }; - -certs(); diff --git a/drizzle-kit/tests/bin.test.ts b/drizzle-kit/tests/bin.test.ts new file mode 100644 index 000000000..826496676 --- /dev/null +++ b/drizzle-kit/tests/bin.test.ts @@ -0,0 +1,58 @@ +import chalk from 'chalk'; +import { assert, test } from 'vitest'; +import { analyzeImports, ChainLink } from '../imports-checker/checker'; + +test('imports-issues', () => { + const issues = analyzeImports({ + basePath: '.', + localPaths: ['src'], + whiteList: [ + '@drizzle-team/brocli', + 'json-diff', + 'path', + 'fs', + 'fs/*', + 'url', + 'zod', + 'node:*', + 'hono', + 'glob', + 'hono/*', + 'hono/**/*', + '@hono/*', + 'crypto', + 'hanji', + 'chalk', + 'dotenv/config', + 'camelcase', + 'semver', + 'env-paths', + ], + entry: 'src/cli/index.ts', + logger: true, + ignoreTypes: true, + }).issues; + + const chainToString = (chains: ChainLink[]) => { + if (chains.length === 0) throw new Error(); + + let out = chains[0]!.file + '\n'; + let indentation = 0; + for (let chain of chains) { + out += ' '.repeat(indentation) + + '└' + + chain.import + + ` ${chalk.gray(chain.file)}\n`; + indentation += 1; + } + return out; + }; + + console.log(); + for (const issue of issues) { + console.log(chalk.red(issue.imports.map((it) => it.name).join('\n'))); + console.log(issue.accessChains.map((it) => chainToString(it)).join('\n')); + } + + assert.equal(issues.length, 0); +}); diff --git a/drizzle-kit/tests/cli-export.test.ts b/drizzle-kit/tests/cli-export.test.ts new file mode 100644 index 000000000..8719ddd6a --- /dev/null +++ b/drizzle-kit/tests/cli-export.test.ts @@ -0,0 +1,79 @@ +import { test as brotest } from '@drizzle-team/brocli'; +import { assert, expect, test } from 'vitest'; +import { exportRaw } from '../src/cli/schema'; + +// good: +// #1 drizzle-kit export --dialect=postgresql --schema=schema.ts +// #3 drizzle-kit export +// #3 drizzle-kit export --config=drizzle1.config.ts + +// errors: +// #1 drizzle-kit export --schema=src/schema.ts +// #2 drizzle-kit export --dialect=postgresql +// #3 drizzle-kit export --dialect=postgresql2 +// #4 drizzle-kit export --config=drizzle.config.ts --schema=schema.ts +// #5 drizzle-kit export --config=drizzle.config.ts --dialect=postgresql + +test('export #1', async (t) => { + const res = await brotest( + exportRaw, + '--dialect=postgresql --schema=schema.ts', + ); + + if (res.type !== 'handler') assert.fail(res.type, 'handler'); + + expect(res.options).toStrictEqual({ + dialect: 'postgresql', + schema: 'schema.ts', + sql: true, + }); +}); + +test('export #2', async (t) => { + const res = await brotest(exportRaw, ''); + + if (res.type !== 'handler') assert.fail(res.type, 'handler'); + expect(res.options).toStrictEqual({ + dialect: 'postgresql', + schema: './schema.ts', + sql: true, + }); +}); + +// custom config path +test('export #3', async (t) => { + const res = await brotest(exportRaw, '--config=expo.config.ts'); + assert.equal(res.type, 'handler'); + if (res.type !== 'handler') assert.fail(res.type, 'handler'); + expect(res.options).toStrictEqual({ + dialect: 'sqlite', + schema: './schema.ts', + sql: true, + }); +}); + +// --- errors --- +test('err #1', async (t) => { + const res = await brotest(exportRaw, '--schema=src/schema.ts'); + assert.equal(res.type, 'error'); +}); + +test('err #2', async (t) => { + const res = await brotest(exportRaw, '--dialect=postgresql'); + assert.equal(res.type, 'error'); +}); + +test('err #3', async (t) => { + const res = await brotest(exportRaw, '--dialect=postgresql2'); + assert.equal(res.type, 'error'); +}); + +test('err #4', async (t) => { + const res = await brotest(exportRaw, '--config=drizzle.config.ts --schema=schema.ts'); + assert.equal(res.type, 'error'); +}); + +test('err #5', async (t) => { + const res = await brotest(exportRaw, '--config=drizzle.config.ts --dialect=postgresql'); + assert.equal(res.type, 'error'); +}); diff --git a/drizzle-kit/tests/cli-generate.test.ts b/drizzle-kit/tests/cli-generate.test.ts index 3e5c0fc22..a4adf979f 100644 --- a/drizzle-kit/tests/cli-generate.test.ts +++ b/drizzle-kit/tests/cli-generate.test.ts @@ -38,6 +38,8 @@ test('generate #1', async (t) => { schema: 'schema.ts', out: 'drizzle', bundle: false, + casing: undefined, + driver: undefined, }); }); @@ -57,11 +59,14 @@ test('generate #2', async (t) => { schema: 'schema.ts', out: 'out', bundle: false, + casing: undefined, + driver: undefined, }); }); test('generate #3', async (t) => { const res = await brotest(generate, ''); + if (res.type !== 'handler') assert.fail(res.type, 'handler'); expect(res.options).toStrictEqual({ dialect: 'postgresql', @@ -72,6 +77,8 @@ test('generate #3', async (t) => { schema: './schema.ts', out: 'drizzle', bundle: false, + casing: undefined, + driver: undefined, }); }); @@ -89,6 +96,8 @@ test('generate #4', async (t) => { schema: './schema.ts', out: 'drizzle', bundle: false, + casing: undefined, + driver: undefined, }); }); @@ -105,6 +114,8 @@ test('generate #5', async (t) => { schema: './schema.ts', out: 'drizzle', bundle: false, + casing: undefined, + driver: undefined, }); }); @@ -121,6 +132,8 @@ test('generate #6', async (t) => { schema: './schema.ts', out: 'drizzle', bundle: false, + casing: undefined, + driver: undefined, }); }); @@ -140,6 +153,8 @@ test('generate #7', async (t) => { schema: './schema.ts', out: 'drizzle', bundle: false, + casing: undefined, + driver: undefined, }); }); @@ -157,6 +172,26 @@ test('generate #8', async (t) => { schema: './schema.ts', out: 'drizzle', bundle: true, // expo driver + casing: undefined, + driver: 'expo', + }); +}); + +test('generate #9', async (t) => { + const res = await brotest(generate, '--config=durable-sqlite.config.ts'); + assert.equal(res.type, 'handler'); + if (res.type !== 'handler') assert.fail(res.type, 'handler'); + expect(res.options).toStrictEqual({ + dialect: 'sqlite', + name: undefined, + custom: false, + prefix: 'index', + breakpoints: true, + schema: './schema.ts', + out: 'drizzle', + bundle: true, // expo driver + casing: undefined, + driver: 'durable-sqlite', }); }); @@ -177,6 +212,8 @@ test('generate #9', async (t) => { schema: 'schema.ts', out: 'out', bundle: false, + casing: undefined, + driver: undefined, }); }); diff --git a/drizzle-kit/tests/cli-migrate.test.ts b/drizzle-kit/tests/cli-migrate.test.ts index a4ffec2f0..1425691f0 100644 --- a/drizzle-kit/tests/cli-migrate.test.ts +++ b/drizzle-kit/tests/cli-migrate.test.ts @@ -31,11 +31,10 @@ test('migrate #2', async (t) => { const res = await brotest(migrate, '--config=turso.config.ts'); if (res.type !== 'handler') assert.fail(res.type, 'handler'); expect(res.options).toStrictEqual({ - dialect: 'sqlite', + dialect: 'turso', out: 'drizzle', credentials: { authToken: 'token', - driver: 'turso', url: 'turso.dev', }, schema: undefined, // drizzle migrations table schema diff --git a/drizzle-kit/tests/cli-push.test.ts b/drizzle-kit/tests/cli-push.test.ts index 1a4bde66d..f5daf2bd0 100644 --- a/drizzle-kit/tests/cli-push.test.ts +++ b/drizzle-kit/tests/cli-push.test.ts @@ -25,8 +25,10 @@ test('push #1', async (t) => { schemaPath: './schema.ts', schemasFilter: ['public'], tablesFilter: [], + entities: undefined, strict: false, verbose: false, + casing: undefined, }); }); @@ -34,10 +36,9 @@ test('push #2', async (t) => { const res = await brotest(push, '--config=turso.config.ts'); if (res.type !== 'handler') assert.fail(res.type, 'handler'); expect(res.options).toStrictEqual({ - dialect: 'sqlite', + dialect: 'turso', credentials: { authToken: 'token', - driver: 'turso', url: 'turso.dev', }, force: false, @@ -46,6 +47,7 @@ test('push #2', async (t) => { tablesFilter: [], strict: false, verbose: false, + casing: undefined, }); }); @@ -66,6 +68,7 @@ test('push #3', async (t) => { tablesFilter: [], strict: false, verbose: false, + casing: undefined, }); }); @@ -85,8 +88,10 @@ test('push #4', async (t) => { schemaPath: './schema.ts', schemasFilter: ['public'], tablesFilter: [], + entities: undefined, strict: false, verbose: false, + casing: undefined, }); }); @@ -107,8 +112,10 @@ test('push #5', async (t) => { schemasFilter: ['public'], tablesFilter: [], strict: false, + entities: undefined, force: false, verbose: false, + casing: undefined, }); }); diff --git a/drizzle-kit/tests/cli/durable-sqlite.config.ts b/drizzle-kit/tests/cli/durable-sqlite.config.ts new file mode 100644 index 000000000..c3f4e44f0 --- /dev/null +++ b/drizzle-kit/tests/cli/durable-sqlite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from '../../src'; + +export default defineConfig({ + schema: './schema.ts', + dialect: 'sqlite', + driver: 'durable-sqlite', +}); diff --git a/drizzle-kit/tests/cli/turso.config.ts b/drizzle-kit/tests/cli/turso.config.ts index 089e4d216..85efe5934 100644 --- a/drizzle-kit/tests/cli/turso.config.ts +++ b/drizzle-kit/tests/cli/turso.config.ts @@ -2,8 +2,7 @@ import { defineConfig } from '../../src'; export default defineConfig({ schema: './schema.ts', - dialect: 'sqlite', - driver: 'turso', + dialect: 'turso', dbCredentials: { url: 'turso.dev', authToken: 'token', diff --git a/drizzle-kit/tests/indexes/pg.test.ts b/drizzle-kit/tests/indexes/pg.test.ts index 8419fd765..57f77c103 100644 --- a/drizzle-kit/tests/indexes/pg.test.ts +++ b/drizzle-kit/tests/indexes/pg.test.ts @@ -64,7 +64,7 @@ const pgSuite: DialectSuite = { }); expect(sqlStatements.length).toBe(1); expect(sqlStatements[0]).toBe( - `CREATE INDEX IF NOT EXISTS "vector_embedding_idx" ON "users" USING hnsw ("name" vector_ip_ops) WITH (m=16,ef_construction=64);`, + `CREATE INDEX "vector_embedding_idx" ON "users" USING hnsw ("name" vector_ip_ops) WITH (m=16,ef_construction=64);`, ); }, @@ -123,15 +123,15 @@ const pgSuite: DialectSuite = { ); expect(sqlStatements).toStrictEqual([ - 'DROP INDEX IF EXISTS "indx";', - 'DROP INDEX IF EXISTS "indx1";', - 'DROP INDEX IF EXISTS "indx2";', - 'DROP INDEX IF EXISTS "indx3";', - 'CREATE INDEX IF NOT EXISTS "indx4" ON "users" USING btree (lower(id)) WHERE true;', - 'CREATE INDEX IF NOT EXISTS "indx" ON "users" USING btree ("name" DESC NULLS LAST);', - 'CREATE INDEX IF NOT EXISTS "indx1" ON "users" USING btree ("name" DESC NULLS LAST) WHERE false;', - 'CREATE INDEX IF NOT EXISTS "indx2" ON "users" USING btree ("name" test) WHERE true;', - 'CREATE INDEX IF NOT EXISTS "indx3" ON "users" USING btree (lower("id")) WHERE true;', + 'DROP INDEX "indx";', + 'DROP INDEX "indx1";', + 'DROP INDEX "indx2";', + 'DROP INDEX "indx3";', + 'CREATE INDEX "indx4" ON "users" USING btree (lower(id)) WHERE true;', + 'CREATE INDEX "indx" ON "users" USING btree ("name" DESC NULLS LAST);', + 'CREATE INDEX "indx1" ON "users" USING btree ("name" DESC NULLS LAST) WHERE false;', + 'CREATE INDEX "indx2" ON "users" USING btree ("name" test) WHERE true;', + 'CREATE INDEX "indx3" ON "users" USING btree (lower("id")) WHERE true;', ]); }, @@ -180,14 +180,14 @@ const pgSuite: DialectSuite = { expression: 'name', isExpression: false, nulls: 'last', - opclass: undefined, + opclass: '', }, { asc: true, expression: 'id', isExpression: false, nulls: 'last', - opclass: undefined, + opclass: '', }, ], concurrently: false, @@ -212,14 +212,14 @@ const pgSuite: DialectSuite = { expression: 'name', isExpression: false, nulls: 'last', - opclass: undefined, + opclass: '', }, { asc: true, expression: '"name"', isExpression: true, nulls: 'last', - opclass: undefined, + opclass: '', }, ], concurrently: false, @@ -234,10 +234,10 @@ const pgSuite: DialectSuite = { }); expect(sqlStatements.length).toBe(2); expect(sqlStatements[0]).toBe( - `CREATE INDEX IF NOT EXISTS "users_name_id_index" ON "users" USING btree ("name" DESC NULLS LAST,"id") WITH (fillfactor=70) WHERE select 1;`, + `CREATE INDEX "users_name_id_index" ON "users" USING btree ("name" DESC NULLS LAST,"id") WITH (fillfactor=70) WHERE select 1;`, ); expect(sqlStatements[1]).toBe( - `CREATE INDEX IF NOT EXISTS "indx1" ON "users" USING hash ("name" DESC NULLS LAST,"name") WITH (fillfactor=70);`, + `CREATE INDEX "indx1" ON "users" USING hash ("name" DESC NULLS LAST,"name") WITH (fillfactor=70);`, ); }, }; diff --git a/drizzle-kit/tests/introspect/libsql.test.ts b/drizzle-kit/tests/introspect/libsql.test.ts new file mode 100644 index 000000000..9211989ca --- /dev/null +++ b/drizzle-kit/tests/introspect/libsql.test.ts @@ -0,0 +1,35 @@ +import { createClient } from '@libsql/client'; +import { sql } from 'drizzle-orm'; +import { int, sqliteTable, sqliteView } from 'drizzle-orm/sqlite-core'; +import fs from 'fs'; +import { introspectLibSQLToFile, introspectMySQLToFile, introspectSQLiteToFile } from 'tests/schemaDiffer'; +import { expect, test } from 'vitest'; + +if (!fs.existsSync('tests/introspect/libsql')) { + fs.mkdirSync('tests/introspect/libsql'); +} + +test('view #1', async () => { + const turso = createClient({ + url: ':memory:', + }); + + const users = sqliteTable('users', { id: int('id') }); + const testView = sqliteView('some_view', { id: int('id') }).as( + sql`SELECT * FROM ${users}`, + ); + + const schema = { + users: users, + testView, + }; + + const { statements, sqlStatements } = await introspectLibSQLToFile( + turso, + schema, + 'view-1', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); diff --git a/drizzle-kit/tests/introspect/mysql.test.ts b/drizzle-kit/tests/introspect/mysql.test.ts index 23cd28a16..2db33416b 100644 --- a/drizzle-kit/tests/introspect/mysql.test.ts +++ b/drizzle-kit/tests/introspect/mysql.test.ts @@ -1,12 +1,30 @@ +import 'dotenv/config'; import Docker from 'dockerode'; import { SQL, sql } from 'drizzle-orm'; -import { int, mysqlTable, text } from 'drizzle-orm/mysql-core'; +import { + bigint, + char, + check, + decimal, + double, + float, + int, + mediumint, + mysqlEnum, + mysqlTable, + mysqlView, + serial, + smallint, + text, + tinyint, + varchar, +} from 'drizzle-orm/mysql-core'; import * as fs from 'fs'; import getPort from 'get-port'; import { Connection, createConnection } from 'mysql2/promise'; import { introspectMySQLToFile } from 'tests/schemaDiffer'; import { v4 as uuid } from 'uuid'; -import { afterAll, beforeAll, expect, test } from 'vitest'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; let client: Connection; let mysqlContainer: Docker.Container; @@ -71,6 +89,12 @@ afterAll(async () => { await mysqlContainer?.stop().catch(console.error); }); +beforeEach(async () => { + await client.query(`drop database if exists \`drizzle\`;`); + await client.query(`create database \`drizzle\`;`); + await client.query(`use \`drizzle\`;`); +}); + if (!fs.existsSync('tests/introspect/mysql')) { fs.mkdirSync('tests/introspect/mysql'); } @@ -95,8 +119,6 @@ test('generated always column: link to another column', async () => { expect(statements.length).toBe(0); expect(sqlStatements.length).toBe(0); - - await client.query(`drop table users;`); }); test('generated always column virtual: link to another column', async () => { @@ -120,6 +142,178 @@ test('generated always column virtual: link to another column', async () => { expect(statements.length).toBe(0); expect(sqlStatements.length).toBe(0); +}); + +test('Default value of character type column: char', async () => { + const schema = { + users: mysqlTable('users', { + id: int('id'), + sortKey: char('sortKey', { length: 255 }).default('0'), + }), + }; + + const { statements, sqlStatements } = await introspectMySQLToFile( + client, + schema, + 'default-value-char-column', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('Default value of character type column: varchar', async () => { + const schema = { + users: mysqlTable('users', { + id: int('id'), + sortKey: varchar('sortKey', { length: 255 }).default('0'), + }), + }; + + const { statements, sqlStatements } = await introspectMySQLToFile( + client, + schema, + 'default-value-varchar-column', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect checks', async () => { + const schema = { + users: mysqlTable('users', { + id: serial('id'), + name: varchar('name', { length: 255 }), + age: int('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const { statements, sqlStatements } = await introspectMySQLToFile( + client, + schema, + 'introspect-checks', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('view #1', async () => { + const users = mysqlTable('users', { id: int('id') }); + const testView = mysqlView('some_view', { id: int('id') }).as( + sql`select \`drizzle\`.\`users\`.\`id\` AS \`id\` from \`drizzle\`.\`users\``, + ); + + const schema = { + users: users, + testView, + }; + + const { statements, sqlStatements } = await introspectMySQLToFile( + client, + schema, + 'view-1', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('view #2', async () => { + const users = mysqlTable('some_users', { id: int('id') }); + const testView = mysqlView('some_view', { id: int('id') }).algorithm('temptable').sqlSecurity('definer').as( + sql`SELECT * FROM ${users}`, + ); + + const schema = { + users: users, + testView, + }; + + const { statements, sqlStatements } = await introspectMySQLToFile( + client, + schema, + 'view-2', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('handle float type', async () => { + const schema = { + table: mysqlTable('table', { + col1: float(), + col2: float({ precision: 2 }), + col3: float({ precision: 2, scale: 1 }), + }), + }; + + const { statements, sqlStatements } = await introspectMySQLToFile( + client, + schema, + 'handle-float-type', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('handle unsigned numerical types', async () => { + const schema = { + table: mysqlTable('table', { + col1: int({ unsigned: true }), + col2: tinyint({ unsigned: true }), + col3: smallint({ unsigned: true }), + col4: mediumint({ unsigned: true }), + col5: bigint({ mode: 'number', unsigned: true }), + col6: float({ unsigned: true }), + col7: float({ precision: 2, scale: 1, unsigned: true }), + col8: double({ unsigned: true }), + col9: double({ precision: 2, scale: 1, unsigned: true }), + col10: decimal({ unsigned: true }), + col11: decimal({ precision: 2, scale: 1, unsigned: true }), + }), + }; + + const { statements, sqlStatements } = await introspectMySQLToFile( + client, + schema, + 'handle-unsigned-numerical-types', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('instrospect strings with single quotes', async () => { + const schema = { + columns: mysqlTable('columns', { + enum: mysqlEnum('my_enum', ['escape\'s quotes "', 'escape\'s quotes 2 "']).default('escape\'s quotes "'), + text: text('text').default('escape\'s quotes " '), + varchar: varchar('varchar', { length: 255 }).default('escape\'s quotes " '), + }), + }; + + const { statements, sqlStatements } = await introspectMySQLToFile( + client, + schema, + 'introspect-strings-with-single-quotes', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); - await client.query(`drop table users;`); + await client.query(`drop table columns;`); }); diff --git a/drizzle-kit/tests/introspect/pg.test.ts b/drizzle-kit/tests/introspect/pg.test.ts index 40b06187f..1d9f0f18c 100644 --- a/drizzle-kit/tests/introspect/pg.test.ts +++ b/drizzle-kit/tests/introspect/pg.test.ts @@ -1,9 +1,47 @@ import { PGlite } from '@electric-sql/pglite'; import { SQL, sql } from 'drizzle-orm'; -import { integer, pgTable, text } from 'drizzle-orm/pg-core'; +import { + bigint, + bigserial, + boolean, + char, + check, + cidr, + date, + doublePrecision, + inet, + integer, + interval, + json, + jsonb, + macaddr, + macaddr8, + numeric, + pgEnum, + pgMaterializedView, + pgPolicy, + pgRole, + pgSchema, + pgTable, + pgView, + real, + serial, + smallint, + smallserial, + text, + time, + timestamp, + uuid, + varchar, +} from 'drizzle-orm/pg-core'; +import fs from 'fs'; import { introspectPgToFile } from 'tests/schemaDiffer'; import { expect, test } from 'vitest'; +if (!fs.existsSync('tests/introspect/postgres')) { + fs.mkdirSync('tests/introspect/postgres'); +} + test('basic introspect test', async () => { const client = new PGlite(); @@ -186,3 +224,671 @@ test('generated column: link to another column', async () => { expect(statements.length).toBe(0); expect(sqlStatements.length).toBe(0); }); + +test('instrospect all column types', async () => { + const client = new PGlite(); + + const myEnum = pgEnum('my_enum', ['a', 'b', 'c']); + const schema = { + enum_: myEnum, + // NOTE: Types from extensions aren't tested due to PGlite not supporting at the moment + columns: pgTable('columns', { + enum: myEnum('my_enum').default('a'), + smallint: smallint('smallint').default(10), + integer: integer('integer').default(10), + numeric: numeric('numeric', { precision: 3, scale: 1 }).default('99.9'), + numeric2: numeric('numeric2', { precision: 1, scale: 1 }).default('99.9'), + numeric3: numeric('numeric3').default('99.9'), + bigint: bigint('bigint', { mode: 'number' }).default(100), + boolean: boolean('boolean').default(true), + text: text('test').default('abc'), + varchar: varchar('varchar', { length: 25 }).default('abc'), + char: char('char', { length: 3 }).default('abc'), + serial: serial('serial'), + bigserial: bigserial('bigserial', { mode: 'number' }), + smallserial: smallserial('smallserial'), + doublePrecision: doublePrecision('doublePrecision').default(100), + real: real('real').default(100), + json: json('json').$type<{ attr: string }>().default({ attr: 'value' }), + jsonb: jsonb('jsonb').$type<{ attr: string }>().default({ attr: 'value' }), + time1: time('time1').default('00:00:00'), + time2: time('time2').defaultNow(), + timestamp1: timestamp('timestamp1', { withTimezone: true, precision: 6 }).default(new Date()), + timestamp2: timestamp('timestamp2', { withTimezone: true, precision: 6 }).defaultNow(), + timestamp3: timestamp('timestamp3', { withTimezone: true, precision: 6 }).default( + sql`timezone('utc'::text, now())`, + ), + date1: date('date1').default('2024-01-01'), + date2: date('date2').defaultNow(), + date3: date('date3').default(sql`CURRENT_TIMESTAMP`), + uuid1: uuid('uuid1').default('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'), + uuid2: uuid('uuid2').defaultRandom(), + inet: inet('inet').default('127.0.0.1'), + cidr: cidr('cidr').default('127.0.0.1/32'), + macaddr: macaddr('macaddr').default('00:00:00:00:00:00'), + macaddr8: macaddr8('macaddr8').default('00:00:00:ff:fe:00:00:00'), + interval: interval('interval').default('1 day 01:00:00'), + }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-all-columns-types', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('instrospect all column array types', async () => { + const client = new PGlite(); + + const myEnum = pgEnum('my_enum', ['a', 'b', 'c']); + const schema = { + enum_: myEnum, + // NOTE: Types from extensions aren't tested due to PGlite not supporting at the moment + columns: pgTable('columns', { + enum: myEnum('my_enum').array().default(['a', 'b']), + smallint: smallint('smallint').array().default([10, 20]), + integer: integer('integer').array().default([10, 20]), + numeric: numeric('numeric', { precision: 3, scale: 1 }).array().default(['99.9', '88.8']), + bigint: bigint('bigint', { mode: 'number' }).array().default([100, 200]), + boolean: boolean('boolean').array().default([true, false]), + text: text('test').array().default(['abc', 'def']), + varchar: varchar('varchar', { length: 25 }).array().default(['abc', 'def']), + char: char('char', { length: 3 }).array().default(['abc', 'def']), + doublePrecision: doublePrecision('doublePrecision').array().default([100, 200]), + real: real('real').array().default([100, 200]), + json: json('json').$type<{ attr: string }>().array().default([{ attr: 'value1' }, { attr: 'value2' }]), + jsonb: jsonb('jsonb').$type<{ attr: string }>().array().default([{ attr: 'value1' }, { attr: 'value2' }]), + time: time('time').array().default(['00:00:00', '01:00:00']), + timestamp: timestamp('timestamp', { withTimezone: true, precision: 6 }) + .array() + .default([new Date(), new Date()]), + date: date('date').array().default(['2024-01-01', '2024-01-02']), + uuid: uuid('uuid').array().default([ + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12', + ]), + inet: inet('inet').array().default(['127.0.0.1', '127.0.0.2']), + cidr: cidr('cidr').array().default(['127.0.0.1/32', '127.0.0.2/32']), + macaddr: macaddr('macaddr').array().default(['00:00:00:00:00:00', '00:00:00:00:00:01']), + macaddr8: macaddr8('macaddr8').array().default(['00:00:00:ff:fe:00:00:00', '00:00:00:ff:fe:00:00:01']), + interval: interval('interval').array().default(['1 day 01:00:00', '1 day 02:00:00']), + }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-all-columns-array-types', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect columns with name with non-alphanumeric characters', async () => { + const client = new PGlite(); + const schema = { + users: pgTable('users', { + 'not:allowed': integer('not:allowed'), + 'nuh--uh': integer('nuh-uh'), + '1_nope': integer('1_nope'), + valid: integer('valid'), + }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-column-with-name-with-non-alphanumeric-characters', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect enum from different schema', async () => { + const client = new PGlite(); + + const schema2 = pgSchema('schema2'); + const myEnumInSchema2 = schema2.enum('my_enum', ['a', 'b', 'c']); + const schema = { + schema2, + myEnumInSchema2, + users: pgTable('users', { + col: myEnumInSchema2('col'), + }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-enum-from-different-schema', + ['public', 'schema2'], + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect enum with same names across different schema', async () => { + const client = new PGlite(); + + const schema2 = pgSchema('schema2'); + const myEnumInSchema2 = schema2.enum('my_enum', ['a', 'b', 'c']); + const myEnum = pgEnum('my_enum', ['a', 'b', 'c']); + const schema = { + schema2, + myEnumInSchema2, + myEnum, + users: pgTable('users', { + col1: myEnumInSchema2('col1'), + col2: myEnum('col2'), + }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-enum-with-same-names-across-different-schema', + ['public', 'schema2'], + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect enum with similar name to native type', async () => { + const client = new PGlite(); + + const timeLeft = pgEnum('time_left', ['short', 'medium', 'long']); + const schema = { + timeLeft, + auction: pgTable('auction', { + col: timeLeft('col1'), + }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-enum-with-similar-name-to-native-type', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('instrospect strings with single quotes', async () => { + const client = new PGlite(); + + const myEnum = pgEnum('my_enum', ['escape\'s quotes " ']); + const schema = { + enum_: myEnum, + columns: pgTable('columns', { + enum: myEnum('my_enum').default('escape\'s quotes " '), + text: text('text').default('escape\'s quotes " '), + varchar: varchar('varchar').default('escape\'s quotes " '), + }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-strings-with-single-quotes', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect checks', async () => { + const client = new PGlite(); + + const schema = { + users: pgTable('users', { + id: serial('id'), + name: varchar('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-checks', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect checks from different schemas with same names', async () => { + const client = new PGlite(); + + const mySchema = pgSchema('schema2'); + const schema = { + mySchema, + users: pgTable('users', { + id: serial('id'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + usersInMySchema: mySchema.table('users', { + id: serial('id'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} < 1`), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-checks-diff-schema-same-names', + ['public', 'schema2'], + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect view #1', async () => { + const client = new PGlite(); + + const users = pgTable('users', { + id: serial('id').primaryKey().notNull(), + name: varchar('users'), + }); + + const view = pgView('some_view').as((qb) => qb.select().from(users)); + const schema = { + view, + users, + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-view', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect view #2', async () => { + const client = new PGlite(); + + const users = pgTable('users', { + id: serial('id').primaryKey().notNull(), + name: varchar('users'), + }); + + const view = pgView('some_view', { id: integer('asd') }).with({ checkOption: 'cascaded' }).as( + sql`SELECT * FROM ${users}`, + ); + const schema = { + view, + users, + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-view-2', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect view in other schema', async () => { + const client = new PGlite(); + + const newSchema = pgSchema('new_schema'); + const users = pgTable('users', { + id: serial('id').primaryKey().notNull(), + name: varchar('users'), + }); + + const view = newSchema.view('some_view', { id: integer('asd') }).with({ checkOption: 'cascaded' }).as( + sql`SELECT * FROM ${users}`, + ); + const schema = { + view, + users, + newSchema, + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-view-in-other-schema', + ['new_schema'], + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect materialized view in other schema', async () => { + const client = new PGlite(); + + const newSchema = pgSchema('new_schema'); + const users = pgTable('users', { + id: serial('id').primaryKey().notNull(), + name: varchar('users'), + }); + + const view = newSchema.materializedView('some_view', { id: integer('asd') }).with({ autovacuumEnabled: true }).as( + sql`SELECT * FROM ${users}`, + ); + const schema = { + view, + users, + newSchema, + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-mat-view-in-other-schema', + ['new_schema'], + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect materialized view #1', async () => { + const client = new PGlite(); + + const users = pgTable('users', { + id: serial('id').primaryKey().notNull(), + name: varchar('users'), + }); + + const view = pgMaterializedView('some_view').using('heap').withNoData().as((qb) => qb.select().from(users)); + const schema = { + view, + users, + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-materialized-view', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect materialized view #2', async () => { + const client = new PGlite(); + + const users = pgTable('users', { + id: serial('id').primaryKey().notNull(), + name: varchar('users'), + }); + + const view = pgMaterializedView('some_view', { id: integer('asd') }).with({ autovacuumFreezeMinAge: 1 }).as( + sql`SELECT * FROM ${users}`, + ); + const schema = { + view, + users, + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'introspect-materialized-view-2', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('basic policy', async () => { + const client = new PGlite(); + + const schema = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test'), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'basic-policy', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('basic policy with "as"', async () => { + const client = new PGlite(); + + const schema = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'basic-policy-as', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test.todo('basic policy with CURRENT_USER role', async () => { + const client = new PGlite(); + + const schema = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { to: 'current_user' }), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'basic-policy', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('basic policy with all fields except "using" and "with"', async () => { + const client = new PGlite(); + + const schema = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', for: 'all', to: ['postgres'] }), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'basic-policy-all-fields', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('basic policy with "using" and "with"', async () => { + const client = new PGlite(); + + const schema = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { using: sql`true`, withCheck: sql`true` }), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'basic-policy-using-withcheck', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('multiple policies', async () => { + const client = new PGlite(); + + const schema = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { using: sql`true`, withCheck: sql`true` }), + rlsPolicy: pgPolicy('newRls'), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'multiple-policies', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('multiple policies with roles', async () => { + const client = new PGlite(); + + client.query(`CREATE ROLE manager;`); + + const schema = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { using: sql`true`, withCheck: sql`true` }), + rlsPolicy: pgPolicy('newRls', { to: ['postgres', 'manager'] }), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'multiple-policies-with-roles', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('basic roles', async () => { + const client = new PGlite(); + + const schema = { + usersRole: pgRole('user'), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'basic-roles', + ['public'], + { roles: { include: ['user'] } }, + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('role with properties', async () => { + const client = new PGlite(); + + const schema = { + usersRole: pgRole('user', { inherit: false, createDb: true, createRole: true }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'roles-with-properties', + ['public'], + { roles: { include: ['user'] } }, + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('role with a few properties', async () => { + const client = new PGlite(); + + const schema = { + usersRole: pgRole('user', { inherit: false, createRole: true }), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'roles-with-few-properties', + ['public'], + { roles: { include: ['user'] } }, + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('multiple policies with roles from schema', async () => { + const client = new PGlite(); + + const usersRole = pgRole('user_role', { inherit: false, createRole: true }); + + const schema = { + usersRole, + + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { using: sql`true`, withCheck: sql`true` }), + rlsPolicy: pgPolicy('newRls', { to: ['postgres', usersRole] }), + })), + }; + + const { statements, sqlStatements } = await introspectPgToFile( + client, + schema, + 'multiple-policies-with-roles-from-schema', + ['public'], + { roles: { include: ['user_role'] } }, + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); diff --git a/drizzle-kit/tests/introspect/singlestore.test.ts b/drizzle-kit/tests/introspect/singlestore.test.ts new file mode 100644 index 000000000..71960c3f7 --- /dev/null +++ b/drizzle-kit/tests/introspect/singlestore.test.ts @@ -0,0 +1,275 @@ +import Docker from 'dockerode'; +import 'dotenv/config'; +import { SQL, sql } from 'drizzle-orm'; +import { + bigint, + char, + decimal, + double, + float, + int, + mediumint, + singlestoreTable, + smallint, + text, + tinyint, + varchar, +} from 'drizzle-orm/singlestore-core'; +import * as fs from 'fs'; +import getPort from 'get-port'; +import { Connection, createConnection } from 'mysql2/promise'; +import { introspectSingleStoreToFile } from 'tests/schemaDiffer'; +import { v4 as uuid } from 'uuid'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; + +let client: Connection; +let singlestoreContainer: Docker.Container; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'ghcr.io/singlestore-labs/singlestoredb-dev:latest'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + singlestoreContainer = await docker.createContainer({ + Image: image, + Env: ['ROOT_PASSWORD=singlestore'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await singlestoreContainer.start(); + await new Promise((resolve) => setTimeout(resolve, 4000)); + + return `singlestore://root:singlestore@localhost:${port}/`; +} + +beforeAll(async () => { + const connectionString = process.env.SINGLESTORE_CONNECTION_STRING ?? await createDockerDB(); + + const sleep = 1000; + let timeLeft = 20000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to SingleStore'); + await client?.end().catch(console.error); + await singlestoreContainer?.stop().catch(console.error); + throw lastError; + } +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await singlestoreContainer?.stop().catch(console.error); +}); + +beforeEach(async () => { + await client.query(`drop database if exists \`drizzle\`;`); + await client.query(`create database \`drizzle\`;`); + await client.query(`use \`drizzle\`;`); +}); + +if (!fs.existsSync('tests/introspect/singlestore')) { + fs.mkdirSync('tests/introspect/singlestore'); +} + +// TODO: Unskip this test when generated column is implemented +/* test.skip('generated always column: link to another column', async () => { + const schema = { + users: singlestoreTable('users', { + id: int('id'), + email: text('email'), + generatedEmail: text('generatedEmail').generatedAlwaysAs( + (): SQL => sql`\`email\``, + ), + }), + }; + + const { statements, sqlStatements } = await introspectSingleStoreToFile( + client, + schema, + 'generated-link-column', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); */ + +// TODO: Unskip this test when generated column is implemented +/* test.skip('generated always column virtual: link to another column', async () => { + const schema = { + users: singlestoreTable('users', { + id: int('id'), + email: text('email'), + generatedEmail: text('generatedEmail').generatedAlwaysAs( + (): SQL => sql`\`email\``, + { mode: 'virtual' }, + ), + }), + }; + + const { statements, sqlStatements } = await introspectSingleStoreToFile( + client, + schema, + 'generated-link-column-virtual', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); */ + +test('Default value of character type column: char', async () => { + const schema = { + users: singlestoreTable('users', { + id: int('id'), + sortKey: char('sortKey', { length: 255 }).default('0'), + }), + }; + + const { statements, sqlStatements } = await introspectSingleStoreToFile( + client, + schema, + 'default-value-char-column', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('Default value of character type column: varchar', async () => { + const schema = { + users: singlestoreTable('users', { + id: int('id'), + sortKey: varchar('sortKey', { length: 255 }).default('0'), + }), + }; + + const { statements, sqlStatements } = await introspectSingleStoreToFile( + client, + schema, + 'default-value-varchar-column', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +// TODO: Unskip this test when views are implemented +/* test('view #1', async () => { + const users = singlestoreTable('users', { id: int('id') }); + const testView = singlestoreView('some_view', { id: int('id') }).as( + sql`select \`drizzle\`.\`users\`.\`id\` AS \`id\` from \`drizzle\`.\`users\``, + ); + + const schema = { + users: users, + testView, + }; + + const { statements, sqlStatements } = await introspectSingleStoreToFile( + client, + schema, + 'view-1', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); */ + +// TODO: Unskip this test when views are implemented +/* test('view #2', async () => { + const users = singlestoreTable('some_users', { id: int('id') }); + const testView = singlestoreView('some_view', { id: int('id') }).algorithm('temptable').sqlSecurity('definer').as( + sql`SELECT * FROM ${users}`, + ); + + const schema = { + users: users, + testView, + }; + + const { statements, sqlStatements } = await introspectSingleStoreToFile( + client, + schema, + 'view-2', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); */ + +test('handle float type', async () => { + const schema = { + table: singlestoreTable('table', { + col1: float(), + col2: float({ precision: 2 }), + col3: float({ precision: 2, scale: 1 }), + }), + }; + + const { statements, sqlStatements } = await introspectSingleStoreToFile( + client, + schema, + 'handle-float-type', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('handle unsigned numerical types', async () => { + const schema = { + table: singlestoreTable('table', { + col1: int({ unsigned: true }), + col2: tinyint({ unsigned: true }), + col3: smallint({ unsigned: true }), + col4: mediumint({ unsigned: true }), + col5: bigint({ mode: 'number', unsigned: true }), + col6: float({ unsigned: true }), + col7: float({ precision: 2, scale: 1, unsigned: true }), + col8: double({ unsigned: true }), + col9: double({ precision: 2, scale: 1, unsigned: true }), + col10: decimal({ unsigned: true }), + col11: decimal({ precision: 2, scale: 1, unsigned: true }), + }), + }; + + const { statements, sqlStatements } = await introspectSingleStoreToFile( + client, + schema, + 'handle-unsigned-numerical-types', + 'drizzle', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); diff --git a/drizzle-kit/tests/introspect/sqlite.test.ts b/drizzle-kit/tests/introspect/sqlite.test.ts index 18473e87b..de13d4e81 100644 --- a/drizzle-kit/tests/introspect/sqlite.test.ts +++ b/drizzle-kit/tests/introspect/sqlite.test.ts @@ -1,6 +1,6 @@ import Database from 'better-sqlite3'; import { SQL, sql } from 'drizzle-orm'; -import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { check, int, sqliteTable, sqliteView, text } from 'drizzle-orm/sqlite-core'; import * as fs from 'fs'; import { introspectSQLiteToFile } from 'tests/schemaDiffer'; import { expect, test } from 'vitest'; @@ -55,3 +55,68 @@ test('generated always column virtual: link to another column', async () => { expect(statements.length).toBe(0); expect(sqlStatements.length).toBe(0); }); + +test('instrospect strings with single quotes', async () => { + const sqlite = new Database(':memory:'); + + const schema = { + columns: sqliteTable('columns', { + text: text('text').default('escape\'s quotes " '), + }), + }; + + const { statements, sqlStatements } = await introspectSQLiteToFile( + sqlite, + schema, + 'introspect-strings-with-single-quotes', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('introspect checks', async () => { + const sqlite = new Database(':memory:'); + + const schema = { + users: sqliteTable('users', { + id: int('id'), + name: text('name'), + age: int('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const { statements, sqlStatements } = await introspectSQLiteToFile( + sqlite, + schema, + 'introspect-checks', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('view #1', async () => { + const sqlite = new Database(':memory:'); + + const users = sqliteTable('users', { id: int('id') }); + const testView = sqliteView('some_view', { id: int('id') }).as( + sql`SELECT * FROM ${users}`, + ); + + const schema = { + users: users, + testView, + }; + + const { statements, sqlStatements } = await introspectSQLiteToFile( + sqlite, + schema, + 'view-1', + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); diff --git a/drizzle-kit/tests/libsql-checks.test.ts b/drizzle-kit/tests/libsql-checks.test.ts new file mode 100644 index 000000000..2a3abf2dc --- /dev/null +++ b/drizzle-kit/tests/libsql-checks.test.ts @@ -0,0 +1,308 @@ +import { sql } from 'drizzle-orm'; +import { check, int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasLibSQL } from './schemaDiffer'; + +test('create table with check', async (t) => { + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasLibSQL({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_create_table', + tableName: 'users', + columns: [ + { + name: 'id', + type: 'integer', + notNull: true, + primaryKey: true, + autoincrement: false, + }, + { + name: 'age', + type: 'integer', + notNull: false, + primaryKey: false, + autoincrement: false, + }, + ], + compositePKs: [], + checkConstraints: ['some_check_name;"users"."age" > 21'], + referenceData: [], + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE TABLE \`users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer, +\tCONSTRAINT "some_check_name" CHECK("users"."age" > 21) +);\n`); +}); + +test('add check contraint to existing table', async (t) => { + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const from = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }), + }; + + const { sqlStatements, statements } = await diffTestSchemasLibSQL(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: ['some_check_name;"users"."age" > 21'], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer, +\tCONSTRAINT "some_check_name" CHECK("__new_users"."age" > 21) +);\n`); + expect(sqlStatements[2]).toBe(`INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('drop check contraint to existing table', async (t) => { + const from = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }), + }; + + const { sqlStatements, statements } = await diffTestSchemasLibSQL(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer +);\n`); + expect(sqlStatements[2]).toBe(`INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('rename check constraint', async (t) => { + const from = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('new_some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasLibSQL(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [`new_some_check_name;"users"."age" > 21`], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer, +\tCONSTRAINT "new_some_check_name" CHECK("__new_users"."age" > 21) +);\n`); + expect(sqlStatements[2]).toBe(`INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('rename check constraint', async (t) => { + const from = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 10`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasLibSQL(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [`some_check_name;"users"."age" > 10`], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer, +\tCONSTRAINT "some_check_name" CHECK("__new_users"."age" > 10) +);\n`); + expect(sqlStatements[2]).toBe(`INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('create checks with same names', async (t) => { + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + name: text('name'), + }, (table) => ({ + checkConstraint1: check('some_check_name', sql`${table.age} > 21`), + checkConstraint2: check('some_check_name', sql`${table.name} != 'Alex'`), + })), + }; + + await expect(diffTestSchemasLibSQL({}, to, [])).rejects.toThrowError(); +}); diff --git a/drizzle-kit/tests/libsql-statements.test.ts b/drizzle-kit/tests/libsql-statements.test.ts new file mode 100644 index 000000000..636496c45 --- /dev/null +++ b/drizzle-kit/tests/libsql-statements.test.ts @@ -0,0 +1,989 @@ +import { foreignKey, index, int, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'; +import { JsonRecreateTableStatement } from 'src/jsonStatements'; +import { expect, test } from 'vitest'; +import { diffTestSchemasLibSQL } from './schemaDiffer'; + +test('drop autoincrement', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + }), + }; + + const { statements } = await diffTestSchemasLibSQL(schema1, schema2, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [{ + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); +}); + +test('set autoincrement', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }), + }; + + const { statements } = await diffTestSchemasLibSQL(schema1, schema2, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [{ + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); +}); + +test('set not null', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text NOT NULL;`, + ); +}); + +test('drop not null', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_drop_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`, + ); +}); + +test('set default. set not null. add column', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull().default('name'), + age: int('age').notNull(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(3); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_default', + tableName: 'users', + columnName: 'name', + newDefaultValue: "'name'", + schema: '', + newDataType: 'text', + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + expect(statements[1]).toStrictEqual({ + type: 'alter_table_alter_column_set_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: "'name'", + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + expect(statements[2]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'users', + referenceData: undefined, + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text NOT NULL DEFAULT 'name';`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` ADD \`age\` integer NOT NULL;`, + ); +}); + +test('drop default. drop not null', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull().default('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_drop_default', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + expect(statements[1]).toStrictEqual({ + type: 'alter_table_alter_column_drop_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`, + ); +}); + +test('set data type. set default', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: int('name').default(123), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_type', + tableName: 'users', + columnName: 'name', + newDataType: 'integer', + oldDataType: 'text', + schema: '', + columnDefault: 123, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + expect(statements[1]).toStrictEqual({ + type: 'alter_table_alter_column_set_default', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'integer', + newDefaultValue: 123, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" integer DEFAULT 123;`, + ); +}); + +test('add foriegn key', async (t) => { + const schema = { + table: sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id').references(() => schema.table.id), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_reference', + tableName: 'users', + data: 'users_table_id_table_id_fk;users;table_id;table;id;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "table_id" TO "table_id" integer REFERENCES table(id) ON DELETE no action ON UPDATE no action;`, + ); +}); + +test('drop foriegn key', async (t) => { + const schema = { + table: sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id').references(() => schema.table.id, { + onDelete: 'cascade', + }), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'table_id', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`table_id\` integer +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "table_id") SELECT "id", "table_id" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('alter foriegn key', async (t) => { + const tableRef = sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }); + const tableRef2 = sqliteTable('table2', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id').references(() => tableRef.id, { + onDelete: 'cascade', + }), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id').references(() => tableRef2.id), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'table_id', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [ + { + columnsFrom: ['table_id'], + columnsTo: ['id'], + name: 'users_table_id_table2_id_fk', + onDelete: 'no action', + onUpdate: 'no action', + tableFrom: 'users', + tableTo: 'table2', + }, + ], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`table_id\` integer, +\tFOREIGN KEY (\`table_id\`) REFERENCES \`table2\`(\`id\`) ON UPDATE no action ON DELETE no action +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "table_id") SELECT "id", "table_id" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe( + 'DROP TABLE `users`;', + ); + expect(sqlStatements[4]).toBe( + 'ALTER TABLE `__new_users` RENAME TO `users`;', + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('add foriegn key for multiple columns', async (t) => { + const tableRef = sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + age: int('age'), + age1: int('age_1'), + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + column: int('column'), + column1: int('column_1'), + }), + tableRef, + }; + + const schema2 = { + tableRef, + users: sqliteTable( + 'users', + { + id: int('id').primaryKey({ autoIncrement: true }), + column: int('column'), + column1: int('column_1'), + }, + (table) => ({ + foreignKey: foreignKey({ + columns: [table.column, table.column1], + foreignColumns: [tableRef.age, tableRef.age1], + }), + }), + ), + }; + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'column', + notNull: false, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'column_1', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [ + { + columnsFrom: ['column', 'column_1'], + columnsTo: ['age', 'age_1'], + name: 'users_column_column_1_table_age_age_1_fk', + onDelete: 'no action', + onUpdate: 'no action', + tableFrom: 'users', + tableTo: 'table', + }, + ], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + } as JsonRecreateTableStatement); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe( + `CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`column\` integer, +\t\`column_1\` integer, +\tFOREIGN KEY (\`column\`,\`column_1\`) REFERENCES \`table\`(\`age\`,\`age_1\`) ON UPDATE no action ON DELETE no action +);\n`, + ); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "column", "column_1") SELECT "id", "column", "column_1" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('drop foriegn key for multiple columns', async (t) => { + const tableRef = sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + age: int('age'), + age1: int('age_1'), + }); + + const schema1 = { + users: sqliteTable( + 'users', + { + id: int('id').primaryKey({ autoIncrement: true }), + column: int('column'), + column1: int('column_1'), + }, + (table) => ({ + foreignKey: foreignKey({ + columns: [table.column, table.column1], + foreignColumns: [tableRef.age, tableRef.age1], + }), + }), + ), + tableRef, + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + column: int('column'), + column1: int('column_1'), + }), + tableRef, + }; + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'column', + notNull: false, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'column_1', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe( + `CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`column\` integer, +\t\`column_1\` integer +);\n`, + ); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "column", "column_1") SELECT "id", "column", "column_1" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('alter column drop generated', async (t) => { + const from = { + users: sqliteTable('table', { + id: int('id').primaryKey().notNull(), + name: text('name').generatedAlwaysAs('drizzle is the best').notNull(), + }), + }; + + const to = { + users: sqliteTable('table', { + id: int('id').primaryKey().notNull(), + name: text('name').notNull(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + from, + to, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'table', + type: 'alter_table_alter_column_drop_generated', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TABLE \`table\` DROP COLUMN \`name\`;`); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`table\` ADD \`name\` text NOT NULL;`, + ); +}); + +test('recreate table with nested references', async (t) => { + let users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }); + let subscriptions = sqliteTable('subscriptions', { + id: int('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + customerId: text('customer_id'), + }); + const schema1 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }); + const schema2 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "name", "age") SELECT "id", "name", "age" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('set not null with index', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }, (table) => ({ + someIndex: index('users_name_index').on(table.name), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }, (table) => ({ + someIndex: index('users_name_index').on(table.name), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(3); + expect(sqlStatements[0]).toBe( + `DROP INDEX "users_name_index";`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text NOT NULL;`, + ); + expect(sqlStatements[2]).toBe( + `CREATE INDEX \`users_name_index\` ON \`users\` (\`name\`);`, + ); +}); + +test('drop not null with two indexes', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + age: int('age').notNull(), + }, (table) => ({ + someUniqeIndex: uniqueIndex('users_name_unique').on(table.name), + someIndex: index('users_age_index').on(table.age), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: int('age').notNull(), + }, (table) => ({ + someUniqeIndex: uniqueIndex('users_name_unique').on(table.name), + someIndex: index('users_age_index').on(table.age), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_drop_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(5); + expect(sqlStatements[0]).toBe( + `DROP INDEX "users_name_unique";`, + ); + expect(sqlStatements[1]).toBe( + `DROP INDEX "users_age_index";`, + ); + expect(sqlStatements[2]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`, + ); + expect(sqlStatements[3]).toBe( + `CREATE UNIQUE INDEX \`users_name_unique\` ON \`users\` (\`name\`);`, + ); + expect(sqlStatements[4]).toBe( + `CREATE INDEX \`users_age_index\` ON \`users\` (\`age\`);`, + ); +}); diff --git a/drizzle-kit/tests/libsql-views.test.ts b/drizzle-kit/tests/libsql-views.test.ts new file mode 100644 index 000000000..bf5cdb04e --- /dev/null +++ b/drizzle-kit/tests/libsql-views.test.ts @@ -0,0 +1,218 @@ +import { sql } from 'drizzle-orm'; +import { int, sqliteTable, sqliteView } from 'drizzle-orm/sqlite-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasLibSQL } from './schemaDiffer'; + +test('create view', async () => { + const users = sqliteTable('users', { id: int('id').default(1) }); + const view = sqliteView('view').as((qb) => qb.select().from(users)); + const to = { + users: users, + testView: view, + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_create_table', + tableName: 'users', + columns: [{ + autoincrement: false, + default: 1, + name: 'id', + type: 'integer', + primaryKey: false, + notNull: false, + }], + compositePKs: [], + uniqueConstraints: [], + referenceData: [], + checkConstraints: [], + }); + expect(statements[1]).toStrictEqual({ + type: 'sqlite_create_view', + name: 'view', + definition: 'select "id" from "users"', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE TABLE \`users\` ( +\t\`id\` integer DEFAULT 1 +);\n`); + expect(sqlStatements[1]).toBe(`CREATE VIEW \`view\` AS select "id" from "users";`); +}); + +test('drop view', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).as(sql`SELECT * FROM users`), + }; + const to = { + users, + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'view', + type: 'drop_view', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `DROP VIEW \`view\`;`, + ); +}); + +test('alter view', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).as(sql`SELECT * FROM users`), + }; + const to = { + users, + testView: sqliteView('view', { id: int('id') }).as(sql`SELECT * FROM users WHERE users.id = 1`), + }; + const { statements, sqlStatements } = await diffTestSchemasLibSQL(from, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + name: 'view', + type: 'drop_view', + }); + expect(statements[1]).toStrictEqual({ + name: 'view', + type: 'sqlite_create_view', + definition: 'SELECT * FROM users WHERE users.id = 1', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `DROP VIEW \`view\`;`, + ); + expect(sqlStatements[1]).toBe( + `CREATE VIEW \`view\` AS SELECT * FROM users WHERE users.id = 1;`, + ); +}); + +test('create view with existing flag', async () => { + const view = sqliteView('view', {}).existing(); + const to = { + testView: view, + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL({}, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop view with existing flag', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).existing(), + }; + const to = { + users, + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('rename view with existing flag', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).existing(), + }; + const to = { + users, + testView: sqliteView('new_view', { id: int('id') }).existing(), + }; + const { statements, sqlStatements } = await diffTestSchemasLibSQL(from, to, ['view->new_view']); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('rename view and drop existing flag', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).existing(), + }; + const to = { + users, + testView: sqliteView('new_view', { id: int('id') }).as(sql`SELECT * FROM users`), + }; + const { statements, sqlStatements } = await diffTestSchemasLibSQL(from, to, ['view->new_view']); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + name: 'view', + type: 'drop_view', + }); + expect(statements[1]).toStrictEqual({ + type: 'sqlite_create_view', + name: 'new_view', + definition: 'SELECT * FROM users', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe('DROP VIEW `view`;'); + expect(sqlStatements[1]).toBe(`CREATE VIEW \`new_view\` AS SELECT * FROM users;`); +}); + +test('rename view and alter ".as"', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).as(sql`SELECT * FROM users`), + }; + const to = { + users, + testView: sqliteView('new_view', { id: int('id') }).as(sql`SELECT * FROM users WHERE 1=1`), + }; + const { statements, sqlStatements } = await diffTestSchemasLibSQL(from, to, ['view->new_view']); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + name: 'view', + type: 'drop_view', + }); + expect(statements[1]).toStrictEqual({ + type: 'sqlite_create_view', + name: 'new_view', + definition: 'SELECT * FROM users WHERE 1=1', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe('DROP VIEW `view`;'); + expect(sqlStatements[1]).toBe(`CREATE VIEW \`new_view\` AS SELECT * FROM users WHERE 1=1;`); +}); diff --git a/drizzle-kit/tests/migrate/libsq-schema.ts b/drizzle-kit/tests/migrate/libsq-schema.ts new file mode 100644 index 000000000..5cb344d51 --- /dev/null +++ b/drizzle-kit/tests/migrate/libsq-schema.ts @@ -0,0 +1,6 @@ +import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), +}); diff --git a/drizzle-kit/tests/migrate/libsql-migrate.test.ts b/drizzle-kit/tests/migrate/libsql-migrate.test.ts new file mode 100644 index 000000000..b937b644f --- /dev/null +++ b/drizzle-kit/tests/migrate/libsql-migrate.test.ts @@ -0,0 +1,58 @@ +import { createClient } from '@libsql/client'; +import { connectToLibSQL } from 'src/cli/connections'; +import { expect, test } from 'vitest'; + +test('validate migrate function', async () => { + const credentials = { + url: ':memory:', + }; + const { migrate, query } = await connectToLibSQL(credentials); + + await migrate({ migrationsFolder: 'tests/migrate/migrations' }); + + const res = await query(`PRAGMA table_info("users");`); + + expect(res).toStrictEqual([{ + cid: 0, + name: 'id', + type: 'INTEGER', + notnull: 0, + dflt_value: null, + pk: 0, + }, { + cid: 1, + name: 'name', + type: 'INTEGER', + notnull: 1, + dflt_value: null, + pk: 0, + }]); +}); + +// test('validate migrate function', async () => { +// const credentials = { +// url: '', +// authToken: '', +// }; +// const { migrate, query } = await connectToLibSQL(credentials); + +// await migrate({ migrationsFolder: 'tests/migrate/migrations' }); + +// const res = await query(`PRAGMA table_info("users");`); + +// expect(res).toStrictEqual([{ +// cid: 0, +// name: 'id', +// type: 'INTEGER', +// notnull: 0, +// dflt_value: null, +// pk: 0, +// }, { +// cid: 1, +// name: 'name', +// type: 'INTEGER', +// notnull: 1, +// dflt_value: null, +// pk: 0, +// }]); +// }); diff --git a/drizzle-kit/tests/migrate/migrations/0000_little_blizzard.sql b/drizzle-kit/tests/migrate/migrations/0000_little_blizzard.sql new file mode 100644 index 000000000..9de0a139d --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/0000_little_blizzard.sql @@ -0,0 +1,4 @@ +CREATE TABLE `users` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL +); diff --git a/drizzle-kit/tests/migrate/migrations/0001_nebulous_storm.sql b/drizzle-kit/tests/migrate/migrations/0001_nebulous_storm.sql new file mode 100644 index 000000000..4309a05c2 --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/0001_nebulous_storm.sql @@ -0,0 +1,10 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_users` ( + `id` integer, + `name` integer NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_users`("id", "name") SELECT "id", "name" FROM `users`;--> statement-breakpoint +DROP TABLE `users`;--> statement-breakpoint +ALTER TABLE `__new_users` RENAME TO `users`;--> statement-breakpoint +PRAGMA foreign_keys=ON; \ No newline at end of file diff --git a/drizzle-kit/tests/migrate/migrations/meta/0000_snapshot.json b/drizzle-kit/tests/migrate/migrations/meta/0000_snapshot.json new file mode 100644 index 000000000..599d02b91 --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/meta/0000_snapshot.json @@ -0,0 +1,40 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2bd46776-9e41-4a6c-b617-5c600bb176f2", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle-kit/tests/migrate/migrations/meta/0001_snapshot.json b/drizzle-kit/tests/migrate/migrations/meta/0001_snapshot.json new file mode 100644 index 000000000..e3b26ba14 --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/meta/0001_snapshot.json @@ -0,0 +1,40 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6c0ec455-42fd-47fd-a22c-4bb4551e1358", + "prevId": "2bd46776-9e41-4a6c-b617-5c600bb176f2", + "tables": { + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle-kit/tests/migrate/migrations/meta/_journal.json b/drizzle-kit/tests/migrate/migrations/meta/_journal.json new file mode 100644 index 000000000..c836eb194 --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1725358702427, + "tag": "0000_little_blizzard", + "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1725358713033, + "tag": "0001_nebulous_storm", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/drizzle-kit/tests/mysql-checks.test.ts b/drizzle-kit/tests/mysql-checks.test.ts new file mode 100644 index 000000000..82e7a5104 --- /dev/null +++ b/drizzle-kit/tests/mysql-checks.test.ts @@ -0,0 +1,291 @@ +import { sql } from 'drizzle-orm'; +import { check, int, mysqlTable, serial, varchar } from 'drizzle-orm/mysql-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasMysql } from './schemaDiffer'; + +test('create table with check', async (t) => { + const to = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasMysql({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + columns: [ + { + name: 'id', + type: 'serial', + notNull: true, + primaryKey: false, + autoincrement: true, + }, + { + name: 'age', + type: 'int', + notNull: false, + primaryKey: false, + autoincrement: false, + }, + ], + compositePKs: [ + 'users_id;id', + ], + checkConstraints: ['some_check_name;\`users\`.\`age\` > 21'], + compositePkName: 'users_id', + uniqueConstraints: [], + schema: undefined, + internals: { + tables: {}, + indexes: {}, + }, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE TABLE \`users\` ( +\t\`id\` serial AUTO_INCREMENT NOT NULL, +\t\`age\` int, +\tCONSTRAINT \`users_id\` PRIMARY KEY(\`id\`), +\tCONSTRAINT \`some_check_name\` CHECK(\`users\`.\`age\` > 21) +);\n`); +}); + +test('add check contraint to existing table', async (t) => { + const from = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }), + }; + + const to = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_check_constraint', + tableName: 'users', + data: 'some_check_name;\`users\`.\`age\` > 21', + schema: '', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ADD CONSTRAINT \`some_check_name\` CHECK (\`users\`.\`age\` > 21);`, + ); +}); + +test('drop check contraint in existing table', async (t) => { + const to = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }), + }; + + const from = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'delete_check_constraint', + tableName: 'users', + schema: '', + constraintName: 'some_check_name', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` DROP CONSTRAINT \`some_check_name\`;`, + ); +}); + +test('rename check constraint', async (t) => { + const from = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('new_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + constraintName: 'some_check_name', + schema: '', + tableName: 'users', + type: 'delete_check_constraint', + }); + expect(statements[1]).toStrictEqual({ + data: 'new_check_name;\`users\`.\`age\` > 21', + schema: '', + tableName: 'users', + type: 'create_check_constraint', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` DROP CONSTRAINT \`some_check_name\`;`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` ADD CONSTRAINT \`new_check_name\` CHECK (\`users\`.\`age\` > 21);`, + ); +}); + +test('alter check constraint', async (t) => { + const from = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('new_check_name', sql`${table.age} > 10`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasMysql(from, to, []); + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + constraintName: 'some_check_name', + schema: '', + tableName: 'users', + type: 'delete_check_constraint', + }); + expect(statements[1]).toStrictEqual({ + data: 'new_check_name;\`users\`.\`age\` > 10', + schema: '', + tableName: 'users', + type: 'create_check_constraint', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` DROP CONSTRAINT \`some_check_name\`;`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` ADD CONSTRAINT \`new_check_name\` CHECK (\`users\`.\`age\` > 10);`, + ); +}); + +test('alter multiple check constraints', async (t) => { + const from = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + name: varchar('name', { length: 255 }), + }, (table) => ({ + checkConstraint1: check('some_check_name_1', sql`${table.age} > 21`), + checkConstraint2: check('some_check_name_2', sql`${table.name} != 'Alex'`), + })), + }; + + const to = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + name: varchar('name', { length: 255 }), + }, (table) => ({ + checkConstraint1: check('some_check_name_3', sql`${table.age} > 21`), + checkConstraint2: check('some_check_name_4', sql`${table.name} != 'Alex'`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasMysql(from, to, []); + expect(statements.length).toBe(4); + expect(statements[0]).toStrictEqual({ + constraintName: 'some_check_name_1', + schema: '', + tableName: 'users', + type: 'delete_check_constraint', + }); + expect(statements[1]).toStrictEqual({ + constraintName: 'some_check_name_2', + schema: '', + tableName: 'users', + type: 'delete_check_constraint', + }); + expect(statements[2]).toStrictEqual({ + data: 'some_check_name_3;\`users\`.\`age\` > 21', + schema: '', + tableName: 'users', + type: 'create_check_constraint', + }); + expect(statements[3]).toStrictEqual({ + data: "some_check_name_4;\`users\`.\`name\` != 'Alex'", + schema: '', + tableName: 'users', + type: 'create_check_constraint', + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` DROP CONSTRAINT \`some_check_name_1\`;`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` DROP CONSTRAINT \`some_check_name_2\`;`, + ); + expect(sqlStatements[2]).toBe( + `ALTER TABLE \`users\` ADD CONSTRAINT \`some_check_name_3\` CHECK (\`users\`.\`age\` > 21);`, + ); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`users\` ADD CONSTRAINT \`some_check_name_4\` CHECK (\`users\`.\`name\` != \'Alex\');`, + ); +}); + +test('create checks with same names', async (t) => { + const to = { + users: mysqlTable('users', { + id: serial('id').primaryKey(), + age: int('age'), + name: varchar('name', { length: 255 }), + }, (table) => ({ + checkConstraint1: check('some_check_name', sql`${table.age} > 21`), + checkConstraint2: check('some_check_name', sql`${table.name} != 'Alex'`), + })), + }; + + await expect(diffTestSchemasMysql({}, to, [])).rejects.toThrowError(); +}); diff --git a/drizzle-kit/tests/mysql-generated.test.ts b/drizzle-kit/tests/mysql-generated.test.ts index c7365f7e3..3531582d0 100644 --- a/drizzle-kit/tests/mysql-generated.test.ts +++ b/drizzle-kit/tests/mysql-generated.test.ts @@ -99,7 +99,7 @@ test('generated as callback: add generated constraint to an exisiting column as }, ]); expect(sqlStatements).toStrictEqual([ - "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED NOT NULL;", ]); }); @@ -151,7 +151,7 @@ test('generated as callback: add generated constraint to an exisiting column as ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE `users` DROP COLUMN `gen_name`;', - "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL NOT NULL;", ]); }); @@ -530,7 +530,7 @@ test('generated as sql: add generated constraint to an exisiting column as store }, ]); expect(sqlStatements).toStrictEqual([ - "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED NOT NULL;", ]); }); @@ -582,7 +582,7 @@ test('generated as sql: add generated constraint to an exisiting column as virtu ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE `users` DROP COLUMN `gen_name`;', - "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL NOT NULL;", ]); }); @@ -961,7 +961,7 @@ test('generated as string: add generated constraint to an exisiting column as st }, ]); expect(sqlStatements).toStrictEqual([ - "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED NOT NULL;", ]); }); @@ -1013,7 +1013,7 @@ test('generated as string: add generated constraint to an exisiting column as vi ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE `users` DROP COLUMN `gen_name`;', - "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL NOT NULL;", ]); }); diff --git a/drizzle-kit/tests/mysql-schemas.test.ts b/drizzle-kit/tests/mysql-schemas.test.ts index 826585d86..6776700e3 100644 --- a/drizzle-kit/tests/mysql-schemas.test.ts +++ b/drizzle-kit/tests/mysql-schemas.test.ts @@ -129,6 +129,7 @@ test('add table to schema #3', async () => { }, compositePkName: '', compositePKs: [], + checkConstraints: [], }); }); diff --git a/drizzle-kit/tests/mysql-views.test.ts b/drizzle-kit/tests/mysql-views.test.ts new file mode 100644 index 000000000..39cd6c09e --- /dev/null +++ b/drizzle-kit/tests/mysql-views.test.ts @@ -0,0 +1,553 @@ +import { sql } from 'drizzle-orm'; +import { int, mysqlTable, mysqlView } from 'drizzle-orm/mysql-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasMysql } from './schemaDiffer'; + +test('create view #1', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + }; + const to = { + users: users, + view: mysqlView('some_view').as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'mysql_create_view', + name: 'some_view', + algorithm: 'undefined', + replace: false, + definition: 'select `id` from `users`', + withCheckOption: undefined, + sqlSecurity: 'definer', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE ALGORITHM = undefined +SQL SECURITY definer +VIEW \`some_view\` AS (select \`id\` from \`users\`);`); +}); + +test('create view #2', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + }; + const to = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'mysql_create_view', + name: 'some_view', + algorithm: 'merge', + replace: false, + definition: 'SELECT * FROM \`users\`', + withCheckOption: 'cascaded', + sqlSecurity: 'definer', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE ALGORITHM = merge +SQL SECURITY definer +VIEW \`some_view\` AS (SELECT * FROM \`users\`) +WITH cascaded CHECK OPTION;`); +}); + +test('create view with existing flag', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + }; + const to = { + users: users, + view: mysqlView('some_view', {}).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop view', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'drop_view', + name: 'some_view', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP VIEW \`some_view\`;`); +}); + +test('drop view with existing flag', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').existing(), + }; + const to = { + users: users, + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('rename view', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + view: mysqlView('new_some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, [ + 'public.some_view->public.new_some_view', + ]); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'rename_view', + nameFrom: 'some_view', + nameTo: 'new_some_view', + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`RENAME TABLE \`some_view\` TO \`new_some_view\`;`); +}); + +test('rename view and alter meta options', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + view: mysqlView('new_some_view', {}).sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, [ + 'public.some_view->public.new_some_view', + ]); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'rename_view', + nameFrom: 'some_view', + nameTo: 'new_some_view', + }); + expect(statements[1]).toStrictEqual({ + algorithm: 'undefined', + columns: {}, + definition: 'SELECT * FROM `users`', + isExisting: false, + name: 'new_some_view', + sqlSecurity: 'definer', + type: 'alter_mysql_view', + withCheckOption: 'cascaded', + }); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`RENAME TABLE \`some_view\` TO \`new_some_view\`;`); + expect(sqlStatements[1]).toBe(`ALTER ALGORITHM = undefined +SQL SECURITY definer +VIEW \`new_some_view\` AS SELECT * FROM \`users\` +WITH cascaded CHECK OPTION;`); +}); + +test('rename view with existing flag', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').existing(), + }; + const to = { + users: users, + view: mysqlView('new_some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, [ + 'public.some_view->public.new_some_view', + ]); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('add meta to view', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + algorithm: 'merge', + columns: {}, + definition: 'SELECT * FROM `users`', + isExisting: false, + name: 'some_view', + sqlSecurity: 'definer', + type: 'alter_mysql_view', + withCheckOption: 'cascaded', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER ALGORITHM = merge +SQL SECURITY definer +VIEW \`some_view\` AS SELECT * FROM \`users\` +WITH cascaded CHECK OPTION;`); +}); + +test('add meta to view with existing flag', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).existing(), + }; + const to = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('alter meta to view', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + algorithm: 'merge', + columns: {}, + definition: 'SELECT * FROM `users`', + isExisting: false, + name: 'some_view', + sqlSecurity: 'definer', + type: 'alter_mysql_view', + withCheckOption: 'cascaded', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER ALGORITHM = merge +SQL SECURITY definer +VIEW \`some_view\` AS SELECT * FROM \`users\` +WITH cascaded CHECK OPTION;`); +}); + +test('alter meta to view with existing flag', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').existing(), + }; + const to = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop meta from view', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + view: mysqlView('some_view', {}).as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + algorithm: 'undefined', + columns: {}, + definition: 'SELECT * FROM `users`', + isExisting: false, + name: 'some_view', + sqlSecurity: 'definer', + type: 'alter_mysql_view', + withCheckOption: undefined, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER ALGORITHM = undefined +SQL SECURITY definer +VIEW \`some_view\` AS SELECT * FROM \`users\`;`); +}); + +test('drop meta from view existing flag', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + + view: mysqlView('some_view', {}).algorithm('merge').sqlSecurity('definer') + .withCheckOption('cascaded').existing(), + }; + const to = { + users: users, + view: mysqlView('some_view', {}).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('alter view ".as" value', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + view: mysqlView('some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users} WHERE ${users.id} = 1`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + algorithm: 'temptable', + definition: 'SELECT * FROM `users` WHERE `users`.`id` = 1', + name: 'some_view', + sqlSecurity: 'invoker', + type: 'mysql_create_view', + withCheckOption: 'cascaded', + replace: true, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE OR REPLACE ALGORITHM = temptable +SQL SECURITY invoker +VIEW \`some_view\` AS (SELECT * FROM \`users\` WHERE \`users\`.\`id\` = 1) +WITH cascaded CHECK OPTION;`); +}); + +test('rename and alter view ".as" value', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + view: mysqlView('new_some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users} WHERE ${users.id} = 1`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, [ + 'public.some_view->public.new_some_view', + ]); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + nameFrom: 'some_view', + nameTo: 'new_some_view', + type: 'rename_view', + }); + expect(statements[1]).toStrictEqual({ + algorithm: 'temptable', + definition: 'SELECT * FROM `users` WHERE `users`.`id` = 1', + name: 'new_some_view', + sqlSecurity: 'invoker', + type: 'mysql_create_view', + withCheckOption: 'cascaded', + replace: true, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`RENAME TABLE \`some_view\` TO \`new_some_view\`;`); + expect(sqlStatements[1]).toBe(`CREATE OR REPLACE ALGORITHM = temptable +SQL SECURITY invoker +VIEW \`new_some_view\` AS (SELECT * FROM \`users\` WHERE \`users\`.\`id\` = 1) +WITH cascaded CHECK OPTION;`); +}); + +test('set existing', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users}`), + }; + const to = { + users: users, + view: mysqlView('new_some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, [ + 'public.some_view->public.new_some_view', + ]); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop existing', async () => { + const users = mysqlTable('users', { + id: int('id').primaryKey().notNull(), + }); + + const from = { + users: users, + view: mysqlView('some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').existing(), + }; + const to = { + users: users, + view: mysqlView('new_some_view', {}).algorithm('temptable').sqlSecurity('invoker') + .withCheckOption('cascaded').as(sql`SELECT * FROM ${users} WHERE ${users.id} = 1`), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, [ + 'public.some_view->public.new_some_view', + ]); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + name: 'new_some_view', + type: 'drop_view', + }); + expect(statements[1]).toStrictEqual({ + algorithm: 'temptable', + definition: 'SELECT * FROM `users` WHERE `users`.`id` = 1', + name: 'new_some_view', + sqlSecurity: 'invoker', + type: 'mysql_create_view', + withCheckOption: 'cascaded', + replace: false, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`DROP VIEW \`new_some_view\`;`); + expect(sqlStatements[1]).toBe(`CREATE ALGORITHM = temptable +SQL SECURITY invoker +VIEW \`new_some_view\` AS (SELECT * FROM \`users\` WHERE \`users\`.\`id\` = 1) +WITH cascaded CHECK OPTION;`); +}); diff --git a/drizzle-kit/tests/mysql.test.ts b/drizzle-kit/tests/mysql.test.ts index e7b0b32a5..881b05ef7 100644 --- a/drizzle-kit/tests/mysql.test.ts +++ b/drizzle-kit/tests/mysql.test.ts @@ -1,5 +1,19 @@ import { sql } from 'drizzle-orm'; -import { index, json, mysqlSchema, mysqlTable, primaryKey, serial, text, uniqueIndex } from 'drizzle-orm/mysql-core'; +import { + foreignKey, + index, + int, + json, + mysqlEnum, + mysqlSchema, + mysqlTable, + primaryKey, + serial, + text, + unique, + uniqueIndex, + varchar, +} from 'drizzle-orm/mysql-core'; import { expect, test } from 'vitest'; import { diffTestSchemasMysql } from './schemaDiffer'; @@ -23,6 +37,7 @@ test('add table #1', async () => { }, uniqueConstraints: [], compositePkName: '', + checkConstraints: [], }); }); @@ -52,6 +67,7 @@ test('add table #2', async () => { compositePKs: ['users_id;id'], compositePkName: 'users_id', uniqueConstraints: [], + checkConstraints: [], internals: { tables: {}, indexes: {}, @@ -96,6 +112,7 @@ test('add table #3', async () => { compositePKs: ['users_pk;id'], uniqueConstraints: [], compositePkName: 'users_pk', + checkConstraints: [], internals: { tables: {}, indexes: {}, @@ -124,6 +141,7 @@ test('add table #4', async () => { compositePKs: [], uniqueConstraints: [], compositePkName: '', + checkConstraints: [], }); expect(statements[1]).toStrictEqual({ type: 'create_table', @@ -137,6 +155,7 @@ test('add table #4', async () => { }, uniqueConstraints: [], compositePkName: '', + checkConstraints: [], }); }); @@ -180,9 +199,11 @@ test('add table #6', async () => { compositePKs: [], uniqueConstraints: [], compositePkName: '', + checkConstraints: [], }); expect(statements[1]).toStrictEqual({ type: 'drop_table', + policies: [], tableName: 'users1', schema: undefined, }); @@ -215,6 +236,7 @@ test('add table #7', async () => { indexes: {}, }, compositePkName: '', + checkConstraints: [], }); expect(statements[1]).toStrictEqual({ type: 'rename_table', @@ -275,6 +297,7 @@ test('change table schema #1', async () => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'drop_table', + policies: [], tableName: 'users', schema: undefined, }); @@ -304,6 +327,7 @@ test('change table schema #2', async () => { uniqueConstraints: [], compositePkName: '', compositePKs: [], + checkConstraints: [], internals: { tables: {}, indexes: {}, @@ -511,6 +535,32 @@ test('drop index', async () => { expect(sqlStatements[0]).toBe('DROP INDEX `name_idx` ON `table`;'); }); +test('drop unique constraint', async () => { + const from = { + users: mysqlTable( + 'table', + { + name: text('name'), + }, + (t) => { + return { + uq: unique('name_uq').on(t.name), + }; + }, + ), + }; + + const to = { + users: mysqlTable('table', { + name: text('name'), + }), + }; + + const { sqlStatements } = await diffTestSchemasMysql(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe('ALTER TABLE `table` DROP INDEX `name_uq`;'); +}); + test('add table with indexes', async () => { const from = {}; @@ -555,3 +605,259 @@ test('add table with indexes', async () => { 'CREATE INDEX `indexColExpr` ON `users` ((lower(`email`)),`email`);', ]); }); + +test('varchar and text default values escape single quotes', async (t) => { + const schema1 = { + table: mysqlTable('table', { + id: serial('id').primaryKey(), + }), + }; + + const schem2 = { + table: mysqlTable('table', { + id: serial('id').primaryKey(), + enum: mysqlEnum('enum', ["escape's quotes", "escape's quotes 2"]).default("escape's quotes"), + text: text('text').default("escape's quotes"), + varchar: varchar('varchar', { length: 255 }).default("escape's quotes"), + }), + }; + + const { sqlStatements } = await diffTestSchemasMysql(schema1, schem2, []); + + expect(sqlStatements.length).toBe(3); + expect(sqlStatements[0]).toStrictEqual( + "ALTER TABLE `table` ADD `enum` enum('escape''s quotes','escape''s quotes 2') DEFAULT 'escape''s quotes';", + ); + expect(sqlStatements[1]).toStrictEqual( + "ALTER TABLE `table` ADD `text` text DEFAULT ('escape''s quotes');", + ); + expect(sqlStatements[2]).toStrictEqual( + "ALTER TABLE `table` ADD `varchar` varchar(255) DEFAULT 'escape''s quotes';", + ); +}); + +test('composite primary key', async () => { + const from = {}; + const to = { + table: mysqlTable('works_to_creators', { + workId: int('work_id').notNull(), + creatorId: int('creator_id').notNull(), + classification: text('classification').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.workId, t.creatorId, t.classification], + }), + })), + }; + + const { sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE `works_to_creators` (\n\t`work_id` int NOT NULL,\n\t`creator_id` int NOT NULL,\n\t`classification` text NOT NULL,\n\tCONSTRAINT `works_to_creators_work_id_creator_id_classification_pk` PRIMARY KEY(`work_id`,`creator_id`,`classification`)\n);\n', + ]); +}); + +test('add column before creating unique constraint', async () => { + const from = { + table: mysqlTable('table', { + id: serial('id').primaryKey(), + }), + }; + const to = { + table: mysqlTable('table', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }, (t) => ({ + uq: unique('uq').on(t.name), + })), + }; + + const { sqlStatements } = await diffTestSchemasMysql(from, to, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `table` ADD `name` text NOT NULL;', + 'ALTER TABLE `table` ADD CONSTRAINT `uq` UNIQUE(`name`);', + ]); +}); + +test('optional db aliases (snake case)', async () => { + const from = {}; + + const t1 = mysqlTable( + 't1', + { + t1Id1: int().notNull().primaryKey(), + t1Col2: int().notNull(), + t1Col3: int().notNull(), + t2Ref: int().notNull().references(() => t2.t2Id), + t1Uni: int().notNull(), + t1UniIdx: int().notNull(), + t1Idx: int().notNull(), + }, + (table) => ({ + uni: unique('t1_uni').on(table.t1Uni), + uniIdx: uniqueIndex('t1_uni_idx').on(table.t1UniIdx), + idx: index('t1_idx').on(table.t1Idx), + fk: foreignKey({ + columns: [table.t1Col2, table.t1Col3], + foreignColumns: [t3.t3Id1, t3.t3Id2], + }), + }), + ); + + const t2 = mysqlTable( + 't2', + { + t2Id: serial().primaryKey(), + }, + ); + + const t3 = mysqlTable( + 't3', + { + t3Id1: int(), + t3Id2: int(), + }, + (table) => ({ + pk: primaryKey({ + columns: [table.t3Id1, table.t3Id2], + }), + }), + ); + + const to = { + t1, + t2, + t3, + }; + + const { sqlStatements } = await diffTestSchemasMysql(from, to, [], false, 'snake_case'); + + const st1 = `CREATE TABLE \`t1\` ( + \`t1_id1\` int NOT NULL, + \`t1_col2\` int NOT NULL, + \`t1_col3\` int NOT NULL, + \`t2_ref\` int NOT NULL, + \`t1_uni\` int NOT NULL, + \`t1_uni_idx\` int NOT NULL, + \`t1_idx\` int NOT NULL, + CONSTRAINT \`t1_t1_id1\` PRIMARY KEY(\`t1_id1\`), + CONSTRAINT \`t1_uni\` UNIQUE(\`t1_uni\`), + CONSTRAINT \`t1_uni_idx\` UNIQUE(\`t1_uni_idx\`) +); +`; + + const st2 = `CREATE TABLE \`t2\` ( + \`t2_id\` serial AUTO_INCREMENT NOT NULL, + CONSTRAINT \`t2_t2_id\` PRIMARY KEY(\`t2_id\`) +); +`; + + const st3 = `CREATE TABLE \`t3\` ( + \`t3_id1\` int NOT NULL, + \`t3_id2\` int NOT NULL, + CONSTRAINT \`t3_t3_id1_t3_id2_pk\` PRIMARY KEY(\`t3_id1\`,\`t3_id2\`) +); +`; + + const st4 = + `ALTER TABLE \`t1\` ADD CONSTRAINT \`t1_t2_ref_t2_t2_id_fk\` FOREIGN KEY (\`t2_ref\`) REFERENCES \`t2\`(\`t2_id\`) ON DELETE no action ON UPDATE no action;`; + + const st5 = + `ALTER TABLE \`t1\` ADD CONSTRAINT \`t1_t1_col2_t1_col3_t3_t3_id1_t3_id2_fk\` FOREIGN KEY (\`t1_col2\`,\`t1_col3\`) REFERENCES \`t3\`(\`t3_id1\`,\`t3_id2\`) ON DELETE no action ON UPDATE no action;`; + + const st6 = `CREATE INDEX \`t1_idx\` ON \`t1\` (\`t1_idx\`);`; + + expect(sqlStatements).toStrictEqual([st1, st2, st3, st4, st5, st6]); +}); + +test('optional db aliases (camel case)', async () => { + const from = {}; + + const t1 = mysqlTable( + 't1', + { + t1_id1: int().notNull().primaryKey(), + t1_col2: int().notNull(), + t1_col3: int().notNull(), + t2_ref: int().notNull().references(() => t2.t2_id), + t1_uni: int().notNull(), + t1_uni_idx: int().notNull(), + t1_idx: int().notNull(), + }, + (table) => ({ + uni: unique('t1Uni').on(table.t1_uni), + uni_idx: uniqueIndex('t1UniIdx').on(table.t1_uni_idx), + idx: index('t1Idx').on(table.t1_idx), + fk: foreignKey({ + columns: [table.t1_col2, table.t1_col3], + foreignColumns: [t3.t3_id1, t3.t3_id2], + }), + }), + ); + + const t2 = mysqlTable( + 't2', + { + t2_id: serial().primaryKey(), + }, + ); + + const t3 = mysqlTable( + 't3', + { + t3_id1: int(), + t3_id2: int(), + }, + (table) => ({ + pk: primaryKey({ + columns: [table.t3_id1, table.t3_id2], + }), + }), + ); + + const to = { + t1, + t2, + t3, + }; + + const { sqlStatements } = await diffTestSchemasMysql(from, to, [], false, 'camelCase'); + + const st1 = `CREATE TABLE \`t1\` ( + \`t1Id1\` int NOT NULL, + \`t1Col2\` int NOT NULL, + \`t1Col3\` int NOT NULL, + \`t2Ref\` int NOT NULL, + \`t1Uni\` int NOT NULL, + \`t1UniIdx\` int NOT NULL, + \`t1Idx\` int NOT NULL, + CONSTRAINT \`t1_t1Id1\` PRIMARY KEY(\`t1Id1\`), + CONSTRAINT \`t1Uni\` UNIQUE(\`t1Uni\`), + CONSTRAINT \`t1UniIdx\` UNIQUE(\`t1UniIdx\`) +); +`; + + const st2 = `CREATE TABLE \`t2\` ( + \`t2Id\` serial AUTO_INCREMENT NOT NULL, + CONSTRAINT \`t2_t2Id\` PRIMARY KEY(\`t2Id\`) +); +`; + + const st3 = `CREATE TABLE \`t3\` ( + \`t3Id1\` int NOT NULL, + \`t3Id2\` int NOT NULL, + CONSTRAINT \`t3_t3Id1_t3Id2_pk\` PRIMARY KEY(\`t3Id1\`,\`t3Id2\`) +); +`; + + const st4 = + `ALTER TABLE \`t1\` ADD CONSTRAINT \`t1_t2Ref_t2_t2Id_fk\` FOREIGN KEY (\`t2Ref\`) REFERENCES \`t2\`(\`t2Id\`) ON DELETE no action ON UPDATE no action;`; + + const st5 = + `ALTER TABLE \`t1\` ADD CONSTRAINT \`t1_t1Col2_t1Col3_t3_t3Id1_t3Id2_fk\` FOREIGN KEY (\`t1Col2\`,\`t1Col3\`) REFERENCES \`t3\`(\`t3Id1\`,\`t3Id2\`) ON DELETE no action ON UPDATE no action;`; + + const st6 = `CREATE INDEX \`t1Idx\` ON \`t1\` (\`t1Idx\`);`; + + expect(sqlStatements).toStrictEqual([st1, st2, st3, st4, st5, st6]); +}); diff --git a/drizzle-kit/tests/pg-array.test.ts b/drizzle-kit/tests/pg-array.test.ts new file mode 100644 index 000000000..e6c06d535 --- /dev/null +++ b/drizzle-kit/tests/pg-array.test.ts @@ -0,0 +1,368 @@ +import { + bigint, + boolean, + date, + integer, + json, + pgEnum, + pgTable, + serial, + text, + timestamp, + uuid, +} from 'drizzle-orm/pg-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemas } from './schemaDiffer'; + +test('array #1: empty array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').array().default([]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { name: 'values', type: 'integer[]', primaryKey: false, notNull: false, default: "'{}'" }, + }); +}); + +test('array #2: integer array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').array().default([1, 2, 3]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { name: 'values', type: 'integer[]', primaryKey: false, notNull: false, default: "'{1,2,3}'" }, + }); +}); + +test('array #3: bigint array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: bigint('values', { mode: 'bigint' }).array().default([BigInt(1), BigInt(2), BigInt(3)]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { name: 'values', type: 'bigint[]', primaryKey: false, notNull: false, default: "'{1,2,3}'" }, + }); +}); + +test('array #4: boolean array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: boolean('values').array().default([true, false, true]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'boolean[]', + primaryKey: false, + notNull: false, + default: "'{true,false,true}'", + }, + }); +}); + +test('array #5: multi-dimensional array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').array().array().default([[1, 2], [3, 4]]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'integer[][]', + primaryKey: false, + notNull: false, + default: "'{{1,2},{3,4}}'", + }, + }); +}); + +test('array #6: date array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: date('values').array().default(['2024-08-06', '2024-08-07']), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'date[]', + primaryKey: false, + notNull: false, + default: '\'{"2024-08-06","2024-08-07"}\'', + }, + }); +}); + +test('array #7: timestamp array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: timestamp('values').array().default([new Date('2024-08-06'), new Date('2024-08-07')]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'timestamp[]', + primaryKey: false, + notNull: false, + default: '\'{"2024-08-06 00:00:00.000","2024-08-07 00:00:00.000"}\'', + }, + }); +}); + +test('array #8: json array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: json('values').array().default([{ a: 1 }, { b: 2 }]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'json[]', + primaryKey: false, + notNull: false, + default: '\'{"{\\"a\\":1}","{\\"b\\":2}"}\'', + }, + }); +}); + +test('array #9: text array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: text('values').array().default(['abc', 'def']), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'text[]', + primaryKey: false, + notNull: false, + default: '\'{"abc","def"}\'', + }, + }); +}); + +test('array #10: uuid array default', async (t) => { + const from = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: uuid('values').array().default([ + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', + 'b0eebc99-9c0b-4ef8-bb6d-cbb9bd380a11', + ]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'uuid[]', + primaryKey: false, + notNull: false, + default: '\'{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11","b0eebc99-9c0b-4ef8-bb6d-cbb9bd380a11"}\'', + }, + }); +}); + +test('array #11: enum array default', async (t) => { + const testEnum = pgEnum('test_enum', ['a', 'b', 'c']); + + const from = { + enum: testEnum, + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + enum: testEnum, + test: pgTable('test', { + id: serial('id').primaryKey(), + values: testEnum('values').array().default(['a', 'b', 'c']), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'test_enum[]', + primaryKey: false, + notNull: false, + default: '\'{"a","b","c"}\'', + }, + }); +}); + +test('array #12: enum empty array default', async (t) => { + const testEnum = pgEnum('test_enum', ['a', 'b', 'c']); + + const from = { + enum: testEnum, + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const to = { + enum: testEnum, + test: pgTable('test', { + id: serial('id').primaryKey(), + values: testEnum('values').array().default([]), + }), + }; + + const { statements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { + name: 'values', + type: 'test_enum[]', + primaryKey: false, + notNull: false, + default: "'{}'", + }, + }); +}); diff --git a/drizzle-kit/tests/pg-checks.test.ts b/drizzle-kit/tests/pg-checks.test.ts new file mode 100644 index 000000000..8033aacef --- /dev/null +++ b/drizzle-kit/tests/pg-checks.test.ts @@ -0,0 +1,282 @@ +import { sql } from 'drizzle-orm'; +import { check, integer, pgTable, serial, varchar } from 'drizzle-orm/pg-core'; +import { JsonCreateTableStatement } from 'src/jsonStatements'; +import { expect, test } from 'vitest'; +import { diffTestSchemas } from './schemaDiffer'; + +test('create table with check', async (t) => { + const to = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: '', + columns: [ + { + name: 'id', + type: 'serial', + notNull: true, + primaryKey: true, + }, + { + name: 'age', + type: 'integer', + notNull: false, + primaryKey: false, + }, + ], + compositePKs: [], + checkConstraints: ['some_check_name;"users"."age" > 21'], + compositePkName: '', + uniqueConstraints: [], + isRLSEnabled: false, + policies: [], + } as JsonCreateTableStatement); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" serial PRIMARY KEY NOT NULL, +\t"age" integer, +\tCONSTRAINT "some_check_name" CHECK ("users"."age" > 21) +);\n`); +}); + +test('add check contraint to existing table', async (t) => { + const from = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }), + }; + + const to = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemas(from, to, []); + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_check_constraint', + tableName: 'users', + schema: '', + data: 'some_check_name;"users"."age" > 21', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE "users" ADD CONSTRAINT "some_check_name" CHECK ("users"."age" > 21);`, + ); +}); + +test('drop check contraint in existing table', async (t) => { + const from = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }), + }; + + const { sqlStatements, statements } = await diffTestSchemas(from, to, []); + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'delete_check_constraint', + tableName: 'users', + schema: '', + constraintName: 'some_check_name', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE "users" DROP CONSTRAINT "some_check_name";`, + ); +}); + +test('rename check constraint', async (t) => { + const from = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }, (table) => ({ + checkConstraint: check('new_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemas(from, to, []); + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + constraintName: 'some_check_name', + schema: '', + tableName: 'users', + type: 'delete_check_constraint', + }); + expect(statements[1]).toStrictEqual({ + data: 'new_check_name;"users"."age" > 21', + schema: '', + tableName: 'users', + type: 'create_check_constraint', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `ALTER TABLE "users" DROP CONSTRAINT "some_check_name";`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE "users" ADD CONSTRAINT "new_check_name" CHECK ("users"."age" > 21);`, + ); +}); + +test('alter check constraint', async (t) => { + const from = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + }, (table) => ({ + checkConstraint: check('new_check_name', sql`${table.age} > 10`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemas(from, to, []); + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + constraintName: 'some_check_name', + schema: '', + tableName: 'users', + type: 'delete_check_constraint', + }); + expect(statements[1]).toStrictEqual({ + data: 'new_check_name;"users"."age" > 10', + schema: '', + tableName: 'users', + type: 'create_check_constraint', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `ALTER TABLE "users" DROP CONSTRAINT "some_check_name";`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE "users" ADD CONSTRAINT "new_check_name" CHECK ("users"."age" > 10);`, + ); +}); + +test('alter multiple check constraints', async (t) => { + const from = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + name: varchar('name'), + }, (table) => ({ + checkConstraint1: check('some_check_name_1', sql`${table.age} > 21`), + checkConstraint2: check('some_check_name_2', sql`${table.name} != 'Alex'`), + })), + }; + + const to = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + name: varchar('name'), + }, (table) => ({ + checkConstraint1: check('some_check_name_3', sql`${table.age} > 21`), + checkConstraint2: check('some_check_name_4', sql`${table.name} != 'Alex'`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemas(from, to, []); + expect(statements.length).toBe(4); + expect(statements[0]).toStrictEqual({ + constraintName: 'some_check_name_1', + schema: '', + tableName: 'users', + type: 'delete_check_constraint', + }); + expect(statements[1]).toStrictEqual({ + constraintName: 'some_check_name_2', + schema: '', + tableName: 'users', + type: 'delete_check_constraint', + }); + expect(statements[2]).toStrictEqual({ + data: 'some_check_name_3;"users"."age" > 21', + schema: '', + tableName: 'users', + type: 'create_check_constraint', + }); + expect(statements[3]).toStrictEqual({ + data: 'some_check_name_4;"users"."name" != \'Alex\'', + schema: '', + tableName: 'users', + type: 'create_check_constraint', + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `ALTER TABLE "users" DROP CONSTRAINT "some_check_name_1";`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE "users" DROP CONSTRAINT "some_check_name_2";`, + ); + expect(sqlStatements[2]).toBe( + `ALTER TABLE "users" ADD CONSTRAINT "some_check_name_3" CHECK ("users"."age" > 21);`, + ); + expect(sqlStatements[3]).toBe( + `ALTER TABLE "users" ADD CONSTRAINT "some_check_name_4" CHECK ("users"."name" != \'Alex\');`, + ); +}); + +test('create checks with same names', async (t) => { + const to = { + users: pgTable('users', { + id: serial('id').primaryKey(), + age: integer('age'), + name: varchar('name'), + }, (table) => ({ + checkConstraint1: check('some_check_name', sql`${table.age} > 21`), + checkConstraint2: check('some_check_name', sql`${table.name} != 'Alex'`), + })), + }; + + await expect(diffTestSchemas({}, to, [])).rejects.toThrowError(); +}); diff --git a/drizzle-kit/tests/pg-columns.test.ts b/drizzle-kit/tests/pg-columns.test.ts index cffeed3ed..ddd744a81 100644 --- a/drizzle-kit/tests/pg-columns.test.ts +++ b/drizzle-kit/tests/pg-columns.test.ts @@ -1,4 +1,4 @@ -import { integer, pgTable, primaryKey, serial, text, uuid } from 'drizzle-orm/pg-core'; +import { integer, pgTable, primaryKey, serial, text, uuid, varchar } from 'drizzle-orm/pg-core'; import { expect, test } from 'vitest'; import { diffTestSchemas } from './schemaDiffer'; @@ -456,3 +456,29 @@ test('add multiple constraints #3', async (t) => { expect(statements.length).toBe(6); }); + +test('varchar and text default values escape single quotes', async (t) => { + const schema1 = { + table: pgTable('table', { + id: serial('id').primaryKey(), + }), + }; + + const schem2 = { + table: pgTable('table', { + id: serial('id').primaryKey(), + text: text('text').default("escape's quotes"), + varchar: varchar('varchar').default("escape's quotes"), + }), + }; + + const { sqlStatements } = await diffTestSchemas(schema1, schem2, []); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toStrictEqual( + 'ALTER TABLE "table" ADD COLUMN "text" text DEFAULT \'escape\'\'s quotes\';', + ); + expect(sqlStatements[1]).toStrictEqual( + 'ALTER TABLE "table" ADD COLUMN "varchar" varchar DEFAULT \'escape\'\'s quotes\';', + ); +}); diff --git a/drizzle-kit/tests/pg-enums.test.ts b/drizzle-kit/tests/pg-enums.test.ts index cd8877a43..2af691d46 100644 --- a/drizzle-kit/tests/pg-enums.test.ts +++ b/drizzle-kit/tests/pg-enums.test.ts @@ -1,4 +1,4 @@ -import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'; +import { integer, pgEnum, pgSchema, pgTable, serial } from 'drizzle-orm/pg-core'; import { expect, test } from 'vitest'; import { diffTestSchemas } from './schemaDiffer'; @@ -7,8 +7,10 @@ test('enums #1', async () => { enum: pgEnum('enum', ['value']), }; - const { statements } = await diffTestSchemas({}, to, []); + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE TYPE "public"."enum" AS ENUM('value');`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ name: 'enum', @@ -24,8 +26,10 @@ test('enums #2', async () => { enum: folder.enum('enum', ['value']), }; - const { statements } = await diffTestSchemas({}, to, []); + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE TYPE "folder"."enum" AS ENUM('value');`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ name: 'enum', @@ -40,8 +44,10 @@ test('enums #3', async () => { enum: pgEnum('enum', ['value']), }; - const { statements } = await diffTestSchemas(from, {}, []); + const { statements, sqlStatements } = await diffTestSchemas(from, {}, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP TYPE "public"."enum";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'drop_type_enum', @@ -57,8 +63,10 @@ test('enums #4', async () => { enum: folder.enum('enum', ['value']), }; - const { statements } = await diffTestSchemas(from, {}, []); + const { statements, sqlStatements } = await diffTestSchemas(from, {}, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP TYPE "folder"."enum";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'drop_type_enum', @@ -81,8 +89,10 @@ test('enums #5', async () => { enum: folder2.enum('enum', ['value']), }; - const { statements } = await diffTestSchemas(from, to, ['folder1->folder2']); + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['folder1->folder2']); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER SCHEMA "folder1" RENAME TO "folder2";\n`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'rename_schema', @@ -107,10 +117,12 @@ test('enums #6', async () => { enum: folder2.enum('enum', ['value']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'folder1.enum->folder2.enum', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder1"."enum" SET SCHEMA "folder2";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -129,8 +141,10 @@ test('enums #7', async () => { enum: pgEnum('enum', ['value1', 'value2']), }; - const { statements } = await diffTestSchemas(from, to, []); + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum" ADD VALUE 'value2';`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'alter_type_add_value', @@ -150,8 +164,11 @@ test('enums #8', async () => { enum: pgEnum('enum', ['value1', 'value2', 'value3']), }; - const { statements } = await diffTestSchemas(from, to, []); + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum" ADD VALUE 'value2';`); + expect(sqlStatements[1]).toBe(`ALTER TYPE "public"."enum" ADD VALUE 'value3';`); expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ type: 'alter_type_add_value', @@ -179,8 +196,10 @@ test('enums #9', async () => { enum: pgEnum('enum', ['value1', 'value2', 'value3']), }; - const { statements } = await diffTestSchemas(from, to, []); + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum" ADD VALUE 'value2' BEFORE 'value3';`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'alter_type_add_value', @@ -201,8 +220,10 @@ test('enums #10', async () => { enum: schema.enum('enum', ['value1', 'value2']), }; - const { statements } = await diffTestSchemas(from, to, []); + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder"."enum" ADD VALUE 'value2';`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'alter_type_add_value', @@ -223,10 +244,12 @@ test('enums #11', async () => { enum: pgEnum('enum', ['value1']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'folder1.enum->public.enum', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder1"."enum" SET SCHEMA "public";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -246,10 +269,12 @@ test('enums #12', async () => { enum: schema1.enum('enum', ['value1']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'public.enum->folder1.enum', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum" SET SCHEMA "folder1";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -268,10 +293,12 @@ test('enums #13', async () => { enum: pgEnum('enum2', ['value1']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'public.enum1->public.enum2', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum1" RENAME TO "enum2";`); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'rename_type_enum', @@ -292,10 +319,13 @@ test('enums #14', async () => { enum: folder2.enum('enum2', ['value1']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'folder1.enum1->folder2.enum2', ]); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder1"."enum1" SET SCHEMA "folder2";`); + expect(sqlStatements[1]).toBe(`ALTER TYPE "folder2"."enum1" RENAME TO "enum2";`); expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -322,10 +352,16 @@ test('enums #15', async () => { enum: folder2.enum('enum2', ['value1', 'value2', 'value3', 'value4']), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'folder1.enum1->folder2.enum2', ]); + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe(`ALTER TYPE "folder1"."enum1" SET SCHEMA "folder2";`); + expect(sqlStatements[1]).toBe(`ALTER TYPE "folder2"."enum1" RENAME TO "enum2";`); + expect(sqlStatements[2]).toBe(`ALTER TYPE "folder2"."enum2" ADD VALUE 'value2' BEFORE 'value4';`); + expect(sqlStatements[3]).toBe(`ALTER TYPE "folder2"."enum2" ADD VALUE 'value3' BEFORE 'value4';`); + expect(statements.length).toBe(4); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -373,10 +409,13 @@ test('enums #16', async () => { }), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'public.enum1->public.enum2', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum1" RENAME TO "enum2";`); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'rename_type_enum', @@ -405,10 +444,14 @@ test('enums #17', async () => { }), }; - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'public.enum1->schema.enum1', ]); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER TYPE "public"."enum1" SET SCHEMA "schema";`); + + expect(sqlStatements.length).toBe(1); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -440,10 +483,14 @@ test('enums #18', async () => { }; // change name and schema of the enum, no table changes - const { statements } = await diffTestSchemas(from, to, [ + const { statements, sqlStatements } = await diffTestSchemas(from, to, [ 'schema1.enum1->schema2.enum2', ]); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TYPE "schema1"."enum1" SET SCHEMA "schema2";`); + expect(sqlStatements[1]).toBe(`ALTER TYPE "schema2"."enum1" RENAME TO "enum2";`); + expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ type: 'move_type_enum', @@ -458,3 +505,247 @@ test('enums #18', async () => { schema: 'schema2', }); }); + +test('enums #19', async () => { + const myEnum = pgEnum('my_enum', ["escape's quotes"]); + + const from = {}; + + const to = { myEnum }; + + const { sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toStrictEqual( + 'CREATE TYPE "public"."my_enum" AS ENUM(\'escape\'\'s quotes\');', + ); +}); + +test('enums #20', async () => { + const myEnum = pgEnum('my_enum', ['one', 'two', 'three']); + + const from = { + myEnum, + table: pgTable('table', { + id: serial('id').primaryKey(), + }), + }; + + const to = { + myEnum, + table: pgTable('table', { + id: serial('id').primaryKey(), + col1: myEnum('col1'), + col2: integer('col2'), + }), + }; + + const { sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "table" ADD COLUMN "col1" "my_enum";', + 'ALTER TABLE "table" ADD COLUMN "col2" integer;', + ]); +}); + +test('enums #21', async () => { + const myEnum = pgEnum('my_enum', ['one', 'two', 'three']); + + const from = { + myEnum, + table: pgTable('table', { + id: serial('id').primaryKey(), + }), + }; + + const to = { + myEnum, + table: pgTable('table', { + id: serial('id').primaryKey(), + col1: myEnum('col1').array(), + col2: integer('col2').array(), + }), + }; + + const { sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "table" ADD COLUMN "col1" "my_enum"[];', + 'ALTER TABLE "table" ADD COLUMN "col2" integer[];', + ]); +}); + +test('drop enum value', async () => { + const enum1 = pgEnum('enum', ['value1', 'value2', 'value3']); + + const from = { + enum1, + }; + + const enum2 = pgEnum('enum', ['value1', 'value3']); + const to = { + enum2, + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`DROP TYPE "public"."enum";`); + expect(sqlStatements[1]).toBe(`CREATE TYPE "public"."enum" AS ENUM('value1', 'value3');`); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columnsWithEnum: [], + deletedValues: [ + 'value2', + ], + name: 'enum', + newValues: [ + 'value1', + 'value3', + ], + schema: 'public', + type: 'alter_type_drop_value', + }); +}); + +test('drop enum value. enum is columns data type', async () => { + const enum1 = pgEnum('enum', ['value1', 'value2', 'value3']); + + const schema = pgSchema('new_schema'); + + const from = { + schema, + enum1, + table: pgTable('table', { + column: enum1('column'), + }), + table2: schema.table('table', { + column: enum1('column'), + }), + }; + + const enum2 = pgEnum('enum', ['value1', 'value3']); + const to = { + schema, + enum2, + table: pgTable('table', { + column: enum1('column'), + }), + table2: schema.table('table', { + column: enum1('column'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`ALTER TABLE "public"."table" ALTER COLUMN "column" SET DATA TYPE text;`); + expect(sqlStatements[1]).toBe(`ALTER TABLE "new_schema"."table" ALTER COLUMN "column" SET DATA TYPE text;`); + expect(sqlStatements[2]).toBe(`DROP TYPE "public"."enum";`); + expect(sqlStatements[3]).toBe(`CREATE TYPE "public"."enum" AS ENUM('value1', 'value3');`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE "public"."table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";`, + ); + expect(sqlStatements[5]).toBe( + `ALTER TABLE "new_schema"."table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";`, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columnsWithEnum: [ + { + column: 'column', + schema: 'public', + table: 'table', + }, + { + column: 'column', + schema: 'new_schema', + table: 'table', + }, + ], + deletedValues: [ + 'value2', + ], + name: 'enum', + newValues: [ + 'value1', + 'value3', + ], + schema: 'public', + type: 'alter_type_drop_value', + }); +}); + +test('shuffle enum values', async () => { + const enum1 = pgEnum('enum', ['value1', 'value2', 'value3']); + + const schema = pgSchema('new_schema'); + + const from = { + schema, + enum1, + table: pgTable('table', { + column: enum1('column'), + }), + table2: schema.table('table', { + column: enum1('column'), + }), + }; + + const enum2 = pgEnum('enum', ['value1', 'value3', 'value2']); + const to = { + schema, + enum2, + table: pgTable('table', { + column: enum1('column'), + }), + table2: schema.table('table', { + column: enum1('column'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`ALTER TABLE "public"."table" ALTER COLUMN "column" SET DATA TYPE text;`); + expect(sqlStatements[1]).toBe(`ALTER TABLE "new_schema"."table" ALTER COLUMN "column" SET DATA TYPE text;`); + expect(sqlStatements[2]).toBe(`DROP TYPE "public"."enum";`); + expect(sqlStatements[3]).toBe(`CREATE TYPE "public"."enum" AS ENUM('value1', 'value3', 'value2');`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE "public"."table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";`, + ); + expect(sqlStatements[5]).toBe( + `ALTER TABLE "new_schema"."table" ALTER COLUMN "column" SET DATA TYPE "public"."enum" USING "column"::"public"."enum";`, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columnsWithEnum: [ + { + column: 'column', + schema: 'public', + table: 'table', + }, + { + column: 'column', + schema: 'new_schema', + table: 'table', + }, + ], + deletedValues: [ + 'value3', + ], + name: 'enum', + newValues: [ + 'value1', + 'value3', + 'value2', + ], + schema: 'public', + type: 'alter_type_drop_value', + }); +}); diff --git a/drizzle-kit/tests/pg-generated.test.ts b/drizzle-kit/tests/pg-generated.test.ts index 2f7f58491..e9f294891 100644 --- a/drizzle-kit/tests/pg-generated.test.ts +++ b/drizzle-kit/tests/pg-generated.test.ts @@ -87,7 +87,7 @@ test('generated as callback: add generated constraint to an exisiting column', a ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE "users" drop column "gen_name";', - 'ALTER TABLE "users" ADD COLUMN "gen_name" text NOT NULL GENERATED ALWAYS AS ("users"."name" || \'to add\') STORED;', + 'ALTER TABLE "users" ADD COLUMN "gen_name" text GENERATED ALWAYS AS ("users"."name" || \'to add\') STORED NOT NULL;', ]); }); @@ -262,7 +262,7 @@ test('generated as sql: add generated constraint to an exisiting column', async ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE "users" drop column "gen_name";', - 'ALTER TABLE "users" ADD COLUMN "gen_name" text NOT NULL GENERATED ALWAYS AS ("users"."name" || \'to add\') STORED;', + 'ALTER TABLE "users" ADD COLUMN "gen_name" text GENERATED ALWAYS AS ("users"."name" || \'to add\') STORED NOT NULL;', ]); }); @@ -437,7 +437,7 @@ test('generated as string: add generated constraint to an exisiting column', asy ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE "users" drop column "gen_name";', - 'ALTER TABLE "users" ADD COLUMN "gen_name" text NOT NULL GENERATED ALWAYS AS ("users"."name" || \'to add\') STORED;', + 'ALTER TABLE "users" ADD COLUMN "gen_name" text GENERATED ALWAYS AS ("users"."name" || \'to add\') STORED NOT NULL;', ]); }); diff --git a/drizzle-kit/tests/pg-identity.test.ts b/drizzle-kit/tests/pg-identity.test.ts index 906d812d4..efb481da3 100644 --- a/drizzle-kit/tests/pg-identity.test.ts +++ b/drizzle-kit/tests/pg-identity.test.ts @@ -45,13 +45,16 @@ test('create table: identity always/by default - no params', async () => { compositePKs: [], compositePkName: '', schema: '', + policies: [], + isRLSEnabled: false, tableName: 'users', type: 'create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ - 'CREATE TABLE IF NOT EXISTS "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1)\n);\n', + 'CREATE TABLE "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1)\n);\n', ]); }); @@ -82,14 +85,17 @@ test('create table: identity always/by default - few params', async () => { ], compositePKs: [], compositePkName: '', + policies: [], schema: '', + isRLSEnabled: false, tableName: 'users', type: 'create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ - 'CREATE TABLE IF NOT EXISTS "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "custom_seq" INCREMENT BY 4 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1)\n);\n', + 'CREATE TABLE "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "custom_seq" INCREMENT BY 4 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1)\n);\n', ]); }); @@ -124,14 +130,17 @@ test('create table: identity always/by default - all params', async () => { ], compositePKs: [], compositePkName: '', + policies: [], + isRLSEnabled: false, schema: '', tableName: 'users', type: 'create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ - 'CREATE TABLE IF NOT EXISTS "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "custom_seq" INCREMENT BY 4 MINVALUE 3 MAXVALUE 1000 START WITH 3 CACHE 200)\n);\n', + 'CREATE TABLE "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "custom_seq" INCREMENT BY 4 MINVALUE 3 MAXVALUE 1000 START WITH 3 CACHE 200)\n);\n', ]); }); diff --git a/drizzle-kit/tests/pg-tables.test.ts b/drizzle-kit/tests/pg-tables.test.ts index 4171af333..4ca01f1fe 100644 --- a/drizzle-kit/tests/pg-tables.test.ts +++ b/drizzle-kit/tests/pg-tables.test.ts @@ -1,6 +1,7 @@ import { sql } from 'drizzle-orm'; import { AnyPgColumn, + foreignKey, geometry, index, integer, @@ -12,6 +13,8 @@ import { primaryKey, serial, text, + unique, + uniqueIndex, vector, } from 'drizzle-orm/pg-core'; import { expect, test } from 'vitest'; @@ -31,7 +34,10 @@ test('add table #1', async () => { schema: '', columns: [], compositePKs: [], + policies: [], uniqueConstraints: [], + checkConstraints: [], + isRLSEnabled: false, compositePkName: '', }); }); @@ -59,7 +65,10 @@ test('add table #2', async () => { }, ], compositePKs: [], + isRLSEnabled: false, + policies: [], uniqueConstraints: [], + checkConstraints: [], compositePkName: '', }); }); @@ -98,7 +107,10 @@ test('add table #3', async () => { }, ], compositePKs: ['id;users_pk'], + policies: [], uniqueConstraints: [], + isRLSEnabled: false, + checkConstraints: [], compositePkName: 'users_pk', }); }); @@ -118,16 +130,22 @@ test('add table #4', async () => { schema: '', columns: [], compositePKs: [], + policies: [], uniqueConstraints: [], + checkConstraints: [], + isRLSEnabled: false, compositePkName: '', }); expect(statements[1]).toStrictEqual({ type: 'create_table', tableName: 'posts', + policies: [], schema: '', columns: [], compositePKs: [], + isRLSEnabled: false, uniqueConstraints: [], + checkConstraints: [], compositePkName: '', }); }); @@ -152,8 +170,11 @@ test('add table #5', async () => { schema: 'folder', columns: [], compositePKs: [], + policies: [], uniqueConstraints: [], compositePkName: '', + checkConstraints: [], + isRLSEnabled: false, }); }); @@ -176,10 +197,14 @@ test('add table #6', async () => { columns: [], compositePKs: [], uniqueConstraints: [], + policies: [], compositePkName: '', + checkConstraints: [], + isRLSEnabled: false, }); expect(statements[1]).toStrictEqual({ type: 'drop_table', + policies: [], tableName: 'users1', schema: '', }); @@ -206,8 +231,11 @@ test('add table #7', async () => { schema: '', columns: [], compositePKs: [], + policies: [], uniqueConstraints: [], compositePkName: '', + isRLSEnabled: false, + checkConstraints: [], }); expect(statements[1]).toStrictEqual({ type: 'rename_table', @@ -233,7 +261,7 @@ test('add table #8: geometry types', async () => { expect(statements.length).toBe(1); expect(sqlStatements).toStrictEqual([ - `CREATE TABLE IF NOT EXISTS "users" (\n\t"geom" geometry(point) NOT NULL,\n\t"geom1" geometry(point) NOT NULL\n);\n`, + `CREATE TABLE "users" (\n\t"geom" geometry(point) NOT NULL,\n\t"geom1" geometry(point) NOT NULL\n);\n`, ]); }); @@ -262,8 +290,11 @@ test('multiproject schema add table #1', async () => { }, ], compositePKs: [], + policies: [], compositePkName: '', + isRLSEnabled: false, uniqueConstraints: [], + checkConstraints: [], }); }); @@ -284,6 +315,7 @@ test('multiproject schema drop table #1', async () => { schema: '', tableName: 'prefix_users', type: 'drop_table', + policies: [], }); }); @@ -328,7 +360,7 @@ test('add table #8: column with pgvector', async () => { const { sqlStatements } = await diffTestSchemas(from, to, []); expect(sqlStatements[0]).toBe( - `CREATE TABLE IF NOT EXISTS "users2" (\n\t"id" serial PRIMARY KEY NOT NULL,\n\t"name" vector(3)\n); + `CREATE TABLE "users2" (\n\t"id" serial PRIMARY KEY NOT NULL,\n\t"name" vector(3)\n); `, ); }); @@ -353,10 +385,13 @@ test('add schema + table #1', async () => { type: 'create_table', tableName: 'users', schema: 'folder', + policies: [], columns: [], compositePKs: [], + isRLSEnabled: false, uniqueConstraints: [], compositePkName: '', + checkConstraints: [], }); }); @@ -610,6 +645,7 @@ test('drop table + rename schema #1', async () => { type: 'drop_table', tableName: 'users', schema: 'folder2', + policies: [], }); }); @@ -635,7 +671,287 @@ test('create table with tsvector', async () => { const { statements, sqlStatements } = await diffTestSchemas(from, to, []); expect(sqlStatements).toStrictEqual([ - 'CREATE TABLE IF NOT EXISTS "posts" (\n\t"id" serial PRIMARY KEY NOT NULL,\n\t"title" text NOT NULL,\n\t"description" text NOT NULL\n);\n', - `CREATE INDEX IF NOT EXISTS "title_search_index" ON "posts" USING gin (to_tsvector('english', "title"));`, + 'CREATE TABLE "posts" (\n\t"id" serial PRIMARY KEY NOT NULL,\n\t"title" text NOT NULL,\n\t"description" text NOT NULL\n);\n', + `CREATE INDEX "title_search_index" ON "posts" USING gin (to_tsvector('english', "title"));`, ]); }); + +test('composite primary key', async () => { + const from = {}; + const to = { + table: pgTable('works_to_creators', { + workId: integer('work_id').notNull(), + creatorId: integer('creator_id').notNull(), + classification: text('classification').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.workId, t.creatorId, t.classification], + }), + })), + }; + + const { sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE "works_to_creators" (\n\t"work_id" integer NOT NULL,\n\t"creator_id" integer NOT NULL,\n\t"classification" text NOT NULL,\n\tCONSTRAINT "works_to_creators_work_id_creator_id_classification_pk" PRIMARY KEY("work_id","creator_id","classification")\n);\n', + ]); +}); + +test('add column before creating unique constraint', async () => { + const from = { + table: pgTable('table', { + id: serial('id').primaryKey(), + }), + }; + const to = { + table: pgTable('table', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }, (t) => ({ + uq: unique('uq').on(t.name), + })), + }; + + const { sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "table" ADD COLUMN "name" text NOT NULL;', + 'ALTER TABLE "table" ADD CONSTRAINT "uq" UNIQUE("name");', + ]); +}); + +test('alter composite primary key', async () => { + const from = { + table: pgTable('table', { + col1: integer('col1').notNull(), + col2: integer('col2').notNull(), + col3: text('col3').notNull(), + }, (t) => ({ + pk: primaryKey({ + name: 'table_pk', + columns: [t.col1, t.col2], + }), + })), + }; + const to = { + table: pgTable('table', { + col1: integer('col1').notNull(), + col2: integer('col2').notNull(), + col3: text('col3').notNull(), + }, (t) => ({ + pk: primaryKey({ + name: 'table_pk', + columns: [t.col2, t.col3], + }), + })), + }; + + const { sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "table" DROP CONSTRAINT "table_pk";\n--> statement-breakpoint\nALTER TABLE "table" ADD CONSTRAINT "table_pk" PRIMARY KEY("col2","col3");', + ]); +}); + +test('add index with op', async () => { + const from = { + users: pgTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }), + }; + const to = { + users: pgTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }, (t) => ({ + nameIdx: index().using('gin', t.name.op('gin_trgm_ops')), + })), + }; + + const { sqlStatements } = await diffTestSchemas(from, to, []); + + expect(sqlStatements).toStrictEqual([ + 'CREATE INDEX "users_name_index" ON "users" USING gin ("name" gin_trgm_ops);', + ]); +}); + +test('optional db aliases (snake case)', async () => { + const from = {}; + + const t1 = pgTable( + 't1', + { + t1Id1: integer().notNull().primaryKey(), + t1Col2: integer().notNull(), + t1Col3: integer().notNull(), + t2Ref: integer().notNull().references(() => t2.t2Id), + t1Uni: integer().notNull(), + t1UniIdx: integer().notNull(), + t1Idx: integer().notNull(), + }, + (table) => ({ + uni: unique('t1_uni').on(table.t1Uni), + uniIdx: uniqueIndex('t1_uni_idx').on(table.t1UniIdx), + idx: index('t1_idx').on(table.t1Idx).where(sql`${table.t1Idx} > 0`), + fk: foreignKey({ + columns: [table.t1Col2, table.t1Col3], + foreignColumns: [t3.t3Id1, t3.t3Id2], + }), + }), + ); + + const t2 = pgTable( + 't2', + { + t2Id: serial().primaryKey(), + }, + ); + + const t3 = pgTable( + 't3', + { + t3Id1: integer(), + t3Id2: integer(), + }, + (table) => ({ + pk: primaryKey({ + columns: [table.t3Id1, table.t3Id2], + }), + }), + ); + + const to = { + t1, + t2, + t3, + }; + + const { sqlStatements } = await diffTestSchemas(from, to, [], false, 'snake_case'); + + const st1 = `CREATE TABLE "t1" ( + "t1_id1" integer PRIMARY KEY NOT NULL, + "t1_col2" integer NOT NULL, + "t1_col3" integer NOT NULL, + "t2_ref" integer NOT NULL, + "t1_uni" integer NOT NULL, + "t1_uni_idx" integer NOT NULL, + "t1_idx" integer NOT NULL, + CONSTRAINT "t1_uni" UNIQUE("t1_uni") +); +`; + + const st2 = `CREATE TABLE "t2" ( + "t2_id" serial PRIMARY KEY NOT NULL +); +`; + + const st3 = `CREATE TABLE "t3" ( + "t3_id1" integer, + "t3_id2" integer, + CONSTRAINT "t3_t3_id1_t3_id2_pk" PRIMARY KEY("t3_id1","t3_id2") +); +`; + + const st4 = + `ALTER TABLE "t1" ADD CONSTRAINT "t1_t2_ref_t2_t2_id_fk" FOREIGN KEY ("t2_ref") REFERENCES "public"."t2"("t2_id") ON DELETE no action ON UPDATE no action;`; + + const st5 = + `ALTER TABLE "t1" ADD CONSTRAINT "t1_t1_col2_t1_col3_t3_t3_id1_t3_id2_fk" FOREIGN KEY ("t1_col2","t1_col3") REFERENCES "public"."t3"("t3_id1","t3_id2") ON DELETE no action ON UPDATE no action;`; + + const st6 = `CREATE UNIQUE INDEX "t1_uni_idx" ON "t1" USING btree ("t1_uni_idx");`; + + const st7 = `CREATE INDEX "t1_idx" ON "t1" USING btree ("t1_idx") WHERE "t1"."t1_idx" > 0;`; + + expect(sqlStatements).toStrictEqual([st1, st2, st3, st4, st5, st6, st7]); +}); + +test('optional db aliases (camel case)', async () => { + const from = {}; + + const t1 = pgTable( + 't1', + { + t1_id1: integer().notNull().primaryKey(), + t1_col2: integer().notNull(), + t1_col3: integer().notNull(), + t2_ref: integer().notNull().references(() => t2.t2_id), + t1_uni: integer().notNull(), + t1_uni_idx: integer().notNull(), + t1_idx: integer().notNull(), + }, + (table) => ({ + uni: unique('t1Uni').on(table.t1_uni), + uni_idx: uniqueIndex('t1UniIdx').on(table.t1_uni_idx), + idx: index('t1Idx').on(table.t1_idx).where(sql`${table.t1_idx} > 0`), + fk: foreignKey({ + columns: [table.t1_col2, table.t1_col3], + foreignColumns: [t3.t3_id1, t3.t3_id2], + }), + }), + ); + + const t2 = pgTable( + 't2', + { + t2_id: serial().primaryKey(), + }, + ); + + const t3 = pgTable( + 't3', + { + t3_id1: integer(), + t3_id2: integer(), + }, + (table) => ({ + pk: primaryKey({ + columns: [table.t3_id1, table.t3_id2], + }), + }), + ); + + const to = { + t1, + t2, + t3, + }; + + const { sqlStatements } = await diffTestSchemas(from, to, [], false, 'camelCase'); + + const st1 = `CREATE TABLE "t1" ( + "t1Id1" integer PRIMARY KEY NOT NULL, + "t1Col2" integer NOT NULL, + "t1Col3" integer NOT NULL, + "t2Ref" integer NOT NULL, + "t1Uni" integer NOT NULL, + "t1UniIdx" integer NOT NULL, + "t1Idx" integer NOT NULL, + CONSTRAINT "t1Uni" UNIQUE("t1Uni") +); +`; + + const st2 = `CREATE TABLE "t2" ( + "t2Id" serial PRIMARY KEY NOT NULL +); +`; + + const st3 = `CREATE TABLE "t3" ( + "t3Id1" integer, + "t3Id2" integer, + CONSTRAINT "t3_t3Id1_t3Id2_pk" PRIMARY KEY("t3Id1","t3Id2") +); +`; + + const st4 = + `ALTER TABLE "t1" ADD CONSTRAINT "t1_t2Ref_t2_t2Id_fk" FOREIGN KEY ("t2Ref") REFERENCES "public"."t2"("t2Id") ON DELETE no action ON UPDATE no action;`; + + const st5 = + `ALTER TABLE "t1" ADD CONSTRAINT "t1_t1Col2_t1Col3_t3_t3Id1_t3Id2_fk" FOREIGN KEY ("t1Col2","t1Col3") REFERENCES "public"."t3"("t3Id1","t3Id2") ON DELETE no action ON UPDATE no action;`; + + const st6 = `CREATE UNIQUE INDEX "t1UniIdx" ON "t1" USING btree ("t1UniIdx");`; + + const st7 = `CREATE INDEX "t1Idx" ON "t1" USING btree ("t1Idx") WHERE "t1"."t1Idx" > 0;`; + + expect(sqlStatements).toStrictEqual([st1, st2, st3, st4, st5, st6, st7]); +}); diff --git a/drizzle-kit/tests/pg-views.test.ts b/drizzle-kit/tests/pg-views.test.ts new file mode 100644 index 000000000..4f24cd776 --- /dev/null +++ b/drizzle-kit/tests/pg-views.test.ts @@ -0,0 +1,1929 @@ +import { sql } from 'drizzle-orm'; +import { integer, pgMaterializedView, pgSchema, pgTable, pgView } from 'drizzle-orm/pg-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemas } from './schemaDiffer'; + +test('create table and view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view: pgView('some_view').as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: '', + columns: [{ + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + uniqueConstraints: [], + isRLSEnabled: false, + compositePkName: '', + checkConstraints: [], + policies: [], + }); + expect(statements[1]).toStrictEqual({ + type: 'create_view', + name: 'some_view', + definition: `select "id" from "users"`, + schema: 'public', + with: undefined, + materialized: false, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[1]).toBe(`CREATE VIEW "public"."some_view" AS (select "id" from "users");`); +}); + +test('create table and view #2', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view: pgView('some_view', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: '', + columns: [{ + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + uniqueConstraints: [], + isRLSEnabled: false, + compositePkName: '', + policies: [], + checkConstraints: [], + }); + expect(statements[1]).toStrictEqual({ + type: 'create_view', + name: 'some_view', + definition: `SELECT * FROM "users"`, + schema: 'public', + with: undefined, + materialized: false, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[1]).toBe(`CREATE VIEW "public"."some_view" AS (SELECT * FROM "users");`); +}); + +test('create table and view #3', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view1: pgView('some_view1', { id: integer('id') }).with({ + checkOption: 'local', + securityBarrier: false, + securityInvoker: true, + }).as(sql`SELECT * FROM ${users}`), + view2: pgView('some_view2').with({ + checkOption: 'cascaded', + securityBarrier: true, + securityInvoker: false, + }).as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(3); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: '', + columns: [{ + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + uniqueConstraints: [], + compositePkName: '', + checkConstraints: [], + isRLSEnabled: false, + policies: [], + }); + expect(statements[1]).toStrictEqual({ + type: 'create_view', + name: 'some_view1', + definition: `SELECT * FROM "users"`, + schema: 'public', + with: { + checkOption: 'local', + securityBarrier: false, + securityInvoker: true, + }, + materialized: false, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + expect(statements[2]).toStrictEqual({ + type: 'create_view', + name: 'some_view2', + definition: `select "id" from "users"`, + schema: 'public', + with: { + checkOption: 'cascaded', + securityBarrier: true, + securityInvoker: false, + }, + materialized: false, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + + expect(sqlStatements.length).toBe(3); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[1]).toBe( + `CREATE VIEW "public"."some_view1" WITH (check_option = local, security_barrier = false, security_invoker = true) AS (SELECT * FROM "users");`, + ); + expect(sqlStatements[2]).toBe( + `CREATE VIEW "public"."some_view2" WITH (check_option = cascaded, security_barrier = true, security_invoker = false) AS (select "id" from "users");`, + ); +}); + +test('create table and view #4', async () => { + const schema = pgSchema('new_schema'); + + const users = schema.table('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + schema, + users: users, + view1: schema.view('some_view1', { id: integer('id') }).with({ + checkOption: 'local', + securityBarrier: false, + securityInvoker: true, + }).as(sql`SELECT * FROM ${users}`), + view2: schema.view('some_view2').with({ + checkOption: 'cascaded', + securityBarrier: true, + securityInvoker: false, + }).as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(4); + expect(statements[0]).toStrictEqual({ + type: 'create_schema', + name: 'new_schema', + }); + expect(statements[1]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: 'new_schema', + columns: [{ + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + uniqueConstraints: [], + compositePkName: '', + isRLSEnabled: false, + policies: [], + checkConstraints: [], + }); + expect(statements[2]).toStrictEqual({ + type: 'create_view', + name: 'some_view1', + definition: `SELECT * FROM "new_schema"."users"`, + schema: 'new_schema', + with: { + checkOption: 'local', + securityBarrier: false, + securityInvoker: true, + }, + materialized: false, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + expect(statements[3]).toStrictEqual({ + type: 'create_view', + name: 'some_view2', + definition: `select "id" from "new_schema"."users"`, + schema: 'new_schema', + with: { + checkOption: 'cascaded', + securityBarrier: true, + securityInvoker: false, + }, + materialized: false, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe(`CREATE SCHEMA "new_schema";\n`); + expect(sqlStatements[1]).toBe(`CREATE TABLE "new_schema"."users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[2]).toBe( + `CREATE VIEW "new_schema"."some_view1" WITH (check_option = local, security_barrier = false, security_invoker = true) AS (SELECT * FROM "new_schema"."users");`, + ); + expect(sqlStatements[3]).toBe( + `CREATE VIEW "new_schema"."some_view2" WITH (check_option = cascaded, security_barrier = true, security_invoker = false) AS (select "id" from "new_schema"."users");`, + ); +}); + +test('create table and view #5', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view1: pgView('some_view', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + view2: pgView('some_view', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + }; + + await expect(diffTestSchemas({}, to, [])).rejects.toThrowError(); +}); + +test('create table and view #6', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view1: pgView('some_view', { id: integer('id') }).with({ checkOption: 'cascaded' }).as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + columns: [ + { + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + ], + compositePKs: [], + compositePkName: '', + schema: '', + tableName: 'users', + type: 'create_table', + uniqueConstraints: [], + checkConstraints: [], + isRLSEnabled: false, + policies: [], + }); + expect(statements[1]).toStrictEqual({ + definition: 'SELECT * FROM "users"', + name: 'some_view', + schema: 'public', + type: 'create_view', + with: { + checkOption: 'cascaded', + }, + materialized: false, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[1]).toBe( + `CREATE VIEW "public"."some_view" WITH (check_option = cascaded) AS (SELECT * FROM "users");`, + ); +}); + +test('create view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + }; + + const to = { + users: users, + view1: pgView('some_view', { id: integer('id') }).with({ checkOption: 'cascaded' }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('create table and materialized view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view: pgMaterializedView('some_view').as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: '', + columns: [{ + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + uniqueConstraints: [], + isRLSEnabled: false, + policies: [], + compositePkName: '', + checkConstraints: [], + }); + expect(statements[1]).toStrictEqual({ + type: 'create_view', + name: 'some_view', + definition: `select "id" from "users"`, + schema: 'public', + with: undefined, + materialized: true, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[1]).toBe(`CREATE MATERIALIZED VIEW "public"."some_view" AS (select "id" from "users");`); +}); + +test('create table and materialized view #2', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view: pgMaterializedView('some_view', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: '', + columns: [{ + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + uniqueConstraints: [], + compositePkName: '', + isRLSEnabled: false, + policies: [], + checkConstraints: [], + }); + expect(statements[1]).toStrictEqual({ + type: 'create_view', + name: 'some_view', + definition: `SELECT * FROM "users"`, + schema: 'public', + with: undefined, + materialized: true, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[1]).toBe(`CREATE MATERIALIZED VIEW "public"."some_view" AS (SELECT * FROM "users");`); +}); + +test('create table and materialized view #3', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view1: pgMaterializedView('some_view1', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + view2: pgMaterializedView('some_view2').tablespace('some_tablespace').using('heap').withNoData().with({ + autovacuumEnabled: true, + autovacuumFreezeMaxAge: 1, + autovacuumFreezeMinAge: 1, + autovacuumFreezeTableAge: 1, + autovacuumMultixactFreezeMaxAge: 1, + autovacuumMultixactFreezeMinAge: 1, + autovacuumMultixactFreezeTableAge: 1, + autovacuumVacuumCostDelay: 1, + autovacuumVacuumCostLimit: 1, + autovacuumVacuumScaleFactor: 1, + autovacuumVacuumThreshold: 1, + fillfactor: 1, + logAutovacuumMinDuration: 1, + parallelWorkers: 1, + toastTupleTarget: 1, + userCatalogTable: true, + vacuumIndexCleanup: 'off', + vacuumTruncate: false, + }).as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(3); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: '', + columns: [{ + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + uniqueConstraints: [], + isRLSEnabled: false, + compositePkName: '', + policies: [], + checkConstraints: [], + }); + expect(statements[1]).toStrictEqual({ + type: 'create_view', + name: 'some_view1', + definition: `SELECT * FROM "users"`, + schema: 'public', + with: undefined, + materialized: true, + withNoData: false, + using: undefined, + tablespace: undefined, + }); + expect(statements[2]).toStrictEqual({ + type: 'create_view', + name: 'some_view2', + definition: `select "id" from "users"`, + schema: 'public', + with: { + autovacuumEnabled: true, + autovacuumFreezeMaxAge: 1, + autovacuumFreezeMinAge: 1, + autovacuumFreezeTableAge: 1, + autovacuumMultixactFreezeMaxAge: 1, + autovacuumMultixactFreezeMinAge: 1, + autovacuumMultixactFreezeTableAge: 1, + autovacuumVacuumCostDelay: 1, + autovacuumVacuumCostLimit: 1, + autovacuumVacuumScaleFactor: 1, + autovacuumVacuumThreshold: 1, + fillfactor: 1, + logAutovacuumMinDuration: 1, + parallelWorkers: 1, + toastTupleTarget: 1, + userCatalogTable: true, + vacuumIndexCleanup: 'off', + vacuumTruncate: false, + }, + materialized: true, + tablespace: 'some_tablespace', + using: 'heap', + withNoData: true, + }); + + expect(sqlStatements.length).toBe(3); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[1]).toBe( + `CREATE MATERIALIZED VIEW "public"."some_view1" AS (SELECT * FROM "users");`, + ); + expect(sqlStatements[2]).toBe( + `CREATE MATERIALIZED VIEW "public"."some_view2" USING "heap" WITH (autovacuum_enabled = true, autovacuum_freeze_max_age = 1, autovacuum_freeze_min_age = 1, autovacuum_freeze_table_age = 1, autovacuum_multixact_freeze_max_age = 1, autovacuum_multixact_freeze_min_age = 1, autovacuum_multixact_freeze_table_age = 1, autovacuum_vacuum_cost_delay = 1, autovacuum_vacuum_cost_limit = 1, autovacuum_vacuum_scale_factor = 1, autovacuum_vacuum_threshold = 1, fillfactor = 1, log_autovacuum_min_duration = 1, parallel_workers = 1, toast_tuple_target = 1, user_catalog_table = true, vacuum_index_cleanup = off, vacuum_truncate = false) TABLESPACE some_tablespace AS (select "id" from "users") WITH NO DATA;`, + ); +}); + +test('create table and materialized view #4', async () => { + // same names + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view1: pgMaterializedView('some_view', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + view2: pgMaterializedView('some_view', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + }; + + await expect(diffTestSchemas({}, to, [])).rejects.toThrowError(); +}); + +test('create table and materialized view #5', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + const to = { + users: users, + view1: pgMaterializedView('some_view', { id: integer('id') }).with({ autovacuumFreezeMinAge: 14 }).as( + sql`SELECT * FROM ${users}`, + ), + }; + + const { statements, sqlStatements } = await diffTestSchemas({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + columns: [ + { + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + ], + compositePKs: [], + compositePkName: '', + schema: '', + tableName: 'users', + type: 'create_table', + uniqueConstraints: [], + isRLSEnabled: false, + policies: [], + checkConstraints: [], + }); + expect(statements[1]).toEqual({ + definition: 'SELECT * FROM "users"', + name: 'some_view', + schema: 'public', + type: 'create_view', + with: { + autovacuumFreezeMinAge: 14, + }, + materialized: true, + tablespace: undefined, + using: undefined, + withNoData: false, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE TABLE "users" ( +\t"id" integer PRIMARY KEY NOT NULL +);\n`); + expect(sqlStatements[1]).toBe( + `CREATE MATERIALIZED VIEW "public"."some_view" WITH (autovacuum_freeze_min_age = 14) AS (SELECT * FROM "users");`, + ); +}); + +test('create materialized view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + }; + + const to = { + users: users, + view1: pgMaterializedView('some_view', { id: integer('id') }).with({ autovacuumEnabled: true }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + }; + + const to = { + users: users, + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'drop_view', + name: 'some_view', + schema: 'public', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP VIEW "public"."some_view";`); +}); + +test('drop view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view', { id: integer('id') }).existing(), + }; + + const to = { + users: users, + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop materialized view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).as(sql`SELECT * FROM ${users}`), + }; + + const to = { + users: users, + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'drop_view', + name: 'some_view', + schema: 'public', + materialized: true, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP MATERIALIZED VIEW "public"."some_view";`); +}); + +test('drop materialized view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).existing(), + }; + + const to = { + users: users, + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('rename view #1', async () => { + const from = { + view: pgView('some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const to = { + view: pgView('new_some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->public.new_some_view']); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'rename_view', + nameFrom: 'some_view', + nameTo: 'new_some_view', + schema: 'public', + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER VIEW "public"."some_view" RENAME TO "new_some_view";`); +}); + +test('rename view with existing flag', async () => { + const from = { + view: pgView('some_view', { id: integer('id') }).existing(), + }; + + const to = { + view: pgView('new_some_view', { id: integer('id') }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->public.new_some_view']); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('rename materialized view #1', async () => { + const from = { + view: pgMaterializedView('some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const to = { + view: pgMaterializedView('new_some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->public.new_some_view']); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'rename_view', + nameFrom: 'some_view', + nameTo: 'new_some_view', + schema: 'public', + materialized: true, + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`ALTER MATERIALIZED VIEW "public"."some_view" RENAME TO "new_some_view";`); +}); + +test('rename materialized view with existing flag', async () => { + const from = { + view: pgMaterializedView('some_view', { id: integer('id') }).existing(), + }; + + const to = { + view: pgMaterializedView('new_some_view', { id: integer('id') }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->public.new_some_view']); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('view alter schema', async () => { + const schema = pgSchema('new_schema'); + + const from = { + view: pgView('some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const to = { + schema, + view: schema.view('some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->new_schema.some_view']); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_schema', + name: 'new_schema', + }); + expect(statements[1]).toStrictEqual({ + type: 'alter_view_alter_schema', + toSchema: 'new_schema', + fromSchema: 'public', + name: 'some_view', + }); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE SCHEMA "new_schema";\n`); + expect(sqlStatements[1]).toBe(`ALTER VIEW "public"."some_view" SET SCHEMA "new_schema";`); +}); + +test('view alter schema with existing flag', async () => { + const schema = pgSchema('new_schema'); + + const from = { + view: pgView('some_view', { id: integer('id') }).existing(), + }; + + const to = { + schema, + view: schema.view('some_view', { id: integer('id') }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->new_schema.some_view']); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_schema', + name: 'new_schema', + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE SCHEMA "new_schema";\n`); +}); + +test('view alter schema for materialized', async () => { + const schema = pgSchema('new_schema'); + + const from = { + view: pgMaterializedView('some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const to = { + schema, + view: schema.materializedView('some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->new_schema.some_view']); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_schema', + name: 'new_schema', + }); + expect(statements[1]).toStrictEqual({ + type: 'alter_view_alter_schema', + toSchema: 'new_schema', + fromSchema: 'public', + name: 'some_view', + materialized: true, + }); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE SCHEMA "new_schema";\n`); + expect(sqlStatements[1]).toBe(`ALTER MATERIALIZED VIEW "public"."some_view" SET SCHEMA "new_schema";`); +}); + +test('view alter schema for materialized with existing flag', async () => { + const schema = pgSchema('new_schema'); + + const from = { + view: pgMaterializedView('some_view', { id: integer('id') }).existing(), + }; + + const to = { + schema, + view: schema.materializedView('some_view', { id: integer('id') }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->new_schema.some_view']); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_schema', + name: 'new_schema', + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE SCHEMA "new_schema";\n`); +}); + +test('add with option to view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view').as((qb) => qb.select().from(users)), + }; + + const to = { + users, + view: pgView('some_view').with({ checkOption: 'cascaded', securityBarrier: true }).as((qb) => + qb.select().from(users) + ), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'some_view', + schema: 'public', + type: 'alter_view_add_with_option', + with: { + checkOption: 'cascaded', + securityBarrier: true, + }, + materialized: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER VIEW "public"."some_view" SET (check_option = cascaded, security_barrier = true);`, + ); +}); + +test('add with option to view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view', {}).existing(), + }; + + const to = { + users, + view: pgView('some_view', {}).with({ checkOption: 'cascaded', securityBarrier: true }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('add with option to materialized view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view').as((qb) => qb.select().from(users)), + }; + + const to = { + users, + view: pgMaterializedView('some_view').with({ autovacuumMultixactFreezeMaxAge: 3 }).as((qb) => + qb.select().from(users) + ), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'some_view', + schema: 'public', + type: 'alter_view_add_with_option', + with: { + autovacuumMultixactFreezeMaxAge: 3, + }, + materialized: true, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" SET (autovacuum_multixact_freeze_max_age = 3);`, + ); +}); + +test('add with option to materialized view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', {}).existing(), + }; + + const to = { + users, + view: pgMaterializedView('some_view', {}).with({ autovacuumMultixactFreezeMaxAge: 3 }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop with option from view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view').with({ checkOption: 'cascaded', securityBarrier: true, securityInvoker: true }).as((qb) => + qb.select().from(users) + ), + }; + + const to = { + users, + view: pgView('some_view').as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'some_view', + schema: 'public', + type: 'alter_view_drop_with_option', + materialized: false, + with: { + checkOption: 'cascaded', + securityBarrier: true, + securityInvoker: true, + }, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER VIEW "public"."some_view" RESET (check_option, security_barrier, security_invoker);`, + ); +}); + +test('drop with option from view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view', {}).with({ checkOption: 'cascaded', securityBarrier: true, securityInvoker: true }) + .existing(), + }; + + const to = { + users, + view: pgView('some_view', {}).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop with option from materialized view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view').with({ autovacuumEnabled: true, autovacuumFreezeMaxAge: 10 }).as((qb) => + qb.select().from(users) + ), + }; + + const to = { + users, + view: pgMaterializedView('some_view').as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'some_view', + schema: 'public', + type: 'alter_view_drop_with_option', + materialized: true, + with: { + autovacuumEnabled: true, + autovacuumFreezeMaxAge: 10, + }, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" RESET (autovacuum_enabled, autovacuum_freeze_max_age);`, + ); +}); + +test('drop with option from materialized view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', {}).with({ autovacuumEnabled: true, autovacuumFreezeMaxAge: 10 }).existing(), + }; + + const to = { + users, + view: pgMaterializedView('some_view', {}).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('alter with option in view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view').with({ securityBarrier: true, securityInvoker: true }).as((qb) => + qb.select().from(users) + ), + }; + + const to = { + users, + view: pgView('some_view').with({ securityBarrier: true }).as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'some_view', + schema: 'public', + type: 'alter_view_drop_with_option', + with: { + securityInvoker: true, + }, + materialized: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER VIEW "public"."some_view" RESET (security_invoker);`, + ); +}); + +test('alter with option in view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view', {}).with({ securityBarrier: true, securityInvoker: true }).existing(), + }; + + const to = { + users, + view: pgView('some_view', {}).with({ securityBarrier: true }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('alter with option in materialized view #1', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view').with({ autovacuumEnabled: true, autovacuumVacuumScaleFactor: 1 }).as((qb) => + qb.select().from(users) + ), + }; + + const to = { + users, + view: pgMaterializedView('some_view').with({ autovacuumEnabled: true }).as((qb) => qb.select().from(users)), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'some_view', + schema: 'public', + type: 'alter_view_drop_with_option', + with: { + autovacuumVacuumScaleFactor: 1, + }, + materialized: true, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" RESET (autovacuum_vacuum_scale_factor);`, + ); +}); + +test('alter with option in materialized view with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', {}).with({ autovacuumEnabled: true, autovacuumVacuumScaleFactor: 1 }) + .existing(), + }; + + const to = { + users, + view: pgMaterializedView('some_view', {}).with({ autovacuumEnabled: true }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('alter with option in view #2', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view').with({ checkOption: 'local', securityBarrier: true, securityInvoker: true }).as((qb) => + qb.selectDistinct().from(users) + ), + }; + + const to = { + users, + view: pgView('some_view').with({ checkOption: 'cascaded', securityBarrier: true, securityInvoker: true }).as((qb) => + qb.selectDistinct().from(users) + ), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_view_add_with_option', + name: 'some_view', + schema: 'public', + with: { + checkOption: 'cascaded', + }, + materialized: false, + }); + + expect(sqlStatements.length).toBe(1); + + expect(sqlStatements[0]).toBe( + `ALTER VIEW "public"."some_view" SET (check_option = cascaded);`, + ); +}); + +test('alter with option in materialized view #2', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view').with({ autovacuumEnabled: true, fillfactor: 1 }).as((qb) => + qb.select().from(users) + ), + }; + + const to = { + users, + view: pgMaterializedView('some_view').with({ autovacuumEnabled: false, fillfactor: 1 }).as((qb) => + qb.select().from(users) + ), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_view_add_with_option', + name: 'some_view', + schema: 'public', + with: { + autovacuumEnabled: false, + }, + materialized: true, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" SET (autovacuum_enabled = false);`, + ); +}); + +test('alter view ".as" value', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view', { id: integer('id') }).with({ + checkOption: 'local', + securityBarrier: true, + securityInvoker: true, + }).as(sql`SELECT '123'`), + }; + + const to = { + users, + view: pgView('some_view', { id: integer('id') }).with({ + checkOption: 'local', + securityBarrier: true, + securityInvoker: true, + }).as(sql`SELECT '1234'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual( + { + name: 'some_view', + schema: 'public', + type: 'drop_view', + }, + ); + expect(statements[1]).toStrictEqual( + { + definition: "SELECT '1234'", + name: 'some_view', + schema: 'public', + type: 'create_view', + materialized: false, + with: { + checkOption: 'local', + securityBarrier: true, + securityInvoker: true, + }, + withNoData: false, + tablespace: undefined, + using: undefined, + }, + ); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe('DROP VIEW "public"."some_view";'); + expect(sqlStatements[1]).toBe( + `CREATE VIEW "public"."some_view" WITH (check_option = local, security_barrier = true, security_invoker = true) AS (SELECT '1234');`, + ); +}); + +test('alter view ".as" value with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view', { id: integer('id') }).with({ + checkOption: 'local', + securityBarrier: true, + securityInvoker: true, + }).existing(), + }; + + const to = { + users, + view: pgView('some_view', { id: integer('id') }).with({ + checkOption: 'local', + securityBarrier: true, + securityInvoker: true, + }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('alter materialized view ".as" value', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT '123'`), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT '1234'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual( + { + name: 'some_view', + schema: 'public', + type: 'drop_view', + materialized: true, + }, + ); + expect(statements[1]).toStrictEqual( + { + definition: "SELECT '1234'", + name: 'some_view', + schema: 'public', + type: 'create_view', + with: { + autovacuumVacuumCostLimit: 1, + }, + materialized: true, + withNoData: false, + tablespace: undefined, + using: undefined, + }, + ); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe('DROP MATERIALIZED VIEW "public"."some_view";'); + expect(sqlStatements[1]).toBe( + `CREATE MATERIALIZED VIEW "public"."some_view" WITH (autovacuum_vacuum_cost_limit = 1) AS (SELECT '1234');`, + ); +}); + +test('alter materialized view ".as" value with existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).existing(), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop existing flag', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).existing(), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toEqual({ + type: 'drop_view', + name: 'some_view', + schema: 'public', + materialized: true, + }); + expect(statements[1]).toEqual({ + definition: "SELECT 'asd'", + materialized: true, + name: 'some_view', + schema: 'public', + tablespace: undefined, + type: 'create_view', + using: undefined, + with: { + autovacuumVacuumCostLimit: 1, + }, + withNoData: false, + }); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`DROP MATERIALIZED VIEW "public"."some_view";`); + expect(sqlStatements[1]).toBe( + `CREATE MATERIALIZED VIEW "public"."some_view" WITH (autovacuum_vacuum_cost_limit = 1) AS (SELECT 'asd');`, + ); +}); + +test('alter tablespace - materialize', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).tablespace('some_tablespace').with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).tablespace('new_tablespace').with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toEqual({ + type: 'alter_view_alter_tablespace', + name: 'some_view', + schema: 'public', + materialized: true, + toTablespace: 'new_tablespace', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" SET TABLESPACE new_tablespace;`, + ); +}); + +test('set tablespace - materialize', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).tablespace('new_tablespace').with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toEqual({ + type: 'alter_view_alter_tablespace', + name: 'some_view', + schema: 'public', + materialized: true, + toTablespace: 'new_tablespace', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" SET TABLESPACE new_tablespace;`, + ); +}); + +test('drop tablespace - materialize', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).tablespace('new_tablespace').with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toEqual({ + type: 'alter_view_alter_tablespace', + name: 'some_view', + schema: 'public', + materialized: true, + toTablespace: 'pg_default', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" SET TABLESPACE pg_default;`, + ); +}); + +test('set existing - materialized', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).tablespace('new_tablespace').with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const to = { + users, + view: pgMaterializedView('new_some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + autovacuumFreezeMinAge: 1, + }).withNoData().existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->public.new_some_view']); + + expect(statements.length).toBe(0); + + expect(sqlStatements.length).toBe(0); +}); + +test('drop existing - materialized', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).tablespace('new_tablespace').with({ + autovacuumVacuumCostLimit: 1, + }).existing(), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + autovacuumFreezeMinAge: 1, + }).withNoData().as(sql`SELECT 'asd'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(2); + + expect(sqlStatements.length).toBe(2); +}); + +test('set existing', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgView('some_view', { id: integer('id') }).with({ + checkOption: 'cascaded', + }).as(sql`SELECT 'asd'`), + }; + + const to = { + users, + view: pgView('new_some_view', { id: integer('id') }).with({ + checkOption: 'cascaded', + securityBarrier: true, + }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->public.new_some_view']); + + expect(statements.length).toBe(0); + + expect(sqlStatements.length).toBe(0); +}); + +test('alter using - materialize', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).tablespace('some_tablespace').using('some_using').with( + { + autovacuumVacuumCostLimit: 1, + }, + ).as(sql`SELECT 'asd'`), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).tablespace('some_tablespace').using('new_using').with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toEqual({ + type: 'alter_view_alter_using', + name: 'some_view', + schema: 'public', + materialized: true, + toUsing: 'new_using', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" SET ACCESS METHOD "new_using";`, + ); +}); + +test('set using - materialize', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).using('new_using').with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toEqual({ + type: 'alter_view_alter_using', + name: 'some_view', + schema: 'public', + materialized: true, + toUsing: 'new_using', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" SET ACCESS METHOD "new_using";`, + ); +}); + +test('drop using - materialize', async () => { + const users = pgTable('users', { + id: integer('id').primaryKey().notNull(), + }); + + const from = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).using('new_using').with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const to = { + users, + view: pgMaterializedView('some_view', { id: integer('id') }).with({ + autovacuumVacuumCostLimit: 1, + }).as(sql`SELECT 'asd'`), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toEqual({ + type: 'alter_view_alter_using', + name: 'some_view', + schema: 'public', + materialized: true, + toUsing: 'heap', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."some_view" SET ACCESS METHOD "heap";`, + ); +}); + +test('rename view and alter view', async () => { + const from = { + view: pgView('some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const to = { + view: pgView('new_some_view', { id: integer('id') }).with({ checkOption: 'cascaded' }).as( + sql`SELECT * FROM "users"`, + ), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->public.new_some_view']); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'rename_view', + nameFrom: 'some_view', + nameTo: 'new_some_view', + schema: 'public', + }); + expect(statements[1]).toStrictEqual({ + materialized: false, + name: 'new_some_view', + schema: 'public', + type: 'alter_view_add_with_option', + with: { + checkOption: 'cascaded', + }, + }); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER VIEW "public"."some_view" RENAME TO "new_some_view";`); + expect(sqlStatements[1]).toBe(`ALTER VIEW "public"."new_some_view" SET (check_option = cascaded);`); +}); + +test('moved schema and alter view', async () => { + const schema = pgSchema('my_schema'); + const from = { + schema, + view: pgView('some_view', { id: integer('id') }).as(sql`SELECT * FROM "users"`), + }; + + const to = { + schema, + view: schema.view('some_view', { id: integer('id') }).with({ checkOption: 'cascaded' }).as( + sql`SELECT * FROM "users"`, + ), + }; + + const { statements, sqlStatements } = await diffTestSchemas(from, to, ['public.some_view->my_schema.some_view']); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + fromSchema: 'public', + name: 'some_view', + toSchema: 'my_schema', + type: 'alter_view_alter_schema', + }); + expect(statements[1]).toStrictEqual({ + name: 'some_view', + schema: 'my_schema', + type: 'alter_view_add_with_option', + materialized: false, + with: { + checkOption: 'cascaded', + }, + }); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER VIEW "public"."some_view" SET SCHEMA "my_schema";`); + expect(sqlStatements[1]).toBe(`ALTER VIEW "my_schema"."some_view" SET (check_option = cascaded);`); +}); diff --git a/drizzle-kit/tests/push/common.ts b/drizzle-kit/tests/push/common.ts index 0c679ca6f..627070f11 100644 --- a/drizzle-kit/tests/push/common.ts +++ b/drizzle-kit/tests/push/common.ts @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, test } from 'vitest'; +import { afterAll, beforeAll, beforeEach, test } from 'vitest'; export interface DialectSuite { allTypes(context?: any): Promise; @@ -15,6 +15,8 @@ export interface DialectSuite { dropGeneratedConstraint(context?: any): Promise; alterGeneratedConstraint(context?: any): Promise; createTableWithGeneratedConstraint(context?: any): Promise; + createCompositePrimaryKey(context?: any): Promise; + renameTableWithCompositePrimaryKey(context?: any): Promise; case1(): Promise; } @@ -22,10 +24,13 @@ export const run = ( suite: DialectSuite, beforeAllFn?: (context: any) => Promise, afterAllFn?: (context: any) => Promise, + beforeEachFn?: (context: any) => Promise, ) => { let context: any = {}; beforeAll(beforeAllFn ? () => beforeAllFn(context) : () => {}); + beforeEach(beforeEachFn ? () => beforeEachFn(context) : () => {}); + test('No diffs for all database types', () => suite.allTypes(context)); test('Adding basic indexes', () => suite.addBasicIndexes(context)); test('Dropping basic index', () => suite.dropIndex(context)); @@ -45,6 +50,9 @@ export const run = ( // should ignore on push test('Alter generated constraint', () => suite.alterGeneratedConstraint(context)); test('Create table with generated column', () => suite.createTableWithGeneratedConstraint(context)); + test('Rename table with composite primary key', () => suite.renameTableWithCompositePrimaryKey(context)); + + test('Create composite primary key', () => suite.createCompositePrimaryKey(context)); afterAll(afterAllFn ? () => afterAllFn(context) : () => {}); }; diff --git a/drizzle-kit/tests/push/libsql.test.ts b/drizzle-kit/tests/push/libsql.test.ts new file mode 100644 index 000000000..2ae2e3811 --- /dev/null +++ b/drizzle-kit/tests/push/libsql.test.ts @@ -0,0 +1,1400 @@ +import { createClient } from '@libsql/client'; +import chalk from 'chalk'; +import { sql } from 'drizzle-orm'; +import { + blob, + check, + foreignKey, + getTableConfig, + index, + int, + integer, + numeric, + real, + sqliteTable, + sqliteView, + text, + uniqueIndex, +} from 'drizzle-orm/sqlite-core'; +import { diffTestSchemasPushLibSQL } from 'tests/schemaDiffer'; +import { expect, test } from 'vitest'; + +test('nothing changed in schema', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), + email: text('email'), + textJson: text('text_json', { mode: 'json' }), + blobJon: blob('blob_json', { mode: 'json' }), + blobBigInt: blob('blob_bigint', { mode: 'bigint' }), + numeric: numeric('numeric'), + createdAt: integer('created_at', { mode: 'timestamp' }), + createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { length: 255 }), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + isConfirmed: integer('is_confirmed', { + mode: 'boolean', + }), + }); + + const schema1 = { + users, + + customers: sqliteTable('customers', { + id: integer('id').primaryKey(), + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + userId: integer('user_id') + .references(() => users.id) + .notNull(), + }), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL(turso, schema1, schema1, [], false); + expect(sqlStatements.length).toBe(0); + expect(statements.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); +}); + +test('added, dropped index', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), + email: text('email'), + textJson: text('text_json', { mode: 'json' }), + blobJon: blob('blob_json', { mode: 'json' }), + blobBigInt: blob('blob_bigint', { mode: 'bigint' }), + numeric: numeric('numeric'), + createdAt: integer('created_at', { mode: 'timestamp' }), + createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { length: 255 }), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + isConfirmed: integer('is_confirmed', { + mode: 'boolean', + }), + }); + + const schema1 = { + users, + customers: sqliteTable( + 'customers', + { + id: integer('id').primaryKey(), + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + userId: integer('user_id').notNull(), + }, + (table) => ({ + uniqueIndex: uniqueIndex('customers_address_unique').on(table.address), + }), + ), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const schema2 = { + users, + customers: sqliteTable( + 'customers', + { + id: integer('id').primaryKey(), + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + userId: integer('user_id').notNull(), + }, + (table) => ({ + uniqueIndex: uniqueIndex('customers_is_confirmed_unique').on( + table.isConfirmed, + ), + }), + ), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL(turso, schema1, schema2, [], false); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'drop_index', + tableName: 'customers', + data: 'customers_address_unique;address;true;', + schema: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'create_index', + tableName: 'customers', + data: 'customers_is_confirmed_unique;is_confirmed;true;', + schema: '', + internal: { indexes: {} }, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `DROP INDEX \`customers_address_unique\`;`, + ); + expect(sqlStatements[1]).toBe( + `CREATE UNIQUE INDEX \`customers_is_confirmed_unique\` ON \`customers\` (\`is_confirmed\`);`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('added column not null and without default to table with data', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + age: integer('age').notNull(), + }), + }; + + const table = getTableConfig(schema1.companies); + + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.name.name}") VALUES ('drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.name.name}") VALUES ('turso');`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'companies', + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + referenceData: undefined, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`delete from companies;`); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`companies\` ADD \`age\` integer NOT NULL;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to add not-null ${ + chalk.underline( + 'age', + ) + } column without default value, which contains 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(1); + expect(tablesToTruncate![0]).toBe('companies'); +}); + +test('added column not null and without default to table without data', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + age: integer('age').notNull(), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL(turso, schema1, schema2, [], false); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'companies', + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + referenceData: undefined, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`companies\` ADD \`age\` integer NOT NULL;`, + ); + + expect(infoToPrint!.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop autoincrement. drop column with data', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + }), + }; + + const table = getTableConfig(schema1.companies); + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (1, 'drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (2, 'turso');`, + ]; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ + { + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL +);\n`, + ); + expect(sqlStatements[1]).toBe(`INSERT INTO \`__new_companies\`("id") SELECT "id" FROM \`companies\`;`); + expect(sqlStatements[2]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + + expect(columnsToRemove!.length).toBe(1); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to delete ${ + chalk.underline( + 'name', + ) + } column in companies table with 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('change autoincrement. table is part of foreign key', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const companies1 = sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + }); + const users1 = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').unique(), + companyId: integer('company_id').references(() => companies1.id), + }); + const schema1 = { + companies: companies1, + users: users1, + }; + + const companies2 = sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + }); + const users2 = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').unique(), + companyId: integer('company_id').references(() => companies2.id), + }); + const schema2 = { + companies: companies2, + users: users2, + }; + + const { name: usersTableName } = getTableConfig(users1); + const { name: companiesTableName } = getTableConfig(companies1); + const seedStatements = [ + `INSERT INTO \`${usersTableName}\` ("${schema1.users.name.name}") VALUES ('drizzle');`, + `INSERT INTO \`${usersTableName}\` ("${schema1.users.name.name}") VALUES ('turso');`, + `INSERT INTO \`${companiesTableName}\` ("${schema1.companies.id.name}") VALUES (1);`, + `INSERT INTO \`${companiesTableName}\` ("${schema1.companies.id.name}") VALUES (2);`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ + { + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL +);\n`, + ); + expect(sqlStatements[1]).toBe( + `INSERT INTO \`__new_companies\`("id") SELECT "id" FROM \`companies\`;`, + ); + expect(sqlStatements[2]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop not null, add not null', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + posts: sqliteTable( + 'posts', + { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + userId: int('user_id'), + }, + ), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + posts: sqliteTable( + 'posts', + { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + userId: int('user_id'), + }, + ), + }; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(2); + expect(statements![0]).toStrictEqual({ + columnAutoIncrement: false, + columnDefault: undefined, + columnName: 'name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_drop_notnull', + }); + expect(statements![1]).toStrictEqual({ + columnAutoIncrement: false, + columnDefault: undefined, + columnName: 'name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'posts', + type: 'alter_table_alter_column_set_notnull', + }); + expect(sqlStatements!.length).toBe(2); + expect(sqlStatements![0]).toBe(`ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`); + expect(sqlStatements![1]).toBe(`ALTER TABLE \`posts\` ALTER COLUMN "name" TO "name" text NOT NULL;`); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop table with data', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + posts: sqliteTable( + 'posts', + { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + userId: int('user_id'), + }, + ), + }; + + const schema2 = { + posts: sqliteTable( + 'posts', + { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + userId: int('user_id'), + }, + ), + }; + + const seedStatements = [ + `INSERT INTO \`users\` ("name") VALUES ('drizzle')`, + ]; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + policies: [], + schema: undefined, + tableName: 'users', + type: 'drop_table', + }); + + expect(sqlStatements!.length).toBe(1); + expect(sqlStatements![0]).toBe(`DROP TABLE \`users\`;`); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe(`· You're about to delete ${chalk.underline('users')} table with 1 items`); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(1); + expect(tablesToRemove![0]).toBe('users'); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('recreate table with nested references', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + let users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }); + let subscriptions = sqliteTable('subscriptions', { + id: int('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + customerId: text('customer_id'), + }); + const schema1 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }); + const schema2 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL(turso, schema1, schema2, []); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements![1]).toBe( + `INSERT INTO \`__new_users\`("id", "name", "age") SELECT "id", "name", "age" FROM \`users\`;`, + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('recreate table with added column not null and without default', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + newColumn: text('new_column').notNull(), + }), + }; + + const seedStatements = [ + `INSERT INTO \`users\` ("name", "age") VALUES ('drizzle', 12)`, + `INSERT INTO \`users\` ("name", "age") VALUES ('turso', 12)`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'new_column', + notNull: true, + generated: undefined, + primaryKey: false, + type: 'text', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements[0]).toBe('DELETE FROM \`users\`;'); + expect(sqlStatements![1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer, +\t\`new_column\` text NOT NULL +);\n`); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to add not-null ${ + chalk.underline('new_column') + } column without default value to table, which contains 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(1); + expect(tablesToTruncate![0]).toBe('users'); +}); + +test('set not null with index', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }, (table) => ({ + someIndex: index('users_name_index').on(table.name), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }, (table) => ({ + someIndex: index('users_name_index').on(table.name), + })), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columnAutoIncrement: false, + columnDefault: undefined, + columnName: 'name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_notnull', + }); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(3); + expect(sqlStatements[0]).toBe( + `DROP INDEX "users_name_index";`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text NOT NULL;`, + ); + expect(sqlStatements[2]).toBe( + `CREATE INDEX \`users_name_index\` ON \`users\` (\`name\`);`, + ); + expect(columnsToRemove!.length).toBe(0), expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop not null with two indexes', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + age: int('age').notNull(), + }, (table) => ({ + someUniqeIndex: uniqueIndex('users_name_unique').on(table.name), + someIndex: index('users_age_index').on(table.age), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: int('age').notNull(), + }, (table) => ({ + someUniqeIndex: uniqueIndex('users_name_unique').on(table.name), + someIndex: index('users_age_index').on(table.age), + })), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_drop_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(5); + expect(sqlStatements[0]).toBe( + `DROP INDEX "users_name_unique";`, + ); + expect(sqlStatements[1]).toBe( + `DROP INDEX "users_age_index";`, + ); + expect(sqlStatements[2]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`, + ); + expect(sqlStatements[3]).toBe( + `CREATE UNIQUE INDEX \`users_name_unique\` ON \`users\` (\`name\`);`, + ); + expect(sqlStatements[4]).toBe( + `CREATE INDEX \`users_age_index\` ON \`users\` (\`age\`);`, + ); + expect(columnsToRemove!.length).toBe(0), expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('add check constraint to table', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: ['some_check;"users"."age" > 21'], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer, +\tCONSTRAINT "some_check" CHECK("__new_users"."age" > 21) +);\n`); + expect(sqlStatements[1]).toBe( + 'INSERT INTO `__new_users`("id", "name", "age") SELECT "id", "name", "age" FROM `users`;', + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop check constraint', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements[1]).toBe( + 'INSERT INTO `__new_users`("id", "name", "age") SELECT "id", "name", "age" FROM `users`;', + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('db has checks. Push with same names', async () => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`some new value`), + })), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + [], + ); + expect(statements).toStrictEqual([]); + expect(sqlStatements).toStrictEqual([]); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('create view', async () => { + const turso = createClient({ + url: ':memory:', + }); + + const table = sqliteTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + }; + + const schema2 = { + test: table, + view: sqliteView('view').as((qb) => qb.select().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements).toStrictEqual([ + { + definition: 'select "id" from "test"', + name: 'view', + type: 'sqlite_create_view', + }, + ]); + expect(sqlStatements).toStrictEqual([ + `CREATE VIEW \`view\` AS select "id" from "test";`, + ]); +}); + +test('drop view', async () => { + const turso = createClient({ + url: ':memory:', + }); + + const table = sqliteTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: sqliteView('view').as((qb) => qb.select().from(table)), + }; + + const schema2 = { + test: table, + }; + + const { statements, sqlStatements } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements).toStrictEqual([ + { + name: 'view', + type: 'drop_view', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'DROP VIEW \`view\`;', + ]); +}); + +test('alter view ".as"', async () => { + const turso = createClient({ + url: ':memory:', + }); + + const table = sqliteTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: sqliteView('view').as((qb) => qb.select().from(table).where(sql`${table.id} = 1`)), + }; + + const schema2 = { + test: table, + view: sqliteView('view').as((qb) => qb.select().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); diff --git a/drizzle-kit/tests/push/mysql-push.test.ts b/drizzle-kit/tests/push/mysql-push.test.ts new file mode 100644 index 000000000..ba64ccddb --- /dev/null +++ b/drizzle-kit/tests/push/mysql-push.test.ts @@ -0,0 +1,345 @@ +import Docker from 'dockerode'; +import { sql } from 'drizzle-orm'; +import { check, int, mysqlTable, mysqlView } from 'drizzle-orm/mysql-core'; +import fs from 'fs'; +import getPort from 'get-port'; +import { Connection, createConnection } from 'mysql2/promise'; +import { diffTestSchemasPushMysql } from 'tests/schemaDiffer'; +import { v4 as uuid } from 'uuid'; +import { afterAll, beforeAll, expect, test } from 'vitest'; + +let client: Connection; +let mysqlContainer: Docker.Container; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'mysql:8'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + mysqlContainer = await docker.createContainer({ + Image: image, + Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await mysqlContainer.start(); + + return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; +} + +beforeAll(async () => { + const connectionString = process.env.MYSQL_CONNECTION_STRING ?? await createDockerDB(); + + const sleep = 1000; + let timeLeft = 20000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); + throw lastError; + } +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +if (!fs.existsSync('tests/push/mysql')) { + fs.mkdirSync('tests/push/mysql'); +} + +test('add check constraint to table', async () => { + const schema1 = { + test: mysqlTable('test', { + id: int('id').primaryKey(), + values: int('values'), + }), + }; + const schema2 = { + test: mysqlTable('test', { + id: int('id').primaryKey(), + values: int('values'), + }, (table) => ({ + checkConstraint1: check('some_check1', sql`${table.values} < 100`), + checkConstraint2: check('some_check2', sql`'test' < 100`), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushMysql( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements).toStrictEqual([ + { + type: 'create_check_constraint', + tableName: 'test', + schema: '', + data: 'some_check1;\`test\`.\`values\` < 100', + }, + { + data: "some_check2;'test' < 100", + schema: '', + tableName: 'test', + type: 'create_check_constraint', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE \`test\` ADD CONSTRAINT \`some_check1\` CHECK (\`test\`.\`values\` < 100);', + `ALTER TABLE \`test\` ADD CONSTRAINT \`some_check2\` CHECK ('test' < 100);`, + ]); + + await client.query(`DROP TABLE \`test\`;`); +}); + +test('drop check constraint to table', async () => { + const schema1 = { + test: mysqlTable('test', { + id: int('id').primaryKey(), + values: int('values'), + }, (table) => ({ + checkConstraint1: check('some_check1', sql`${table.values} < 100`), + checkConstraint2: check('some_check2', sql`'test' < 100`), + })), + }; + const schema2 = { + test: mysqlTable('test', { + id: int('id').primaryKey(), + values: int('values'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushMysql( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements).toStrictEqual([ + { + type: 'delete_check_constraint', + tableName: 'test', + schema: '', + constraintName: 'some_check1', + }, + { + constraintName: 'some_check2', + schema: '', + tableName: 'test', + type: 'delete_check_constraint', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE \`test\` DROP CONSTRAINT \`some_check1\`;', + `ALTER TABLE \`test\` DROP CONSTRAINT \`some_check2\`;`, + ]); + + await client.query(`DROP TABLE \`test\`;`); +}); + +test('db has checks. Push with same names', async () => { + const schema1 = { + test: mysqlTable('test', { + id: int('id').primaryKey(), + values: int('values').default(1), + }, (table) => ({ + checkConstraint: check('some_check', sql`${table.values} < 100`), + })), + }; + const schema2 = { + test: mysqlTable('test', { + id: int('id').primaryKey(), + values: int('values').default(1), + }, (table) => ({ + checkConstraint: check('some_check', sql`some new value`), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushMysql( + client, + schema1, + schema2, + [], + 'drizzle', + ); + + expect(statements).toStrictEqual([]); + expect(sqlStatements).toStrictEqual([]); + + await client.query(`DROP TABLE \`test\`;`); +}); + +test('create view', async () => { + const table = mysqlTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + }; + + const schema2 = { + test: table, + view: mysqlView('view').as((qb) => qb.select().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushMysql( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements).toStrictEqual([ + { + definition: 'select \`id\` from \`test\`', + name: 'view', + type: 'mysql_create_view', + replace: false, + sqlSecurity: 'definer', + withCheckOption: undefined, + algorithm: 'undefined', + }, + ]); + expect(sqlStatements).toStrictEqual([ + `CREATE ALGORITHM = undefined +SQL SECURITY definer +VIEW \`view\` AS (select \`id\` from \`test\`);`, + ]); + + await client.query(`DROP TABLE \`test\`;`); +}); + +test('drop view', async () => { + const table = mysqlTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: mysqlView('view').as((qb) => qb.select().from(table)), + }; + + const schema2 = { + test: table, + }; + + const { statements, sqlStatements } = await diffTestSchemasPushMysql( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements).toStrictEqual([ + { + name: 'view', + type: 'drop_view', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'DROP VIEW \`view\`;', + ]); + await client.query(`DROP TABLE \`test\`;`); + await client.query(`DROP VIEW \`view\`;`); +}); + +test('alter view ".as"', async () => { + const table = mysqlTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: mysqlView('view').as((qb) => qb.select().from(table).where(sql`${table.id} = 1`)), + }; + + const schema2 = { + test: table, + view: mysqlView('view').as((qb) => qb.select().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushMysql( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); + + await client.query(`DROP TABLE \`test\`;`); + await client.query(`DROP VIEW \`view\`;`); +}); + +test('alter meta options with distinct in definition', async () => { + const table = mysqlTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: mysqlView('view').withCheckOption('cascaded').sqlSecurity('definer').algorithm('merge').as(( + qb, + ) => qb.selectDistinct().from(table).where(sql`${table.id} = 1`)), + }; + + const schema2 = { + test: table, + view: mysqlView('view').withCheckOption('cascaded').sqlSecurity('definer').algorithm('undefined').as((qb) => + qb.selectDistinct().from(table) + ), + }; + + await expect(diffTestSchemasPushMysql( + client, + schema1, + schema2, + [], + 'drizzle', + false, + )).rejects.toThrowError(); + + await client.query(`DROP TABLE \`test\`;`); +}); diff --git a/drizzle-kit/tests/push/mysql.test.ts b/drizzle-kit/tests/push/mysql.test.ts index 5cad140be..6c7f5efc2 100644 --- a/drizzle-kit/tests/push/mysql.test.ts +++ b/drizzle-kit/tests/push/mysql.test.ts @@ -1,3 +1,4 @@ +import 'dotenv/config'; import Docker from 'dockerode'; import { SQL, sql } from 'drizzle-orm'; import { @@ -14,6 +15,7 @@ import { mediumint, mysqlEnum, mysqlTable, + primaryKey, serial, smallint, text, @@ -28,7 +30,7 @@ import getPort from 'get-port'; import { Connection, createConnection } from 'mysql2/promise'; import { diffTestSchemasMysql, diffTestSchemasPushMysql } from 'tests/schemaDiffer'; import { v4 as uuid } from 'uuid'; -import { expect } from 'vitest'; +import { expect, test } from 'vitest'; import { DialectSuite, run } from './common'; async function createDockerDB(context: any): Promise { @@ -662,6 +664,88 @@ const mysqlSuite: DialectSuite = { createTableWithGeneratedConstraint: function(context?: any): Promise { return {} as any; }, + createCompositePrimaryKey: async function(context: any): Promise { + const schema1 = {}; + + const schema2 = { + table: mysqlTable('table', { + col1: int('col1').notNull(), + col2: int('col2').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.col1, t.col2], + }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushMysql( + context.client as Connection, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements).toStrictEqual([ + { + type: 'create_table', + tableName: 'table', + schema: undefined, + internals: { + indexes: {}, + tables: {}, + }, + compositePKs: ['table_col1_col2_pk;col1,col2'], + compositePkName: 'table_col1_col2_pk', + uniqueConstraints: [], + checkConstraints: [], + columns: [ + { name: 'col1', type: 'int', primaryKey: false, notNull: true, autoincrement: false }, + { name: 'col2', type: 'int', primaryKey: false, notNull: true, autoincrement: false }, + ], + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE `table` (\n\t`col1` int NOT NULL,\n\t`col2` int NOT NULL,\n\tCONSTRAINT `table_col1_col2_pk` PRIMARY KEY(`col1`,`col2`)\n);\n', + ]); + }, + renameTableWithCompositePrimaryKey: async function(context?: any): Promise { + const productsCategoriesTable = (tableName: string) => { + return mysqlTable(tableName, { + productId: varchar('product_id', { length: 10 }).notNull(), + categoryId: varchar('category_id', { length: 10 }).notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.productId, t.categoryId], + }), + })); + }; + + const schema1 = { + table: productsCategoriesTable('products_categories'), + }; + const schema2 = { + test: productsCategoriesTable('products_to_categories'), + }; + + const { sqlStatements } = await diffTestSchemasPushMysql( + context.client as Connection, + schema1, + schema2, + ['public.products_categories->public.products_to_categories'], + 'drizzle', + false, + ); + + expect(sqlStatements).toStrictEqual([ + 'RENAME TABLE `products_categories` TO `products_to_categories`;', + 'ALTER TABLE `products_to_categories` DROP PRIMARY KEY;', + 'ALTER TABLE `products_to_categories` ADD PRIMARY KEY(`product_id`,`category_id`);', + ]); + + await context.client.query(`DROP TABLE \`products_categories\``); + }, }; run( @@ -696,4 +780,9 @@ run( await context.client?.end().catch(console.error); await context.mysqlContainer?.stop().catch(console.error); }, + async (context: any) => { + await context.client?.query(`drop database if exists \`drizzle\`;`); + await context.client?.query(`create database \`drizzle\`;`); + await context.client?.query(`use \`drizzle\`;`); + }, ); diff --git a/drizzle-kit/tests/push/pg.test.ts b/drizzle-kit/tests/push/pg.test.ts index 1439d864e..a7bed413d 100644 --- a/drizzle-kit/tests/push/pg.test.ts +++ b/drizzle-kit/tests/push/pg.test.ts @@ -1,9 +1,11 @@ import { PGlite } from '@electric-sql/pglite'; +import chalk from 'chalk'; import { bigint, bigserial, boolean, char, + check, date, doublePrecision, index, @@ -13,9 +15,14 @@ import { jsonb, numeric, pgEnum, + pgMaterializedView, + pgPolicy, + pgRole, pgSchema, pgSequence, pgTable, + pgView, + primaryKey, real, serial, smallint, @@ -25,13 +32,12 @@ import { uniqueIndex, uuid, varchar, - vector, } from 'drizzle-orm/pg-core'; import { drizzle } from 'drizzle-orm/pglite'; -import { SQL, sql } from 'drizzle-orm/sql'; +import { eq, SQL, sql } from 'drizzle-orm/sql'; import { pgSuggestions } from 'src/cli/commands/pgPushUtils'; -import { diffTestSchemasPush } from 'tests/schemaDiffer'; -import { afterEach, expect, test } from 'vitest'; +import { diffTestSchemas, diffTestSchemasPush } from 'tests/schemaDiffer'; +import { expect, test } from 'vitest'; import { DialectSuite, run } from './common'; const pgSuite: DialectSuite = { @@ -40,10 +46,7 @@ const pgSuite: DialectSuite = { const customSchema = pgSchema('schemass'); - const transactionStatusEnum = customSchema.enum( - 'TransactionStatusEnum', - ['PENDING', 'FAILED', 'SUCCESS'], - ); + const transactionStatusEnum = customSchema.enum('TransactionStatusEnum', ['PENDING', 'FAILED', 'SUCCESS']); const enumname = pgEnum('enumname', ['three', 'two', 'one']); @@ -53,11 +56,7 @@ const pgSuite: DialectSuite = { enumname: pgEnum('enumname', ['three', 'two', 'one']), customSchema: customSchema, - transactionStatusEnum: customSchema.enum('TransactionStatusEnum', [ - 'PENDING', - 'FAILED', - 'SUCCESS', - ]), + transactionStatusEnum: customSchema.enum('TransactionStatusEnum', ['PENDING', 'FAILED', 'SUCCESS']), allSmallSerials: pgTable('schema_test', { columnAll: uuid('column_all').defaultRandom(), @@ -95,15 +94,9 @@ const pgSuite: DialectSuite = { withTimezone: true, mode: 'string', }).defaultNow(), - columnAll: timestamp('column_all', { mode: 'string' }).default( - '2023-03-01 12:47:29.792', - ), - column: timestamp('column', { mode: 'string' }).default( - sql`'2023-02-28 16:18:31.18'`, - ), - column2: timestamp('column2', { mode: 'string', precision: 3 }).default( - sql`'2023-02-28 16:18:31.18'`, - ), + columnAll: timestamp('column_all', { mode: 'string' }).default('2023-03-01 12:47:29.792'), + column: timestamp('column', { mode: 'string' }).default(sql`'2023-02-28 16:18:31.18'`), + column2: timestamp('column2', { mode: 'string', precision: 3 }).default(sql`'2023-02-28 16:18:31.18'`), }), allUuids: customSchema.table('all_uuids', { @@ -113,9 +106,7 @@ const pgSuite: DialectSuite = { allDates: customSchema.table('all_dates', { column_date_now: date('column_date_now').defaultNow(), - column_all: date('column_all', { mode: 'date' }) - .default(new Date()) - .notNull(), + column_all: date('column_all', { mode: 'date' }).default(new Date()).notNull(), column: date('column'), }), @@ -126,9 +117,7 @@ const pgSuite: DialectSuite = { }), allBigints: pgTable('all_bigints', { - columnAll: bigint('column_all', { mode: 'number' }) - .default(124) - .notNull(), + columnAll: bigint('column_all', { mode: 'number' }).default(124).notNull(), column: bigint('column', { mode: 'number' }), }), @@ -146,9 +135,7 @@ const pgSuite: DialectSuite = { columnMinToSec: interval('column_min_to_sec', { fields: 'minute to second', }), - columnWithoutFields: interval('column_without_fields') - .default('00:00:01') - .notNull(), + columnWithoutFields: interval('column_without_fields').default('00:00:01').notNull(), column: interval('column'), column5: interval('column5', { fields: 'minute to second', @@ -200,9 +187,7 @@ const pgSuite: DialectSuite = { }), allJsonb: customSchema.table('all_jsonb', { - columnDefaultObject: jsonb('column_default_object') - .default({ hello: 'world world' }) - .notNull(), + columnDefaultObject: jsonb('column_default_object').default({ hello: 'world world' }).notNull(), columnDefaultArray: jsonb('column_default_array').default({ hello: { 'world world': ['foo', 'bar'] }, }), @@ -210,9 +195,7 @@ const pgSuite: DialectSuite = { }), allJson: customSchema.table('all_json', { - columnDefaultObject: json('column_default_object') - .default({ hello: 'world world' }) - .notNull(), + columnDefaultObject: json('column_default_object').default({ hello: 'world world' }).notNull(), columnDefaultArray: json('column_default_array').default({ hello: { 'world world': ['foo', 'bar'] }, foo: 'bar', @@ -228,22 +211,16 @@ const pgSuite: DialectSuite = { }), allNumerics: customSchema.table('all_numerics', { - columnAll: numeric('column_all', { precision: 1, scale: 1 }) - .default('32') - .notNull(), + columnAll: numeric('column_all', { precision: 1, scale: 1 }).default('32').notNull(), column: numeric('column'), columnPrimary: numeric('column_primary').primaryKey().notNull(), }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema1, - [], - false, - ['public', 'schemass'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema1, [], false, [ + 'public', + 'schemass', + ]); expect(statements.length).toBe(0); }, @@ -276,14 +253,7 @@ const pgSuite: DialectSuite = { ), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ schema: '', @@ -348,10 +318,10 @@ const pgSuite: DialectSuite = { }); expect(sqlStatements.length).toBe(2); expect(sqlStatements[0]).toBe( - `CREATE INDEX IF NOT EXISTS "users_name_id_index" ON "users" USING btree ("name" DESC NULLS LAST,"id") WITH (fillfactor=70) WHERE select 1;`, + `CREATE INDEX "users_name_id_index" ON "users" USING btree ("name" DESC NULLS LAST,"id") WITH (fillfactor=70) WHERE select 1;`, ); expect(sqlStatements[1]).toBe( - `CREATE INDEX IF NOT EXISTS "indx1" ON "users" USING hash ("name" DESC NULLS LAST,"name") WITH (fillfactor=70);`, + `CREATE INDEX "indx1" ON "users" USING hash ("name" DESC NULLS LAST,"name") WITH (fillfactor=70);`, ); }, @@ -370,20 +340,11 @@ const pgSuite: DialectSuite = { id: integer('id'), id2: integer('id2'), name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${schema2.users.name}`, - ), + generatedName: text('gen_name').generatedAlwaysAs((): SQL => sql`${schema2.users.name}`), }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -427,20 +388,11 @@ const pgSuite: DialectSuite = { id: integer('id'), id2: integer('id2'), name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${schema2.users.name}`, - ), + generatedName: text('gen_name').generatedAlwaysAs((): SQL => sql`${schema2.users.name}`), }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -478,9 +430,7 @@ const pgSuite: DialectSuite = { id: integer('id'), id2: integer('id2'), name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${schema1.users.name}`, - ), + generatedName: text('gen_name').generatedAlwaysAs((): SQL => sql`${schema1.users.name}`), }), }; const schema2 = { @@ -492,14 +442,7 @@ const pgSuite: DialectSuite = { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -516,9 +459,7 @@ const pgSuite: DialectSuite = { type: 'alter_table_alter_column_drop_generated', }, ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER TABLE "users" ALTER COLUMN "gen_name" DROP EXPRESSION;', - ]); + expect(sqlStatements).toStrictEqual(['ALTER TABLE "users" ALTER COLUMN "gen_name" DROP EXPRESSION;']); }, async alterGeneratedConstraint() { @@ -529,9 +470,7 @@ const pgSuite: DialectSuite = { id: integer('id'), id2: integer('id2'), name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${schema1.users.name}`, - ), + generatedName: text('gen_name').generatedAlwaysAs((): SQL => sql`${schema1.users.name}`), }), }; const schema2 = { @@ -539,20 +478,11 @@ const pgSuite: DialectSuite = { id: integer('id'), id2: integer('id2'), name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${schema2.users.name} || 'hello'`, - ), + generatedName: text('gen_name').generatedAlwaysAs((): SQL => sql`${schema2.users.name} || 'hello'`), }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([]); expect(sqlStatements).toStrictEqual([]); @@ -567,20 +497,11 @@ const pgSuite: DialectSuite = { id: integer('id'), id2: integer('id2'), name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${schema2.users.name} || 'hello'`, - ), + generatedName: text('gen_name').generatedAlwaysAs((): SQL => sql`${schema2.users.name} || 'hello'`), }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -616,14 +537,17 @@ const pgSuite: DialectSuite = { ], compositePKs: [], compositePkName: '', + isRLSEnabled: false, schema: '', tableName: 'users', + policies: [], type: 'create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ - 'CREATE TABLE IF NOT EXISTS "users" (\n\t"id" integer,\n\t"id2" integer,\n\t"name" text,\n\t"gen_name" text GENERATED ALWAYS AS ("users"."name" || \'hello\') STORED\n);\n', + 'CREATE TABLE "users" (\n\t"id" integer,\n\t"id2" integer,\n\t"name" text,\n\t"gen_name" text GENERATED ALWAYS AS ("users"."name" || \'hello\') STORED\n);\n', ]); }, @@ -638,14 +562,7 @@ const pgSuite: DialectSuite = { seq: pgSequence('my_seq', { startWith: 100 }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements.length).toBe(0); }, @@ -661,20 +578,13 @@ const pgSuite: DialectSuite = { }, (t) => ({ removeColumn: index('removeColumn').on(t.name, t.id), - addColumn: index('addColumn') - .on(t.name.desc()) - .with({ fillfactor: 70 }), + addColumn: index('addColumn').on(t.name.desc()).with({ fillfactor: 70 }), removeExpression: index('removeExpression') .on(t.name.desc(), sql`name`) .concurrently(), addExpression: index('addExpression').on(t.id.desc()), - changeExpression: index('changeExpression').on( - t.id.desc(), - sql`name`, - ), - changeName: index('changeName') - .on(t.name.desc(), t.id.asc().nullsLast()) - .with({ fillfactor: 70 }), + changeExpression: index('changeExpression').on(t.id.desc(), sql`name`), + changeName: index('changeName').on(t.name.desc(), t.id.asc().nullsLast()).with({ fillfactor: 70 }), changeWith: index('changeWith').on(t.name).with({ fillfactor: 70 }), changeUsing: index('changeUsing').on(t.name), }), @@ -690,17 +600,10 @@ const pgSuite: DialectSuite = { }, (t) => ({ removeColumn: index('removeColumn').on(t.name), - addColumn: index('addColumn') - .on(t.name.desc(), t.id.nullsLast()) - .with({ fillfactor: 70 }), - removeExpression: index('removeExpression') - .on(t.name.desc()) - .concurrently(), + addColumn: index('addColumn').on(t.name.desc(), t.id.nullsLast()).with({ fillfactor: 70 }), + removeExpression: index('removeExpression').on(t.name.desc()).concurrently(), addExpression: index('addExpression').on(t.id.desc()), - changeExpression: index('changeExpression').on( - t.id.desc(), - sql`name desc`, - ), + changeExpression: index('changeExpression').on(t.id.desc(), sql`name desc`), changeName: index('newName') .on(t.name.desc(), sql`name`) .with({ fillfactor: 70 }), @@ -710,30 +613,23 @@ const pgSuite: DialectSuite = { ), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(sqlStatements).toStrictEqual([ - 'DROP INDEX IF EXISTS "changeName";', - 'DROP INDEX IF EXISTS "addColumn";', - 'DROP INDEX IF EXISTS "changeExpression";', - 'DROP INDEX IF EXISTS "changeUsing";', - 'DROP INDEX IF EXISTS "changeWith";', - 'DROP INDEX IF EXISTS "removeColumn";', - 'DROP INDEX IF EXISTS "removeExpression";', - 'CREATE INDEX IF NOT EXISTS "newName" ON "users" USING btree ("name" DESC NULLS LAST,name) WITH (fillfactor=70);', - 'CREATE INDEX IF NOT EXISTS "addColumn" ON "users" USING btree ("name" DESC NULLS LAST,"id") WITH (fillfactor=70);', - 'CREATE INDEX IF NOT EXISTS "changeExpression" ON "users" USING btree ("id" DESC NULLS LAST,name desc);', - 'CREATE INDEX IF NOT EXISTS "changeUsing" ON "users" USING hash ("name");', - 'CREATE INDEX IF NOT EXISTS "changeWith" ON "users" USING btree ("name") WITH (fillfactor=90);', - 'CREATE INDEX IF NOT EXISTS "removeColumn" ON "users" USING btree ("name");', - 'CREATE INDEX CONCURRENTLY IF NOT EXISTS "removeExpression" ON "users" USING btree ("name" DESC NULLS LAST);', + 'DROP INDEX "changeName";', + 'DROP INDEX "addColumn";', + 'DROP INDEX "changeExpression";', + 'DROP INDEX "changeUsing";', + 'DROP INDEX "changeWith";', + 'DROP INDEX "removeColumn";', + 'DROP INDEX "removeExpression";', + 'CREATE INDEX "newName" ON "users" USING btree ("name" DESC NULLS LAST,name) WITH (fillfactor=70);', + 'CREATE INDEX "addColumn" ON "users" USING btree ("name" DESC NULLS LAST,"id") WITH (fillfactor=70);', + 'CREATE INDEX "changeExpression" ON "users" USING btree ("id" DESC NULLS LAST,name desc);', + 'CREATE INDEX "changeUsing" ON "users" USING hash ("name");', + 'CREATE INDEX "changeWith" ON "users" USING btree ("name") WITH (fillfactor=90);', + 'CREATE INDEX "removeColumn" ON "users" USING btree ("name");', + 'CREATE INDEX CONCURRENTLY "removeExpression" ON "users" USING btree ("name" DESC NULLS LAST);', ]); }, @@ -748,9 +644,7 @@ const pgSuite: DialectSuite = { name: text('name'), }, (t) => ({ - indx: index() - .on(t.name.desc(), t.id.asc().nullsLast()) - .with({ fillfactor: 70 }), + indx: index().on(t.name.desc(), t.id.asc().nullsLast()).with({ fillfactor: 70 }), }), ), }; @@ -762,14 +656,7 @@ const pgSuite: DialectSuite = { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ @@ -780,9 +667,7 @@ const pgSuite: DialectSuite = { }); expect(sqlStatements.length).toBe(1); - expect(sqlStatements[0]).toBe( - `DROP INDEX IF EXISTS "users_name_id_index";`, - ); + expect(sqlStatements[0]).toBe(`DROP INDEX "users_name_id_index";`); }, async indexesToBeNotTriggered() { @@ -832,14 +717,7 @@ const pgSuite: DialectSuite = { ), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements.length).toBe(0); }, @@ -883,14 +761,7 @@ const pgSuite: DialectSuite = { ), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements.length).toBe(0); }, @@ -956,14 +827,7 @@ const pgSuite: DialectSuite = { ), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); const query = async (sql: string, params?: any[]) => { const result = await client.query(sql, params ?? []); return result.rows as any[]; @@ -971,9 +835,7 @@ const pgSuite: DialectSuite = { const { statementsToExecute } = await pgSuggestions({ query }, statements); - expect(statementsToExecute).toStrictEqual([ - 'ALTER TABLE "User" ALTER COLUMN "email" SET NOT NULL;', - ]); + expect(statementsToExecute).toStrictEqual(['ALTER TABLE "User" ALTER COLUMN "email" SET NOT NULL;']); }, async addNotNullWithDataNoRollback() { @@ -1038,6 +900,37 @@ const pgSuite: DialectSuite = { ), }; + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + const query = async (sql: string, params?: any[]) => { + const result = await client.query(sql, params ?? []); + return result.rows as any[]; + }; + + await db.insert(schema1.users).values({ id: 'str', email: 'email@gmail' }); + + const { statementsToExecute, shouldAskForApprove } = await pgSuggestions({ query }, statements); + + expect(statementsToExecute).toStrictEqual(['ALTER TABLE "User" ALTER COLUMN "email" SET NOT NULL;']); + + expect(shouldAskForApprove).toBeFalsy(); + }, + + async createCompositePrimaryKey() { + const client = new PGlite(); + + const schema1 = {}; + + const schema2 = { + table: pgTable('table', { + col1: integer('col1').notNull(), + col2: integer('col2').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.col1, t.col2], + }), + })), + }; + const { statements, sqlStatements } = await diffTestSchemasPush( client, schema1, @@ -1046,23 +939,63 @@ const pgSuite: DialectSuite = { false, ['public'], ); - const query = async (sql: string, params?: any[]) => { - const result = await client.query(sql, params ?? []); - return result.rows as any[]; + + expect(statements).toStrictEqual([ + { + type: 'create_table', + tableName: 'table', + schema: '', + compositePKs: ['col1,col2;table_col1_col2_pk'], + compositePkName: 'table_col1_col2_pk', + isRLSEnabled: false, + policies: [], + uniqueConstraints: [], + checkConstraints: [], + columns: [ + { name: 'col1', type: 'integer', primaryKey: false, notNull: true }, + { name: 'col2', type: 'integer', primaryKey: false, notNull: true }, + ], + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE "table" (\n\t"col1" integer NOT NULL,\n\t"col2" integer NOT NULL,\n\tCONSTRAINT "table_col1_col2_pk" PRIMARY KEY("col1","col2")\n);\n', + ]); + }, + + async renameTableWithCompositePrimaryKey() { + const client = new PGlite(); + + const productsCategoriesTable = (tableName: string) => { + return pgTable(tableName, { + productId: text('product_id').notNull(), + categoryId: text('category_id').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.productId, t.categoryId], + }), + })); }; - await db.insert(schema1.users).values({ id: 'str', email: 'email@gmail' }); + const schema1 = { + table: productsCategoriesTable('products_categories'), + }; + const schema2 = { + test: productsCategoriesTable('products_to_categories'), + }; - const { statementsToExecute, shouldAskForApprove } = await pgSuggestions( - { query }, - statements, + const { sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + ['public.products_categories->public.products_to_categories'], + false, + ['public'], ); - - expect(statementsToExecute).toStrictEqual([ - 'ALTER TABLE "User" ALTER COLUMN "email" SET NOT NULL;', + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "products_categories" RENAME TO "products_to_categories";', + 'ALTER TABLE "products_to_categories" DROP CONSTRAINT "products_categories_product_id_category_id_pk";', + 'ALTER TABLE "products_to_categories" ADD CONSTRAINT "products_to_categories_product_id_category_id_pk" PRIMARY KEY("product_id","category_id");', ]); - - expect(shouldAskForApprove).toBeFalsy(); }, // async addVectorIndexes() { @@ -1143,14 +1076,7 @@ test('full sequence: no changes', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements.length).toBe(0); expect(sqlStatements.length).toBe(0); @@ -1185,14 +1111,7 @@ test('basic sequence: change fields', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1260,9 +1179,7 @@ test('basic sequence: change name', async () => { type: 'rename_sequence', }, ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER SEQUENCE "public"."my_seq" RENAME TO "my_seq2";', - ]); + expect(sqlStatements).toStrictEqual(['ALTER SEQUENCE "public"."my_seq" RENAME TO "my_seq2";']); for (const st of sqlStatements) { await client.query(st); @@ -1348,14 +1265,7 @@ test('create table: identity always/by default - no params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1386,12 +1296,15 @@ test('create table: identity always/by default - no params', async () => { compositePkName: '', schema: '', tableName: 'users', + policies: [], type: 'create_table', uniqueConstraints: [], + isRLSEnabled: false, + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ - 'CREATE TABLE IF NOT EXISTS "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),\n\t"id1" bigint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id1_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),\n\t"id2" smallint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id2_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 32767 START WITH 1 CACHE 1)\n);\n', + 'CREATE TABLE "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),\n\t"id1" bigint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id1_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),\n\t"id2" smallint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id2_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 32767 START WITH 1 CACHE 1)\n);\n', ]); for (const st of sqlStatements) { @@ -1415,14 +1328,7 @@ test('create table: identity always/by default - few params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1451,14 +1357,17 @@ test('create table: identity always/by default - few params', async () => { ], compositePKs: [], compositePkName: '', + policies: [], schema: '', tableName: 'users', type: 'create_table', + isRLSEnabled: false, uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ - 'CREATE TABLE IF NOT EXISTS "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 4 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),\n\t"id1" bigint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id1_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 17000 START WITH 120 CACHE 1),\n\t"id2" smallint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id2_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 32767 START WITH 1 CACHE 1 CYCLE)\n);\n', + 'CREATE TABLE "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 4 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),\n\t"id1" bigint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id1_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 17000 START WITH 120 CACHE 1),\n\t"id2" smallint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id2_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 32767 START WITH 1 CACHE 1 CYCLE)\n);\n', ]); for (const st of sqlStatements) { @@ -1488,14 +1397,7 @@ test('create table: identity always/by default - all params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1527,11 +1429,14 @@ test('create table: identity always/by default - all params', async () => { schema: '', tableName: 'users', type: 'create_table', + policies: [], + isRLSEnabled: false, uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ - 'CREATE TABLE IF NOT EXISTS "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 4 MINVALUE 100 MAXVALUE 2147483647 START WITH 100 CACHE 1),\n\t"id1" bigint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id1_seq" INCREMENT BY 3 MINVALUE 1 MAXVALUE 17000 START WITH 120 CACHE 100 CYCLE),\n\t"id2" smallint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id2_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 32767 START WITH 1 CACHE 1 CYCLE)\n);\n', + 'CREATE TABLE "users" (\n\t"id" integer GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 4 MINVALUE 100 MAXVALUE 2147483647 START WITH 100 CACHE 1),\n\t"id1" bigint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id1_seq" INCREMENT BY 3 MINVALUE 1 MAXVALUE 17000 START WITH 120 CACHE 100 CYCLE),\n\t"id2" smallint GENERATED BY DEFAULT AS IDENTITY (sequence name "users_id2_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 32767 START WITH 1 CACHE 1 CYCLE)\n);\n', ]); for (const st of sqlStatements) { @@ -1556,14 +1461,7 @@ test('no diff: identity always/by default - no params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([]); expect(sqlStatements).toStrictEqual([]); @@ -1596,14 +1494,7 @@ test('no diff: identity always/by default - few params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([]); expect(sqlStatements).toStrictEqual([]); @@ -1656,14 +1547,7 @@ test('no diff: identity always/by default - all params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([]); expect(sqlStatements).toStrictEqual([]); @@ -1684,14 +1568,7 @@ test('drop identity from a column - no params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1701,9 +1578,7 @@ test('drop identity from a column - no params', async () => { type: 'alter_table_alter_column_drop_identity', }, ]); - expect(sqlStatements).toStrictEqual([ - `ALTER TABLE \"users\" ALTER COLUMN \"id\" DROP IDENTITY;`, - ]); + expect(sqlStatements).toStrictEqual([`ALTER TABLE \"users\" ALTER COLUMN \"id\" DROP IDENTITY;`]); for (const st of sqlStatements) { await client.query(st); @@ -1735,14 +1610,7 @@ test('drop identity from a column - few params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1810,14 +1678,7 @@ test('drop identity from a column - all params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1865,14 +1726,7 @@ test('alter identity from a column - no params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1884,9 +1738,7 @@ test('alter identity from a column - no params', async () => { type: 'alter_table_alter_column_change_identity', }, ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER TABLE "users" ALTER COLUMN "id" SET START WITH 100;', - ]); + expect(sqlStatements).toStrictEqual(['ALTER TABLE "users" ALTER COLUMN "id" SET START WITH 100;']); for (const st of sqlStatements) { await client.query(st); @@ -1912,14 +1764,7 @@ test('alter identity from a column - few params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -1960,14 +1805,7 @@ test('alter identity from a column - by default to always', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -2011,14 +1849,7 @@ test('alter identity from a column - always to by default', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -2063,14 +1894,7 @@ test('add column with identity - few params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -2128,14 +1952,7 @@ test('add identity to column - few params', async () => { }), }; - const { statements, sqlStatements } = await diffTestSchemasPush( - client, - schema1, - schema2, - [], - false, - ['public'], - ); + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); expect(statements).toStrictEqual([ { @@ -2162,3 +1979,2363 @@ test('add identity to column - few params', async () => { // await client.query(st); // } }); + +test('add array column - empty array default', async () => { + const client = new PGlite(); + + const schema1 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const schema2 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').array().default([]), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([ + { + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { name: 'values', type: 'integer[]', primaryKey: false, notNull: false, default: "'{}'" }, + }, + ]); + expect(sqlStatements).toStrictEqual(['ALTER TABLE "test" ADD COLUMN "values" integer[] DEFAULT \'{}\';']); +}); + +test('add array column - default', async () => { + const client = new PGlite(); + + const schema1 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + }), + }; + const schema2 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').array().default([1, 2, 3]), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([ + { + type: 'alter_table_add_column', + tableName: 'test', + schema: '', + column: { name: 'values', type: 'integer[]', primaryKey: false, notNull: false, default: "'{1,2,3}'" }, + }, + ]); + expect(sqlStatements).toStrictEqual(['ALTER TABLE "test" ADD COLUMN "values" integer[] DEFAULT \'{1,2,3}\';']); +}); + +test('create view', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + }; + + const schema2 = { + test: table, + view: pgView('view').as((qb) => qb.selectDistinct().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([ + { + definition: 'select distinct "id" from "test"', + name: 'view', + schema: 'public', + type: 'create_view', + with: undefined, + materialized: false, + tablespace: undefined, + using: undefined, + withNoData: false, + }, + ]); + expect(sqlStatements).toStrictEqual(['CREATE VIEW "public"."view" AS (select distinct "id" from "test");']); +}); + +test('add check constraint to table', async () => { + const client = new PGlite(); + + const schema1 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').array().default([1, 2, 3]), + }), + }; + const schema2 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').array().default([1, 2, 3]), + }, (table) => ({ + checkConstraint1: check('some_check1', sql`${table.values} < 100`), + checkConstraint2: check('some_check2', sql`'test' < 100`), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([ + { + type: 'create_check_constraint', + tableName: 'test', + schema: '', + data: 'some_check1;"test"."values" < 100', + }, + { + data: "some_check2;'test' < 100", + schema: '', + tableName: 'test', + type: 'create_check_constraint', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "test" ADD CONSTRAINT "some_check1" CHECK ("test"."values" < 100);', + `ALTER TABLE "test" ADD CONSTRAINT "some_check2" CHECK ('test' < 100);`, + ]); +}); + +test('create materialized view', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + }; + + const schema2 = { + test: table, + view: pgMaterializedView('view') + .withNoData() + .using('heap') + .as((qb) => qb.selectDistinct().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([ + { + definition: 'select distinct "id" from "test"', + name: 'view', + schema: 'public', + type: 'create_view', + with: undefined, + materialized: true, + tablespace: undefined, + using: 'heap', + withNoData: true, + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'CREATE MATERIALIZED VIEW "public"."view" USING "heap" AS (select distinct "id" from "test") WITH NO DATA;', + ]); +}); + +test('drop check constraint', async () => { + const client = new PGlite(); + + const schema1 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').default(1), + }, (table) => ({ + checkConstraint: check('some_check', sql`${table.values} < 100`), + })), + }; + const schema2 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').default(1), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements).toStrictEqual([ + { + type: 'delete_check_constraint', + tableName: 'test', + schema: '', + constraintName: 'some_check', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "test" DROP CONSTRAINT "some_check";', + ]); +}); + +test('Column with same name as enum', async () => { + const client = new PGlite(); + const statusEnum = pgEnum('status', ['inactive', 'active', 'banned']); + + const schema1 = { + statusEnum, + table1: pgTable('table1', { + id: serial('id').primaryKey(), + }), + }; + + const schema2 = { + statusEnum, + table1: pgTable('table1', { + id: serial('id').primaryKey(), + status: statusEnum('status').default('inactive'), + }), + table2: pgTable('table2', { + id: serial('id').primaryKey(), + status: statusEnum('status').default('inactive'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements).toStrictEqual([ + { + type: 'create_table', + tableName: 'table2', + schema: '', + compositePKs: [], + compositePkName: '', + isRLSEnabled: false, + policies: [], + uniqueConstraints: [], + checkConstraints: [], + columns: [ + { name: 'id', type: 'serial', primaryKey: true, notNull: true }, + { + name: 'status', + type: 'status', + typeSchema: 'public', + primaryKey: false, + notNull: false, + default: "'inactive'", + }, + ], + }, + { + type: 'alter_table_add_column', + tableName: 'table1', + schema: '', + column: { + name: 'status', + type: 'status', + typeSchema: 'public', + primaryKey: false, + notNull: false, + default: "'inactive'", + }, + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE "table2" (\n\t"id" serial PRIMARY KEY NOT NULL,\n\t"status" "status" DEFAULT \'inactive\'\n);\n', + 'ALTER TABLE "table1" ADD COLUMN "status" "status" DEFAULT \'inactive\';', + ]); +}); + +test('db has checks. Push with same names', async () => { + const client = new PGlite(); + + const schema1 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').default(1), + }, (table) => ({ + checkConstraint: check('some_check', sql`${table.values} < 100`), + })), + }; + const schema2 = { + test: pgTable('test', { + id: serial('id').primaryKey(), + values: integer('values').default(1), + }, (table) => ({ + checkConstraint: check('some_check', sql`some new value`), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements).toStrictEqual([]); + expect(sqlStatements).toStrictEqual([]); +}); + +test('drop view', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgView('view').as((qb) => qb.selectDistinct().from(table)), + }; + + const schema2 = { + test: table, + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([ + { + name: 'view', + schema: 'public', + type: 'drop_view', + }, + ]); + expect(sqlStatements).toStrictEqual(['DROP VIEW "public"."view";']); +}); + +test('drop materialized view', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgMaterializedView('view').as((qb) => qb.selectDistinct().from(table)), + }; + + const schema2 = { + test: table, + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([ + { + name: 'view', + schema: 'public', + type: 'drop_view', + materialized: true, + }, + ]); + expect(sqlStatements).toStrictEqual(['DROP MATERIALIZED VIEW "public"."view";']); +}); + +test('push view with same name', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgView('view').as((qb) => qb.selectDistinct().from(table)), + }; + + const schema2 = { + test: table, + view: pgView('view').as((qb) => qb.selectDistinct().from(table).where(eq(table.id, 1))), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([]); + expect(sqlStatements).toStrictEqual([]); +}); + +test('push materialized view with same name', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgMaterializedView('view').as((qb) => qb.selectDistinct().from(table)), + }; + + const schema2 = { + test: table, + view: pgMaterializedView('view').as((qb) => qb.selectDistinct().from(table).where(eq(table.id, 1))), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements).toStrictEqual([]); + expect(sqlStatements).toStrictEqual([]); +}); + +test('add with options for materialized view', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgMaterializedView('view').as((qb) => qb.selectDistinct().from(table)), + }; + + const schema2 = { + test: table, + view: pgMaterializedView('view') + .with({ autovacuumFreezeTableAge: 1, autovacuumEnabled: false }) + .as((qb) => qb.selectDistinct().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'view', + schema: 'public', + type: 'alter_view_add_with_option', + with: { + autovacuumFreezeTableAge: 1, + autovacuumEnabled: false, + }, + materialized: true, + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."view" SET (autovacuum_enabled = false, autovacuum_freeze_table_age = 1);`, + ); +}); + +test('add with options to materialized', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgMaterializedView('view').as((qb) => qb.selectDistinct().from(table)), + }; + + const schema2 = { + test: table, + view: pgMaterializedView('view') + .with({ autovacuumVacuumCostDelay: 100, vacuumTruncate: false }) + .as((qb) => qb.selectDistinct().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'view', + schema: 'public', + type: 'alter_view_add_with_option', + with: { + autovacuumVacuumCostDelay: 100, + vacuumTruncate: false, + }, + materialized: true, + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER MATERIALIZED VIEW "public"."view" SET (vacuum_truncate = false, autovacuum_vacuum_cost_delay = 100);`, + ); +}); + +test('add with options to materialized with existing flag', async () => { + const client = new PGlite(); + + const table = pgTable('test', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgMaterializedView('view', {}).as(sql`SELECT id FROM "test"`), + }; + + const schema2 = { + test: table, + view: pgMaterializedView('view', {}).with({ autovacuumVacuumCostDelay: 100, vacuumTruncate: false }).existing(), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush(client, schema1, schema2, [], false, ['public']); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop mat view with data', async () => { + const client = new PGlite(); + + const table = pgTable('table', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgMaterializedView('view', {}).as(sql`SELECT * FROM ${table}`), + }; + + const schema2 = { + test: table, + }; + + const seedStatements = [`INSERT INTO "public"."table" ("id") VALUES (1), (2), (3)`]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + schemasToRemove, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + matViewsToRemove, + } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + undefined, + { after: seedStatements }, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + materialized: true, + name: 'view', + schema: 'public', + type: 'drop_view', + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP MATERIALIZED VIEW "public"."view";`); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe(`· You're about to delete "${chalk.underline('view')}" materialized view with 3 items`); + expect(columnsToRemove!.length).toBe(0); + expect(schemasToRemove!.length).toBe(0); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(matViewsToRemove!.length).toBe(1); +}); + +test('drop mat view without data', async () => { + const client = new PGlite(); + + const table = pgTable('table', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgMaterializedView('view', {}).as(sql`SELECT * FROM ${table}`), + }; + + const schema2 = { + test: table, + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + schemasToRemove, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + matViewsToRemove, + } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + materialized: true, + name: 'view', + schema: 'public', + type: 'drop_view', + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP MATERIALIZED VIEW "public"."view";`); + expect(infoToPrint!.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(schemasToRemove!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(matViewsToRemove!.length).toBe(0); +}); + +test('drop view with data', async () => { + const client = new PGlite(); + + const table = pgTable('table', { + id: serial('id').primaryKey(), + }); + const schema1 = { + test: table, + view: pgView('view', {}).as(sql`SELECT * FROM ${table}`), + }; + + const schema2 = { + test: table, + }; + + const seedStatements = [`INSERT INTO "public"."table" ("id") VALUES (1), (2), (3)`]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + schemasToRemove, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + matViewsToRemove, + } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + undefined, + { after: seedStatements }, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'view', + schema: 'public', + type: 'drop_view', + }); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`DROP VIEW "public"."view";`); + expect(infoToPrint!.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(schemasToRemove!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(matViewsToRemove!.length).toBe(0); +}); + +test('enums ordering', async () => { + const enum1 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'custMgf', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema1 = {}; + + const schema2 = { + enum1, + }; + + const { sqlStatements: createEnum } = await diffTestSchemas(schema1, schema2, []); + + const enum2 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'custMgf', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema3 = { + enum2, + }; + + const { sqlStatements: addedValueSql } = await diffTestSchemas(schema2, schema3, []); + + const enum3 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'addedToMiddle', + 'custMgf', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema4 = { + enum3, + }; + + const client = new PGlite(); + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema3, + schema4, + [], + false, + ['public'], + undefined, + undefined, + { before: [...createEnum, ...addedValueSql], runApply: false }, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + before: 'custMgf', + name: 'enum_users_customer_and_ship_to_settings_roles', + schema: 'public', + type: 'alter_type_add_value', + value: 'addedToMiddle', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TYPE "public"."enum_users_customer_and_ship_to_settings_roles" ADD VALUE 'addedToMiddle' BEFORE 'custMgf';`, + ); +}); + +test('drop enum values', async () => { + const newSchema = pgSchema('mySchema'); + const enum3 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'addedToMiddle', + 'custMgf', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema1 = { + enum3, + table: pgTable('enum_table', { + id: enum3(), + }), + newSchema, + table1: newSchema.table('enum_table', { + id: enum3(), + }), + }; + + const enum4 = pgEnum('enum_users_customer_and_ship_to_settings_roles', [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ]); + const schema2 = { + enum4, + table: pgTable('enum_table', { + id: enum4(), + }), + newSchema, + table1: newSchema.table('enum_table', { + id: enum4(), + }), + }; + + const client = new PGlite(); + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public', 'mySchema'], + undefined, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'enum_users_customer_and_ship_to_settings_roles', + schema: 'public', + type: 'alter_type_drop_value', + newValues: [ + 'addedToTop', + 'custAll', + 'custAdmin', + 'custClerk', + 'custInvoiceManager', + 'custApprover', + 'custOrderWriter', + 'custBuyer', + ], + deletedValues: ['addedToMiddle', 'custMgf'], + columnsWithEnum: [{ + column: 'id', + schema: 'public', + table: 'enum_table', + }, { + column: 'id', + schema: 'mySchema', + table: 'enum_table', + }], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe( + `ALTER TABLE "public"."enum_table" ALTER COLUMN "id" SET DATA TYPE text;`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE "mySchema"."enum_table" ALTER COLUMN "id" SET DATA TYPE text;`, + ); + expect(sqlStatements[2]).toBe( + `DROP TYPE "public"."enum_users_customer_and_ship_to_settings_roles";`, + ); + expect(sqlStatements[3]).toBe( + `CREATE TYPE "public"."enum_users_customer_and_ship_to_settings_roles" AS ENUM('addedToTop', 'custAll', 'custAdmin', 'custClerk', 'custInvoiceManager', 'custApprover', 'custOrderWriter', 'custBuyer');`, + ); + expect(sqlStatements[4]).toBe( + `ALTER TABLE "public"."enum_table" ALTER COLUMN "id" SET DATA TYPE "public"."enum_users_customer_and_ship_to_settings_roles" USING "id"::"public"."enum_users_customer_and_ship_to_settings_roles";`, + ); + expect(sqlStatements[5]).toBe( + `ALTER TABLE "mySchema"."enum_table" ALTER COLUMN "id" SET DATA TYPE "public"."enum_users_customer_and_ship_to_settings_roles" USING "id"::"public"."enum_users_customer_and_ship_to_settings_roles";`, + ); +}); + +// Policies and Roles push test +test('full policy: no changes', async () => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('add policy', async () => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements).toStrictEqual([ + { type: 'enable_rls', tableName: 'users', schema: '' }, + { + type: 'create_policy', + tableName: 'users', + data: { + name: 'test', + as: 'PERMISSIVE', + for: 'ALL', + to: ['public'], + on: undefined, + }, + schema: '', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR ALL TO public;', + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('drop policy', async () => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements).toStrictEqual([ + { type: 'disable_rls', tableName: 'users', schema: '' }, + { + schema: '', + tableName: 'users', + type: 'disable_rls', + }, + { + type: 'drop_policy', + tableName: 'users', + data: { + name: 'test', + as: 'PERMISSIVE', + for: 'ALL', + to: ['public'], + on: undefined, + }, + schema: '', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" DISABLE ROW LEVEL SECURITY;', + 'DROP POLICY "test" ON "users" CASCADE;', + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('add policy without enable rls', async () => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + newrls: pgPolicy('newRls'), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements).toStrictEqual([ + { + type: 'create_policy', + tableName: 'users', + data: { + name: 'newRls', + as: 'PERMISSIVE', + for: 'ALL', + to: ['public'], + on: undefined, + }, + schema: '', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'CREATE POLICY "newRls" ON "users" AS PERMISSIVE FOR ALL TO public;', + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('drop policy without disable rls', async () => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + oldRls: pgPolicy('oldRls'), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(statements).toStrictEqual([ + { + type: 'drop_policy', + tableName: 'users', + data: { + name: 'oldRls', + as: 'PERMISSIVE', + for: 'ALL', + to: ['public'], + on: undefined, + }, + schema: '', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "oldRls" ON "users" CASCADE;', + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +//// + +test('alter policy without recreation: changing roles', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', to: 'current_role' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" TO current_role;', + ]); + expect(statements).toStrictEqual([ + { + newData: 'test--PERMISSIVE--ALL--current_role--undefined', + oldData: 'test--PERMISSIVE--ALL--public--undefined', + schema: '', + tableName: 'users', + type: 'alter_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter policy without recreation: changing using', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', using: sql`true` }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([]); + expect(statements).toStrictEqual([]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter policy without recreation: changing with check', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', withCheck: sql`true` }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([]); + expect(statements).toStrictEqual([]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter policy with recreation: changing as', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'restrictive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS RESTRICTIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'RESTRICTIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter policy with recreation: changing for', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', for: 'delete' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR DELETE TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'PERMISSIVE', + for: 'DELETE', + name: 'test', + to: ['public'], + on: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter policy with recreation: changing both "as" and "for"', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'restrictive', for: 'insert' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS RESTRICTIVE FOR INSERT TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'RESTRICTIVE', + for: 'INSERT', + name: 'test', + to: ['public'], + on: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter policy with recreation: changing all fields', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', for: 'select', using: sql`true` }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'restrictive', to: 'current_role', withCheck: sql`true` }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS RESTRICTIVE FOR ALL TO current_role;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'SELECT', + name: 'test', + to: ['public'], + on: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'RESTRICTIVE', + for: 'ALL', + name: 'test', + to: ['current_role'], + on: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('rename policy', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('newName', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + ['public.users.test->public.users.newName'], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" RENAME TO "newName";', + ]); + expect(statements).toStrictEqual([ + { + newName: 'newName', + oldName: 'test', + schema: '', + tableName: 'users', + type: 'rename_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('rename policy in renamed table', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users2', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('newName', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [ + 'public.users->public.users2', + 'public.users2.test->public.users2.newName', + ], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" RENAME TO "users2";', + 'ALTER POLICY "test" ON "users2" RENAME TO "newName";', + ]); + expect(statements).toStrictEqual([ + { + fromSchema: '', + tableNameFrom: 'users', + tableNameTo: 'users2', + toSchema: '', + type: 'rename_table', + }, + { + newName: 'newName', + oldName: 'test', + schema: '', + tableName: 'users2', + type: 'rename_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('create table with a policy', async (t) => { + const client = new PGlite(); + + const schema1 = {}; + + const schema2 = { + users: pgTable('users2', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE "users2" (\n\t"id" integer PRIMARY KEY NOT NULL\n);\n', + 'ALTER TABLE "users2" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test" ON "users2" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + columns: [ + { + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + ], + checkConstraints: [], + compositePKs: [], + isRLSEnabled: false, + compositePkName: '', + policies: [ + 'test--PERMISSIVE--ALL--public--undefined', + ], + schema: '', + tableName: 'users2', + type: 'create_table', + uniqueConstraints: [], + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: [ + 'public', + ], + on: undefined, + }, + schema: '', + tableName: 'users2', + type: 'create_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('drop table with a policy', async (t) => { + const client = new PGlite(); + + const schema1 = { + users: pgTable('users2', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = {}; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users2" CASCADE;', + 'DROP TABLE "users2" CASCADE;', + ]); + expect(statements).toStrictEqual([ + { + policies: [ + 'test--PERMISSIVE--ALL--public--undefined', + ], + schema: '', + tableName: 'users2', + type: 'drop_table', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('add policy with multiple "to" roles', async (t) => { + const client = new PGlite(); + + client.query(`CREATE ROLE manager;`); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const role = pgRole('manager').existing(); + + const schema2 = { + role, + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { to: ['current_role', role] }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + ); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR ALL TO current_role, "manager";', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'enable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: undefined, + to: ['current_role', 'manager'], + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('rename policy that is linked', async (t) => { + const client = new PGlite(); + + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const { sqlStatements: createUsers } = await diffTestSchemas({}, { users }, []); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const schema2 = { + users, + rls: pgPolicy('newName', { as: 'permissive' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + ['public.users.test->public.users.newName'], + false, + ['public'], + undefined, + undefined, + { before: createUsers }, + ); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" RENAME TO "newName";', + ]); + expect(statements).toStrictEqual([ + { + newName: 'newName', + oldName: 'test', + schema: '', + tableName: 'users', + type: 'rename_policy', + }, + ]); +}); + +test('alter policy that is linked', async (t) => { + const client = new PGlite(); + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const { sqlStatements: createUsers } = await diffTestSchemas({}, { users }, []); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const schema2 = { + users, + rls: pgPolicy('test', { as: 'permissive', to: 'current_role' }).link(users), + }; + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + undefined, + { before: createUsers }, + ); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" TO current_role;', + ]); + expect(statements).toStrictEqual([{ + newData: 'test--PERMISSIVE--ALL--current_role--undefined', + oldData: 'test--PERMISSIVE--ALL--public--undefined', + schema: '', + tableName: 'users', + type: 'alter_policy', + }]); +}); + +test('alter policy that is linked: withCheck', async (t) => { + const client = new PGlite(); + + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const { sqlStatements: createUsers } = await diffTestSchemas({}, { users }, []); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive', withCheck: sql`true` }).link(users), + }; + + const schema2 = { + users, + rls: pgPolicy('test', { as: 'permissive', withCheck: sql`false` }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + undefined, + { before: createUsers }, + ); + + expect(sqlStatements).toStrictEqual([]); + expect(statements).toStrictEqual([]); +}); + +test('alter policy that is linked: using', async (t) => { + const client = new PGlite(); + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const { sqlStatements: createUsers } = await diffTestSchemas({}, { users }, []); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive', using: sql`true` }).link(users), + }; + + const schema2 = { + users, + rls: pgPolicy('test', { as: 'permissive', using: sql`false` }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + undefined, + { before: createUsers }, + ); + + expect(sqlStatements).toStrictEqual([]); + expect(statements).toStrictEqual([]); +}); + +test('alter policy that is linked: using', async (t) => { + const client = new PGlite(); + + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const { sqlStatements: createUsers } = await diffTestSchemas({}, { users }, []); + + const schema1 = { + rls: pgPolicy('test', { for: 'insert' }).link(users), + }; + + const schema2 = { + users, + rls: pgPolicy('test', { for: 'delete' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + undefined, + { before: createUsers }, + ); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR DELETE TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'INSERT', + name: 'test', + on: undefined, + to: [ + 'public', + ], + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'PERMISSIVE', + for: 'DELETE', + name: 'test', + on: undefined, + to: [ + 'public', + ], + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +//// + +test('create role', async (t) => { + const client = new PGlite(); + + const schema1 = {}; + + const schema2 = { + manager: pgRole('manager'), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager'] } }, + ); + + expect(sqlStatements).toStrictEqual(['CREATE ROLE "manager";']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'create_role', + values: { + createDb: false, + createRole: false, + inherit: true, + }, + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('create role with properties', async (t) => { + const client = new PGlite(); + + const schema1 = {}; + + const schema2 = { + manager: pgRole('manager', { createDb: true, inherit: false, createRole: true }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager'] } }, + ); + + expect(sqlStatements).toStrictEqual(['CREATE ROLE "manager" WITH CREATEDB CREATEROLE NOINHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'create_role', + values: { + createDb: true, + createRole: true, + inherit: false, + }, + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('create role with some properties', async (t) => { + const client = new PGlite(); + + const schema1 = {}; + + const schema2 = { + manager: pgRole('manager', { createDb: true, inherit: false }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager'] } }, + ); + + expect(sqlStatements).toStrictEqual(['CREATE ROLE "manager" WITH CREATEDB NOINHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'create_role', + values: { + createDb: true, + createRole: false, + inherit: false, + }, + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('drop role', async (t) => { + const client = new PGlite(); + + const schema1 = { manager: pgRole('manager') }; + + const schema2 = {}; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager'] } }, + ); + + expect(sqlStatements).toStrictEqual(['DROP ROLE "manager";']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'drop_role', + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('create and drop role', async (t) => { + const client = new PGlite(); + + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + admin: pgRole('admin'), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager', 'admin'] } }, + ); + + expect(sqlStatements).toStrictEqual(['DROP ROLE "manager";', 'CREATE ROLE "admin";']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'drop_role', + }, + { + name: 'admin', + type: 'create_role', + values: { + createDb: false, + createRole: false, + inherit: true, + }, + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('rename role', async (t) => { + const client = new PGlite(); + + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + admin: pgRole('admin'), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + ['manager->admin'], + false, + ['public'], + undefined, + { roles: { include: ['manager', 'admin'] } }, + ); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" RENAME TO "admin";']); + expect(statements).toStrictEqual([ + { nameFrom: 'manager', nameTo: 'admin', type: 'rename_role' }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter all role field', async (t) => { + const client = new PGlite(); + + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + manager: pgRole('manager', { createDb: true, createRole: true, inherit: false }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager'] } }, + ); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" WITH CREATEDB CREATEROLE NOINHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'alter_role', + values: { + createDb: true, + createRole: true, + inherit: false, + }, + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter createdb in role', async (t) => { + const client = new PGlite(); + + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + manager: pgRole('manager', { createDb: true }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager'] } }, + ); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" WITH CREATEDB NOCREATEROLE INHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'alter_role', + values: { + createDb: true, + createRole: false, + inherit: true, + }, + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter createrole in role', async (t) => { + const client = new PGlite(); + + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + manager: pgRole('manager', { createRole: true }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager'] } }, + ); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" WITH NOCREATEDB CREATEROLE INHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'alter_role', + values: { + createDb: false, + createRole: true, + inherit: true, + }, + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); + +test('alter inherit in role', async (t) => { + const client = new PGlite(); + + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + manager: pgRole('manager', { inherit: false }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPush( + client, + schema1, + schema2, + [], + false, + ['public'], + undefined, + { roles: { include: ['manager'] } }, + ); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" WITH NOCREATEDB NOCREATEROLE NOINHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'alter_role', + values: { + createDb: false, + createRole: false, + inherit: false, + }, + }, + ]); + + for (const st of sqlStatements) { + await client.query(st); + } +}); diff --git a/drizzle-kit/tests/push/singlestore-push.test.ts b/drizzle-kit/tests/push/singlestore-push.test.ts new file mode 100644 index 000000000..4ad3c6c0e --- /dev/null +++ b/drizzle-kit/tests/push/singlestore-push.test.ts @@ -0,0 +1,266 @@ +import Docker from 'dockerode'; +import { int, singlestoreTable } from 'drizzle-orm/singlestore-core'; +import fs from 'fs'; +import getPort from 'get-port'; +import { Connection, createConnection } from 'mysql2/promise'; +import { diffTestSchemasPushSingleStore } from 'tests/schemaDiffer'; +import { v4 as uuid } from 'uuid'; +import { afterAll, beforeAll, expect, test } from 'vitest'; + +let client: Connection; +let singlestoreContainer: Docker.Container; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'ghcr.io/singlestore-labs/singlestoredb-dev:latest'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + singlestoreContainer = await docker.createContainer({ + Image: image, + Env: ['ROOT_PASSWORD=singlestore'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await singlestoreContainer.start(); + await new Promise((resolve) => setTimeout(resolve, 4000)); + + return `singlestore://root:singlestore@localhost:${port}/`; +} + +beforeAll(async () => { + const connectionString = process.env.MYSQL_CONNECTION_STRING ?? (await createDockerDB()); + + const sleep = 1000; + let timeLeft = 20000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await singlestoreContainer?.stop().catch(console.error); + throw lastError; + } + + await client.query('DROP DATABASE IF EXISTS drizzle;'); + await client.query('CREATE DATABASE drizzle;'); + await client.query('USE drizzle;'); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await singlestoreContainer?.stop().catch(console.error); +}); + +if (!fs.existsSync('tests/push/singlestore')) { + fs.mkdirSync('tests/push/singlestore'); +} + +test('db has checks. Push with same names', async () => { + const schema1 = { + test: singlestoreTable('test', { + id: int('id').primaryKey(), + values: int('values').default(1), + }), + }; + const schema2 = { + test: singlestoreTable('test', { + id: int('id').primaryKey(), + values: int('values').default(1), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushSingleStore( + client, + schema1, + schema2, + [], + 'drizzle', + ); + + expect(statements).toStrictEqual([]); + expect(sqlStatements).toStrictEqual([]); + + await client.query(`DROP TABLE \`test\`;`); +}); + +// TODO: Unskip this test when views are implemented +/* test.skip.skip('create view', async () => { + const table = singlestoreTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + }; + + const schema2 = { + test: table, + view: singlestoreView('view').as((qb) => qb.select().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushSingleStore( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements).toStrictEqual([ + { + definition: 'select `id` from `test`', + name: 'view', + type: 'singlestore_create_view', + replace: false, + sqlSecurity: 'definer', + withCheckOption: undefined, + algorithm: 'undefined', + }, + ]); + expect(sqlStatements).toStrictEqual([ + `CREATE ALGORITHM = undefined +SQL SECURITY definer +VIEW \`view\` AS (select \`id\` from \`test\`);`, + ]); + + await client.query(`DROP TABLE \`test\`;`); +}); */ + +// TODO: Unskip this test when views are implemented +/* test.skip('drop view', async () => { + const table = singlestoreTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: singlestoreView('view').as((qb) => qb.select().from(table)), + }; + + const schema2 = { + test: table, + }; + + const { statements, sqlStatements } = await diffTestSchemasPushSingleStore( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements).toStrictEqual([ + { + name: 'view', + type: 'drop_view', + }, + ]); + expect(sqlStatements).toStrictEqual(['DROP VIEW `view`;']); + await client.query(`DROP TABLE \`test\`;`); + await client.query(`DROP VIEW \`view\`;`); +}); */ + +// TODO: Unskip this test when views are implemented +/* test.skip('alter view ".as"', async () => { + const table = singlestoreTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: singlestoreView('view').as((qb) => + qb + .select() + .from(table) + .where(sql`${table.id} = 1`) + ), + }; + + const schema2 = { + test: table, + view: singlestoreView('view').as((qb) => qb.select().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushSingleStore( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); + + await client.query(`DROP TABLE \`test\`;`); + await client.query(`DROP VIEW \`view\`;`); +}); */ + +// TODO: Unskip this test when views are implemented +/* test.skip('alter meta options with distinct in definition', async () => { + const table = singlestoreTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: singlestoreView('view') + .withCheckOption('cascaded') + .sqlSecurity('definer') + .algorithm('merge') + .as((qb) => + qb + .selectDistinct() + .from(table) + .where(sql`${table.id} = 1`) + ), + }; + + const schema2 = { + test: table, + view: singlestoreView('view') + .withCheckOption('cascaded') + .sqlSecurity('definer') + .algorithm('undefined') + .as((qb) => qb.selectDistinct().from(table)), + }; + + await expect( + diffTestSchemasPushSingleStore( + client, + schema1, + schema2, + [], + 'drizzle', + false, + ), + ).rejects.toThrowError(); + + await client.query(`DROP TABLE \`test\`;`); +}); */ diff --git a/drizzle-kit/tests/push/singlestore.test.ts b/drizzle-kit/tests/push/singlestore.test.ts new file mode 100644 index 000000000..dea28759c --- /dev/null +++ b/drizzle-kit/tests/push/singlestore.test.ts @@ -0,0 +1,447 @@ +import Docker from 'dockerode'; +import { SQL, sql } from 'drizzle-orm'; +import { + bigint, + binary, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + primaryKey, + serial, + singlestoreEnum, + singlestoreTable, + smallint, + text, + time, + timestamp, + tinyint, + varbinary, + varchar, + vector, + year, +} from 'drizzle-orm/singlestore-core'; +import getPort from 'get-port'; +import { Connection, createConnection } from 'mysql2/promise'; +import { diffTestSchemasPushSingleStore, diffTestSchemasSingleStore } from 'tests/schemaDiffer'; +import { v4 as uuid } from 'uuid'; +import { expect } from 'vitest'; +import { DialectSuite, run } from './common'; + +async function createDockerDB(context: any): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'ghcr.io/singlestore-labs/singlestoredb-dev:latest'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + context.singlestoreContainer = await docker.createContainer({ + Image: image, + Env: ['ROOT_PASSWORD=singlestore'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await context.singlestoreContainer.start(); + await new Promise((resolve) => setTimeout(resolve, 4000)); + + return `singlestore://root:singlestore@localhost:${port}/`; +} + +const singlestoreSuite: DialectSuite = { + allTypes: async function(context: any): Promise { + const schema1 = { + allBigInts: singlestoreTable('all_big_ints', { + simple: bigint('simple', { mode: 'number' }), + columnNotNull: bigint('column_not_null', { mode: 'number' }).notNull(), + columnDefault: bigint('column_default', { mode: 'number' }).default(12), + columnDefaultSql: bigint('column_default_sql', { + mode: 'number', + }).default(12), + }), + allBools: singlestoreTable('all_bools', { + simple: tinyint('simple'), + columnNotNull: tinyint('column_not_null').notNull(), + columnDefault: tinyint('column_default').default(1), + }), + allChars: singlestoreTable('all_chars', { + simple: char('simple', { length: 1 }), + columnNotNull: char('column_not_null', { length: 45 }).notNull(), + // columnDefault: char("column_default", { length: 1 }).default("h"), + columnDefaultSql: char('column_default_sql', { length: 1 }).default( + 'h', + ), + }), + // allDateTimes: singlestoreTable("all_date_times", { + // simple: datetime("simple", { mode: "string", fsp: 1 }), + // columnNotNull: datetime("column_not_null", { + // mode: "string", + // }).notNull(), + // columnDefault: datetime("column_default", { mode: "string" }).default( + // "2023-03-01 14:05:29" + // ), + // }), + allDates: singlestoreTable('all_dates', { + simple: date('simple', { mode: 'string' }), + column_not_null: date('column_not_null', { mode: 'string' }).notNull(), + column_default: date('column_default', { mode: 'string' }).default( + '2023-03-01', + ), + }), + allDecimals: singlestoreTable('all_decimals', { + simple: decimal('simple', { precision: 1, scale: 0 }), + columnNotNull: decimal('column_not_null', { + precision: 45, + scale: 3, + }).notNull(), + columnDefault: decimal('column_default', { + precision: 10, + scale: 0, + }).default('100'), + columnDefaultSql: decimal('column_default_sql', { + precision: 10, + scale: 0, + }).default('101'), + }), + + allDoubles: singlestoreTable('all_doubles', { + simple: double('simple'), + columnNotNull: double('column_not_null').notNull(), + columnDefault: double('column_default').default(100), + columnDefaultSql: double('column_default_sql').default(101), + }), + + allEnums: singlestoreTable('all_enums', { + simple: singlestoreEnum('simple', ['hi', 'hello']), + }), + + allEnums1: singlestoreTable('all_enums1', { + simple: singlestoreEnum('simple', ['hi', 'hello']).default('hi'), + }), + + allFloats: singlestoreTable('all_floats', { + columnNotNull: float('column_not_null').notNull(), + columnDefault: float('column_default').default(100), + columnDefaultSql: float('column_default_sql').default(101), + }), + + allInts: singlestoreTable('all_ints', { + simple: int('simple'), + columnNotNull: int('column_not_null').notNull(), + columnDefault: int('column_default').default(100), + columnDefaultSql: int('column_default_sql').default(101), + }), + + allIntsRef: singlestoreTable('all_ints_ref', { + simple: int('simple'), + columnNotNull: int('column_not_null').notNull(), + columnDefault: int('column_default').default(100), + columnDefaultSql: int('column_default_sql').default(101), + }), + + // allJsons: singlestoreTable("all_jsons", { + // columnDefaultObject: json("column_default_object") + // .default({ hello: "world world" }) + // .notNull(), + // columnDefaultArray: json("column_default_array").default({ + // hello: { "world world": ["foo", "bar"] }, + // foo: "bar", + // fe: 23, + // }), + // column: json("column"), + // }), + + allMInts: singlestoreTable('all_m_ints', { + simple: mediumint('simple'), + columnNotNull: mediumint('column_not_null').notNull(), + columnDefault: mediumint('column_default').default(100), + columnDefaultSql: mediumint('column_default_sql').default(101), + }), + + allReals: singlestoreTable('all_reals', { + simple: double('simple', { precision: 5, scale: 2 }), + columnNotNull: double('column_not_null').notNull(), + columnDefault: double('column_default').default(100), + columnDefaultSql: double('column_default_sql').default(101), + }), + + allSInts: singlestoreTable('all_s_ints', { + simple: smallint('simple'), + columnNotNull: smallint('column_not_null').notNull(), + columnDefault: smallint('column_default').default(100), + columnDefaultSql: smallint('column_default_sql').default(101), + }), + + // allSmallSerials: singlestoreTable("all_small_serials", { + // columnAll: serial("column_all").notNull(), + // }), + + allTInts: singlestoreTable('all_t_ints', { + simple: tinyint('simple'), + columnNotNull: tinyint('column_not_null').notNull(), + columnDefault: tinyint('column_default').default(10), + columnDefaultSql: tinyint('column_default_sql').default(11), + }), + + allTexts: singlestoreTable('all_texts', { + simple: text('simple'), + columnNotNull: text('column_not_null').notNull(), + columnDefault: text('column_default').default('hello'), + columnDefaultSql: text('column_default_sql').default('hello'), + }), + + allTimes: singlestoreTable('all_times', { + // simple: time("simple", { fsp: 1 }), + columnNotNull: time('column_not_null').notNull(), + columnDefault: time('column_default').default('22:12:12'), + }), + + allTimestamps: singlestoreTable('all_timestamps', { + // columnDateNow: timestamp("column_date_now", { + // fsp: 1, + // mode: "string", + // }).default(sql`(now())`), + columnAll: timestamp('column_all', { mode: 'string' }) + .default('2023-03-01 14:05:29') + .notNull(), + column: timestamp('column', { mode: 'string' }).default( + '2023-02-28 16:18:31', + ), + }), + + allVarChars: singlestoreTable('all_var_chars', { + simple: varchar('simple', { length: 100 }), + columnNotNull: varchar('column_not_null', { length: 45 }).notNull(), + columnDefault: varchar('column_default', { length: 100 }).default( + 'hello', + ), + columnDefaultSql: varchar('column_default_sql', { + length: 100, + }).default('hello'), + }), + + allVarbinaries: singlestoreTable('all_varbinaries', { + simple: varbinary('simple', { length: 100 }), + columnNotNull: varbinary('column_not_null', { length: 100 }).notNull(), + columnDefault: varbinary('column_default', { length: 12 }), + }), + + allYears: singlestoreTable('all_years', { + simple: year('simple'), + columnNotNull: year('column_not_null').notNull(), + columnDefault: year('column_default').default(2022), + }), + + binafry: singlestoreTable('binary', { + simple: binary('simple', { length: 1 }), + columnNotNull: binary('column_not_null', { length: 1 }).notNull(), + columnDefault: binary('column_default', { length: 12 }), + }), + + allVectors: singlestoreTable('all_vectors', { + vectorSimple: vector('vector_simple', { dimensions: 1 }), + vectorElementType: vector('vector_element_type', { dimensions: 1, elementType: 'I8' }), + vectorNotNull: vector('vector_not_null', { dimensions: 1 }).notNull(), + vectorDefault: vector('vector_default', { dimensions: 1 }).default([1]), + }), + }; + + const { statements } = await diffTestSchemasPushSingleStore( + context.client as Connection, + schema1, + schema1, + [], + 'drizzle', + false, + ); + console.log(statements); + expect(statements.length).toBe(0); + expect(statements).toEqual([]); + + const { sqlStatements: dropStatements } = await diffTestSchemasSingleStore( + schema1, + {}, + [], + false, + ); + + for (const st of dropStatements) { + await context.client.query(st); + } + }, + addBasicIndexes: function(context?: any): Promise { + return {} as any; + }, + changeIndexFields: function(context?: any): Promise { + return {} as any; + }, + dropIndex: function(context?: any): Promise { + return {} as any; + }, + indexesToBeNotTriggered: function(context?: any): Promise { + return {} as any; + }, + indexesTestCase1: function(context?: any): Promise { + return {} as any; + }, + async case1() { + // TODO: implement if needed + expect(true).toBe(true); + }, + addNotNull: function(context?: any): Promise { + return {} as any; + }, + addNotNullWithDataNoRollback: function(context?: any): Promise { + return {} as any; + }, + addBasicSequences: function(context?: any): Promise { + return {} as any; + }, + addGeneratedColumn: async function(context: any): Promise { + return {} as any; + }, + addGeneratedToColumn: async function(context: any): Promise { + return {} as any; + }, + dropGeneratedConstraint: async function(context: any): Promise { + return {} as any; + }, + alterGeneratedConstraint: async function(context: any): Promise { + return {} as any; + }, + createTableWithGeneratedConstraint: function(context?: any): Promise { + return {} as any; + }, + createCompositePrimaryKey: async function(context: any): Promise { + const schema1 = {}; + + const schema2 = { + table: singlestoreTable('table', { + col1: int('col1').notNull(), + col2: int('col2').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.col1, t.col2], + }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushSingleStore( + context.client as Connection, + schema1, + schema2, + [], + 'drizzle', + false, + ); + + expect(statements).toStrictEqual([ + { + type: 'create_table', + tableName: 'table', + schema: undefined, + internals: { + indexes: {}, + tables: {}, + }, + compositePKs: ['table_col1_col2_pk;col1,col2'], + compositePkName: 'table_col1_col2_pk', + uniqueConstraints: [], + columns: [ + { name: 'col1', type: 'int', primaryKey: false, notNull: true, autoincrement: false }, + { name: 'col2', type: 'int', primaryKey: false, notNull: true, autoincrement: false }, + ], + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE `table` (\n\t`col1` int NOT NULL,\n\t`col2` int NOT NULL,\n\tCONSTRAINT `table_col1_col2_pk` PRIMARY KEY(`col1`,`col2`)\n);\n', + ]); + }, + renameTableWithCompositePrimaryKey: async function(context?: any): Promise { + const productsCategoriesTable = (tableName: string) => { + return singlestoreTable(tableName, { + productId: varchar('product_id', { length: 10 }).notNull(), + categoryId: varchar('category_id', { length: 10 }).notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.productId, t.categoryId], + }), + })); + }; + + const schema1 = { + table: productsCategoriesTable('products_categories'), + }; + const schema2 = { + test: productsCategoriesTable('products_to_categories'), + }; + + const { sqlStatements } = await diffTestSchemasPushSingleStore( + context.client as Connection, + schema1, + schema2, + ['public.products_categories->public.products_to_categories'], + 'drizzle', + false, + ); + + // It's not possible to create/alter/drop primary keys in SingleStore + expect(sqlStatements).toStrictEqual([ + 'RENAME TABLE `products_categories` TO `products_to_categories`;', + ]); + + await context.client.query(`DROP TABLE \`products_categories\``); + }, +}; + +run( + singlestoreSuite, + async (context: any) => { + const connectionString = process.env.SINGLESTORE_CONNECTION_STRING + ?? (await createDockerDB(context)); + + const sleep = 1000; + let timeLeft = 20000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + context.client = await createConnection(connectionString); + await context.client.connect(); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to SingleStore'); + await context.client?.end().catch(console.error); + await context.singlestoreContainer?.stop().catch(console.error); + throw lastError; + } + + await context.client.query(`DROP DATABASE IF EXISTS \`drizzle\`;`); + await context.client.query('CREATE DATABASE drizzle;'); + await context.client.query('USE drizzle;'); + }, + async (context: any) => { + await context.client?.end().catch(console.error); + await context.singlestoreContainer?.stop().catch(console.error); + }, +); diff --git a/drizzle-kit/tests/push/sqlite.test.ts b/drizzle-kit/tests/push/sqlite.test.ts index cf468d3ec..e2c85233a 100644 --- a/drizzle-kit/tests/push/sqlite.test.ts +++ b/drizzle-kit/tests/push/sqlite.test.ts @@ -1,384 +1,638 @@ import Database from 'better-sqlite3'; -import { SQL, sql } from 'drizzle-orm'; -import { blob, foreignKey, int, integer, numeric, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; -import { diffTestSchemasPushSqlite } from 'tests/schemaDiffer'; +import chalk from 'chalk'; +import { sql } from 'drizzle-orm'; +import { + blob, + check, + foreignKey, + getTableConfig, + int, + integer, + numeric, + primaryKey, + real, + sqliteTable, + sqliteView, + text, + uniqueIndex, +} from 'drizzle-orm/sqlite-core'; +import { diffTestSchemasPushSqlite, introspectSQLiteToFile } from 'tests/schemaDiffer'; import { expect, test } from 'vitest'; -import { DialectSuite, run } from './common'; - -const sqliteSuite: DialectSuite = { - addBasicIndexes: function(context?: any): Promise { - return {} as any; - }, - changeIndexFields: function(context?: any): Promise { - return {} as any; - }, - dropIndex: function(context?: any): Promise { - return {} as any; - }, - - async allTypes() { - const sqlite = new Database(':memory:'); - - const Users = sqliteTable('users', { - id: integer('id').primaryKey().notNull(), - name: text('name').notNull(), - email: text('email'), - textJson: text('text_json', { mode: 'json' }), - blobJon: blob('blob_json', { mode: 'json' }), - blobBigInt: blob('blob_bigint', { mode: 'bigint' }), - numeric: numeric('numeric'), - createdAt: integer('created_at', { mode: 'timestamp' }), - createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), - real: real('real'), - text: text('text', { length: 255 }), - role: text('role', { enum: ['admin', 'user'] }).default('user'), - isConfirmed: integer('is_confirmed', { - mode: 'boolean', - }), - }); - const schema1 = { - Users, +test('nothing changed in schema', async (t) => { + const client = new Database(':memory:'); + + const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), + email: text('email'), + textJson: text('text_json', { mode: 'json' }), + blobJon: blob('blob_json', { mode: 'json' }), + blobBigInt: blob('blob_bigint', { mode: 'bigint' }), + numeric: numeric('numeric'), + createdAt: integer('created_at', { mode: 'timestamp' }), + createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { length: 255 }), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + isConfirmed: integer('is_confirmed', { + mode: 'boolean', + }), + }); + + const schema1 = { + users, + + customers: sqliteTable('customers', { + id: integer('id').primaryKey(), + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + userId: integer('user_id') + .references(() => users.id) + .notNull(), + }), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema1, [], false); + + expect(sqlStatements.length).toBe(0); + expect(statements.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); +}); + +test('dropped, added unique index', async (t) => { + const client = new Database(':memory:'); + + const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), + email: text('email'), + textJson: text('text_json', { mode: 'json' }), + blobJon: blob('blob_json', { mode: 'json' }), + blobBigInt: blob('blob_bigint', { mode: 'bigint' }), + numeric: numeric('numeric'), + createdAt: integer('created_at', { mode: 'timestamp' }), + createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { length: 255 }), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + isConfirmed: integer('is_confirmed', { + mode: 'boolean', + }), + }); + + const schema1 = { + users, - Customers: sqliteTable('customers', { + customers: sqliteTable( + 'customers', + { id: integer('id').primaryKey(), - address: text('address').notNull(), + address: text('address').notNull().unique(), isConfirmed: integer('is_confirmed', { mode: 'boolean' }), registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) .notNull() .$defaultFn(() => new Date()), - userId: integer('user_id') - .references(() => Users.id) - .notNull(), + userId: integer('user_id').notNull(), + }, + (table) => ({ + uniqueIndex: uniqueIndex('customers_address_unique').on(table.address), }), + ), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const schema2 = { + users, - Posts: sqliteTable('posts', { + customers: sqliteTable( + 'customers', + { id: integer('id').primaryKey(), - content: text('content'), - authorId: integer('author_id'), - }), - }; - - const { statements } = await diffTestSchemasPushSqlite( - sqlite, - schema1, - schema1, - [], - false, - ); - expect(statements.length).toBe(0); - }, - indexesToBeNotTriggered: function(context?: any): Promise { - return {} as any; - }, - indexesTestCase1: function(context?: any): Promise { - return {} as any; - }, - async case1(): Promise { - const sqlite = new Database(':memory:'); - - const schema1 = { - users: sqliteTable('users', { - id: text('id').notNull().primaryKey(), - firstName: text('first_name').notNull(), - lastName: text('last_name').notNull(), - username: text('username').notNull().unique(), - email: text('email').notNull().unique(), - password: text('password').notNull(), - avatarUrl: text('avatar_url').notNull(), - postsCount: integer('posts_count').notNull().default(0), - followersCount: integer('followers_count').notNull().default(0), - followingsCount: integer('followings_count').notNull().default(0), - createdAt: integer('created_at').notNull(), - }), - }; - - const schema2 = { - users: sqliteTable('users', { - id: text('id').notNull().primaryKey(), - firstName: text('first_name').notNull(), - lastName: text('last_name').notNull(), - username: text('username').notNull().unique(), - email: text('email').notNull().unique(), - password: text('password').notNull(), - avatarUrl: text('avatar_url').notNull(), - followersCount: integer('followers_count').notNull().default(0), - followingsCount: integer('followings_count').notNull().default(0), - createdAt: integer('created_at').notNull(), - }), - }; - - const { statements } = await diffTestSchemasPushSqlite( - sqlite, - schema1, - schema2, - [], - false, - ); - expect(statements.length).toBe(1); - expect(statements[0]).toStrictEqual({ - type: 'alter_table_drop_column', - tableName: 'users', - columnName: 'posts_count', - schema: '', - }); - }, - addNotNull: function(context?: any): Promise { - return {} as any; - }, - addNotNullWithDataNoRollback: function(context?: any): Promise { - return {} as any; - }, - addBasicSequences: function(context?: any): Promise { - return {} as any; - }, - // --- - addGeneratedColumn: async function(context?: any): Promise { - const sqlite = new Database(':memory:'); - - const from = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - }), - }; - const to = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'stored' }, - ), - }), - }; - - const { statements, sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, - from, - to, - [], - ); - - expect(statements).toStrictEqual([]); - expect(sqlStatements).toStrictEqual([]); - }, - addGeneratedToColumn: async function(context?: any): Promise { - const sqlite = new Database(':memory:'); - - const from = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').notNull(), - generatedName1: text('gen_name1'), - }), - }; - const to = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name') + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) .notNull() - .generatedAlwaysAs((): SQL => sql`${to.users.name} || 'hello'`, { - mode: 'stored', - }), - generatedName1: text('gen_name1').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'virtual' }, + .$defaultFn(() => new Date()), + userId: integer('user_id').notNull(), + }, + (table) => ({ + uniqueIndex: uniqueIndex('customers_is_confirmed_unique').on( + table.isConfirmed, ), }), - }; + ), - const { statements, sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, - from, - to, - [], - ); + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; - expect(statements).toStrictEqual([ + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, [], false); + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'drop_index', + tableName: 'customers', + data: 'customers_address_unique;address;true;', + schema: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'create_index', + tableName: 'customers', + data: 'customers_is_confirmed_unique;is_confirmed;true;', + schema: '', + internal: { + indexes: {}, + }, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `DROP INDEX \`customers_address_unique\`;`, + ); + expect(sqlStatements[1]).toBe( + `CREATE UNIQUE INDEX \`customers_is_confirmed_unique\` ON \`customers\` (\`is_confirmed\`);`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('added column not null and without default to table with data', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + age: integer('age').notNull(), + }), + }; + + const table = getTableConfig(schema1.companies); + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.name.name}") VALUES ('drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.name.name}") VALUES ('turso');`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'companies', + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + referenceData: undefined, + }); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`delete from companies;`); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`companies\` ADD \`age\` integer NOT NULL;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to add not-null ${ + chalk.underline( + 'age', + ) + } column without default value, which contains 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(1); + expect(tablesToTruncate![0]).toBe('companies'); +}); + +test('added column not null and without default to table without data', async (t) => { + const turso = new Database(':memory:'); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + age: integer('age').notNull(), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(turso, schema1, schema2, [], false); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'companies', + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + referenceData: undefined, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`companies\` ADD \`age\` integer NOT NULL;`, + ); + + expect(infoToPrint!.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop autoincrement. drop column with data', async (t) => { + const turso = new Database(':memory:'); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + }), + }; + + const table = getTableConfig(schema1.companies); + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (1, 'drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (2, 'turso');`, + ]; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ { - columnAutoIncrement: false, - columnDefault: undefined, - columnGenerated: { - as: '("name" || \'hello\')', - type: 'virtual', - }, - columnName: 'gen_name1', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', - tableName: 'users', - type: 'alter_table_alter_column_set_generated', + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, }, - ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER TABLE `users` DROP COLUMN `gen_name1`;', - 'ALTER TABLE `users` ADD `gen_name1` text GENERATED ALWAYS AS ("name" || \'hello\') VIRTUAL;', - ]); - - for (const st of sqlStatements) { - sqlite.exec(st); - } - }, - dropGeneratedConstraint: async function(context?: any): Promise { - const sqlite = new Database(':memory:'); - - const from = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'stored' }, - ), - generatedName1: text('gen_name1').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'virtual' }, - ), - }), - }; - const to = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name'), - generatedName1: text('gen_name1'), - }), - }; + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL +);\n`, + ); + expect(sqlStatements[1]).toBe( + `INSERT INTO \`__new_companies\`("id") SELECT "id" FROM \`companies\`;`, + ); + expect(sqlStatements[2]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + + expect(columnsToRemove!.length).toBe(1); + expect(columnsToRemove![0]).toBe('name'); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to delete ${ + chalk.underline( + 'name', + ) + } column in companies table with 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop autoincrement. drop column with data with pragma off', async (t) => { + const client = new Database(':memory:'); + + client.exec('PRAGMA foreign_keys=OFF;'); - const { statements, sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, - from, - to, - [], - ); + const users = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + }); + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), + user_id: integer('user_id').references(() => users.id), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + user_id: integer('user_id').references(() => users.id), + }), + }; - expect(statements).toStrictEqual([ + const table = getTableConfig(schema1.companies); + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (1, 'drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (2, 'turso');`, + ]; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ { - columnAutoIncrement: false, - columnDefault: undefined, - columnGenerated: undefined, - columnName: 'gen_name', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', - tableName: 'users', - type: 'alter_table_alter_column_drop_generated', + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, }, { - columnAutoIncrement: false, - columnDefault: undefined, - columnGenerated: undefined, - columnName: 'gen_name1', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', - tableName: 'users', - type: 'alter_table_alter_column_drop_generated', + name: 'user_id', + type: 'integer', + autoincrement: false, + notNull: false, + primaryKey: false, + generated: undefined, }, - ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER TABLE `users` DROP COLUMN `gen_name`;', - 'ALTER TABLE `users` ADD `gen_name` text;', - 'ALTER TABLE `users` DROP COLUMN `gen_name1`;', - 'ALTER TABLE `users` ADD `gen_name1` text;', - ]); - - for (const st of sqlStatements) { - sqlite.exec(st); - } - }, - alterGeneratedConstraint: async function(context?: any): Promise { - const sqlite = new Database(':memory:'); - - const from = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'stored' }, - ), - generatedName1: text('gen_name1').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'virtual' }, - ), - }), - }; - const to = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${to.users.name}`, - { mode: 'stored' }, - ), - generatedName1: text('gen_name1').generatedAlwaysAs( - (): SQL => sql`${to.users.name}`, - { mode: 'virtual' }, - ), - }), - }; + ], + compositePKs: [], + referenceData: [ + { + columnsFrom: [ + 'user_id', + ], + columnsTo: [ + 'id', + ], + name: '', + onDelete: 'no action', + onUpdate: 'no action', + tableFrom: 'companies', + tableTo: 'users', + }, + ], + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`user_id\` integer, +\tFOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE no action +);\n`, + ); + expect(sqlStatements[1]).toBe( + `INSERT INTO \`__new_companies\`("id", "user_id") SELECT "id", "user_id" FROM \`companies\`;`, + ); + expect(sqlStatements[2]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + + expect(columnsToRemove!.length).toBe(1); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to delete ${ + chalk.underline( + 'name', + ) + } column in companies table with 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('change autoincrement. other table references current', async (t) => { + const client = new Database(':memory:'); + + const companies1 = sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + }); + const users1 = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').unique(), + companyId: text('company_id').references(() => companies1.id), + }); + const schema1 = { + companies: companies1, + users: users1, + }; + + const companies2 = sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + }); + const users2 = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').unique(), + companyId: text('company_id').references(() => companies1.id), + }); + const schema2 = { + companies: companies2, + users: users2, + }; - const { statements, sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, - from, - to, - [], - ); + const { name: usersTableName } = getTableConfig(users1); + const { name: companiesTableName } = getTableConfig(companies1); + const seedStatements = [ + `INSERT INTO \`${usersTableName}\` ("${schema1.users.name.name}") VALUES ('drizzle');`, + `INSERT INTO \`${usersTableName}\` ("${schema1.users.name.name}") VALUES ('turso');`, + `INSERT INTO \`${companiesTableName}\` ("${schema1.companies.id.name}") VALUES ('1');`, + `INSERT INTO \`${companiesTableName}\` ("${schema1.companies.id.name}") VALUES ('2');`, + ]; - expect(statements).toStrictEqual([ + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ { - columnAutoIncrement: false, - columnDefault: undefined, - columnGenerated: { - as: '("name")', - type: 'virtual', - }, - columnName: 'gen_name1', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', - tableName: 'users', - type: 'alter_table_alter_column_alter_generated', + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, }, - ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER TABLE `users` DROP COLUMN `gen_name1`;', - 'ALTER TABLE `users` ADD `gen_name1` text GENERATED ALWAYS AS ("name") VIRTUAL;', - ]); - - for (const st of sqlStatements) { - sqlite.exec(st); - } - }, - createTableWithGeneratedConstraint: function(context?: any): Promise { - return {} as any; - }, -}; - -run(sqliteSuite); + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL +);\n`, + ); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_companies\`("id") SELECT "id" FROM \`companies\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); test('create table with custom name references', async (t) => { - const sqlite = new Database(':memory:'); + const client = new Database(':memory:'); const users = sqliteTable('users', { id: int('id').primaryKey({ autoIncrement: true }), @@ -424,7 +678,7 @@ test('create table with custom name references', async (t) => { }; const { sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, + client, schema1, schema2, [], @@ -432,3 +686,928 @@ test('create table with custom name references', async (t) => { expect(sqlStatements!.length).toBe(0); }); + +test('drop not null, add not null', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + posts: sqliteTable('posts', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + userId: int('user_id'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + posts: sqliteTable('posts', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + userId: int('user_id'), + }), + }; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, []); + + expect(statements!.length).toBe(2); + expect(statements![0]).toStrictEqual({ + checkConstraints: [], + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + expect(statements![1]).toStrictEqual({ + checkConstraints: [], + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'name', + notNull: true, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + generated: undefined, + name: 'user_id', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'posts', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(8); + expect(sqlStatements[0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`name\` text +);\n`); + expect(sqlStatements[1]).toBe( + `INSERT INTO \`__new_users\`("id", "name") SELECT "id", "name" FROM \`users\`;`, + ); + expect(sqlStatements[2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(sqlStatements![4]).toBe(`CREATE TABLE \`__new_posts\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`name\` text NOT NULL, +\t\`user_id\` integer +);\n`); + expect(sqlStatements![5]).toBe( + `INSERT INTO \`__new_posts\`("id", "name", "user_id") SELECT "id", "name", "user_id" FROM \`posts\`;`, + ); + expect(sqlStatements![6]).toBe(`DROP TABLE \`posts\`;`); + expect(sqlStatements![7]).toBe( + `ALTER TABLE \`__new_posts\` RENAME TO \`posts\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('rename table and change data type', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('old_users', { + id: int('id').primaryKey({ autoIncrement: true }), + age: text('age'), + }), + }; + + const schema2 = { + users: sqliteTable('new_users', { + id: int('id').primaryKey({ autoIncrement: true }), + age: integer('age'), + }), + }; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, [ + 'public.old_users->public.new_users', + ]); + + expect(statements!.length).toBe(2); + expect(statements![0]).toStrictEqual({ + fromSchema: undefined, + tableNameFrom: 'old_users', + tableNameTo: 'new_users', + toSchema: undefined, + type: 'rename_table', + }); + expect(statements![1]).toStrictEqual({ + columns: [ + { + autoincrement: true, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'new_users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements!.length).toBe(5); + expect(sqlStatements![0]).toBe( + `ALTER TABLE \`old_users\` RENAME TO \`new_users\`;`, + ); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`age\` integer +);\n`); + expect(sqlStatements![2]).toBe( + `INSERT INTO \`__new_new_users\`("id", "age") SELECT "id", "age" FROM \`new_users\`;`, + ); + expect(sqlStatements![3]).toBe(`DROP TABLE \`new_users\`;`); + expect(sqlStatements![4]).toBe( + `ALTER TABLE \`__new_new_users\` RENAME TO \`new_users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('rename column and change data type', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + age: integer('age'), + }), + }; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, [ + 'public.users.name->public.users.age', + ]); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`age\` integer +);\n`); + expect(sqlStatements![1]).toBe( + `INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`, + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('recreate table with nested references', async (t) => { + const client = new Database(':memory:'); + + let users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }); + let subscriptions = sqliteTable('subscriptions', { + id: int('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + customerId: text('customer_id'), + }); + const schema1 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }); + const schema2 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, [ + 'public.users.name->public.users.age', + ]); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements!.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements![1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements![2]).toBe( + `INSERT INTO \`__new_users\`("id", "name", "age") SELECT "id", "name", "age" FROM \`users\`;`, + ); + expect(sqlStatements![3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe('PRAGMA foreign_keys=ON;'); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('recreate table with added column not null and without default with data', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + newColumn: text('new_column').notNull(), + }), + }; + + const seedStatements = [ + `INSERT INTO \`users\` ("name", "age") VALUES ('drizzle', 12)`, + `INSERT INTO \`users\` ("name", "age") VALUES ('turso', 12)`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'new_column', + notNull: true, + generated: undefined, + primaryKey: false, + type: 'text', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements[0]).toBe('DELETE FROM \`users\`;'); + expect(sqlStatements![1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer, +\t\`new_column\` text NOT NULL +);\n`); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to add not-null ${ + chalk.underline('new_column') + } column without default value to table, which contains 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(1); + expect(tablesToTruncate![0]).toBe('users'); +}); + +test('add check constraint to table', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: ['some_check;"users"."age" > 21'], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer, +\tCONSTRAINT "some_check" CHECK("__new_users"."age" > 21) +);\n`); + expect(sqlStatements[1]).toBe( + 'INSERT INTO `__new_users`("id", "name", "age") SELECT "id", "name", "age" FROM `users`;', + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop check constraint', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements[1]).toBe( + 'INSERT INTO `__new_users`("id", "name", "age") SELECT "id", "name", "age" FROM `users`;', + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('db has checks. Push with same names', async () => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`${table.age} > 21`), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }, (table) => ({ + someCheck: check('some_check', sql`some new value`), + })), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + schemasToRemove, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + [], + ); + expect(statements).toStrictEqual([]); + expect(sqlStatements).toStrictEqual([]); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('create view', async () => { + const client = new Database(':memory:'); + + const table = sqliteTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + }; + + const schema2 = { + test: table, + view: sqliteView('view').as((qb) => qb.select().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + ); + + expect(statements).toStrictEqual([ + { + definition: 'select "id" from "test"', + name: 'view', + type: 'sqlite_create_view', + }, + ]); + expect(sqlStatements).toStrictEqual([ + `CREATE VIEW \`view\` AS select "id" from "test";`, + ]); +}); + +test('drop view', async () => { + const client = new Database(':memory:'); + + const table = sqliteTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: sqliteView('view').as((qb) => qb.select().from(table)), + }; + + const schema2 = { + test: table, + }; + + const { statements, sqlStatements } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + ); + + expect(statements).toStrictEqual([ + { + name: 'view', + type: 'drop_view', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'DROP VIEW \`view\`;', + ]); +}); + +test('alter view ".as"', async () => { + const client = new Database(':memory:'); + + const table = sqliteTable('test', { + id: int('id').primaryKey(), + }); + + const schema1 = { + test: table, + view: sqliteView('view').as((qb) => qb.select().from(table).where(sql`${table.id} = 1`)), + }; + + const schema2 = { + test: table, + view: sqliteView('view').as((qb) => qb.select().from(table)), + }; + + const { statements, sqlStatements } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('create composite primary key', async (t) => { + const client = new Database(':memory:'); + + const schema1 = {}; + + const schema2 = { + table: sqliteTable('table', { + col1: integer('col1').notNull(), + col2: integer('col2').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.col1, t.col2], + }), + })), + }; + + const { + statements, + sqlStatements, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + ); + + expect(statements).toStrictEqual([{ + type: 'sqlite_create_table', + tableName: 'table', + compositePKs: [['col1', 'col2']], + uniqueConstraints: [], + referenceData: [], + checkConstraints: [], + columns: [ + { name: 'col1', type: 'integer', primaryKey: false, notNull: true, autoincrement: false }, + { name: 'col2', type: 'integer', primaryKey: false, notNull: true, autoincrement: false }, + ], + }]); + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE `table` (\n\t`col1` integer NOT NULL,\n\t`col2` integer NOT NULL,\n\tPRIMARY KEY(`col1`, `col2`)\n);\n', + ]); +}); + +test('rename table with composite primary key', async () => { + const client = new Database(':memory:'); + + const productsCategoriesTable = (tableName: string) => { + return sqliteTable(tableName, { + productId: text('product_id').notNull(), + categoryId: text('category_id').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.productId, t.categoryId], + }), + })); + }; + + const schema1 = { + table: productsCategoriesTable('products_categories'), + }; + const schema2 = { + test: productsCategoriesTable('products_to_categories'), + }; + + const { sqlStatements } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + ['public.products_categories->public.products_to_categories'], + false, + ); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `products_categories` RENAME TO `products_to_categories`;', + ]); +}); diff --git a/drizzle-kit/tests/rls/pg-policy.test.ts b/drizzle-kit/tests/rls/pg-policy.test.ts new file mode 100644 index 000000000..3d5dcbd14 --- /dev/null +++ b/drizzle-kit/tests/rls/pg-policy.test.ts @@ -0,0 +1,1656 @@ +import { sql } from 'drizzle-orm'; +import { integer, pgPolicy, pgRole, pgSchema, pgTable } from 'drizzle-orm/pg-core'; +import { diffTestSchemas } from 'tests/schemaDiffer'; +import { expect, test } from 'vitest'; + +test('add policy + enable rls', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'enable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('drop policy + disable rls', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" DISABLE ROW LEVEL SECURITY;', + 'DROP POLICY "test" ON "users" CASCADE;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'disable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + ]); +}); + +test('add policy without enable rls', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + newrls: pgPolicy('newRls'), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'CREATE POLICY "newRls" ON "users" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'newRls', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('drop policy without disable rls', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + oldRls: pgPolicy('oldRls'), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "oldRls" ON "users" CASCADE;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'oldRls', + to: ['public'], + using: undefined, + on: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + ]); +}); + +test('alter policy without recreation: changing roles', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', to: 'current_role' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" TO current_role;', + ]); + expect(statements).toStrictEqual([ + { + newData: 'test--PERMISSIVE--ALL--current_role--undefined--undefined--undefined', + oldData: 'test--PERMISSIVE--ALL--public--undefined--undefined--undefined', + schema: '', + tableName: 'users', + type: 'alter_policy', + }, + ]); +}); + +test('alter policy without recreation: changing using', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', using: sql`true` }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" TO public USING (true);', + ]); + expect(statements).toStrictEqual([ + { + newData: 'test--PERMISSIVE--ALL--public--true--undefined--undefined', + oldData: 'test--PERMISSIVE--ALL--public--undefined--undefined--undefined', + schema: '', + tableName: 'users', + type: 'alter_policy', + }, + ]); +}); + +test('alter policy without recreation: changing with check', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', withCheck: sql`true` }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" TO public WITH CHECK (true);', + ]); + expect(statements).toStrictEqual([ + { + newData: 'test--PERMISSIVE--ALL--public--undefined--true--undefined', + oldData: 'test--PERMISSIVE--ALL--public--undefined--undefined--undefined', + schema: '', + tableName: 'users', + type: 'alter_policy', + }, + ]); +}); + +/// + +test('alter policy with recreation: changing as', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'restrictive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS RESTRICTIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + using: undefined, + on: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'RESTRICTIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('alter policy with recreation: changing for', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', for: 'delete' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR DELETE TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'PERMISSIVE', + for: 'DELETE', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('alter policy with recreation: changing both "as" and "for"', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'restrictive', for: 'insert' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS RESTRICTIVE FOR INSERT TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + using: undefined, + on: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'RESTRICTIVE', + for: 'INSERT', + name: 'test', + to: ['public'], + using: undefined, + on: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('alter policy with recreation: changing all fields', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive', for: 'select', using: sql`true` }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'restrictive', to: 'current_role', withCheck: sql`true` }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS RESTRICTIVE FOR ALL TO current_role WITH CHECK (true);', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'SELECT', + name: 'test', + to: ['public'], + using: 'true', + on: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'RESTRICTIVE', + for: 'ALL', + name: 'test', + on: undefined, + to: ['current_role'], + using: undefined, + withCheck: 'true', + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('rename policy', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('newName', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, [ + 'public.users.test->public.users.newName', + ]); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" RENAME TO "newName";', + ]); + expect(statements).toStrictEqual([ + { + newName: 'newName', + oldName: 'test', + schema: '', + tableName: 'users', + type: 'rename_policy', + }, + ]); +}); + +test('rename policy in renamed table', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = { + users: pgTable('users2', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('newName', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, [ + 'public.users->public.users2', + 'public.users2.test->public.users2.newName', + ]); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" RENAME TO "users2";', + 'ALTER POLICY "test" ON "users2" RENAME TO "newName";', + ]); + expect(statements).toStrictEqual([ + { + fromSchema: '', + tableNameFrom: 'users', + tableNameTo: 'users2', + toSchema: '', + type: 'rename_table', + }, + { + newName: 'newName', + oldName: 'test', + schema: '', + tableName: 'users2', + type: 'rename_policy', + }, + ]); +}); + +test('create table with a policy', async (t) => { + const schema1 = {}; + + const schema2 = { + users: pgTable('users2', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE "users2" (\n\t"id" integer PRIMARY KEY NOT NULL\n);\n', + 'ALTER TABLE "users2" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test" ON "users2" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + columns: [ + { + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + ], + compositePKs: [], + checkConstraints: [], + compositePkName: '', + policies: [ + 'test--PERMISSIVE--ALL--public--undefined--undefined--undefined', + ], + schema: '', + tableName: 'users2', + isRLSEnabled: false, + type: 'create_table', + uniqueConstraints: [], + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: [ + 'public', + ], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users2', + type: 'create_policy', + }, + ]); +}); + +test('drop table with a policy', async (t) => { + const schema1 = { + users: pgTable('users2', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { as: 'permissive' }), + })), + }; + + const schema2 = {}; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users2" CASCADE;', + 'DROP TABLE "users2" CASCADE;', + ]); + expect(statements).toStrictEqual([ + { + policies: [ + 'test--PERMISSIVE--ALL--public--undefined--undefined--undefined', + ], + schema: '', + tableName: 'users2', + type: 'drop_table', + }, + ]); +}); + +test('add policy with multiple "to" roles', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const role = pgRole('manager').existing(); + + const schema2 = { + role, + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { to: ['current_role', role] }), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR ALL TO current_role, "manager";', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'enable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: undefined, + to: ['current_role', 'manager'], + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('create table with rls enabled', async (t) => { + const schema1 = {}; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }).enableRLS(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + `CREATE TABLE "users" (\n\t"id" integer PRIMARY KEY NOT NULL\n); +`, + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + ]); +}); + +test('enable rls force', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }).enableRLS(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;']); +}); + +test('disable rls force', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }).enableRLS(), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['ALTER TABLE "users" DISABLE ROW LEVEL SECURITY;']); +}); + +test('drop policy with enabled rls', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { to: ['current_role', role] }), + })).enableRLS(), + }; + + const role = pgRole('manager').existing(); + + const schema2 = { + role, + users: pgTable('users', { + id: integer('id').primaryKey(), + }).enableRLS(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + ]); +}); + +test('add policy with enabled rls', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }).enableRLS(), + }; + + const role = pgRole('manager').existing(); + + const schema2 = { + role, + users: pgTable('users', { + id: integer('id').primaryKey(), + }, () => ({ + rls: pgPolicy('test', { to: ['current_role', role] }), + })).enableRLS(), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR ALL TO current_role, "manager";', + ]); +}); + +test('add policy + link table', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema2 = { + users, + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'enable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('link table', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + rls: pgPolicy('test', { as: 'permissive' }), + }; + + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema2 = { + users, + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'enable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('unlink table', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + users, + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const schema2 = { + users, + rls: pgPolicy('test', { as: 'permissive' }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" DISABLE ROW LEVEL SECURITY;', + 'DROP POLICY "test" ON "users" CASCADE;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'disable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + ]); +}); + +test('drop policy with link', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + users, + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const schema2 = { + users, + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" DISABLE ROW LEVEL SECURITY;', + 'DROP POLICY "test" ON "users" CASCADE;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'disable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + ]); +}); + +test('add policy in table and with link table', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + const users = pgTable('users', { + id: integer('id').primaryKey(), + }, () => [ + pgPolicy('test1', { to: 'current_user' }), + ]); + + const schema2 = { + users, + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test1" ON "users" AS PERMISSIVE FOR ALL TO current_user;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'enable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test1', + to: ['current_user'], + on: undefined, + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + to: ['public'], + using: undefined, + on: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); + +test('link non-schema table', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = {}; + + const schema2 = { + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'CREATE POLICY "test" ON "public"."users" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + tableName: '"public"."users"', + type: 'create_ind_policy', + }, + ]); +}); + +test('unlink non-schema table', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const schema2 = { + rls: pgPolicy('test', { as: 'permissive' }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "public"."users" CASCADE;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + tableName: '"public"."users"', + type: 'drop_ind_policy', + }, + ]); +}); + +test('add policy + link non-schema table', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const cities = pgTable('cities', { + id: integer('id').primaryKey(), + }); + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test2'), + ]), + rls: pgPolicy('test', { as: 'permissive' }).link(cities), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test2" ON "users" AS PERMISSIVE FOR ALL TO public;', + 'CREATE POLICY "test" ON "public"."cities" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'enable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test2', + on: undefined, + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."cities"', + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + tableName: '"public"."cities"', + type: 'create_ind_policy', + }, + ]); +}); + +test('add policy + link non-schema table from auth schema', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }), + }; + + const authSchema = pgSchema('auth'); + + const cities = authSchema.table('cities', { + id: integer('id').primaryKey(), + }); + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test2'), + ]), + rls: pgPolicy('test', { as: 'permissive' }).link(cities), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;', + 'CREATE POLICY "test2" ON "users" AS PERMISSIVE FOR ALL TO public;', + 'CREATE POLICY "test" ON "auth"."cities" AS PERMISSIVE FOR ALL TO public;', + ]); + expect(statements).toStrictEqual([ + { + schema: '', + tableName: 'users', + type: 'enable_rls', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test2', + on: undefined, + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + { + data: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"auth"."cities"', + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + tableName: '"auth"."cities"', + type: 'create_ind_policy', + }, + ]); +}); + +test('rename policy that is linked', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const schema2 = { + rls: pgPolicy('newName', { as: 'permissive' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, [ + '"public"."users".test->"public"."users".newName', + ]); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "public"."users" RENAME TO "newName";', + ]); + expect(statements).toStrictEqual([ + { + newName: 'newName', + oldName: 'test', + tableKey: '"public"."users"', + type: 'rename_ind_policy', + }, + ]); +}); + +test('alter policy that is linked', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive' }).link(users), + }; + + const schema2 = { + rls: pgPolicy('test', { as: 'permissive', to: 'current_role' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "public"."users" TO current_role;', + ]); + expect(statements).toStrictEqual([ + { + newData: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."users"', + to: [ + 'current_role', + ], + using: undefined, + withCheck: undefined, + }, + oldData: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + type: 'alter_ind_policy', + }, + ]); +}); + +test('alter policy that is linked: withCheck', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive', withCheck: sql`true` }).link(users), + }; + + const schema2 = { + rls: pgPolicy('test', { as: 'permissive', withCheck: sql`false` }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "public"."users" TO public WITH CHECK (false);', + ]); + expect(statements).toStrictEqual([ + { + newData: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: undefined, + withCheck: 'false', + }, + oldData: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: undefined, + withCheck: 'true', + }, + type: 'alter_ind_policy', + }, + ]); +}); + +test('alter policy that is linked: using', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + rls: pgPolicy('test', { as: 'permissive', using: sql`true` }).link(users), + }; + + const schema2 = { + rls: pgPolicy('test', { as: 'permissive', using: sql`false` }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "public"."users" TO public USING (false);', + ]); + expect(statements).toStrictEqual([ + { + newData: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: 'false', + withCheck: undefined, + }, + oldData: { + as: 'PERMISSIVE', + for: 'ALL', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: 'true', + withCheck: undefined, + }, + type: 'alter_ind_policy', + }, + ]); +}); + +test('alter policy that is linked: using', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + rls: pgPolicy('test', { for: 'insert' }).link(users), + }; + + const schema2 = { + rls: pgPolicy('test', { for: 'delete' }).link(users), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "public"."users" CASCADE;', + 'CREATE POLICY "test" ON "public"."users" AS PERMISSIVE FOR DELETE TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'INSERT', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + tableName: '"public"."users"', + type: 'drop_ind_policy', + }, + { + data: { + as: 'PERMISSIVE', + for: 'DELETE', + name: 'test', + on: '"public"."users"', + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + tableName: '"public"."users"', + type: 'create_ind_policy', + }, + ]); +}); + +//// + +test('alter policy in the table', async (t) => { + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test', { as: 'permissive' }), + ]), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test', { as: 'permissive', to: 'current_role' }), + ]), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" TO current_role;', + ]); + expect(statements).toStrictEqual([ + { + newData: 'test--PERMISSIVE--ALL--current_role--undefined--undefined--undefined', + oldData: 'test--PERMISSIVE--ALL--public--undefined--undefined--undefined', + schema: '', + tableName: 'users', + type: 'alter_policy', + }, + ]); +}); + +test('alter policy in the table: withCheck', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test', { as: 'permissive', withCheck: sql`true` }), + ]), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test', { as: 'permissive', withCheck: sql`false` }), + ]), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" TO public WITH CHECK (false);', + ]); + expect(statements).toStrictEqual([ + { + newData: 'test--PERMISSIVE--ALL--public--undefined--false--undefined', + oldData: 'test--PERMISSIVE--ALL--public--undefined--true--undefined', + schema: '', + tableName: 'users', + type: 'alter_policy', + }, + ]); +}); + +test('alter policy in the table: using', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test', { as: 'permissive', using: sql`true` }), + ]), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test', { as: 'permissive', using: sql`false` }), + ]), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER POLICY "test" ON "users" TO public USING (false);', + ]); + expect(statements).toStrictEqual([ + { + newData: 'test--PERMISSIVE--ALL--public--false--undefined--undefined', + oldData: 'test--PERMISSIVE--ALL--public--true--undefined--undefined', + schema: '', + tableName: 'users', + type: 'alter_policy', + }, + ]); +}); + +test('alter policy in the table: using', async (t) => { + const users = pgTable('users', { + id: integer('id').primaryKey(), + }); + + const schema1 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test', { for: 'insert' }), + ]), + }; + + const schema2 = { + users: pgTable('users', { + id: integer('id').primaryKey(), + }, (t) => [ + pgPolicy('test', { for: 'delete' }), + ]), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual([ + 'DROP POLICY "test" ON "users" CASCADE;', + 'CREATE POLICY "test" ON "users" AS PERMISSIVE FOR DELETE TO public;', + ]); + expect(statements).toStrictEqual([ + { + data: { + as: 'PERMISSIVE', + for: 'INSERT', + name: 'test', + on: undefined, + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'drop_policy', + }, + { + data: { + as: 'PERMISSIVE', + for: 'DELETE', + name: 'test', + on: undefined, + to: [ + 'public', + ], + using: undefined, + withCheck: undefined, + }, + schema: '', + tableName: 'users', + type: 'create_policy', + }, + ]); +}); diff --git a/drizzle-kit/tests/rls/pg-role.test.ts b/drizzle-kit/tests/rls/pg-role.test.ts new file mode 100644 index 000000000..a6b762955 --- /dev/null +++ b/drizzle-kit/tests/rls/pg-role.test.ts @@ -0,0 +1,234 @@ +import { pgRole } from 'drizzle-orm/pg-core'; +import { diffTestSchemas } from 'tests/schemaDiffer'; +import { expect, test } from 'vitest'; + +test('create role', async (t) => { + const schema1 = {}; + + const schema2 = { + manager: pgRole('manager'), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['CREATE ROLE "manager";']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'create_role', + values: { + createDb: false, + createRole: false, + inherit: true, + }, + }, + ]); +}); + +test('create role with properties', async (t) => { + const schema1 = {}; + + const schema2 = { + manager: pgRole('manager', { createDb: true, inherit: false, createRole: true }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['CREATE ROLE "manager" WITH CREATEDB CREATEROLE NOINHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'create_role', + values: { + createDb: true, + createRole: true, + inherit: false, + }, + }, + ]); +}); + +test('create role with some properties', async (t) => { + const schema1 = {}; + + const schema2 = { + manager: pgRole('manager', { createDb: true, inherit: false }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['CREATE ROLE "manager" WITH CREATEDB NOINHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'create_role', + values: { + createDb: true, + createRole: false, + inherit: false, + }, + }, + ]); +}); + +test('drop role', async (t) => { + const schema1 = { manager: pgRole('manager') }; + + const schema2 = {}; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['DROP ROLE "manager";']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'drop_role', + }, + ]); +}); + +test('create and drop role', async (t) => { + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + admin: pgRole('admin'), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['DROP ROLE "manager";', 'CREATE ROLE "admin";']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'drop_role', + }, + { + name: 'admin', + type: 'create_role', + values: { + createDb: false, + createRole: false, + inherit: true, + }, + }, + ]); +}); + +test('rename role', async (t) => { + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + admin: pgRole('admin'), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, ['manager->admin']); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" RENAME TO "admin";']); + expect(statements).toStrictEqual([ + { nameFrom: 'manager', nameTo: 'admin', type: 'rename_role' }, + ]); +}); + +test('alter all role field', async (t) => { + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + manager: pgRole('manager', { createDb: true, createRole: true, inherit: false }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" WITH CREATEDB CREATEROLE NOINHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'alter_role', + values: { + createDb: true, + createRole: true, + inherit: false, + }, + }, + ]); +}); + +test('alter createdb in role', async (t) => { + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + manager: pgRole('manager', { createDb: true }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" WITH CREATEDB NOCREATEROLE INHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'alter_role', + values: { + createDb: true, + createRole: false, + inherit: true, + }, + }, + ]); +}); + +test('alter createrole in role', async (t) => { + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + manager: pgRole('manager', { createRole: true }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" WITH NOCREATEDB CREATEROLE INHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'alter_role', + values: { + createDb: false, + createRole: true, + inherit: true, + }, + }, + ]); +}); + +test('alter inherit in role', async (t) => { + const schema1 = { + manager: pgRole('manager'), + }; + + const schema2 = { + manager: pgRole('manager', { inherit: false }), + }; + + const { statements, sqlStatements } = await diffTestSchemas(schema1, schema2, []); + + expect(sqlStatements).toStrictEqual(['ALTER ROLE "manager" WITH NOCREATEDB NOCREATEROLE NOINHERIT;']); + expect(statements).toStrictEqual([ + { + name: 'manager', + type: 'alter_role', + values: { + createDb: false, + createRole: false, + inherit: false, + }, + }, + ]); +}); diff --git a/drizzle-kit/tests/schemaDiffer.ts b/drizzle-kit/tests/schemaDiffer.ts index 4a14d920b..9c7f212aa 100644 --- a/drizzle-kit/tests/schemaDiffer.ts +++ b/drizzle-kit/tests/schemaDiffer.ts @@ -1,55 +1,108 @@ import { PGlite } from '@electric-sql/pglite'; +import { Client } from '@libsql/client/.'; import { Database } from 'better-sqlite3'; import { is } from 'drizzle-orm'; -import { MySqlSchema, MySqlTable } from 'drizzle-orm/mysql-core'; -import { isPgEnum, isPgSequence, PgEnum, PgSchema, PgSequence, PgTable } from 'drizzle-orm/pg-core'; -import { SQLiteTable } from 'drizzle-orm/sqlite-core'; +import { MySqlSchema, MySqlTable, MySqlView } from 'drizzle-orm/mysql-core'; +import { + getMaterializedViewConfig, + isPgEnum, + isPgMaterializedView, + isPgSequence, + isPgView, + PgEnum, + PgMaterializedView, + PgPolicy, + PgRole, + PgSchema, + PgSequence, + PgTable, + PgView, +} from 'drizzle-orm/pg-core'; +import { SingleStoreSchema, SingleStoreTable } from 'drizzle-orm/singlestore-core'; +import { SQLiteTable, SQLiteView } from 'drizzle-orm/sqlite-core'; import * as fs from 'fs'; import { Connection } from 'mysql2/promise'; +import { libSqlLogSuggestionsAndReturn } from 'src/cli/commands/libSqlPushUtils'; import { columnsResolver, enumsResolver, + indPolicyResolver, + mySqlViewsResolver, Named, + policyResolver, + roleResolver, schemasResolver, sequencesResolver, + sqliteViewsResolver, tablesResolver, + viewsResolver, } from 'src/cli/commands/migrate'; +import { pgSuggestions } from 'src/cli/commands/pgPushUtils'; import { logSuggestionsAndReturn } from 'src/cli/commands/sqlitePushUtils'; +import { Entities } from 'src/cli/validations/cli'; +import { CasingType } from 'src/cli/validations/common'; import { schemaToTypeScript as schemaToTypeScriptMySQL } from 'src/introspect-mysql'; import { schemaToTypeScript } from 'src/introspect-pg'; +import { schemaToTypeScript as schemaToTypeScriptSingleStore } from 'src/introspect-singlestore'; import { schemaToTypeScript as schemaToTypeScriptSQLite } from 'src/introspect-sqlite'; import { prepareFromMySqlImports } from 'src/serializer/mysqlImports'; -import { mysqlSchema, squashMysqlScheme } from 'src/serializer/mysqlSchema'; -import { generateMySqlSnapshot } from 'src/serializer/mysqlSerializer'; -import { fromDatabase as fromMySqlDatabase } from 'src/serializer/mysqlSerializer'; +import { mysqlSchema, squashMysqlScheme, ViewSquashed } from 'src/serializer/mysqlSchema'; +import { fromDatabase as fromMySqlDatabase, generateMySqlSnapshot } from 'src/serializer/mysqlSerializer'; import { prepareFromPgImports } from 'src/serializer/pgImports'; -import { pgSchema, squashPgScheme } from 'src/serializer/pgSchema'; +import { pgSchema, Policy, Role, squashPgScheme, View } from 'src/serializer/pgSchema'; import { fromDatabase, generatePgSnapshot } from 'src/serializer/pgSerializer'; +import { prepareFromSingleStoreImports } from 'src/serializer/singlestoreImports'; +import { singlestoreSchema, squashSingleStoreScheme } from 'src/serializer/singlestoreSchema'; +import { + fromDatabase as fromSingleStoreDatabase, + generateSingleStoreSnapshot, +} from 'src/serializer/singlestoreSerializer'; import { prepareFromSqliteImports } from 'src/serializer/sqliteImports'; -import { sqliteSchema, squashSqliteScheme } from 'src/serializer/sqliteSchema'; -import { fromDatabase as fromSqliteDatabase } from 'src/serializer/sqliteSerializer'; -import { generateSqliteSnapshot } from 'src/serializer/sqliteSerializer'; +import { sqliteSchema, squashSqliteScheme, View as SqliteView } from 'src/serializer/sqliteSchema'; +import { fromDatabase as fromSqliteDatabase, generateSqliteSnapshot } from 'src/serializer/sqliteSerializer'; import { + applyLibSQLSnapshotsDiff, applyMysqlSnapshotsDiff, applyPgSnapshotsDiff, + applySingleStoreSnapshotsDiff, applySqliteSnapshotsDiff, Column, ColumnsResolverInput, ColumnsResolverOutput, Enum, + PolicyResolverInput, + PolicyResolverOutput, ResolverInput, ResolverOutput, ResolverOutputWithMoved, + RolesResolverInput, + RolesResolverOutput, Sequence, Table, + TablePolicyResolverInput, + TablePolicyResolverOutput, } from 'src/snapshotsDiffer'; export type PostgresSchema = Record< string, - PgTable | PgEnum | PgSchema | PgSequence + | PgTable + | PgEnum + | PgSchema + | PgSequence + | PgView + | PgMaterializedView + | PgRole + | PgPolicy +>; +export type MysqlSchema = Record< + string, + MySqlTable | MySqlSchema | MySqlView +>; +export type SqliteSchema = Record | SQLiteView>; +export type SinglestoreSchema = Record< + string, + SingleStoreTable | SingleStoreSchema /* | SingleStoreView */ >; -export type MysqlSchema = Record | MySqlSchema>; -export type SqliteSchema = Record>; export const testSchemasResolver = (renames: Set) => async (input: ResolverInput): Promise> => { @@ -402,265 +455,1182 @@ async ( } }; -export const diffTestSchemasPush = async ( - client: PGlite, - left: PostgresSchema, - right: PostgresSchema, - renamesArr: string[], - cli: boolean = false, - schemas: string[] = ['public'], -) => { - const { sqlStatements } = await applyPgDiffs(left); - for (const st of sqlStatements) { - await client.query(st); - } - - // do introspect into PgSchemaInternal - const introspectedSchema = await fromDatabase( - { - query: async (query: string, values?: any[] | undefined) => { - const res = await client.query(query, values); - return res.rows as any[]; - }, - }, - undefined, - schemas, - ); - - const leftTables = Object.values(right).filter((it) => is(it, PgTable)) as PgTable[]; - - const leftSchemas = Object.values(right).filter((it) => is(it, PgSchema)) as PgSchema[]; +export const testPolicyResolver = (renames: Set) => +async ( + input: TablePolicyResolverInput, +): Promise> => { + try { + if ( + input.created.length === 0 + || input.deleted.length === 0 + || renames.size === 0 + ) { + return { + tableName: input.tableName, + schema: input.schema, + created: input.created, + renamed: [], + deleted: input.deleted, + }; + } - const leftEnums = Object.values(right).filter((it) => isPgEnum(it)) as PgEnum[]; + let createdPolicies = [...input.created]; + let deletedPolicies = [...input.deleted]; - const leftSequences = Object.values(right).filter((it) => isPgSequence(it)) as PgSequence[]; + const renamed: { from: Policy; to: Policy }[] = []; - const serialized2 = generatePgSnapshot( - leftTables, - leftEnums, - leftSchemas, - leftSequences, - ); + const schema = input.schema || 'public'; - const { version: v1, dialect: d1, ...rest1 } = introspectedSchema; - const { version: v2, dialect: d2, ...rest2 } = serialized2; + for (let rename of renames) { + const [from, to] = rename.split('->'); - const sch1 = { - version: '7', - dialect: 'postgresql', - id: '0', - prevId: '0', - ...rest1, - } as const; + const idxFrom = deletedPolicies.findIndex((it) => { + return `${schema}.${input.tableName}.${it.name}` === from; + }); - const sch2 = { - version: '7', - dialect: 'postgresql', - id: '0', - prevId: '0', - ...rest2, - } as const; + if (idxFrom >= 0) { + const idxTo = createdPolicies.findIndex((it) => { + return `${schema}.${input.tableName}.${it.name}` === to; + }); - const sn1 = squashPgScheme(sch1, 'push'); - const sn2 = squashPgScheme(sch2, 'push'); + renamed.push({ + from: deletedPolicies[idxFrom], + to: createdPolicies[idxTo], + }); - const validatedPrev = pgSchema.parse(sch1); - const validatedCur = pgSchema.parse(sch2); + delete createdPolicies[idxTo]; + delete deletedPolicies[idxFrom]; - const renames = new Set(renamesArr); + createdPolicies = createdPolicies.filter(Boolean); + deletedPolicies = deletedPolicies.filter(Boolean); + } + } - if (!cli) { - const { sqlStatements, statements } = await applyPgSnapshotsDiff( - sn1, - sn2, - testSchemasResolver(renames), - testEnumsResolver(renames), - testSequencesResolver(renames), - testTablesResolver(renames), - testColumnsResolver(renames), - validatedPrev, - validatedCur, - 'push', - ); - return { sqlStatements, statements }; - } else { - const { sqlStatements, statements } = await applyPgSnapshotsDiff( - sn1, - sn2, - schemasResolver, - enumsResolver, - sequencesResolver, - tablesResolver, - columnsResolver, - validatedPrev, - validatedCur, - 'push', - ); - return { sqlStatements, statements }; + return { + tableName: input.tableName, + schema: input.schema, + created: createdPolicies, + deleted: deletedPolicies, + renamed, + }; + } catch (e) { + console.error(e); + throw e; } }; -export const applyPgDiffs = async (sn: PostgresSchema) => { - const dryRun = { - version: '7', - dialect: 'postgresql', - id: '0', - prevId: '0', - tables: {}, - enums: {}, - schemas: {}, - sequences: {}, - _meta: { - schemas: {}, - tables: {}, - columns: {}, - }, - } as const; - - const tables = Object.values(sn).filter((it) => is(it, PgTable)) as PgTable[]; +export const testIndPolicyResolver = (renames: Set) => +async ( + input: PolicyResolverInput, +): Promise> => { + try { + if ( + input.created.length === 0 + || input.deleted.length === 0 + || renames.size === 0 + ) { + return { + created: input.created, + renamed: [], + deleted: input.deleted, + }; + } - const schemas = Object.values(sn).filter((it) => is(it, PgSchema)) as PgSchema[]; + let createdPolicies = [...input.created]; + let deletedPolicies = [...input.deleted]; - const enums = Object.values(sn).filter((it) => isPgEnum(it)) as PgEnum[]; + const renamed: { from: Policy; to: Policy }[] = []; - const sequences = Object.values(sn).filter((it) => isPgSequence(it)) as PgSequence[]; + for (let rename of renames) { + const [from, to] = rename.split('->'); - const serialized1 = generatePgSnapshot(tables, enums, schemas, sequences); + const idxFrom = deletedPolicies.findIndex((it) => { + return `${it.on}.${it.name}` === from; + }); - const { version: v1, dialect: d1, ...rest1 } = serialized1; + if (idxFrom >= 0) { + const idxTo = createdPolicies.findIndex((it) => { + return `${it.on}.${it.name}` === to; + }); - const sch1 = { - version: '7', - dialect: 'postgresql', - id: '0', - prevId: '0', - ...rest1, - } as const; + renamed.push({ + from: deletedPolicies[idxFrom], + to: createdPolicies[idxTo], + }); - const sn1 = squashPgScheme(sch1); + delete createdPolicies[idxTo]; + delete deletedPolicies[idxFrom]; - const validatedPrev = pgSchema.parse(dryRun); - const validatedCur = pgSchema.parse(sch1); + createdPolicies = createdPolicies.filter(Boolean); + deletedPolicies = deletedPolicies.filter(Boolean); + } + } - const { sqlStatements, statements } = await applyPgSnapshotsDiff( - dryRun, - sn1, - testSchemasResolver(new Set()), - testEnumsResolver(new Set()), - testSequencesResolver(new Set()), - testTablesResolver(new Set()), - testColumnsResolver(new Set()), - validatedPrev, - validatedCur, - ); - return { sqlStatements, statements }; + return { + created: createdPolicies, + deleted: deletedPolicies, + renamed, + }; + } catch (e) { + console.error(e); + throw e; + } }; -export const diffTestSchemas = async ( - left: PostgresSchema, - right: PostgresSchema, - renamesArr: string[], - cli: boolean = false, -) => { - const leftTables = Object.values(left).filter((it) => is(it, PgTable)) as PgTable[]; +export const testRolesResolver = (renames: Set) => +async ( + input: RolesResolverInput, +): Promise> => { + try { + if ( + input.created.length === 0 + || input.deleted.length === 0 + || renames.size === 0 + ) { + return { + created: input.created, + renamed: [], + deleted: input.deleted, + }; + } - const rightTables = Object.values(right).filter((it) => is(it, PgTable)) as PgTable[]; + let createdPolicies = [...input.created]; + let deletedPolicies = [...input.deleted]; - const leftSchemas = Object.values(left).filter((it) => is(it, PgSchema)) as PgSchema[]; + const renamed: { from: Policy; to: Policy }[] = []; - const rightSchemas = Object.values(right).filter((it) => is(it, PgSchema)) as PgSchema[]; + for (let rename of renames) { + const [from, to] = rename.split('->'); - const leftEnums = Object.values(left).filter((it) => isPgEnum(it)) as PgEnum[]; + const idxFrom = deletedPolicies.findIndex((it) => { + return `${it.name}` === from; + }); - const rightEnums = Object.values(right).filter((it) => isPgEnum(it)) as PgEnum[]; + if (idxFrom >= 0) { + const idxTo = createdPolicies.findIndex((it) => { + return `${it.name}` === to; + }); - const leftSequences = Object.values(left).filter((it) => isPgSequence(it)) as PgSequence[]; + renamed.push({ + from: deletedPolicies[idxFrom], + to: createdPolicies[idxTo], + }); - const rightSequences = Object.values(right).filter((it) => isPgSequence(it)) as PgSequence[]; + delete createdPolicies[idxTo]; + delete deletedPolicies[idxFrom]; - const serialized1 = generatePgSnapshot( - leftTables, - leftEnums, - leftSchemas, - leftSequences, - ); - const serialized2 = generatePgSnapshot( - rightTables, - rightEnums, - rightSchemas, - rightSequences, - ); + createdPolicies = createdPolicies.filter(Boolean); + deletedPolicies = deletedPolicies.filter(Boolean); + } + } - const { version: v1, dialect: d1, ...rest1 } = serialized1; - const { version: v2, dialect: d2, ...rest2 } = serialized2; + return { + created: createdPolicies, + deleted: deletedPolicies, + renamed, + }; + } catch (e) { + console.error(e); + throw e; + } +}; - const sch1 = { +export const testViewsResolver = (renames: Set) => +async ( + input: ResolverInput, +): Promise> => { + try { + if ( + input.created.length === 0 + || input.deleted.length === 0 + || renames.size === 0 + ) { + return { + created: input.created, + moved: [], + renamed: [], + deleted: input.deleted, + }; + } + + let createdViews = [...input.created]; + let deletedViews = [...input.deleted]; + + const result: { + created: View[]; + moved: { name: string; schemaFrom: string; schemaTo: string }[]; + renamed: { from: View; to: View }[]; + deleted: View[]; + } = { created: [], renamed: [], deleted: [], moved: [] }; + + for (let rename of renames) { + const [from, to] = rename.split('->'); + + const idxFrom = deletedViews.findIndex((it) => { + return `${it.schema || 'public'}.${it.name}` === from; + }); + + if (idxFrom >= 0) { + const idxTo = createdViews.findIndex((it) => { + return `${it.schema || 'public'}.${it.name}` === to; + }); + + const viewFrom = deletedViews[idxFrom]; + const viewTo = createdViews[idxFrom]; + + if (viewFrom.schema !== viewTo.schema) { + result.moved.push({ + name: viewFrom.name, + schemaFrom: viewFrom.schema, + schemaTo: viewTo.schema, + }); + } + + if (viewFrom.name !== viewTo.name) { + result.renamed.push({ + from: deletedViews[idxFrom], + to: createdViews[idxTo], + }); + } + + delete createdViews[idxTo]; + delete deletedViews[idxFrom]; + + createdViews = createdViews.filter(Boolean); + deletedViews = deletedViews.filter(Boolean); + } + } + + result.created = createdViews; + result.deleted = deletedViews; + + return result; + } catch (e) { + console.error(e); + throw e; + } +}; + +export const testViewsResolverMySql = (renames: Set) => +async ( + input: ResolverInput, +): Promise> => { + try { + if ( + input.created.length === 0 + || input.deleted.length === 0 + || renames.size === 0 + ) { + return { + created: input.created, + moved: [], + renamed: [], + deleted: input.deleted, + }; + } + + let createdViews = [...input.created]; + let deletedViews = [...input.deleted]; + + const result: { + created: ViewSquashed[]; + moved: { name: string; schemaFrom: string; schemaTo: string }[]; + renamed: { from: ViewSquashed; to: ViewSquashed }[]; + deleted: ViewSquashed[]; + } = { created: [], renamed: [], deleted: [], moved: [] }; + + for (let rename of renames) { + const [from, to] = rename.split('->'); + + const idxFrom = deletedViews.findIndex((it) => { + return `${it.schema || 'public'}.${it.name}` === from; + }); + + if (idxFrom >= 0) { + const idxTo = createdViews.findIndex((it) => { + return `${it.schema || 'public'}.${it.name}` === to; + }); + + const viewFrom = deletedViews[idxFrom]; + const viewTo = createdViews[idxFrom]; + + if (viewFrom.schema !== viewTo.schema) { + result.moved.push({ + name: viewFrom.name, + schemaFrom: viewFrom.schema, + schemaTo: viewTo.schema, + }); + } + + if (viewFrom.name !== viewTo.name) { + result.renamed.push({ + from: deletedViews[idxFrom], + to: createdViews[idxTo], + }); + } + + delete createdViews[idxTo]; + delete deletedViews[idxFrom]; + + createdViews = createdViews.filter(Boolean); + deletedViews = deletedViews.filter(Boolean); + } + } + + result.created = createdViews; + result.deleted = deletedViews; + + return result; + } catch (e) { + console.error(e); + throw e; + } +}; + +export const testViewsResolverSingleStore = (renames: Set) => +async ( + input: ResolverInput, +): Promise> => { + try { + if ( + input.created.length === 0 + || input.deleted.length === 0 + || renames.size === 0 + ) { + return { + created: input.created, + moved: [], + renamed: [], + deleted: input.deleted, + }; + } + + let createdViews = [...input.created]; + let deletedViews = [...input.deleted]; + + const result: { + created: ViewSquashed[]; + moved: { name: string; schemaFrom: string; schemaTo: string }[]; + renamed: { from: ViewSquashed; to: ViewSquashed }[]; + deleted: ViewSquashed[]; + } = { created: [], renamed: [], deleted: [], moved: [] }; + + for (let rename of renames) { + const [from, to] = rename.split('->'); + + const idxFrom = deletedViews.findIndex((it) => { + return `${it.schema || 'public'}.${it.name}` === from; + }); + + if (idxFrom >= 0) { + const idxTo = createdViews.findIndex((it) => { + return `${it.schema || 'public'}.${it.name}` === to; + }); + + const viewFrom = deletedViews[idxFrom]; + const viewTo = createdViews[idxFrom]; + + if (viewFrom.schema !== viewTo.schema) { + result.moved.push({ + name: viewFrom.name, + schemaFrom: viewFrom.schema, + schemaTo: viewTo.schema, + }); + } + + if (viewFrom.name !== viewTo.name) { + result.renamed.push({ + from: deletedViews[idxFrom], + to: createdViews[idxTo], + }); + } + + delete createdViews[idxTo]; + delete deletedViews[idxFrom]; + + createdViews = createdViews.filter(Boolean); + deletedViews = deletedViews.filter(Boolean); + } + } + + result.created = createdViews; + result.deleted = deletedViews; + + return result; + } catch (e) { + console.error(e); + throw e; + } +}; + +export const testViewsResolverSqlite = (renames: Set) => +async ( + input: ResolverInput, +): Promise> => { + try { + if ( + input.created.length === 0 + || input.deleted.length === 0 + || renames.size === 0 + ) { + return { + created: input.created, + moved: [], + renamed: [], + deleted: input.deleted, + }; + } + + let createdViews = [...input.created]; + let deletedViews = [...input.deleted]; + + const result: { + created: SqliteView[]; + moved: { name: string; schemaFrom: string; schemaTo: string }[]; + renamed: { from: SqliteView; to: SqliteView }[]; + deleted: SqliteView[]; + } = { created: [], renamed: [], deleted: [], moved: [] }; + + for (let rename of renames) { + const [from, to] = rename.split('->'); + + const idxFrom = deletedViews.findIndex((it) => { + return it.name === from; + }); + + if (idxFrom >= 0) { + const idxTo = createdViews.findIndex((it) => { + return it.name === to; + }); + + const viewFrom = deletedViews[idxFrom]; + const viewTo = createdViews[idxFrom]; + + if (viewFrom.name !== viewTo.name) { + result.renamed.push({ + from: deletedViews[idxFrom], + to: createdViews[idxTo], + }); + } + + delete createdViews[idxTo]; + delete deletedViews[idxFrom]; + + createdViews = createdViews.filter(Boolean); + deletedViews = deletedViews.filter(Boolean); + } + } + + result.created = createdViews; + result.deleted = deletedViews; + + return result; + } catch (e) { + console.error(e); + throw e; + } +}; + +export const diffTestSchemasPush = async ( + client: PGlite, + left: PostgresSchema, + right: PostgresSchema, + renamesArr: string[], + cli: boolean = false, + schemas: string[] = ['public'], + casing?: CasingType | undefined, + entities?: Entities, + sqlStatementsToRun: { + before?: string[]; + after?: string[]; + runApply?: boolean; + } = { + before: [], + after: [], + runApply: true, + }, +) => { + const shouldRunApply = sqlStatementsToRun.runApply === undefined + ? true + : sqlStatementsToRun.runApply; + + for (const st of sqlStatementsToRun.before ?? []) { + await client.query(st); + } + + if (shouldRunApply) { + const res = await applyPgDiffs(left, casing); + for (const st of res.sqlStatements) { + await client.query(st); + } + } + + for (const st of sqlStatementsToRun.after ?? []) { + await client.query(st); + } + + const materializedViewsForRefresh = Object.values(left).filter((it) => + isPgMaterializedView(it) + ) as PgMaterializedView[]; + + // refresh all mat views + for (const view of materializedViewsForRefresh) { + const viewConf = getMaterializedViewConfig(view); + if (viewConf.isExisting) continue; + + await client.exec( + `REFRESH MATERIALIZED VIEW "${viewConf.schema ?? 'public'}"."${viewConf.name}"${ + viewConf.withNoData ? ' WITH NO DATA;' : ';' + }`, + ); + } + + // do introspect into PgSchemaInternal + const introspectedSchema = await fromDatabase( + { + query: async (query: string, values?: any[] | undefined) => { + const res = await client.query(query, values); + return res.rows as any[]; + }, + }, + undefined, + schemas, + entities, + ); + + const leftTables = Object.values(right).filter((it) => is(it, PgTable)) as PgTable[]; + + const leftSchemas = Object.values(right).filter((it) => is(it, PgSchema)) as PgSchema[]; + + const leftEnums = Object.values(right).filter((it) => isPgEnum(it)) as PgEnum[]; + + const leftSequences = Object.values(right).filter((it) => isPgSequence(it)) as PgSequence[]; + + const leftRoles = Object.values(right).filter((it) => is(it, PgRole)) as PgRole[]; + + const leftPolicies = Object.values(right).filter((it) => is(it, PgPolicy)) as PgPolicy[]; + + const leftViews = Object.values(right).filter((it) => isPgView(it)) as PgView[]; + + const leftMaterializedViews = Object.values(right).filter((it) => isPgMaterializedView(it)) as PgMaterializedView[]; + + const serialized2 = generatePgSnapshot( + leftTables, + leftEnums, + leftSchemas, + leftSequences, + leftRoles, + leftPolicies, + leftViews, + leftMaterializedViews, + casing, + ); + + const { version: v1, dialect: d1, ...rest1 } = introspectedSchema; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '7', + dialect: 'postgresql', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sch2 = { + version: '7', + dialect: 'postgresql', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn1 = squashPgScheme(sch1, 'push'); + const sn2 = squashPgScheme(sch2, 'push'); + + const validatedPrev = pgSchema.parse(sch1); + const validatedCur = pgSchema.parse(sch2); + + const renames = new Set(renamesArr); + + if (!cli) { + const { sqlStatements, statements } = await applyPgSnapshotsDiff( + sn1, + sn2, + testSchemasResolver(renames), + testEnumsResolver(renames), + testSequencesResolver(renames), + testPolicyResolver(renames), + testIndPolicyResolver(renames), + testRolesResolver(renames), + testTablesResolver(renames), + testColumnsResolver(renames), + testViewsResolver(renames), + validatedPrev, + validatedCur, + 'push', + ); + + const { + shouldAskForApprove, + statementsToExecute, + columnsToRemove, + tablesToRemove, + tablesToTruncate, + infoToPrint, + schemasToRemove, + matViewsToRemove, + } = await pgSuggestions( + { + query: async (sql: string, params: any[] = []) => { + return (await client.query(sql, params)).rows as T[]; + }, + }, + statements, + ); + + return { + sqlStatements: statementsToExecute, + statements, + shouldAskForApprove, + columnsToRemove, + tablesToRemove, + tablesToTruncate, + infoToPrint, + schemasToRemove, + matViewsToRemove, + }; + } else { + const { sqlStatements, statements } = await applyPgSnapshotsDiff( + sn1, + sn2, + schemasResolver, + enumsResolver, + sequencesResolver, + policyResolver, + indPolicyResolver, + roleResolver, + tablesResolver, + columnsResolver, + viewsResolver, + validatedPrev, + validatedCur, + 'push', + ); + return { sqlStatements, statements }; + } +}; + +export const applyPgDiffs = async ( + sn: PostgresSchema, + casing: CasingType | undefined, +) => { + const dryRun = { + version: '7', + dialect: 'postgresql', + id: '0', + prevId: '0', + tables: {}, + enums: {}, + views: {}, + schemas: {}, + sequences: {}, + policies: {}, + roles: {}, + _meta: { + schemas: {}, + tables: {}, + columns: {}, + }, + } as const; + + const tables = Object.values(sn).filter((it) => is(it, PgTable)) as PgTable[]; + + const schemas = Object.values(sn).filter((it) => is(it, PgSchema)) as PgSchema[]; + + const enums = Object.values(sn).filter((it) => isPgEnum(it)) as PgEnum[]; + + const sequences = Object.values(sn).filter((it) => isPgSequence(it)) as PgSequence[]; + + const roles = Object.values(sn).filter((it) => is(it, PgRole)) as PgRole[]; + + const views = Object.values(sn).filter((it) => isPgView(it)) as PgView[]; + + const policies = Object.values(sn).filter((it) => is(it, PgPolicy)) as PgPolicy[]; + + const materializedViews = Object.values(sn).filter((it) => isPgMaterializedView(it)) as PgMaterializedView[]; + + const serialized1 = generatePgSnapshot( + tables, + enums, + schemas, + sequences, + roles, + policies, + views, + materializedViews, + casing, + ); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + + const sch1 = { + version: '7', + dialect: 'postgresql', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sn1 = squashPgScheme(sch1); + + const validatedPrev = pgSchema.parse(dryRun); + const validatedCur = pgSchema.parse(sch1); + + const { sqlStatements, statements } = await applyPgSnapshotsDiff( + dryRun, + sn1, + testSchemasResolver(new Set()), + testEnumsResolver(new Set()), + testSequencesResolver(new Set()), + testPolicyResolver(new Set()), + testIndPolicyResolver(new Set()), + testRolesResolver(new Set()), + testTablesResolver(new Set()), + testColumnsResolver(new Set()), + testViewsResolver(new Set()), + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; +}; + +export const diffTestSchemas = async ( + left: PostgresSchema, + right: PostgresSchema, + renamesArr: string[], + cli: boolean = false, + casing?: CasingType | undefined, +) => { + const leftTables = Object.values(left).filter((it) => is(it, PgTable)) as PgTable[]; + + const rightTables = Object.values(right).filter((it) => is(it, PgTable)) as PgTable[]; + + const leftSchemas = Object.values(left).filter((it) => is(it, PgSchema)) as PgSchema[]; + + const rightSchemas = Object.values(right).filter((it) => is(it, PgSchema)) as PgSchema[]; + + const leftEnums = Object.values(left).filter((it) => isPgEnum(it)) as PgEnum[]; + + const rightEnums = Object.values(right).filter((it) => isPgEnum(it)) as PgEnum[]; + + const leftSequences = Object.values(left).filter((it) => isPgSequence(it)) as PgSequence[]; + + const rightSequences = Object.values(right).filter((it) => isPgSequence(it)) as PgSequence[]; + + const leftRoles = Object.values(left).filter((it) => is(it, PgRole)) as PgRole[]; + + const rightRoles = Object.values(right).filter((it) => is(it, PgRole)) as PgRole[]; + + const leftPolicies = Object.values(left).filter((it) => is(it, PgPolicy)) as PgPolicy[]; + + const rightPolicies = Object.values(right).filter((it) => is(it, PgPolicy)) as PgPolicy[]; + + const leftViews = Object.values(left).filter((it) => isPgView(it)) as PgView[]; + + const rightViews = Object.values(right).filter((it) => isPgView(it)) as PgView[]; + + const leftMaterializedViews = Object.values(left).filter((it) => isPgMaterializedView(it)) as PgMaterializedView[]; + + const rightMaterializedViews = Object.values(right).filter((it) => isPgMaterializedView(it)) as PgMaterializedView[]; + + const serialized1 = generatePgSnapshot( + leftTables, + leftEnums, + leftSchemas, + leftSequences, + leftRoles, + leftPolicies, + leftViews, + leftMaterializedViews, + casing, + ); + const serialized2 = generatePgSnapshot( + rightTables, + rightEnums, + rightSchemas, + rightSequences, + rightRoles, + rightPolicies, + rightViews, + rightMaterializedViews, + casing, + ); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '7', + dialect: 'postgresql', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sch2 = { version: '7', dialect: 'postgresql', id: '0', prevId: '0', + ...rest2, + } as const; + + const sn1 = squashPgScheme(sch1); + const sn2 = squashPgScheme(sch2); + + const validatedPrev = pgSchema.parse(sch1); + const validatedCur = pgSchema.parse(sch2); + + const renames = new Set(renamesArr); + + if (!cli) { + const { sqlStatements, statements } = await applyPgSnapshotsDiff( + sn1, + sn2, + testSchemasResolver(renames), + testEnumsResolver(renames), + testSequencesResolver(renames), + testPolicyResolver(renames), + testIndPolicyResolver(renames), + testRolesResolver(renames), + testTablesResolver(renames), + testColumnsResolver(renames), + testViewsResolver(renames), + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; + } else { + const { sqlStatements, statements } = await applyPgSnapshotsDiff( + sn1, + sn2, + schemasResolver, + enumsResolver, + sequencesResolver, + policyResolver, + indPolicyResolver, + roleResolver, + tablesResolver, + columnsResolver, + viewsResolver, + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; + } +}; + +export const diffTestSchemasPushMysql = async ( + client: Connection, + left: MysqlSchema, + right: MysqlSchema, + renamesArr: string[], + schema: string, + cli: boolean = false, + casing?: CasingType | undefined, +) => { + const { sqlStatements } = await applyMySqlDiffs(left, casing); + for (const st of sqlStatements) { + await client.query(st); + } + // do introspect into PgSchemaInternal + const introspectedSchema = await fromMySqlDatabase( + { + query: async (sql: string, params?: any[]) => { + const res = await client.execute(sql, params); + return res[0] as any; + }, + }, + schema, + ); + + const leftTables = Object.values(right).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + + const leftViews = Object.values(right).filter((it) => is(it, MySqlView)) as MySqlView[]; + + const serialized2 = generateMySqlSnapshot(leftTables, leftViews, casing); + + const { version: v1, dialect: d1, ...rest1 } = introspectedSchema; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '5', + dialect: 'mysql', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sch2 = { + version: '5', + dialect: 'mysql', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn1 = squashMysqlScheme(sch1); + const sn2 = squashMysqlScheme(sch2); + + const validatedPrev = mysqlSchema.parse(sch1); + const validatedCur = mysqlSchema.parse(sch2); + + const renames = new Set(renamesArr); + + if (!cli) { + const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + sn1, + sn2, + testTablesResolver(renames), + testColumnsResolver(renames), + testViewsResolverMySql(renames), + validatedPrev, + validatedCur, + 'push', + ); + return { sqlStatements, statements }; + } else { + const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + sn1, + sn2, + tablesResolver, + columnsResolver, + mySqlViewsResolver, + validatedPrev, + validatedCur, + 'push', + ); + return { sqlStatements, statements }; + } +}; + +export const applyMySqlDiffs = async ( + sn: MysqlSchema, + casing: CasingType | undefined, +) => { + const dryRun = { + version: '5', + dialect: 'mysql', + id: '0', + prevId: '0', + views: {}, + tables: {}, + enums: {}, + schemas: {}, + _meta: { + schemas: {}, + tables: {}, + columns: {}, + }, + } as const; + + const tables = Object.values(sn).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + + const views = Object.values(sn).filter((it) => is(it, MySqlView)) as MySqlView[]; + + const serialized1 = generateMySqlSnapshot(tables, views, casing); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + + const sch1 = { + version: '5', + dialect: 'mysql', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sn1 = squashMysqlScheme(sch1); + + const validatedPrev = mysqlSchema.parse(dryRun); + const validatedCur = mysqlSchema.parse(sch1); + + const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + dryRun, + sn1, + testTablesResolver(new Set()), + testColumnsResolver(new Set()), + testViewsResolverMySql(new Set()), + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; +}; + +export const diffTestSchemasMysql = async ( + left: MysqlSchema, + right: MysqlSchema, + renamesArr: string[], + cli: boolean = false, + casing?: CasingType | undefined, +) => { + const leftTables = Object.values(left).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + + const leftViews = Object.values(left).filter((it) => is(it, MySqlView)) as MySqlView[]; + + const rightTables = Object.values(right).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + + const rightViews = Object.values(right).filter((it) => is(it, MySqlView)) as MySqlView[]; + + const serialized1 = generateMySqlSnapshot(leftTables, leftViews, casing); + const serialized2 = generateMySqlSnapshot(rightTables, rightViews, casing); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '5', + dialect: 'mysql', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sch2 = { + version: '5', + dialect: 'mysql', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn1 = squashMysqlScheme(sch1); + const sn2 = squashMysqlScheme(sch2); + + const validatedPrev = mysqlSchema.parse(sch1); + const validatedCur = mysqlSchema.parse(sch2); + + const renames = new Set(renamesArr); + + if (!cli) { + const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + sn1, + sn2, + testTablesResolver(renames), + testColumnsResolver(renames), + testViewsResolverMySql(renames), + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; + } + + const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + sn1, + sn2, + tablesResolver, + columnsResolver, + mySqlViewsResolver, + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; +}; + +export const diffTestSchemasSingleStore = async ( + left: SinglestoreSchema, + right: SinglestoreSchema, + renamesArr: string[], + cli: boolean = false, + casing?: CasingType | undefined, +) => { + const leftTables = Object.values(left).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; + + /* const leftViews = Object.values(left).filter((it) => is(it, SingleStoreView)) as SingleStoreView[]; */ + + const rightTables = Object.values(right).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; + + /* const rightViews = Object.values(right).filter((it) => is(it, SingleStoreView)) as SingleStoreView[]; */ + + const serialized1 = generateSingleStoreSnapshot( + leftTables, + /* leftViews, */ + casing, + ); + const serialized2 = generateSingleStoreSnapshot( + rightTables, + /* rightViews, */ + casing, + ); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '1', + dialect: 'singlestore', + id: '0', + prevId: '0', ...rest1, } as const; const sch2 = { - version: '7', - dialect: 'postgresql', + version: '1', + dialect: 'singlestore', id: '0', prevId: '0', ...rest2, } as const; - const sn1 = squashPgScheme(sch1); - const sn2 = squashPgScheme(sch2); + const sn1 = squashSingleStoreScheme(sch1); + const sn2 = squashSingleStoreScheme(sch2); - const validatedPrev = pgSchema.parse(sch1); - const validatedCur = pgSchema.parse(sch2); + const validatedPrev = singlestoreSchema.parse(sch1); + const validatedCur = singlestoreSchema.parse(sch2); const renames = new Set(renamesArr); if (!cli) { - const { sqlStatements, statements } = await applyPgSnapshotsDiff( + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( sn1, sn2, - testSchemasResolver(renames), - testEnumsResolver(renames), - testSequencesResolver(renames), testTablesResolver(renames), testColumnsResolver(renames), - validatedPrev, - validatedCur, - ); - return { sqlStatements, statements }; - } else { - const { sqlStatements, statements } = await applyPgSnapshotsDiff( - sn1, - sn2, - schemasResolver, - enumsResolver, - sequencesResolver, - tablesResolver, - columnsResolver, + /* testViewsResolverSingleStore(renames), */ validatedPrev, validatedCur, ); return { sqlStatements, statements }; } + + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( + sn1, + sn2, + tablesResolver, + columnsResolver, + /* singleStoreViewsResolver, */ + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; }; -export const diffTestSchemasPushMysql = async ( +export const diffTestSchemasPushSingleStore = async ( client: Connection, - left: MysqlSchema, - right: MysqlSchema, + left: SinglestoreSchema, + right: SinglestoreSchema, renamesArr: string[], schema: string, cli: boolean = false, + casing?: CasingType | undefined, ) => { - const { sqlStatements } = await applyMySqlDiffs(left); + const { sqlStatements } = await applySingleStoreDiffs(left, casing); for (const st of sqlStatements) { await client.query(st); } // do introspect into PgSchemaInternal - const introspectedSchema = await fromMySqlDatabase( + const introspectedSchema = await fromSingleStoreDatabase( { query: async (sql: string, params?: any[]) => { const res = await client.execute(sql, params); @@ -670,54 +1640,62 @@ export const diffTestSchemasPushMysql = async ( schema, ); - const leftTables = Object.values(right).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + const leftTables = Object.values(right).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; - const serialized2 = generateMySqlSnapshot(leftTables); + /* const leftViews = Object.values(right).filter((it) => is(it, SingleStoreView)) as SingleStoreView[]; */ + + const serialized2 = generateSingleStoreSnapshot( + leftTables, + /* leftViews, */ + casing, + ); const { version: v1, dialect: d1, ...rest1 } = introspectedSchema; const { version: v2, dialect: d2, ...rest2 } = serialized2; const sch1 = { - version: '5', - dialect: 'mysql', + version: '1', + dialect: 'singlestore', id: '0', prevId: '0', ...rest1, } as const; const sch2 = { - version: '5', - dialect: 'mysql', + version: '1', + dialect: 'singlestore', id: '0', prevId: '0', ...rest2, } as const; - const sn1 = squashMysqlScheme(sch1); - const sn2 = squashMysqlScheme(sch2); + const sn1 = squashSingleStoreScheme(sch1); + const sn2 = squashSingleStoreScheme(sch2); - const validatedPrev = mysqlSchema.parse(sch1); - const validatedCur = mysqlSchema.parse(sch2); + const validatedPrev = singlestoreSchema.parse(sch1); + const validatedCur = singlestoreSchema.parse(sch2); const renames = new Set(renamesArr); if (!cli) { - const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( sn1, sn2, testTablesResolver(renames), testColumnsResolver(renames), + /* testViewsResolverSingleStore(renames), */ validatedPrev, validatedCur, 'push', ); return { sqlStatements, statements }; } else { - const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( sn1, sn2, tablesResolver, columnsResolver, + /* singleStoreViewsResolver, */ validatedPrev, validatedCur, 'push', @@ -726,13 +1704,17 @@ export const diffTestSchemasPushMysql = async ( } }; -export const applyMySqlDiffs = async (sn: MysqlSchema) => { +export const applySingleStoreDiffs = async ( + sn: SinglestoreSchema, + casing: CasingType | undefined, +) => { const dryRun = { - version: '5', - dialect: 'mysql', + version: '1', + dialect: 'singlestore', id: '0', prevId: '0', tables: {}, + views: {}, enums: {}, schemas: {}, _meta: { @@ -742,118 +1724,188 @@ export const applyMySqlDiffs = async (sn: MysqlSchema) => { }, } as const; - const tables = Object.values(sn).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + const tables = Object.values(sn).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; + + /* const views = Object.values(sn).filter((it) => is(it, SingleStoreView)) as SingleStoreView[]; */ - const serialized1 = generateMySqlSnapshot(tables); + const serialized1 = generateSingleStoreSnapshot(tables, /* views, */ casing); const { version: v1, dialect: d1, ...rest1 } = serialized1; const sch1 = { - version: '5', - dialect: 'mysql', + version: '1', + dialect: 'singlestore', id: '0', prevId: '0', ...rest1, } as const; - const sn1 = squashMysqlScheme(sch1); + const sn1 = squashSingleStoreScheme(sch1); - const validatedPrev = mysqlSchema.parse(dryRun); - const validatedCur = mysqlSchema.parse(sch1); + const validatedPrev = singlestoreSchema.parse(dryRun); + const validatedCur = singlestoreSchema.parse(sch1); - const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( dryRun, sn1, testTablesResolver(new Set()), testColumnsResolver(new Set()), + /* testViewsResolverSingleStore(new Set()), */ validatedPrev, validatedCur, ); return { sqlStatements, statements }; }; -export const diffTestSchemasMysql = async ( - left: MysqlSchema, - right: MysqlSchema, +export const diffTestSchemasPushSqlite = async ( + client: Database, + left: SqliteSchema, + right: SqliteSchema, renamesArr: string[], cli: boolean = false, + seedStatements: string[] = [], + casing?: CasingType | undefined, ) => { - const leftTables = Object.values(left).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + const { sqlStatements } = await applySqliteDiffs(left, 'push'); - const rightTables = Object.values(right).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + for (const st of sqlStatements) { + client.exec(st); + } - const serialized1 = generateMySqlSnapshot(leftTables); - const serialized2 = generateMySqlSnapshot(rightTables); + for (const st of seedStatements) { + client.exec(st); + } - const { version: v1, dialect: d1, ...rest1 } = serialized1; + // do introspect into PgSchemaInternal + const introspectedSchema = await fromSqliteDatabase( + { + query: async (sql: string, params: any[] = []) => { + return client.prepare(sql).bind(params).all() as T[]; + }, + run: async (query: string) => { + client.prepare(query).run(); + }, + }, + undefined, + ); + + const rightTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + + const rightViews = Object.values(right).filter((it) => is(it, SQLiteView)) as SQLiteView[]; + + const serialized2 = generateSqliteSnapshot(rightTables, rightViews, casing); + + const { version: v1, dialect: d1, ...rest1 } = introspectedSchema; const { version: v2, dialect: d2, ...rest2 } = serialized2; const sch1 = { - version: '5', - dialect: 'mysql', + version: '6', + dialect: 'sqlite', id: '0', prevId: '0', ...rest1, } as const; const sch2 = { - version: '5', - dialect: 'mysql', + version: '6', + dialect: 'sqlite', id: '0', prevId: '0', ...rest2, } as const; - const sn1 = squashMysqlScheme(sch1); - const sn2 = squashMysqlScheme(sch2); - - const validatedPrev = mysqlSchema.parse(sch1); - const validatedCur = mysqlSchema.parse(sch2); + const sn1 = squashSqliteScheme(sch1, 'push'); + const sn2 = squashSqliteScheme(sch2, 'push'); const renames = new Set(renamesArr); if (!cli) { - const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( + const { sqlStatements, statements, _meta } = await applySqliteSnapshotsDiff( sn1, sn2, testTablesResolver(renames), testColumnsResolver(renames), - validatedPrev, - validatedCur, + testViewsResolverSqlite(renames), + sch1, + sch2, + 'push', + ); + + const { + statementsToExecute, + columnsToRemove, + infoToPrint, + schemasToRemove, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await logSuggestionsAndReturn( + { + query: async (sql: string, params: any[] = []) => { + return client.prepare(sql).bind(params).all() as T[]; + }, + run: async (query: string) => { + client.prepare(query).run(); + }, + }, + statements, + sn1, + sn2, + _meta!, + ); + + return { + sqlStatements: statementsToExecute, + statements, + columnsToRemove, + infoToPrint, + schemasToRemove, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + }; + } else { + const { sqlStatements, statements } = await applySqliteSnapshotsDiff( + sn1, + sn2, + tablesResolver, + columnsResolver, + sqliteViewsResolver, + sch1, + sch2, + 'push', ); return { sqlStatements, statements }; } - - const { sqlStatements, statements } = await applyMysqlSnapshotsDiff( - sn1, - sn2, - tablesResolver, - columnsResolver, - validatedPrev, - validatedCur, - ); - return { sqlStatements, statements }; }; -export const diffTestSchemasPushSqlite = async ( - client: Database, +export async function diffTestSchemasPushLibSQL( + client: Client, left: SqliteSchema, right: SqliteSchema, renamesArr: string[], cli: boolean = false, -) => { - const { sqlStatements } = await applySqliteDiffs(left, 'push'); + seedStatements: string[] = [], + casing?: CasingType | undefined, +) { + const { sqlStatements } = await applyLibSQLDiffs(left, 'push'); + for (const st of sqlStatements) { - client.exec(st); + await client.execute(st); } - // do introspect into PgSchemaInternal + + for (const st of seedStatements) { + await client.execute(st); + } + const introspectedSchema = await fromSqliteDatabase( { - query: async (sql: string, params: any[] = []) => { - return client.prepare(sql).bind(params).all() as T[]; + query: async (sql: string, params?: any[]) => { + const res = await client.execute({ sql, args: params || [] }); + return res.rows as T[]; }, run: async (query: string) => { - client.prepare(query).run(); + await client.execute(query); }, }, undefined, @@ -861,7 +1913,9 @@ export const diffTestSchemasPushSqlite = async ( const leftTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; - const serialized2 = generateSqliteSnapshot(leftTables); + const leftViews = Object.values(right).filter((it) => is(it, SQLiteView)) as SQLiteView[]; + + const serialized2 = generateSqliteSnapshot(leftTables, leftViews, casing); const { version: v1, dialect: d1, ...rest1 } = introspectedSchema; const { version: v2, dialect: d2, ...rest2 } = serialized2; @@ -888,23 +1942,32 @@ export const diffTestSchemasPushSqlite = async ( const renames = new Set(renamesArr); if (!cli) { - const { sqlStatements, statements, _meta } = await applySqliteSnapshotsDiff( + const { sqlStatements, statements, _meta } = await applyLibSQLSnapshotsDiff( sn1, sn2, testTablesResolver(renames), testColumnsResolver(renames), + testViewsResolverSqlite(renames), sch1, sch2, 'push', ); - const { statementsToExecute } = await logSuggestionsAndReturn( + const { + statementsToExecute, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await libSqlLogSuggestionsAndReturn( { - query: async (sql: string, params: any[] = []) => { - return client.prepare(sql).bind(params).all() as T[]; + query: async (sql: string, params?: any[]) => { + const res = await client.execute({ sql, args: params || [] }); + return res.rows as T[]; }, run: async (query: string) => { - client.prepare(query).run(); + await client.execute(query); }, }, statements, @@ -913,24 +1976,87 @@ export const diffTestSchemasPushSqlite = async ( _meta!, ); - return { sqlStatements: statementsToExecute, statements }; + return { + sqlStatements: statementsToExecute, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + }; } else { - const { sqlStatements, statements } = await applySqliteSnapshotsDiff( + const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff( sn1, sn2, tablesResolver, columnsResolver, + sqliteViewsResolver, sch1, sch2, 'push', ); return { sqlStatements, statements }; } +} + +export const applySqliteDiffs = async ( + sn: SqliteSchema, + action?: 'push' | undefined, + casing?: CasingType | undefined, +) => { + const dryRun = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + tables: {}, + enums: {}, + views: {}, + schemas: {}, + _meta: { + schemas: {}, + tables: {}, + columns: {}, + }, + } as const; + + const tables = Object.values(sn).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + + const views = Object.values(sn).filter((it) => is(it, SQLiteView)) as SQLiteView[]; + + const serialized1 = generateSqliteSnapshot(tables, views, casing); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + + const sch1 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sn1 = squashSqliteScheme(sch1, action); + + const { sqlStatements, statements } = await applySqliteSnapshotsDiff( + dryRun, + sn1, + testTablesResolver(new Set()), + testColumnsResolver(new Set()), + testViewsResolverSqlite(new Set()), + dryRun, + sch1, + action, + ); + + return { sqlStatements, statements }; }; -export const applySqliteDiffs = async ( +export const applyLibSQLDiffs = async ( sn: SqliteSchema, action?: 'push' | undefined, + casing?: CasingType | undefined, ) => { const dryRun = { version: '6', @@ -938,6 +2064,7 @@ export const applySqliteDiffs = async ( id: '0', prevId: '0', tables: {}, + views: {}, enums: {}, schemas: {}, _meta: { @@ -949,7 +2076,9 @@ export const applySqliteDiffs = async ( const tables = Object.values(sn).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; - const serialized1 = generateSqliteSnapshot(tables); + const views = Object.values(sn).filter((it) => is(it, SQLiteView)) as SQLiteView[]; + + const serialized1 = generateSqliteSnapshot(tables, views, casing); const { version: v1, dialect: d1, ...rest1 } = serialized1; @@ -963,11 +2092,12 @@ export const applySqliteDiffs = async ( const sn1 = squashSqliteScheme(sch1, action); - const { sqlStatements, statements } = await applySqliteSnapshotsDiff( + const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff( dryRun, sn1, testTablesResolver(new Set()), testColumnsResolver(new Set()), + testViewsResolverSqlite(new Set()), dryRun, sch1, action, @@ -981,13 +2111,18 @@ export const diffTestSchemasSqlite = async ( right: SqliteSchema, renamesArr: string[], cli: boolean = false, + casing?: CasingType | undefined, ) => { const leftTables = Object.values(left).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + const leftViews = Object.values(left).filter((it) => is(it, SQLiteView)) as SQLiteView[]; + const rightTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; - const serialized1 = generateSqliteSnapshot(leftTables); - const serialized2 = generateSqliteSnapshot(rightTables); + const rightViews = Object.values(right).filter((it) => is(it, SQLiteView)) as SQLiteView[]; + + const serialized1 = generateSqliteSnapshot(leftTables, leftViews, casing); + const serialized2 = generateSqliteSnapshot(rightTables, rightViews, casing); const { version: v1, dialect: d1, ...rest1 } = serialized1; const { version: v2, dialect: d2, ...rest2 } = serialized2; @@ -1019,6 +2154,7 @@ export const diffTestSchemasSqlite = async ( sn2, testTablesResolver(renames), testColumnsResolver(renames), + testViewsResolverSqlite(renames), sch1, sch2, ); @@ -1030,6 +2166,74 @@ export const diffTestSchemasSqlite = async ( sn2, tablesResolver, columnsResolver, + sqliteViewsResolver, + sch1, + sch2, + ); + return { sqlStatements, statements }; +}; + +export const diffTestSchemasLibSQL = async ( + left: SqliteSchema, + right: SqliteSchema, + renamesArr: string[], + cli: boolean = false, + casing?: CasingType | undefined, +) => { + const leftTables = Object.values(left).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + + const leftViews = Object.values(left).filter((it) => is(it, SQLiteView)) as SQLiteView[]; + + const rightTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + + const rightViews = Object.values(right).filter((it) => is(it, SQLiteView)) as SQLiteView[]; + + const serialized1 = generateSqliteSnapshot(leftTables, leftViews, casing); + const serialized2 = generateSqliteSnapshot(rightTables, rightViews, casing); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sch2 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn1 = squashSqliteScheme(sch1); + const sn2 = squashSqliteScheme(sch2); + + const renames = new Set(renamesArr); + + if (!cli) { + const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff( + sn1, + sn2, + testTablesResolver(renames), + testColumnsResolver(renames), + testViewsResolverSqlite(renames), + sch1, + sch2, + ); + return { sqlStatements, statements }; + } + + const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff( + sn1, + sn2, + tablesResolver, + columnsResolver, + sqliteViewsResolver, sch1, sch2, ); @@ -1043,9 +2247,11 @@ export const introspectPgToFile = async ( initSchema: PostgresSchema, testName: string, schemas: string[] = ['public'], + entities?: Entities, + casing?: CasingType | undefined, ) => { // put in db - const { sqlStatements } = await applyPgDiffs(initSchema); + const { sqlStatements } = await applyPgDiffs(initSchema, casing); for (const st of sqlStatements) { await client.query(st); } @@ -1060,14 +2266,30 @@ export const introspectPgToFile = async ( }, undefined, schemas, + entities, ); + const { version: initV, dialect: initD, ...initRest } = introspectedSchema; + + const initSch = { + version: '7', + dialect: 'postgresql', + id: '0', + prevId: '0', + ...initRest, + } as const; + + const initSn = squashPgScheme(initSch); + const validatedCur = pgSchema.parse(initSch); + + // write to ts file const file = schemaToTypeScript(introspectedSchema, 'camel'); - fs.writeFileSync(`tests/introspect/${testName}.ts`, file.file); + fs.writeFileSync(`tests/introspect/postgres/${testName}.ts`, file.file); + // generate snapshot from ts file const response = await prepareFromPgImports([ - `tests/introspect/${testName}.ts`, + `tests/introspect/postgres/${testName}.ts`, ]); const afterFileImports = generatePgSnapshot( @@ -1075,6 +2297,11 @@ export const introspectPgToFile = async ( response.enums, response.schemas, response.sequences, + response.roles, + response.policies, + response.views, + response.matViews, + casing, ); const { version: v2, dialect: d2, ...rest2 } = afterFileImports; @@ -1090,50 +2317,111 @@ export const introspectPgToFile = async ( const sn2AfterIm = squashPgScheme(sch2); const validatedCurAfterImport = pgSchema.parse(sch2); - const leftTables = Object.values(initSchema).filter((it) => is(it, PgTable)) as PgTable[]; + const { + sqlStatements: afterFileSqlStatements, + statements: afterFileStatements, + } = await applyPgSnapshotsDiff( + initSn, + sn2AfterIm, + testSchemasResolver(new Set()), + testEnumsResolver(new Set()), + testSequencesResolver(new Set()), + testPolicyResolver(new Set()), + testIndPolicyResolver(new Set()), + testRolesResolver(new Set()), + testTablesResolver(new Set()), + testColumnsResolver(new Set()), + testViewsResolver(new Set()), + validatedCur, + validatedCurAfterImport, + ); - const leftSchemas = Object.values(initSchema).filter((it) => is(it, PgSchema)) as PgSchema[]; + fs.rmSync(`tests/introspect/postgres/${testName}.ts`); - const leftEnums = Object.values(initSchema).filter((it) => isPgEnum(it)) as PgEnum[]; + return { + sqlStatements: afterFileSqlStatements, + statements: afterFileStatements, + }; +}; - const leftSequences = Object.values(initSchema).filter((it) => isPgSequence(it)) as PgSequence[]; +export const introspectMySQLToFile = async ( + client: Connection, + initSchema: MysqlSchema, + testName: string, + schema: string, + casing?: CasingType | undefined, +) => { + // put in db + const { sqlStatements } = await applyMySqlDiffs(initSchema, casing); + for (const st of sqlStatements) { + await client.query(st); + } - const initSnapshot = generatePgSnapshot( - leftTables, - leftEnums, - leftSchemas, - leftSequences, + // introspect to schema + const introspectedSchema = await fromMySqlDatabase( + { + query: async (sql: string, params?: any[] | undefined) => { + const res = await client.execute(sql, params); + return res[0] as any; + }, + }, + schema, ); - const { version: initV, dialect: initD, ...initRest } = initSnapshot; + const { version: initV, dialect: initD, ...initRest } = introspectedSchema; const initSch = { - version: '7', - dialect: 'postgresql', + version: '5', + dialect: 'mysql', id: '0', prevId: '0', ...initRest, } as const; - const initSn = squashPgScheme(initSch); - const validatedCur = pgSchema.parse(initSch); + const initSn = squashMysqlScheme(initSch); + const validatedCur = mysqlSchema.parse(initSch); + + const file = schemaToTypeScriptMySQL(introspectedSchema, 'camel'); + + fs.writeFileSync(`tests/introspect/mysql/${testName}.ts`, file.file); + + const response = await prepareFromMySqlImports([ + `tests/introspect/mysql/${testName}.ts`, + ]); + + const afterFileImports = generateMySqlSnapshot( + response.tables, + response.views, + casing, + ); + + const { version: v2, dialect: d2, ...rest2 } = afterFileImports; + + const sch2 = { + version: '5', + dialect: 'mysql', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn2AfterIm = squashMysqlScheme(sch2); + const validatedCurAfterImport = mysqlSchema.parse(sch2); const { sqlStatements: afterFileSqlStatements, statements: afterFileStatements, - } = await applyPgSnapshotsDiff( + } = await applyMysqlSnapshotsDiff( sn2AfterIm, initSn, - testSchemasResolver(new Set()), - testEnumsResolver(new Set()), - testSequencesResolver(new Set()), testTablesResolver(new Set()), testColumnsResolver(new Set()), + testViewsResolverMySql(new Set()), validatedCurAfterImport, validatedCur, ); - fs.rmSync(`tests/introspect/${testName}.ts`); + fs.rmSync(`tests/introspect/mysql/${testName}.ts`); return { sqlStatements: afterFileSqlStatements, @@ -1141,20 +2429,21 @@ export const introspectPgToFile = async ( }; }; -export const introspectMySQLToFile = async ( +export const introspectSingleStoreToFile = async ( client: Connection, - initSchema: MysqlSchema, + initSchema: SinglestoreSchema, testName: string, schema: string, + casing?: CasingType | undefined, ) => { // put in db - const { sqlStatements } = await applyMySqlDiffs(initSchema); + const { sqlStatements } = await applySingleStoreDiffs(initSchema, casing); for (const st of sqlStatements) { await client.query(st); } // introspect to schema - const introspectedSchema = await fromMySqlDatabase( + const introspectedSchema = await fromSingleStoreDatabase( { query: async (sql: string, params?: any[] | undefined) => { const res = await client.execute(sql, params); @@ -1164,59 +2453,68 @@ export const introspectMySQLToFile = async ( schema, ); - const file = schemaToTypeScriptMySQL(introspectedSchema, 'camel'); + const file = schemaToTypeScriptSingleStore(introspectedSchema, 'camel'); - fs.writeFileSync(`tests/introspect/mysql/${testName}.ts`, file.file); + fs.writeFileSync(`tests/introspect/singlestore/${testName}.ts`, file.file); - const response = await prepareFromMySqlImports([ - `tests/introspect/mysql/${testName}.ts`, + const response = await prepareFromSingleStoreImports([ + `tests/introspect/singlestore/${testName}.ts`, ]); - const afterFileImports = generateMySqlSnapshot(response.tables); + const afterFileImports = generateSingleStoreSnapshot( + response.tables, + /* response.views, */ + casing, + ); const { version: v2, dialect: d2, ...rest2 } = afterFileImports; const sch2 = { - version: '5', - dialect: 'mysql', + version: '1', + dialect: 'singlestore', id: '0', prevId: '0', ...rest2, } as const; - const sn2AfterIm = squashMysqlScheme(sch2); - const validatedCurAfterImport = mysqlSchema.parse(sch2); + const sn2AfterIm = squashSingleStoreScheme(sch2); + const validatedCurAfterImport = singlestoreSchema.parse(sch2); - const leftTables = Object.values(initSchema).filter((it) => is(it, MySqlTable)) as MySqlTable[]; + const leftTables = Object.values(initSchema).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; - const initSnapshot = generateMySqlSnapshot(leftTables); + const initSnapshot = generateSingleStoreSnapshot( + leftTables, + /* response.views, */ + casing, + ); const { version: initV, dialect: initD, ...initRest } = initSnapshot; const initSch = { - version: '5', - dialect: 'mysql', + version: '1', + dialect: 'singlestore', id: '0', prevId: '0', ...initRest, } as const; - const initSn = squashMysqlScheme(initSch); - const validatedCur = mysqlSchema.parse(initSch); + const initSn = squashSingleStoreScheme(initSch); + const validatedCur = singlestoreSchema.parse(initSch); const { sqlStatements: afterFileSqlStatements, statements: afterFileStatements, - } = await applyMysqlSnapshotsDiff( + } = await applySingleStoreSnapshotsDiff( sn2AfterIm, initSn, testTablesResolver(new Set()), testColumnsResolver(new Set()), + /* testViewsResolverSingleStore(new Set()), */ validatedCurAfterImport, validatedCur, ); - fs.rmSync(`tests/introspect/mysql/${testName}.ts`); + fs.rmSync(`tests/introspect/singlestore/${testName}.ts`); return { sqlStatements: afterFileSqlStatements, @@ -1228,6 +2526,7 @@ export const introspectSQLiteToFile = async ( client: Database, initSchema: SqliteSchema, testName: string, + casing?: CasingType | undefined, ) => { // put in db const { sqlStatements } = await applySqliteDiffs(initSchema); @@ -1248,6 +2547,20 @@ export const introspectSQLiteToFile = async ( undefined, ); + const { version: initV, dialect: initD, ...initRest } = introspectedSchema; + + const initSch = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...initRest, + } as const; + + const initSn = squashSqliteScheme(initSch); + + const validatedCur = sqliteSchema.parse(initSch); + const file = schemaToTypeScriptSQLite(introspectedSchema, 'camel'); fs.writeFileSync(`tests/introspect/sqlite/${testName}.ts`, file.file); @@ -1256,7 +2569,11 @@ export const introspectSQLiteToFile = async ( `tests/introspect/sqlite/${testName}.ts`, ]); - const afterFileImports = generateSqliteSnapshot(response.tables); + const afterFileImports = generateSqliteSnapshot( + response.tables, + response.views, + casing, + ); const { version: v2, dialect: d2, ...rest2 } = afterFileImports; @@ -1271,11 +2588,53 @@ export const introspectSQLiteToFile = async ( const sn2AfterIm = squashSqliteScheme(sch2); const validatedCurAfterImport = sqliteSchema.parse(sch2); - const leftTables = Object.values(initSchema).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + const { + sqlStatements: afterFileSqlStatements, + statements: afterFileStatements, + } = await applySqliteSnapshotsDiff( + sn2AfterIm, + initSn, + testTablesResolver(new Set()), + testColumnsResolver(new Set()), + testViewsResolverSqlite(new Set()), + validatedCurAfterImport, + validatedCur, + ); - const initSnapshot = generateSqliteSnapshot(leftTables); + fs.rmSync(`tests/introspect/sqlite/${testName}.ts`); - const { version: initV, dialect: initD, ...initRest } = initSnapshot; + return { + sqlStatements: afterFileSqlStatements, + statements: afterFileStatements, + }; +}; + +export const introspectLibSQLToFile = async ( + client: Client, + initSchema: SqliteSchema, + testName: string, + casing?: CasingType | undefined, +) => { + // put in db + const { sqlStatements } = await applyLibSQLDiffs(initSchema); + for (const st of sqlStatements) { + client.execute(st); + } + + // introspect to schema + const introspectedSchema = await fromSqliteDatabase( + { + query: async (sql: string, params: any[] = []) => { + return (await client.execute({ sql, args: params })).rows as T[]; + }, + run: async (query: string) => { + client.execute(query); + }, + }, + undefined, + ); + + const { version: initV, dialect: initD, ...initRest } = introspectedSchema; const initSch = { version: '6', @@ -1286,21 +2645,50 @@ export const introspectSQLiteToFile = async ( } as const; const initSn = squashSqliteScheme(initSch); + const validatedCur = sqliteSchema.parse(initSch); + const file = schemaToTypeScriptSQLite(introspectedSchema, 'camel'); + + fs.writeFileSync(`tests/introspect/libsql/${testName}.ts`, file.file); + + const response = await prepareFromSqliteImports([ + `tests/introspect/libsql/${testName}.ts`, + ]); + + const afterFileImports = generateSqliteSnapshot( + response.tables, + response.views, + casing, + ); + + const { version: v2, dialect: d2, ...rest2 } = afterFileImports; + + const sch2 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn2AfterIm = squashSqliteScheme(sch2); + const validatedCurAfterImport = sqliteSchema.parse(sch2); + const { sqlStatements: afterFileSqlStatements, statements: afterFileStatements, - } = await applySqliteSnapshotsDiff( + } = await applyLibSQLSnapshotsDiff( sn2AfterIm, initSn, testTablesResolver(new Set()), testColumnsResolver(new Set()), + testViewsResolverSqlite(new Set()), validatedCurAfterImport, validatedCur, ); - fs.rmSync(`tests/introspect/sqlite/${testName}.ts`); + fs.rmSync(`tests/introspect/libsql/${testName}.ts`); return { sqlStatements: afterFileSqlStatements, diff --git a/drizzle-kit/tests/singlestore-generated.test.ts b/drizzle-kit/tests/singlestore-generated.test.ts new file mode 100644 index 000000000..8944f3b21 --- /dev/null +++ b/drizzle-kit/tests/singlestore-generated.test.ts @@ -0,0 +1,1290 @@ +import { SQL, sql } from 'drizzle-orm'; +import { int, singlestoreTable, text } from 'drizzle-orm/singlestore-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasSingleStore } from './schemaDiffer'; + +test('generated as callback: add column with generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${to.users.name} || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + column: { + generated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + autoincrement: false, + name: 'gen_name', + notNull: false, + primaryKey: false, + type: 'text', + }, + schema: '', + tableName: 'users', + type: 'alter_table_add_column', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as callback: add generated constraint to an exisiting column as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs((): SQL => sql`${from.users.name} || 'to add'`, { + mode: 'stored', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'stored', + }, + columnAutoIncrement: false, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + ]); +}); + +test('generated as callback: add generated constraint to an exisiting column as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs((): SQL => sql`${from.users.name} || 'to add'`, { + mode: 'virtual', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + ]); +}); + +test('generated as callback: drop generated constraint as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name} || 'to delete'`, + { mode: 'stored' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'stored', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` MODIFY COLUMN `gen_name` text;', + ]); +}); + +test('generated as callback: drop generated constraint as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name} || 'to delete'`, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'virtual', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + tableName: 'users', + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + 'ALTER TABLE `users` ADD `gen_name` text;', + ]); +}); + +test('generated as callback: change generated constraint type from virtual to stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name}`, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${to.users.name} || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as callback: change generated constraint type from stored to virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name}`, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${to.users.name} || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +test('generated as callback: change generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name}`, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${to.users.name} || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +// --- + +test('generated as sql: add column with generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + column: { + generated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + autoincrement: false, + name: 'gen_name', + notNull: false, + primaryKey: false, + type: 'text', + }, + schema: '', + tableName: 'users', + type: 'alter_table_add_column', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as sql: add generated constraint to an exisiting column as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs(sql`\`users\`.\`name\` || 'to add'`, { + mode: 'stored', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'stored', + }, + columnAutoIncrement: false, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + ]); +}); + +test('generated as sql: add generated constraint to an exisiting column as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs(sql`\`users\`.\`name\` || 'to add'`, { + mode: 'virtual', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + ]); +}); + +test('generated as sql: drop generated constraint as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'to delete'`, + { mode: 'stored' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'stored', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` MODIFY COLUMN `gen_name` text;', + ]); +}); + +test('generated as sql: drop generated constraint as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'to delete'`, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'virtual', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + tableName: 'users', + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + 'ALTER TABLE `users` ADD `gen_name` text;', + ]); +}); + +test('generated as sql: change generated constraint type from virtual to stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\``, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as sql: change generated constraint type from stored to virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\``, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +test('generated as sql: change generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\``, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +// --- + +test('generated as string: add column with generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + column: { + generated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + autoincrement: false, + name: 'gen_name', + notNull: false, + primaryKey: false, + type: 'text', + }, + schema: '', + tableName: 'users', + type: 'alter_table_add_column', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as string: add generated constraint to an exisiting column as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs(`\`users\`.\`name\` || 'to add'`, { + mode: 'stored', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'stored', + }, + columnAutoIncrement: false, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + ]); +}); + +test('generated as string: add generated constraint to an exisiting column as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs(`\`users\`.\`name\` || 'to add'`, { + mode: 'virtual', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + ]); +}); + +test('generated as string: drop generated constraint as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'to delete'`, + { mode: 'stored' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'stored', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` MODIFY COLUMN `gen_name` text;', + ]); +}); + +test('generated as string: drop generated constraint as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'to delete'`, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'virtual', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + tableName: 'users', + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + 'ALTER TABLE `users` ADD `gen_name` text;', + ]); +}); + +test('generated as string: change generated constraint type from virtual to stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs(`\`users\`.\`name\``, { + mode: 'virtual', + }), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as string: change generated constraint type from stored to virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs(`\`users\`.\`name\``), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +test('generated as string: change generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs(`\`users\`.\`name\``), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); diff --git a/drizzle-kit/tests/singlestore-schemas.test.ts b/drizzle-kit/tests/singlestore-schemas.test.ts new file mode 100644 index 000000000..db9fe0480 --- /dev/null +++ b/drizzle-kit/tests/singlestore-schemas.test.ts @@ -0,0 +1,155 @@ +import { singlestoreSchema, singlestoreTable } from 'drizzle-orm/singlestore-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasSingleStore } from './schemaDiffer'; + +// We don't manage databases(schemas) in MySQL with Drizzle Kit +test('add schema #1', async () => { + const to = { + devSchema: singlestoreSchema('dev'), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(0); +}); + +test('add schema #2', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + }; + const to = { + devSchema: singlestoreSchema('dev'), + devSchema2: singlestoreSchema('dev2'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, []); + + expect(statements.length).toBe(0); +}); + +test('delete schema #1', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, {}, []); + + expect(statements.length).toBe(0); +}); + +test('delete schema #2', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + devSchema2: singlestoreSchema('dev2'), + }; + const to = { + devSchema: singlestoreSchema('dev'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, []); + + expect(statements.length).toBe(0); +}); + +test('rename schema #1', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + }; + const to = { + devSchema2: singlestoreSchema('dev2'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev->dev2']); + + expect(statements.length).toBe(0); +}); + +test('rename schema #2', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + devSchema1: singlestoreSchema('dev1'), + }; + const to = { + devSchema: singlestoreSchema('dev'), + devSchema2: singlestoreSchema('dev2'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); + +test('add table to schema #1', async () => { + const dev = singlestoreSchema('dev'); + const from = {}; + const to = { + dev, + users: dev.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); + +test('add table to schema #2', async () => { + const dev = singlestoreSchema('dev'); + const from = { dev }; + const to = { + dev, + users: dev.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); + +test('add table to schema #3', async () => { + const dev = singlestoreSchema('dev'); + const from = { dev }; + const to = { + dev, + usersInDev: dev.table('users', {}), + users: singlestoreTable('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + uniqueConstraints: [], + internals: { + tables: {}, + indexes: {}, + }, + compositePkName: '', + compositePKs: [], + }); +}); + +test('remove table from schema #1', async () => { + const dev = singlestoreSchema('dev'); + const from = { dev, users: dev.table('users', {}) }; + const to = { + dev, + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); + +test('remove table from schema #2', async () => { + const dev = singlestoreSchema('dev'); + const from = { dev, users: dev.table('users', {}) }; + const to = {}; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); diff --git a/drizzle-kit/tests/singlestore.test.ts b/drizzle-kit/tests/singlestore.test.ts new file mode 100644 index 000000000..3bdccab81 --- /dev/null +++ b/drizzle-kit/tests/singlestore.test.ts @@ -0,0 +1,580 @@ +import { sql } from 'drizzle-orm'; +import { + index, + json, + primaryKey, + serial, + singlestoreSchema, + singlestoreTable, + text, + uniqueIndex, +} from 'drizzle-orm/singlestore-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasSingleStore } from './schemaDiffer'; + +test('add table #1', async () => { + const to = { + users: singlestoreTable('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + compositePKs: [], + internals: { + tables: {}, + indexes: {}, + }, + uniqueConstraints: [], + compositePkName: '', + }); +}); + +test('add table #2', async () => { + const to = { + users: singlestoreTable('users', { + id: serial('id').primaryKey(), + }), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [ + { + name: 'id', + notNull: true, + primaryKey: false, + type: 'serial', + autoincrement: true, + }, + ], + compositePKs: ['users_id;id'], + compositePkName: 'users_id', + uniqueConstraints: [], + internals: { + tables: {}, + indexes: {}, + }, + }); +}); + +test('add table #3', async () => { + const to = { + users: singlestoreTable( + 'users', + { + id: serial('id'), + }, + (t) => { + return { + pk: primaryKey({ + name: 'users_pk', + columns: [t.id], + }), + }; + }, + ), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [ + { + name: 'id', + notNull: true, + primaryKey: false, + type: 'serial', + autoincrement: true, + }, + ], + compositePKs: ['users_pk;id'], + uniqueConstraints: [], + compositePkName: 'users_pk', + internals: { + tables: {}, + indexes: {}, + }, + }); +}); + +test('add table #4', async () => { + const to = { + users: singlestoreTable('users', {}), + posts: singlestoreTable('posts', {}), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + internals: { + tables: {}, + indexes: {}, + }, + compositePKs: [], + uniqueConstraints: [], + compositePkName: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'create_table', + tableName: 'posts', + schema: undefined, + columns: [], + compositePKs: [], + internals: { + tables: {}, + indexes: {}, + }, + uniqueConstraints: [], + compositePkName: '', + }); +}); + +test('add table #5', async () => { + const schema = singlestoreSchema('folder'); + const from = { + schema, + }; + + const to = { + schema, + users: schema.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, []); + + expect(statements.length).toBe(0); +}); + +test('add table #6', async () => { + const from = { + users1: singlestoreTable('users1', {}), + }; + + const to = { + users2: singlestoreTable('users2', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users2', + schema: undefined, + columns: [], + internals: { + tables: {}, + indexes: {}, + }, + compositePKs: [], + uniqueConstraints: [], + compositePkName: '', + }); + expect(statements[1]).toStrictEqual({ + policies: [], + type: 'drop_table', + tableName: 'users1', + schema: undefined, + }); +}); + +test('add table #7', async () => { + const from = { + users1: singlestoreTable('users1', {}), + }; + + const to = { + users: singlestoreTable('users', {}), + users2: singlestoreTable('users2', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'public.users1->public.users2', + ]); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + compositePKs: [], + uniqueConstraints: [], + internals: { + tables: {}, + indexes: {}, + }, + compositePkName: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'rename_table', + tableNameFrom: 'users1', + tableNameTo: 'users2', + fromSchema: undefined, + toSchema: undefined, + }); +}); + +test('add schema + table #1', async () => { + const schema = singlestoreSchema('folder'); + + const to = { + schema, + users: schema.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(0); +}); + +test('change schema with tables #1', async () => { + const schema = singlestoreSchema('folder'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema, + users: schema.table('users', {}), + }; + const to = { + schema2, + users: schema2.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder->folder2', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #1', async () => { + const schema = singlestoreSchema('folder'); + const from = { + schema, + users: singlestoreTable('users', {}), + }; + const to = { + schema, + users: schema.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'public.users->folder.users', + ]); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + policies: [], + type: 'drop_table', + tableName: 'users', + schema: undefined, + }); +}); + +test('change table schema #2', async () => { + const schema = singlestoreSchema('folder'); + const from = { + schema, + users: schema.table('users', {}), + }; + const to = { + schema, + users: singlestoreTable('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder.users->public.users', + ]); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + uniqueConstraints: [], + compositePkName: '', + compositePKs: [], + internals: { + tables: {}, + indexes: {}, + }, + }); +}); + +test('change table schema #3', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, + schema2, + users: schema1.table('users', {}), + }; + const to = { + schema1, + schema2, + users: schema2.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1.users->folder2.users', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #4', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, + users: schema1.table('users', {}), + }; + const to = { + schema1, + schema2, // add schema + users: schema2.table('users', {}), // move table + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1.users->folder2.users', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #5', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, // remove schema + users: schema1.table('users', {}), + }; + const to = { + schema2, // add schema + users: schema2.table('users', {}), // move table + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1.users->folder2.users', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #5', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, + schema2, + users: schema1.table('users', {}), + }; + const to = { + schema1, + schema2, + users: schema2.table('users2', {}), // rename and move table + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1.users->folder2.users2', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #6', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, + users: schema1.table('users', {}), + }; + const to = { + schema2, // rename schema + users: schema2.table('users2', {}), // rename table + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1->folder2', + 'folder2.users->folder2.users2', + ]); + + expect(statements.length).toBe(0); +}); + +test('add table #10', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default({}), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + "CREATE TABLE `table` (\n\t`json` json DEFAULT '{}'\n);\n", + ); +}); + +test('add table #11', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default([]), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + "CREATE TABLE `table` (\n\t`json` json DEFAULT '[]'\n);\n", + ); +}); + +test('add table #12', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default([1, 2, 3]), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + "CREATE TABLE `table` (\n\t`json` json DEFAULT '[1,2,3]'\n);\n", + ); +}); + +test('add table #13', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default({ key: 'value' }), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + 'CREATE TABLE `table` (\n\t`json` json DEFAULT \'{"key":"value"}\'\n);\n', + ); +}); + +test('add table #14', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default({ + key: 'value', + arr: [1, 2, 3], + }), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + 'CREATE TABLE `table` (\n\t`json` json DEFAULT \'{"key":"value","arr":[1,2,3]}\'\n);\n', + ); +}); + +// TODO: add bson type tests + +// TODO: add blob type tests + +// TODO: add uuid type tests + +// TODO: add guid type tests + +// TODO: add vector type tests + +// TODO: add geopoint type tests + +test('drop index', async () => { + const from = { + users: singlestoreTable( + 'table', + { + name: text('name'), + }, + (t) => { + return { + idx: index('name_idx').on(t.name), + }; + }, + ), + }; + + const to = { + users: singlestoreTable('table', { + name: text('name'), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe('DROP INDEX `name_idx` ON `table`;'); +}); + +test('add table with indexes', async () => { + const from = {}; + + const to = { + users: singlestoreTable( + 'users', + { + id: serial('id').primaryKey(), + name: text('name'), + email: text('email'), + }, + (t) => ({ + uniqueExpr: uniqueIndex('uniqueExpr').on(sql`(lower(${t.email}))`), + indexExpr: index('indexExpr').on(sql`(lower(${t.email}))`), + indexExprMultiple: index('indexExprMultiple').on( + sql`(lower(${t.email}))`, + sql`(lower(${t.email}))`, + ), + + uniqueCol: uniqueIndex('uniqueCol').on(t.email), + indexCol: index('indexCol').on(t.email), + indexColMultiple: index('indexColMultiple').on(t.email, t.email), + + indexColExpr: index('indexColExpr').on( + sql`(lower(${t.email}))`, + t.email, + ), + }), + ), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore(from, to, []); + expect(sqlStatements.length).toBe(6); + expect(sqlStatements).toStrictEqual([ + `CREATE TABLE \`users\` (\n\t\`id\` serial AUTO_INCREMENT NOT NULL,\n\t\`name\` text,\n\t\`email\` text,\n\tCONSTRAINT \`users_id\` PRIMARY KEY(\`id\`),\n\tCONSTRAINT \`uniqueExpr\` UNIQUE((lower(\`email\`))),\n\tCONSTRAINT \`uniqueCol\` UNIQUE(\`email\`) +); +`, + 'CREATE INDEX `indexExpr` ON `users` ((lower(`email`)));', + 'CREATE INDEX `indexExprMultiple` ON `users` ((lower(`email`)),(lower(`email`)));', + 'CREATE INDEX `indexCol` ON `users` (`email`);', + 'CREATE INDEX `indexColMultiple` ON `users` (`email`,`email`);', + 'CREATE INDEX `indexColExpr` ON `users` ((lower(`email`)),`email`);', + ]); +}); diff --git a/drizzle-kit/tests/sqlite-checks.test.ts b/drizzle-kit/tests/sqlite-checks.test.ts new file mode 100644 index 000000000..d1824e441 --- /dev/null +++ b/drizzle-kit/tests/sqlite-checks.test.ts @@ -0,0 +1,308 @@ +import { sql } from 'drizzle-orm'; +import { check, int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasSqlite } from './schemaDiffer'; + +test('create table with check', async (t) => { + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasSqlite({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_create_table', + tableName: 'users', + columns: [ + { + name: 'id', + type: 'integer', + notNull: true, + primaryKey: true, + autoincrement: false, + }, + { + name: 'age', + type: 'integer', + notNull: false, + primaryKey: false, + autoincrement: false, + }, + ], + compositePKs: [], + checkConstraints: ['some_check_name;"users"."age" > 21'], + referenceData: [], + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe(`CREATE TABLE \`users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer, +\tCONSTRAINT "some_check_name" CHECK("users"."age" > 21) +);\n`); +}); + +test('add check contraint to existing table', async (t) => { + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const from = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }), + }; + + const { sqlStatements, statements } = await diffTestSchemasSqlite(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: ['some_check_name;"users"."age" > 21'], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer, +\tCONSTRAINT "some_check_name" CHECK("__new_users"."age" > 21) +);\n`); + expect(sqlStatements[2]).toBe(`INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('drop check contraint to existing table', async (t) => { + const from = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }), + }; + + const { sqlStatements, statements } = await diffTestSchemasSqlite(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer +);\n`); + expect(sqlStatements[2]).toBe(`INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('rename check constraint', async (t) => { + const from = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('new_some_check_name', sql`${table.age} > 21`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasSqlite(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [`new_some_check_name;"users"."age" > 21`], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer, +\tCONSTRAINT "new_some_check_name" CHECK("__new_users"."age" > 21) +);\n`); + expect(sqlStatements[2]).toBe(`INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('rename check constraint', async (t) => { + const from = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 21`), + })), + }; + + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + }, (table) => ({ + checkConstraint: check('some_check_name', sql`${table.age} > 10`), + })), + }; + + const { sqlStatements, statements } = await diffTestSchemasSqlite(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [`some_check_name;"users"."age" > 10`], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`age\` integer, +\tCONSTRAINT "some_check_name" CHECK("__new_users"."age" > 10) +);\n`); + expect(sqlStatements[2]).toBe(`INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('create checks with same names', async (t) => { + const to = { + users: sqliteTable('users', { + id: int('id').primaryKey(), + age: int('age'), + name: text('name'), + }, (table) => ({ + checkConstraint1: check('some_check_name', sql`${table.age} > 21`), + checkConstraint2: check('some_check_name', sql`${table.name} != 'Alex'`), + })), + }; + + await expect(diffTestSchemasSqlite({}, to, [])).rejects.toThrowError(); +}); diff --git a/drizzle-kit/tests/sqlite-columns.test.ts b/drizzle-kit/tests/sqlite-columns.test.ts index 8a258072a..0cb34c220 100644 --- a/drizzle-kit/tests/sqlite-columns.test.ts +++ b/drizzle-kit/tests/sqlite-columns.test.ts @@ -8,6 +8,7 @@ import { sqliteTable, text, } from 'drizzle-orm/sqlite-core'; +import { JsonCreateIndexStatement, JsonRecreateTableStatement } from 'src/jsonStatements'; import { expect, test } from 'vitest'; import { diffTestSchemasSqlite } from './schemaDiffer'; @@ -36,6 +37,7 @@ test('create table with id', async (t) => { uniqueConstraints: [], referenceData: [], compositePKs: [], + checkConstraints: [], }); }); @@ -223,7 +225,7 @@ test('add columns #5', async (t) => { const { statements } = await diffTestSchemasSqlite(schema1, schema2, []); // TODO: Fix here - expect(statements.length).toBe(2); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'sqlite_alter_table_add_column', tableName: 'users', @@ -332,12 +334,39 @@ test('add foreign key #1', async (t) => { const { statements } = await diffTestSchemasSqlite(schema1, schema2, []); expect(statements.length).toBe(1); - expect(statements[0]).toStrictEqual({ - type: 'create_reference', - tableName: 'users', - schema: '', - data: 'users_report_to_users_id_fk;users;report_to;users;id;no action;no action', - }); + expect(statements[0]).toStrictEqual( + { + type: 'recreate_table', + columns: [{ + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, { + autoincrement: false, + generated: undefined, + name: 'report_to', + notNull: false, + primaryKey: false, + type: 'integer', + }], + compositePKs: [], + referenceData: [{ + columnsFrom: ['report_to'], + columnsTo: ['id'], + name: 'users_report_to_users_id_fk', + tableFrom: 'users', + tableTo: 'users', + onDelete: 'no action', + onUpdate: 'no action', + }], + tableName: 'users', + uniqueConstraints: [], + checkConstraints: [], + } as JsonRecreateTableStatement, + ); }); test('add foreign key #2', async (t) => { @@ -371,11 +400,36 @@ test('add foreign key #2', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'create_reference', + type: 'recreate_table', + columns: [{ + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, { + autoincrement: false, + generated: undefined, + name: 'report_to', + notNull: false, + primaryKey: false, + type: 'integer', + }], + compositePKs: [], + referenceData: [{ + columnsFrom: ['report_to'], + columnsTo: ['id'], + name: 'reportee_fk', + tableFrom: 'users', + tableTo: 'users', + onDelete: 'no action', + onUpdate: 'no action', + }], tableName: 'users', - schema: '', - data: 'reportee_fk;users;report_to;users;id;no action;no action', - }); + uniqueConstraints: [], + checkConstraints: [], + } as JsonRecreateTableStatement); }); test('alter column change name #1', async (t) => { @@ -513,9 +567,27 @@ test('alter table add composite pk', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'create_composite_pk', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'id1', + notNull: false, + primaryKey: false, + type: 'integer', + }, { + autoincrement: false, + generated: undefined, + name: 'id2', + notNull: false, + primaryKey: false, + type: 'integer', + }], + compositePKs: [['id1', 'id2']], + referenceData: [], tableName: 'table', - data: 'id1,id2', + uniqueConstraints: [], + checkConstraints: [], }); }); @@ -540,16 +612,20 @@ test('alter column drop not null', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'alter_table_alter_column_drop_notnull', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }], + compositePKs: [], + referenceData: [], tableName: 'table', - columnName: 'name', - schema: '', - newDataType: 'text', - columnDefault: undefined, - columnOnUpdate: undefined, - columnNotNull: false, - columnAutoIncrement: false, - columnPk: false, + uniqueConstraints: [], + checkConstraints: [], }); }); @@ -574,16 +650,20 @@ test('alter column add not null', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'alter_table_alter_column_set_notnull', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: true, + primaryKey: false, + type: 'text', + }], + compositePKs: [], + referenceData: [], tableName: 'table', - columnName: 'name', - schema: '', - newDataType: 'text', - columnDefault: undefined, - columnOnUpdate: undefined, - columnNotNull: true, - columnAutoIncrement: false, - columnPk: false, + uniqueConstraints: [], + checkConstraints: [], }); }); @@ -608,16 +688,21 @@ test('alter column add default', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'alter_table_alter_column_set_default', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + default: "'dan'", + }], + compositePKs: [], + referenceData: [], tableName: 'table', - columnName: 'name', - schema: '', - newDataType: 'text', - columnNotNull: false, - columnOnUpdate: undefined, - columnAutoIncrement: false, - newDefaultValue: "'dan'", - columnPk: false, + uniqueConstraints: [], + checkConstraints: [], }); }); @@ -642,16 +727,20 @@ test('alter column drop default', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'alter_table_alter_column_drop_default', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }], + compositePKs: [], + referenceData: [], tableName: 'table', - columnName: 'name', - schema: '', - newDataType: 'text', - columnNotNull: false, - columnOnUpdate: undefined, - columnDefault: undefined, - columnAutoIncrement: false, - columnPk: false, + uniqueConstraints: [], + checkConstraints: [], }); }); @@ -674,32 +763,86 @@ test('alter column add default not null', async (t) => { [], ); - expect(statements.length).toBe(2); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - columnAutoIncrement: false, - columnName: 'name', - columnNotNull: true, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - newDefaultValue: "'dan'", - schema: '', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: true, + primaryKey: false, + type: 'text', + default: "'dan'", + }], + compositePKs: [], + referenceData: [], tableName: 'table', - type: 'alter_table_alter_column_set_default', + uniqueConstraints: [], + checkConstraints: [], }); +}); + +test('alter column add default not null with indexes', async (t) => { + const from = { + users: sqliteTable('table', { + name: text('name'), + }, (table) => ({ + someIndex: index('index_name').on(table.name), + })), + }; + + const to = { + users: sqliteTable('table', { + name: text('name').notNull().default('dan'), + }, (table) => ({ + someIndex: index('index_name').on(table.name), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite( + from, + to, + [], + ); + expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ - columnAutoIncrement: false, - columnName: 'name', - columnNotNull: true, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - newDefaultValue: "'dan'", + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: true, + primaryKey: false, + type: 'text', + default: "'dan'", + }], + compositePKs: [], + referenceData: [], + tableName: 'table', + uniqueConstraints: [], + checkConstraints: [], + }); + expect(statements[1]).toStrictEqual({ + data: 'index_name;name;false;', schema: '', tableName: 'table', - type: 'alter_table_alter_column_set_default', + type: 'create_index', + internal: undefined, }); + expect(sqlStatements.length).toBe(7); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_table\` ( +\t\`name\` text DEFAULT 'dan' NOT NULL +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_table\`("name") SELECT "name" FROM \`table\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`table\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_table\` RENAME TO \`table\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); + expect(sqlStatements[6]).toBe(`CREATE INDEX \`index_name\` ON \`table\` (\`name\`);`); }); test('alter column drop default not null', async (t) => { @@ -721,30 +864,186 @@ test('alter column drop default not null', async (t) => { [], ); - expect(statements.length).toBe(2); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - columnAutoIncrement: false, - columnDefault: undefined, - columnName: 'name', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }], + compositePKs: [], + referenceData: [], tableName: 'table', - type: 'alter_table_alter_column_drop_default', + uniqueConstraints: [], + checkConstraints: [], }); + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_table\` ( +\t\`name\` text +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_table\`("name") SELECT "name" FROM \`table\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`table\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_table\` RENAME TO \`table\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('alter column drop generated', async (t) => { + const from = { + users: sqliteTable('table', { + id: int('id').primaryKey().notNull(), + name: text('name').generatedAlwaysAs('drizzle is the best').notNull(), + }), + }; + + const to = { + users: sqliteTable('table', { + id: int('id').primaryKey().notNull(), + name: text('name').notNull(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite( + from, + to, + [], + ); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ columnAutoIncrement: false, columnDefault: undefined, + columnGenerated: undefined, columnName: 'name', - columnNotNull: false, + columnNotNull: true, columnOnUpdate: undefined, columnPk: false, newDataType: 'text', schema: '', tableName: 'table', - type: 'alter_table_alter_column_drop_default', + type: 'alter_table_alter_column_drop_generated', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TABLE \`table\` DROP COLUMN \`name\`;`); + expect(sqlStatements[1]).toBe(`ALTER TABLE \`table\` ADD \`name\` text NOT NULL;`); +}); + +test('recreate table with nested references', async (t) => { + let users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }); + let subscriptions = sqliteTable('subscriptions', { + id: int('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + customerId: text('customer_id'), }); + const schema1 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references(() => subscriptions.id), + }), + }; + + users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }); + const schema2 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references(() => subscriptions.id), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "name", "age") SELECT "id", "name", "age" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('text default values escape single quotes', async (t) => { + const schema1 = { + table: sqliteTable('table', { + id: integer('id').primaryKey(), + }), + }; + + const schem2 = { + table: sqliteTable('table', { + id: integer('id').primaryKey(), + text: text('text').default("escape's quotes"), + }), + }; + + const { sqlStatements } = await diffTestSchemasSqlite(schema1, schem2, []); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toStrictEqual( + "ALTER TABLE `table` ADD `text` text DEFAULT 'escape''s quotes';", + ); }); diff --git a/drizzle-kit/tests/sqlite-generated.test.ts b/drizzle-kit/tests/sqlite-generated.test.ts index 3e1129be4..2d3ceed97 100644 --- a/drizzle-kit/tests/sqlite-generated.test.ts +++ b/drizzle-kit/tests/sqlite-generated.test.ts @@ -171,7 +171,7 @@ test('generated as callback: add generated constraint to an exisiting column as ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE `users` DROP COLUMN `gen_name`;', - 'ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS ("name" || \'to add\') VIRTUAL;', + 'ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS ("name" || \'to add\') VIRTUAL NOT NULL;', ]); }); @@ -508,6 +508,7 @@ test('generated as callback: add table with column with stored generated constra tableName: 'users', type: 'sqlite_create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ @@ -576,6 +577,7 @@ test('generated as callback: add table with column with virtual generated constr tableName: 'users', type: 'sqlite_create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ @@ -744,7 +746,7 @@ test('generated as sql: add generated constraint to an exisiting column as virtu ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE `users` DROP COLUMN `gen_name`;', - 'ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS ("users"."name" || \'to add\') VIRTUAL;', + 'ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS ("users"."name" || \'to add\') VIRTUAL NOT NULL;', ]); }); @@ -1076,6 +1078,7 @@ test('generated as sql: add table with column with stored generated constraint', tableName: 'users', type: 'sqlite_create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ @@ -1144,6 +1147,7 @@ test('generated as sql: add table with column with virtual generated constraint' tableName: 'users', type: 'sqlite_create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ @@ -1312,7 +1316,7 @@ test('generated as string: add generated constraint to an exisiting column as vi ]); expect(sqlStatements).toStrictEqual([ 'ALTER TABLE `users` DROP COLUMN `gen_name`;', - 'ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS ("users"."name" || \'to add\') VIRTUAL;', + 'ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS ("users"."name" || \'to add\') VIRTUAL NOT NULL;', ]); }); @@ -1644,6 +1648,7 @@ test('generated as string: add table with column with stored generated constrain tableName: 'users', type: 'sqlite_create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ @@ -1712,6 +1717,7 @@ test('generated as string: add table with column with virtual generated constrai tableName: 'users', type: 'sqlite_create_table', uniqueConstraints: [], + checkConstraints: [], }, ]); expect(sqlStatements).toStrictEqual([ diff --git a/drizzle-kit/tests/sqlite-tables.test.ts b/drizzle-kit/tests/sqlite-tables.test.ts index d7781f150..651c3633c 100644 --- a/drizzle-kit/tests/sqlite-tables.test.ts +++ b/drizzle-kit/tests/sqlite-tables.test.ts @@ -1,5 +1,15 @@ import { sql } from 'drizzle-orm'; -import { AnySQLiteColumn, index, int, primaryKey, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'; +import { + AnySQLiteColumn, + foreignKey, + index, + int, + primaryKey, + sqliteTable, + text, + unique, + uniqueIndex, +} from 'drizzle-orm/sqlite-core'; import { expect, test } from 'vitest'; import { diffTestSchemasSqlite } from './schemaDiffer'; @@ -18,6 +28,7 @@ test('add table #1', async () => { compositePKs: [], uniqueConstraints: [], referenceData: [], + checkConstraints: [], }); }); @@ -46,6 +57,7 @@ test('add table #2', async () => { compositePKs: [], referenceData: [], uniqueConstraints: [], + checkConstraints: [], }); }); @@ -85,6 +97,7 @@ test('add table #3', async () => { compositePKs: [], uniqueConstraints: [], referenceData: [], + checkConstraints: [], }); }); @@ -104,6 +117,7 @@ test('add table #4', async () => { compositePKs: [], uniqueConstraints: [], referenceData: [], + checkConstraints: [], }); expect(statements[1]).toStrictEqual({ type: 'sqlite_create_table', @@ -112,6 +126,7 @@ test('add table #4', async () => { compositePKs: [], uniqueConstraints: [], referenceData: [], + checkConstraints: [], }); }); @@ -138,11 +153,13 @@ test('add table #6', async () => { compositePKs: [], uniqueConstraints: [], referenceData: [], + checkConstraints: [], }); expect(statements[1]).toStrictEqual({ type: 'drop_table', tableName: 'users1', schema: undefined, + policies: [], }); }); @@ -162,19 +179,20 @@ test('add table #7', async () => { expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ + type: 'rename_table', + tableNameFrom: 'users1', + tableNameTo: 'users2', + fromSchema: undefined, + toSchema: undefined, + }); + expect(statements[1]).toStrictEqual({ type: 'sqlite_create_table', tableName: 'users', columns: [], compositePKs: [], uniqueConstraints: [], referenceData: [], - }); - expect(statements[1]).toStrictEqual({ - type: 'rename_table', - tableNameFrom: 'users1', - tableNameTo: 'users2', - fromSchema: undefined, - toSchema: undefined, + checkConstraints: [], }); }); @@ -212,6 +230,7 @@ test('add table #8', async () => { ], compositePKs: [], uniqueConstraints: [], + checkConstraints: [], referenceData: [ { columnsFrom: ['reportee_id'], @@ -267,6 +286,7 @@ test('add table #9', async () => { compositePKs: [], uniqueConstraints: [], referenceData: [], + checkConstraints: [], }); expect(statements[1]).toStrictEqual({ @@ -397,3 +417,221 @@ test('add table with indexes', async () => { 'CREATE INDEX `indexColExpr` ON `users` ((lower("email")),`email`);', ]); }); + +test('composite primary key', async () => { + const from = {}; + const to = { + table: sqliteTable('works_to_creators', { + workId: int('work_id').notNull(), + creatorId: int('creator_id').notNull(), + classification: text('classification').notNull(), + }, (t) => ({ + pk: primaryKey({ + columns: [t.workId, t.creatorId, t.classification], + }), + })), + }; + + const { sqlStatements } = await diffTestSchemasSqlite(from, to, []); + + expect(sqlStatements).toStrictEqual([ + 'CREATE TABLE `works_to_creators` (\n\t`work_id` integer NOT NULL,\n\t`creator_id` integer NOT NULL,\n\t`classification` text NOT NULL,\n\tPRIMARY KEY(`work_id`, `creator_id`, `classification`)\n);\n', + ]); +}); + +test('add column before creating unique constraint', async () => { + const from = { + table: sqliteTable('table', { + id: int('id').primaryKey(), + }), + }; + const to = { + table: sqliteTable('table', { + id: int('id').primaryKey(), + name: text('name').notNull(), + }, (t) => ({ + uq: unique('uq').on(t.name), + })), + }; + + const { sqlStatements } = await diffTestSchemasSqlite(from, to, []); + + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `table` ADD `name` text NOT NULL;', + 'CREATE UNIQUE INDEX `uq` ON `table` (`name`);', + ]); +}); + +test('optional db aliases (snake case)', async () => { + const from = {}; + + const t1 = sqliteTable( + 't1', + { + t1Id1: int().notNull().primaryKey(), + t1Col2: int().notNull(), + t1Col3: int().notNull(), + t2Ref: int().notNull().references(() => t2.t2Id), + t1Uni: int().notNull(), + t1UniIdx: int().notNull(), + t1Idx: int().notNull(), + }, + (table) => ({ + uni: unique('t1_uni').on(table.t1Uni), + uniIdx: uniqueIndex('t1_uni_idx').on(table.t1UniIdx), + idx: index('t1_idx').on(table.t1Idx), + fk: foreignKey({ + columns: [table.t1Col2, table.t1Col3], + foreignColumns: [t3.t3Id1, t3.t3Id2], + }), + }), + ); + + const t2 = sqliteTable( + 't2', + { + t2Id: int().primaryKey({ autoIncrement: true }), + }, + ); + + const t3 = sqliteTable( + 't3', + { + t3Id1: int(), + t3Id2: int(), + }, + (table) => ({ + pk: primaryKey({ + columns: [table.t3Id1, table.t3Id2], + }), + }), + ); + + const to = { + t1, + t2, + t3, + }; + + const { sqlStatements } = await diffTestSchemasSqlite(from, to, [], false, 'snake_case'); + + const st1 = `CREATE TABLE \`t1\` ( + \`t1_id1\` integer PRIMARY KEY NOT NULL, + \`t1_col2\` integer NOT NULL, + \`t1_col3\` integer NOT NULL, + \`t2_ref\` integer NOT NULL, + \`t1_uni\` integer NOT NULL, + \`t1_uni_idx\` integer NOT NULL, + \`t1_idx\` integer NOT NULL, + FOREIGN KEY (\`t2_ref\`) REFERENCES \`t2\`(\`t2_id\`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (\`t1_col2\`,\`t1_col3\`) REFERENCES \`t3\`(\`t3_id1\`,\`t3_id2\`) ON UPDATE no action ON DELETE no action +); +`; + + const st2 = `CREATE UNIQUE INDEX \`t1_uni_idx\` ON \`t1\` (\`t1_uni_idx\`);`; + + const st3 = `CREATE INDEX \`t1_idx\` ON \`t1\` (\`t1_idx\`);`; + + const st4 = `CREATE UNIQUE INDEX \`t1_uni\` ON \`t1\` (\`t1_uni\`);`; + + const st5 = `CREATE TABLE \`t2\` ( + \`t2_id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL +); +`; + + const st6 = `CREATE TABLE \`t3\` ( + \`t3_id1\` integer, + \`t3_id2\` integer, + PRIMARY KEY(\`t3_id1\`, \`t3_id2\`) +); +`; + + expect(sqlStatements).toStrictEqual([st1, st2, st3, st4, st5, st6]); +}); + +test('optional db aliases (camel case)', async () => { + const from = {}; + + const t1 = sqliteTable( + 't1', + { + t1_id1: int().notNull().primaryKey(), + t1_col2: int().notNull(), + t1_col3: int().notNull(), + t2_ref: int().notNull().references(() => t2.t2_id), + t1_uni: int().notNull(), + t1_uni_idx: int().notNull(), + t1_idx: int().notNull(), + }, + (table) => ({ + uni: unique('t1Uni').on(table.t1_uni), + uni_idx: uniqueIndex('t1UniIdx').on(table.t1_uni_idx), + idx: index('t1Idx').on(table.t1_idx), + fk: foreignKey({ + columns: [table.t1_col2, table.t1_col3], + foreignColumns: [t3.t3_id1, t3.t3_id2], + }), + }), + ); + + const t2 = sqliteTable( + 't2', + { + t2_id: int().primaryKey({ autoIncrement: true }), + }, + ); + + const t3 = sqliteTable( + 't3', + { + t3_id1: int(), + t3_id2: int(), + }, + (table) => ({ + pk: primaryKey({ + columns: [table.t3_id1, table.t3_id2], + }), + }), + ); + + const to = { + t1, + t2, + t3, + }; + + const { sqlStatements } = await diffTestSchemasSqlite(from, to, [], false, 'camelCase'); + + const st1 = `CREATE TABLE \`t1\` ( + \`t1Id1\` integer PRIMARY KEY NOT NULL, + \`t1Col2\` integer NOT NULL, + \`t1Col3\` integer NOT NULL, + \`t2Ref\` integer NOT NULL, + \`t1Uni\` integer NOT NULL, + \`t1UniIdx\` integer NOT NULL, + \`t1Idx\` integer NOT NULL, + FOREIGN KEY (\`t2Ref\`) REFERENCES \`t2\`(\`t2Id\`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (\`t1Col2\`,\`t1Col3\`) REFERENCES \`t3\`(\`t3Id1\`,\`t3Id2\`) ON UPDATE no action ON DELETE no action +); +`; + + const st2 = `CREATE UNIQUE INDEX \`t1UniIdx\` ON \`t1\` (\`t1UniIdx\`);`; + + const st3 = `CREATE INDEX \`t1Idx\` ON \`t1\` (\`t1Idx\`);`; + + const st4 = `CREATE UNIQUE INDEX \`t1Uni\` ON \`t1\` (\`t1Uni\`);`; + + const st5 = `CREATE TABLE \`t2\` ( + \`t2Id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL +); +`; + + const st6 = `CREATE TABLE \`t3\` ( + \`t3Id1\` integer, + \`t3Id2\` integer, + PRIMARY KEY(\`t3Id1\`, \`t3Id2\`) +); +`; + + expect(sqlStatements).toStrictEqual([st1, st2, st3, st4, st5, st6]); +}); diff --git a/drizzle-kit/tests/sqlite-views.test.ts b/drizzle-kit/tests/sqlite-views.test.ts new file mode 100644 index 000000000..8021ba37e --- /dev/null +++ b/drizzle-kit/tests/sqlite-views.test.ts @@ -0,0 +1,218 @@ +import { sql } from 'drizzle-orm'; +import { int, sqliteTable, sqliteView } from 'drizzle-orm/sqlite-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasSqlite } from './schemaDiffer'; + +test('create view', async () => { + const users = sqliteTable('users', { id: int('id').default(1) }); + const view = sqliteView('view').as((qb) => qb.select().from(users)); + const to = { + users: users, + testView: view, + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_create_table', + tableName: 'users', + columns: [{ + autoincrement: false, + default: 1, + name: 'id', + type: 'integer', + primaryKey: false, + notNull: false, + }], + compositePKs: [], + uniqueConstraints: [], + referenceData: [], + checkConstraints: [], + }); + expect(statements[1]).toStrictEqual({ + type: 'sqlite_create_view', + name: 'view', + definition: 'select "id" from "users"', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`CREATE TABLE \`users\` ( +\t\`id\` integer DEFAULT 1 +);\n`); + expect(sqlStatements[1]).toBe(`CREATE VIEW \`view\` AS select "id" from "users";`); +}); + +test('drop view', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).as(sql`SELECT * FROM users`), + }; + const to = { + users, + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite(from, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + name: 'view', + type: 'drop_view', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `DROP VIEW \`view\`;`, + ); +}); + +test('alter view', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).as(sql`SELECT * FROM users`), + }; + const to = { + users, + testView: sqliteView('view', { id: int('id') }).as(sql`SELECT * FROM users WHERE users.id = 1`), + }; + const { statements, sqlStatements } = await diffTestSchemasSqlite(from, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + name: 'view', + type: 'drop_view', + }); + expect(statements[1]).toStrictEqual({ + name: 'view', + type: 'sqlite_create_view', + definition: 'SELECT * FROM users WHERE users.id = 1', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `DROP VIEW \`view\`;`, + ); + expect(sqlStatements[1]).toBe( + `CREATE VIEW \`view\` AS SELECT * FROM users WHERE users.id = 1;`, + ); +}); + +test('create view with existing flag', async () => { + const view = sqliteView('view', {}).existing(); + const to = { + testView: view, + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite({}, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('drop view with existing flag', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).existing(), + }; + const to = { + users, + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite(from, to, []); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('rename view with existing flag', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).existing(), + }; + const to = { + users, + testView: sqliteView('new_view', { id: int('id') }).existing(), + }; + const { statements, sqlStatements } = await diffTestSchemasSqlite(from, to, ['view->new_view']); + + expect(statements.length).toBe(0); + expect(sqlStatements.length).toBe(0); +}); + +test('rename view and drop existing flag', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).existing(), + }; + const to = { + users, + testView: sqliteView('new_view', { id: int('id') }).as(sql`SELECT * FROM users`), + }; + const { statements, sqlStatements } = await diffTestSchemasSqlite(from, to, ['view->new_view']); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + name: 'view', + type: 'drop_view', + }); + expect(statements[1]).toStrictEqual({ + type: 'sqlite_create_view', + name: 'new_view', + definition: 'SELECT * FROM users', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`DROP VIEW \`view\`;`); + expect(sqlStatements[1]).toBe(`CREATE VIEW \`new_view\` AS SELECT * FROM users;`); +}); + +test('rename view and alter ".as"', async () => { + const users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }); + + const from = { + users: users, + testView: sqliteView('view', { id: int('id') }).as(sql`SELECT * FROM users`), + }; + const to = { + users, + testView: sqliteView('new_view', { id: int('id') }).as(sql`SELECT * FROM users WHERE 1=1`), + }; + const { statements, sqlStatements } = await diffTestSchemasSqlite(from, to, ['view->new_view']); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + name: 'view', + type: 'drop_view', + }); + expect(statements[1]).toStrictEqual({ + type: 'sqlite_create_view', + name: 'new_view', + definition: 'SELECT * FROM users WHERE 1=1', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe('DROP VIEW `view`;'); + expect(sqlStatements[1]).toBe(`CREATE VIEW \`new_view\` AS SELECT * FROM users WHERE 1=1;`); +}); diff --git a/drizzle-kit/tests/statements-combiner/libsql-statements-combiner.test.ts b/drizzle-kit/tests/statements-combiner/libsql-statements-combiner.test.ts new file mode 100644 index 000000000..449b61c6c --- /dev/null +++ b/drizzle-kit/tests/statements-combiner/libsql-statements-combiner.test.ts @@ -0,0 +1,1812 @@ +import { JsonAddColumnStatement, JsonSqliteAddColumnStatement, JsonStatement } from 'src/jsonStatements'; +import { SQLiteSchemaSquashed } from 'src/serializer/sqliteSchema'; +import { SQLiteAlterTableAddColumnConvertor } from 'src/sqlgenerator'; +import { libSQLCombineStatements } from 'src/statementCombiner'; +import { expect, test } from 'vitest'; + +/** + * ! before: + * + * user: { + * id INT; + * first_name INT; + * iq INT; + * PRIMARY KEY (id, iq) + * INDEXES: { + * UNIQUE id; + * } + * } + * + * ! after: + * + * new_user: { + * id INT; + * first_name INT; + * iq INT; + * PRIMARY KEY (id, iq) + * INDEXES: {} + * } + * + * rename table and drop unique index + * expect to get "rename_table" statement and then "recreate_table" + */ +test(`rename table and drop index`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'rename_table', + fromSchema: '', + toSchema: '', + tableNameFrom: 'user', + tableNameTo: 'new_user', + }, + { + type: 'drop_index', + tableName: 'new_user', + data: 'user_first_name_unique;first_name;true;', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + first_name: { + name: 'first_name', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: { + user_first_name_unique: 'user_first_name_unique;first_name;true;', + }, + foreignKeys: {}, + compositePrimaryKeys: { + user_id_iq_pk: 'id,iq', + }, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + new_user: { + name: 'new_user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + first_name: { + name: 'first_name', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: { + new_user_id_iq_pk: 'id,iq', + }, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'rename_table', + fromSchema: '', + toSchema: '', + tableNameFrom: 'user', + tableNameTo: 'new_user', + }, + { + type: 'drop_index', + tableName: 'new_user', + data: 'user_first_name_unique;first_name;true;', + schema: '', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +/** + * ! before: + * + * autoincrement1: { + * id INT PRIMARY KEY; + * } + * + * autoincrement2: { + * id INT PRIMARY KEY AUTOINCREMENT; + * } + * + * dropNotNull: { + * id INT NOT NULL; + * } + * + * ! after: + * + * autoincrement1: { + * id INT PRIMARY KEY AUTOINCREMENT; + * } + * + * autoincrement2: { + * id INT PRI { + const statements: JsonStatement[] = [ + { + type: 'alter_table_alter_column_set_autoincrement', + tableName: 'autoincrement1', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: true, + columnPk: true, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_drop_autoincrement', + tableName: 'autoincrement2', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: true, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_drop_notnull', + tableName: 'dropNotNull', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + autoincrement1: { + name: 'autoincrement1', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + autoincrement2: { + name: 'autoincrement2', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: false, + autoincrement: true, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + dropNotNull: { + name: 'dropNotNull', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + autoincrement1: { + name: 'autoincrement1', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: true, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + autoincrement2: { + name: 'autoincrement2', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + dropNotNull: { + name: 'dropNotNull', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'autoincrement1', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: true, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + { + type: 'recreate_table', + tableName: 'autoincrement2', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + { + type: 'alter_table_alter_column_drop_notnull', + tableName: 'dropNotNull', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +/** + * ! before: + * + * pk1: { + * id INT; + * } + * + * pk2: { + * id INT PRIMARY KEY; + * } + * + * ref_table: { + * id INT; + * } + * + * create_reference: { + * id INT; + * } + * + * ! after: + * + * pk1: { + * id INT PRIMARY KEY; + * } + * + * pk2: { + * id INT; + * } + * + * ref_table: { + * id INT; + * } + * + * create_reference: { + * id INT -> ref_table INT; + * } + * + * drop primary key for pk2 + * set primary key for pk1 + * "create_reference" reference on "ref_table" + * + * expect to: + * - "recreate_table" statement for pk1 + * - "recreate_table" statement for pk2 + * - "create_reference" statement for create_reference + */ +test(`drop and set primary key. create reference`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_alter_column_set_pk', + tableName: 'pk1', + schema: '', + columnName: 'id', + }, + { + type: 'alter_table_alter_column_set_notnull', + tableName: 'pk1', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: true, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_drop_pk', + tableName: 'pk2', + columnName: 'id', + schema: '', + }, + { + type: 'alter_table_alter_column_drop_notnull', + tableName: 'pk2', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'create_reference', + tableName: 'create_reference', + data: 'create_reference_id_ref_table_id_fk;create_reference;id;ref_table;id;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'int', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + create_reference: { + name: 'create_reference', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + pk1: { + name: 'pk1', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + pk2: { + name: 'pk2', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + ref_table: { + name: 'ref_table', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + create_reference: { + name: 'create_reference', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + create_reference_id_ref_table_id_fk: + 'create_reference_id_ref_table_id_fk;create_reference;id;ref_table;id;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + pk1: { + name: 'pk1', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + pk2: { + name: 'pk2', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + ref_table: { + name: 'ref_table', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'pk1', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + { + type: 'recreate_table', + tableName: 'pk2', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + { + type: 'create_reference', + tableName: 'create_reference', + data: 'create_reference_id_ref_table_id_fk;create_reference;id;ref_table;id;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'int', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +/** + * ! before: + * + * fk1: { + * fk_id INT; + * fk_id1 INT; + * } + * + * fk2: { + * fk2_id INT; -> composite reference on ref_table id INT + * fk2_id1 INT; -> composite reference on ref_table id1 INT + * } + * + * ref_table: { + * id INT; + * id1 INT; + * } + * + * ! after: + * + * fk1: { + * fk_id INT; -> composite reference on ref_table id INT + * fk_id1 INT; -> composite reference on ref_table id1 INT + * } + * + * fk2: { + * fk2_id INT; + * fk2_id1 INT; + * } + * + * ref_table: { + * id INT; + * id1 INT; + * } + * + * set multi column reference for fk1 + * drop multi column reference for fk2 + * + * expect to: + * - "recreate_table" statement for fk1 + * - "recreate_table" statement for fk2 + */ +test(`set and drop multiple columns reference`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'delete_reference', + tableName: 'fk1', + data: 'fk1_fk_id_fk_id1_ref_table_id_id1_fk;fk1;fk_id,fk_id1;ref_table;id,id1;no action;no action', + schema: '', + isMulticolumn: true, + }, + { + type: 'create_reference', + tableName: 'fk2', + data: 'fk2_fk2_id_fk2_id1_ref_table_id_id1_fk;fk2;fk2_id,fk2_id1;ref_table;id,id1;no action;no action', + schema: '', + isMulticolumn: true, + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + fk1: { + name: 'fk1', + columns: { + fk_id: { + name: 'fk_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + fk_id1: { + name: 'fk_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + fk1_fk_id_fk_id1_ref_table_id_id1_fk: + 'fk1_fk_id_fk_id1_ref_table_id_id1_fk;fk1;fk_id,fk_id1;ref_table;id,id1;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + fk2: { + name: 'fk2', + columns: { + fk2_id: { + name: 'fk2_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + fk2_id1: { + name: 'fk2_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + ref_table: { + name: 'ref_table', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + id1: { + name: 'id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + fk1: { + name: 'fk1', + columns: { + fk_id: { + name: 'fk_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + fk_id1: { + name: 'fk_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + fk2: { + name: 'fk2', + columns: { + fk2_id: { + name: 'fk2_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + fk2_id1: { + name: 'fk2_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + fk2_fk2_id_fk2_id1_ref_table_id_id1_fk: + 'fk2_fk2_id_fk2_id1_ref_table_id_id1_fk;fk2;fk2_id,fk2_id1;ref_table;id,id1;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + ref_table: { + name: 'ref_table', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + id1: { + name: 'id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'fk1', + columns: [ + { + name: 'fk_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'fk_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + { + type: 'recreate_table', + tableName: 'fk2', + columns: [ + { + name: 'fk2_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'fk2_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [ + { + name: 'fk2_fk2_id_fk2_id1_ref_table_id_id1_fk', + tableFrom: 'fk2', + tableTo: 'ref_table', + columnsFrom: ['fk2_id', 'fk2_id1'], + columnsTo: ['id', 'id1'], + onDelete: 'no action', + onUpdate: 'no action', + }, + ], + uniqueConstraints: [], + checkConstraints: [], + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +/** + * ! before: + * + * pk: { + * pk TEXT PRIMARY KEY; + * } + * + * simple: { + * simple TEXT; + * } + * + * unique: { + * unique INT UNIQUE; + * } + * + * ! after: + * + * pk: { + * pk INT PRIMARY KEY; + * } + * + * simple: { + * simple INT; + * } + * + * unique: { + * unique TEXT UNIQUE; + * } + * + * set new type for primary key column + * set new type for unique column + * set new type for column without pk or unique + * + * expect to: + * - "recreate_table" statement for pk + * - "recreate_table" statement for unique + * - "alter_table_alter_column_set_type" statement for simple + * - "create_index" statement for unique + */ +test(`set new type for primary key, unique and normal column`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_alter_column_set_type', + tableName: 'pk', + columnName: 'pk', + newDataType: 'int', + oldDataType: 'text', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: true, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_set_type', + tableName: 'simple', + columnName: 'simple', + newDataType: 'int', + oldDataType: 'text', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_set_type', + tableName: 'unique', + columnName: 'unique', + newDataType: 'text', + oldDataType: 'int', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + pk: { + name: 'pk', + columns: { + pk: { + name: 'pk', + type: 'text', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + simple: { + name: 'simple', + columns: { + simple: { + name: 'simple', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + unique: { + name: 'unique', + columns: { + unique: { + name: 'unique', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: { + unique_unique_unique: 'unique_unique_unique;unique;true;', + }, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + pk: { + name: 'pk', + columns: { + pk: { + name: 'pk', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + simple: { + name: 'simple', + columns: { + simple: { + name: 'simple', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + unique: { + name: 'unique', + columns: { + unique: { + name: 'unique', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: { + unique_unique_unique: 'unique_unique_unique;unique;true;', + }, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'pk', + columns: [ + { + name: 'pk', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + { + type: 'alter_table_alter_column_set_type', + tableName: 'simple', + columnName: 'simple', + newDataType: 'int', + oldDataType: 'text', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }, + { + type: 'alter_table_alter_column_set_type', + tableName: 'unique', + columnName: 'unique', + newDataType: 'text', + oldDataType: 'int', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add columns. set fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add column and fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_test1_user_new_age_fk: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add column and fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_test1_user_new_age_fk: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); diff --git a/drizzle-kit/tests/statements-combiner/sqlite-statements-combiner.test.ts b/drizzle-kit/tests/statements-combiner/sqlite-statements-combiner.test.ts new file mode 100644 index 000000000..20f953da6 --- /dev/null +++ b/drizzle-kit/tests/statements-combiner/sqlite-statements-combiner.test.ts @@ -0,0 +1,1211 @@ +import { JsonStatement } from 'src/jsonStatements'; +import { SQLiteSchemaSquashed } from 'src/serializer/sqliteSchema'; +import { sqliteCombineStatements } from 'src/statementCombiner'; +import { expect, test } from 'vitest'; + +test(`renamed column and altered this column type`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'user', + oldColumnName: 'lastName', + newColumnName: 'lastName123', + schema: '', + }, + { + type: 'alter_table_alter_column_set_type', + tableName: 'user', + columnName: 'lastName123', + newDataType: 'int', + oldDataType: 'text', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + columnIsUnique: false, + } as unknown as JsonStatement, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + firstName: { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + lastName: { + name: 'lastName', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + firstName: { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + lastName: { + name: 'lastName123', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'user', + columns: [ + { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + { + name: 'lastName123', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'test', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`renamed column and droped column "test"`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'user', + oldColumnName: 'lastName', + newColumnName: 'lastName123', + schema: '', + }, + { + type: 'alter_table_drop_column', + tableName: 'user', + columnName: 'test', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + firstName: { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + lastName: { + name: 'lastName', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + firstName: { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + lastName: { + name: 'lastName123', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'user', + oldColumnName: 'lastName', + newColumnName: 'lastName123', + schema: '', + }, + { + type: 'alter_table_drop_column', + tableName: 'user', + columnName: 'test', + schema: '', + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`droped column that is part of composite pk`, async (t) => { + const statements: JsonStatement[] = [ + { type: 'delete_composite_pk', tableName: 'user', data: 'id,iq' }, + { + type: 'alter_table_alter_column_set_pk', + tableName: 'user', + schema: '', + columnName: 'id', + }, + { + type: 'alter_table_drop_column', + tableName: 'user', + columnName: 'iq', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + first_nam: { + name: 'first_nam', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: { + user_id_iq_pk: 'id,iq', + }, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: false, + autoincrement: false, + }, + first_nam: { + name: 'first_nam', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements: JsonStatement[] = [ + { + type: 'recreate_table', + tableName: 'user', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: true, + notNull: false, + autoincrement: false, + }, + { + name: 'first_nam', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`drop column "ref"."name", rename column "ref"."age". dropped primary key "user"."id". Set not null to "user"."iq"`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'ref', + oldColumnName: 'age', + newColumnName: 'age1', + schema: '', + }, + { + type: 'alter_table_alter_column_drop_pk', + tableName: 'user', + columnName: 'id', + schema: '', + }, + { + type: 'alter_table_alter_column_drop_autoincrement', + tableName: 'user', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_drop_notnull', + tableName: 'user', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_set_notnull', + tableName: 'user', + columnName: 'iq', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'alter_table_drop_column', + tableName: 'ref', + columnName: 'text', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: true, + }, + user_iq: { + name: 'user_iq', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + name: { + name: 'name', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + age: { + name: 'age', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_user_iq_user_iq_fk: 'ref_user_iq_user_iq_fk;ref;user_iq;user;iq;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: true, + }, + first_name: { + name: 'first_name', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + user_iq: { + name: 'user_iq', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + age1: { + name: 'age1', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_user_iq_user_iq_fk: 'ref_user_iq_user_iq_fk;ref;user_iq;user;iq;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + first_name: { + name: 'first_name', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'ref', + oldColumnName: 'age', + newColumnName: 'age1', + schema: '', + }, + { + type: 'alter_table_drop_column', + tableName: 'ref', + columnName: 'text', + schema: '', + }, + { + type: 'recreate_table', + tableName: 'user', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + { + name: 'first_name', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + checkConstraints: [], + }, + ]; + + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`create reference on exising column (table includes unique index). expect to recreate column and recreate index`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'create_reference', + tableName: 'unique', + data: 'unique_ref_pk_pk_pk_fk;unique;ref_pk;pk;pk;no action;no action', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + pk: { + name: 'pk', + columns: { + pk: { + name: 'pk', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + unique: { + name: 'unique', + columns: { + unique: { + name: 'unique', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ref_pk: { + name: 'ref_pk', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: { + unique_unique_unique: 'unique_unique_unique;unique;true;', + }, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + pk: { + name: 'pk', + columns: { + pk: { + name: 'pk', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + unique: { + name: 'unique', + columns: { + unique: { + name: 'unique', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ref_pk: { + name: 'ref_pk', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: { + unique_unique_unique: 'unique_unique_unique;unique;true;', + }, + foreignKeys: { + unique_ref_pk_pk_pk_fk: 'unique_ref_pk_pk_pk_fk;unique;ref_pk;pk;pk;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements: JsonStatement[] = [ + { + type: 'recreate_table', + tableName: 'unique', + columns: [ + { + name: 'unique', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'ref_pk', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [ + { + name: 'unique_ref_pk_pk_pk_fk', + tableFrom: 'unique', + tableTo: 'pk', + columnsFrom: ['ref_pk'], + columnsTo: ['pk'], + onDelete: 'no action', + onUpdate: 'no action', + }, + ], + uniqueConstraints: [], + checkConstraints: [], + }, + { + data: 'unique_unique_unique;unique;true;', + internal: undefined, + schema: '', + tableName: 'unique', + type: 'create_index', + }, + ]; + + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add columns. set fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + columns: [ + { + autoincrement: false, + name: 'id1', + notNull: true, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'new_age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'test', + notNull: false, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'test1', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [ + { + columnsFrom: [ + 'new_age', + ], + columnsTo: [ + 'new_age', + ], + name: 'ref_new_age_user_new_age_fk', + onDelete: 'no action', + onUpdate: 'no action', + tableFrom: 'ref', + tableTo: 'user', + }, + ], + tableName: 'ref', + type: 'recreate_table', + uniqueConstraints: [], + checkConstraints: [], + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add column and fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_test1_user_new_age_fk: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + checkConstraints: {}, + }, + }, + enums: {}, + views: {}, + }; + + const newJsonStatements = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); diff --git a/drizzle-kit/tests/testmysql.ts b/drizzle-kit/tests/testmysql.ts deleted file mode 100644 index 092f0a9e1..000000000 --- a/drizzle-kit/tests/testmysql.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { index, mysqlTable, text } from 'drizzle-orm/mysql-core'; -import { diffTestSchemasMysql } from './schemaDiffer'; - -const from = { - users: mysqlTable( - 'table', - { - name: text('name'), - }, - (t) => { - return { - idx: index('name_idx').on(t.name), - }; - }, - ), -}; - -const to = { - users: mysqlTable('table', { - name: text('name'), - }), -}; - -const { statements, sqlStatements } = await diffTestSchemasMysql(from, to, []); - -console.log(statements); -console.log(sqlStatements); diff --git a/drizzle-kit/tests/testsinglestore.ts b/drizzle-kit/tests/testsinglestore.ts new file mode 100644 index 000000000..1dc97d9c3 --- /dev/null +++ b/drizzle-kit/tests/testsinglestore.ts @@ -0,0 +1,29 @@ +import { index, singlestoreTable, text } from 'drizzle-orm/singlestore-core'; +import { diffTestSchemasSingleStore } from './schemaDiffer'; + +const from = { + users: singlestoreTable( + 'table', + { + name: text('name'), + }, + (t) => { + return { + idx: index('name_idx').on(t.name), + }; + }, + ), +}; + +const to = { + users: singlestoreTable('table', { + name: text('name'), + }), +}; + +diffTestSchemasSingleStore(from, to, []).then((res) => { + const { statements, sqlStatements } = res; + + console.log(statements); + console.log(sqlStatements); +}); diff --git a/drizzle-kit/tests/testsqlite.ts b/drizzle-kit/tests/testsqlite.ts deleted file mode 100644 index e68bbc195..000000000 --- a/drizzle-kit/tests/testsqlite.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; -import { diffTestSchemasMysql, diffTestSchemasSqlite } from './schemaDiffer'; - -const from = { - users: sqliteTable('table', { - password: text('password'), - }), -}; - -const to = { - users: sqliteTable('table1', { - password_hash: text('password_hash'), - }), -}; - -const { statements, sqlStatements } = await diffTestSchemasSqlite(from, to, [], true); - -console.log(statements); -console.log(sqlStatements); diff --git a/drizzle-kit/tests/validations.test.ts b/drizzle-kit/tests/validations.test.ts index 04d0096ff..8a64603bb 100644 --- a/drizzle-kit/tests/validations.test.ts +++ b/drizzle-kit/tests/validations.test.ts @@ -1,5 +1,6 @@ import { mysqlCredentials } from 'src/cli/validations/mysql'; import { postgresCredentials } from 'src/cli/validations/postgres'; +import { singlestoreCredentials } from 'src/cli/validations/singlestore'; import { sqliteCredentials } from 'src/cli/validations/sqlite'; import { expect, test } from 'vitest'; @@ -270,6 +271,38 @@ test('AWS Data API #8', () => { }).toThrowError(); }); +test('PGlite #1', () => { + expect( + postgresCredentials.parse({ + dialect: 'postgres', + driver: 'pglite', + url: './my.db', + }), + ).toStrictEqual({ + driver: 'pglite', + url: './my.db', + }); +}); + +test('PGlite #2', () => { + expect(() => { + postgresCredentials.parse({ + dialect: 'postgres', + driver: 'pglite', + url: '', + }); + }).toThrowError(); +}); + +test('PGlite #3', () => { + expect(() => { + postgresCredentials.parse({ + dialect: 'postgres', + driver: 'pglite', + }); + }).toThrowError(); +}); + test('postgres #1', () => { expect( postgresCredentials.parse({ @@ -666,3 +699,171 @@ test('mysql #17', () => { }); }).toThrowError(); }); + +test('singlestore #1', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: 'database', + host: 'host', + }), + ).toStrictEqual({ + database: 'database', + host: 'host', + }); +}); + +test('singlestore #2', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: 'database', + host: 'host', + }), + ).toStrictEqual({ + database: 'database', + host: 'host', + }); +}); + +test('singlestore #3', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: 'host', + port: 1234, + user: 'user', + password: 'password', + database: 'database', + ssl: 'require', + }), + ).toStrictEqual({ + host: 'host', + port: 1234, + user: 'user', + password: 'password', + database: 'database', + ssl: 'require', + }); +}); + +test('singlestore #4', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: 'host', + database: 'database', + ssl: 'allow', + }), + ).toStrictEqual({ + host: 'host', + database: 'database', + ssl: 'allow', + }); +}); + +test('singlestore #5', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: 'host', + database: 'database', + ssl: { + ca: 'ca', + cert: 'cert', + }, + }), + ).toStrictEqual({ + host: 'host', + database: 'database', + ssl: { + ca: 'ca', + cert: 'cert', + }, + }); +}); + +test('singlestore #6', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + }); + }).toThrowError(); +}); + +test('singlestore #7', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + url: undefined, + }); + }).toThrowError(); +}); + +test('singlestore #8', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + url: '', + }); + }).toThrowError(); +}); + +test('singlestore #9', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: '', + database: '', + }); + }).toThrowError(); +}); + +test('singlestore #10', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: '', + }); + }).toThrowError(); +}); + +test('singlestore #11', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: '', + }); + }).toThrowError(); +}); + +test('singlestore #12', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: ' ', + host: '', + }); + }).toThrowError(); +}); + +test('singlestore #13', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: '', + host: ' ', + }); + }).toThrowError(); +}); + +test('singlestore #14', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: ' ', + host: ' ', + port: '', + }); + }).toThrowError(); +}); diff --git a/drizzle-kit/tests/wrap-param.test.ts b/drizzle-kit/tests/wrap-param.test.ts index 542998bda..a27d27d45 100644 --- a/drizzle-kit/tests/wrap-param.test.ts +++ b/drizzle-kit/tests/wrap-param.test.ts @@ -7,6 +7,9 @@ test('wrapParam', () => { expect(wrapParam('url', 'mysql://user:password@localhost:3306/database', false, 'url')).toBe( ` [${chalk.green('✓')}] url: 'mysql://user:****@localhost:3306/database'`, ); + expect(wrapParam('url', 'singlestore://user:password@localhost:3306/database', false, 'url')).toBe( + ` [${chalk.green('✓')}] url: 'singlestore://user:****@localhost:3306/database'`, + ); expect(wrapParam('url', 'postgresql://user:password@localhost:5432/database', false, 'url')).toBe( ` [${chalk.green('✓')}] url: 'postgresql://user:****@localhost:5432/database'`, ); diff --git a/drizzle-kit/tsconfig.build.json b/drizzle-kit/tsconfig.build.json new file mode 100644 index 000000000..b57ab6b00 --- /dev/null +++ b/drizzle-kit/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"] +} diff --git a/drizzle-kit/vitest.config.ts b/drizzle-kit/vitest.config.ts index 602e96ede..fd728eb11 100644 --- a/drizzle-kit/vitest.config.ts +++ b/drizzle-kit/vitest.config.ts @@ -5,8 +5,17 @@ export default defineConfig({ test: { include: [ 'tests/**/*.test.ts', + // Need to test it first before pushing changes + // 'tests/singlestore-schemas.test.ts', + // 'tests/singlestore-views.test.ts', + // 'tests/push/singlestore-push.test.ts', + // 'tests/push/singlestore.test.ts', ], + // This one was excluded because we need to modify an API for SingleStore-generated columns. + // It’s in the backlog. + exclude: ['tests/**/singlestore-generated.test.ts'], + typecheck: { tsconfig: 'tsconfig.json', }, diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index 74c3726f7..7e49ec522 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -1,10 +1,11 @@ { "name": "drizzle-orm", - "version": "0.32.1", + "version": "0.38.4", "description": "Drizzle ORM package for SQL databases", "type": "module", "scripts": { - "build": "scripts/build.ts", + "p": "prisma generate --schema src/prisma/schema.prisma", + "build": "pnpm p && scripts/build.ts", "b": "pnpm build", "test:types": "cd type-tests && tsc", "test": "vitest run", @@ -28,6 +29,7 @@ "orm", "pg", "mysql", + "singlestore", "postgresql", "postgres", "sqlite", @@ -44,10 +46,11 @@ }, "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=3", - "@electric-sql/pglite": ">=0.1.1", - "@libsql/client": "*", - "@neondatabase/serverless": ">=0.1", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", @@ -61,7 +64,7 @@ "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", - "expo-sqlite": ">=13.2.0", + "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", @@ -129,6 +132,9 @@ "@libsql/client": { "optional": true }, + "@libsql/client-wasm": { + "optional": true + }, "@opentelemetry/api": { "optional": true }, @@ -159,10 +165,12 @@ }, "devDependencies": { "@aws-sdk/client-rds-data": "^3.549.0", - "@cloudflare/workers-types": "^4.20230904.0", - "@electric-sql/pglite": "^0.1.1", - "@libsql/client": "^0.5.6", - "@neondatabase/serverless": "^0.9.0", + "@cloudflare/workers-types": "^4.20241112.0", + "@electric-sql/pglite": "^0.2.12", + "@libsql/client": "^0.10.0", + "@libsql/client-wasm": "^0.10.0", + "@miniflare/d1": "^2.14.4", + "@neondatabase/serverless": "^0.10.0", "@op-engineering/op-sqlite": "^2.0.16", "@opentelemetry/api": "^1.4.1", "@originjs/vite-plugin-commonjs": "^1.0.3", @@ -179,7 +187,7 @@ "better-sqlite3": "^8.4.0", "bun-types": "^0.6.6", "cpy": "^10.1.0", - "expo-sqlite": "^13.2.0", + "expo-sqlite": "^14.0.0", "knex": "^2.4.2", "kysely": "^0.25.0", "mysql2": "^3.3.3", diff --git a/drizzle-orm/scripts/fix-imports.ts b/drizzle-orm/scripts/fix-imports.ts index f2035eeda..6fc63a48f 100755 --- a/drizzle-orm/scripts/fix-imports.ts +++ b/drizzle-orm/scripts/fix-imports.ts @@ -54,6 +54,12 @@ await Promise.all(cjsFiles.map(async (file) => { path.value.argument.value = resolvePathAlias(path.value.argument.value, file); this.traverse(path); }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.cjs'); + } + this.traverse(path); + }, }); await fs.writeFile(file, print(code).code); @@ -83,6 +89,12 @@ await Promise.all(esmFiles.map(async (file) => { path.value.argument.value = fixImportPath(path.value.argument.value, file, '.js'); this.traverse(path); }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.js'); + } + this.traverse(path); + }, }); await fs.writeFile(file, print(code).code); diff --git a/drizzle-orm/src/aws-data-api/pg/driver.ts b/drizzle-orm/src/aws-data-api/pg/driver.ts index 5174c24d0..eb05913e2 100644 --- a/drizzle-orm/src/aws-data-api/pg/driver.ts +++ b/drizzle-orm/src/aws-data-api/pg/driver.ts @@ -1,6 +1,5 @@ +import { RDSDataClient, type RDSDataClientConfig } from '@aws-sdk/client-rds-data'; import { entityKind, is } from '~/entity.ts'; -import type { SQL, SQLWrapper } from '~/index.ts'; -import { Param, sql, Table } from '~/index.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; import { PgDatabase } from '~/pg-core/db.ts'; @@ -14,6 +13,8 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; +import { Param, type SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; +import { Table } from '~/table.ts'; import type { DrizzleConfig, UpdateSet } from '~/utils.ts'; import type { AwsDataApiClient, AwsDataApiPgQueryResult, AwsDataApiPgQueryResultHKT } from './session.ts'; import { AwsDataApiSession } from './session.ts'; @@ -36,39 +37,42 @@ export interface DrizzleAwsDataApiPgConfig< export class AwsDataApiPgDatabase< TSchema extends Record = Record, > extends PgDatabase { - static readonly [entityKind]: string = 'AwsDataApiPgDatabase'; + static override readonly [entityKind]: string = 'AwsDataApiPgDatabase'; override execute< TRow extends Record = Record, - >(query: SQLWrapper): PgRaw> { + >(query: SQLWrapper | string): PgRaw> { return super.execute(query); } } export class AwsPgDialect extends PgDialect { - static readonly [entityKind]: string = 'AwsPgDialect'; + static override readonly [entityKind]: string = 'AwsPgDialect'; override escapeParam(num: number): string { return `:${num + 1}`; } override buildInsertQuery( - { table, values, onConflict, returning }: PgInsertConfig>, + { table, values, onConflict, returning, select, withList }: PgInsertConfig>, ): SQL { const columns: Record = table[Table.Symbol.Columns]; - for (const value of values) { - for (const fieldName of Object.keys(columns)) { - const colValue = value[fieldName]; - if ( - is(colValue, Param) && colValue.value !== undefined && is(colValue.encoder, PgArray) - && Array.isArray(colValue.value) - ) { - value[fieldName] = sql`cast(${colValue} as ${sql.raw(colValue.encoder.getSQLType())})`; + + if (!select) { + for (const value of (values as Record[])) { + for (const fieldName of Object.keys(columns)) { + const colValue = value[fieldName]; + if ( + is(colValue, Param) && colValue.value !== undefined && is(colValue.encoder, PgArray) + && Array.isArray(colValue.value) + ) { + value[fieldName] = sql`cast(${colValue} as ${sql.raw(colValue.encoder.getSQLType())})`; + } } } } - return super.buildInsertQuery({ table, values, onConflict, returning }); + return super.buildInsertQuery({ table, values, onConflict, returning, withList }); } override buildUpdateSet(table: PgTable, set: UpdateSet): SQL { @@ -87,11 +91,13 @@ export class AwsPgDialect extends PgDialect { } } -export function drizzle = Record>( +function construct = Record>( client: AwsDataApiClient, config: DrizzleAwsDataApiPgConfig, -): AwsDataApiPgDatabase { - const dialect = new AwsPgDialect(); +): AwsDataApiPgDatabase & { + $client: AwsDataApiClient; +} { + const dialect = new AwsPgDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -113,5 +119,66 @@ export function drizzle = Record; + const db = new AwsDataApiPgDatabase(dialect, session, schema as any); + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends AwsDataApiClient = RDSDataClient, +>( + ...params: [ + TClient, + DrizzleAwsDataApiPgConfig, + ] | [ + ( + | ( + & DrizzleConfig + & { + connection: RDSDataClientConfig & Omit; + } + ) + | ( + & DrizzleAwsDataApiPgConfig + & { + client: TClient; + } + ) + ), + ] +): AwsDataApiPgDatabase & { + $client: TClient; +} { + // eslint-disable-next-line no-instanceof/no-instanceof + if (params[0] instanceof RDSDataClient) { + return construct(params[0] as TClient, params[1] as DrizzleAwsDataApiPgConfig) as any; + } + + if ((params[0] as { client?: TClient }).client) { + const { client, ...drizzleConfig } = params[0] as { + client: TClient; + } & DrizzleAwsDataApiPgConfig; + + return construct(client, drizzleConfig) as any; + } + + const { connection, ...drizzleConfig } = params[0] as { + connection: RDSDataClientConfig & Omit; + } & DrizzleConfig; + const { resourceArn, database, secretArn, ...rdsConfig } = connection; + + const instance = new RDSDataClient(rdsConfig); + return construct(instance, { resourceArn, database, secretArn, ...drizzleConfig }) as any; +} + +export namespace drizzle { + export function mock = Record>( + config: DrizzleAwsDataApiPgConfig, + ): AwsDataApiPgDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/aws-data-api/pg/migrator.ts b/drizzle-orm/src/aws-data-api/pg/migrator.ts index 2afa79412..c58ba7ab1 100644 --- a/drizzle-orm/src/aws-data-api/pg/migrator.ts +++ b/drizzle-orm/src/aws-data-api/pg/migrator.ts @@ -4,7 +4,7 @@ import type { AwsDataApiPgDatabase } from './driver.ts'; export async function migrate>( db: AwsDataApiPgDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); await db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/aws-data-api/pg/session.ts b/drizzle-orm/src/aws-data-api/pg/session.ts index 4fc43ddf6..974f6d3ff 100644 --- a/drizzle-orm/src/aws-data-api/pg/session.ts +++ b/drizzle-orm/src/aws-data-api/pg/session.ts @@ -27,7 +27,7 @@ export type AwsDataApiClient = RDSDataClient; export class AwsDataApiPreparedQuery< T extends PreparedQueryConfig & { values: AwsDataApiPgQueryResult }, > extends PgPreparedQuery { - static readonly [entityKind]: string = 'AwsDataApiPreparedQuery'; + static override readonly [entityKind]: string = 'AwsDataApiPreparedQuery'; private rawQuery: ExecuteStatementCommand; @@ -154,7 +154,7 @@ export class AwsDataApiSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgSession { - static readonly [entityKind]: string = 'AwsDataApiSession'; + static override readonly [entityKind]: string = 'AwsDataApiSession'; /** @internal */ readonly rawQuery: AwsDataApiQueryBase; @@ -239,7 +239,7 @@ export class AwsDataApiTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgTransaction { - static readonly [entityKind]: string = 'AwsDataApiTransaction'; + static override readonly [entityKind]: string = 'AwsDataApiTransaction'; override async transaction( transaction: (tx: AwsDataApiTransaction) => Promise, diff --git a/drizzle-orm/src/better-sqlite3/driver.ts b/drizzle-orm/src/better-sqlite3/driver.ts index 728586e57..68eac53ac 100644 --- a/drizzle-orm/src/better-sqlite3/driver.ts +++ b/drizzle-orm/src/better-sqlite3/driver.ts @@ -1,4 +1,5 @@ -import type { Database, RunResult } from 'better-sqlite3'; +import Client, { type Database, type Options, type RunResult } from 'better-sqlite3'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { createTableRelationsHelpers, @@ -8,18 +9,31 @@ import { } from '~/relations.ts'; import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts'; import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import { BetterSQLiteSession } from './session.ts'; -export type BetterSQLite3Database< - TSchema extends Record = Record, -> = BaseSQLiteDatabase<'sync', RunResult, TSchema>; +export type DrizzleBetterSQLite3DatabaseConfig = + | ({ + source?: + | string + | Buffer; + } & Options) + | string + | undefined; + +export class BetterSQLite3Database = Record> + extends BaseSQLiteDatabase<'sync', RunResult, TSchema> +{ + static override readonly [entityKind]: string = 'BetterSQLite3Database'; +} -export function drizzle = Record>( +function construct = Record>( client: Database, config: DrizzleConfig = {}, -): BetterSQLite3Database { - const dialect = new SQLiteSyncDialect(); +): BetterSQLite3Database & { + $client: Database; +} { + const dialect = new SQLiteSyncDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -41,5 +55,75 @@ export function drizzle = Record; + const db = new BetterSQLite3Database('sync', dialect, session, schema); + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, +>( + ...params: + | [] + | [ + Database | string, + ] + | [ + Database | string, + DrizzleConfig, + ] + | [ + ( + & DrizzleConfig + & ({ + connection?: DrizzleBetterSQLite3DatabaseConfig; + } | { + client: Database; + }) + ), + ] +): BetterSQLite3Database & { + $client: Database; +} { + if (params[0] === undefined || typeof params[0] === 'string') { + const instance = params[0] === undefined ? new Client() : new Client(params[0]); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { + connection?: DrizzleBetterSQLite3DatabaseConfig; + client?: Database; + } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + if (typeof connection === 'object') { + const { source, ...options } = connection; + + const instance = new Client(source, options); + + return construct(instance, drizzleConfig) as any; + } + + const instance = new Client(connection); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as Database, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): BetterSQLite3Database & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/better-sqlite3/migrator.ts b/drizzle-orm/src/better-sqlite3/migrator.ts index 1cbd2fe56..cea198257 100644 --- a/drizzle-orm/src/better-sqlite3/migrator.ts +++ b/drizzle-orm/src/better-sqlite3/migrator.ts @@ -4,7 +4,7 @@ import type { BetterSQLite3Database } from './driver.ts'; export function migrate>( db: BetterSQLite3Database, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/better-sqlite3/session.ts b/drizzle-orm/src/better-sqlite3/session.ts index 5b8d29b81..8a02eb37e 100644 --- a/drizzle-orm/src/better-sqlite3/session.ts +++ b/drizzle-orm/src/better-sqlite3/session.ts @@ -26,7 +26,7 @@ export class BetterSQLiteSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteSession<'sync', RunResult, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'BetterSQLiteSession'; + static override readonly [entityKind]: string = 'BetterSQLiteSession'; private logger: Logger; @@ -73,7 +73,7 @@ export class BetterSQLiteTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteTransaction<'sync', RunResult, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'BetterSQLiteTransaction'; + static override readonly [entityKind]: string = 'BetterSQLiteTransaction'; override transaction(transaction: (tx: BetterSQLiteTransaction) => T): T { const savepointName = `sp${this.nestedIndex}`; @@ -93,7 +93,7 @@ export class BetterSQLiteTransaction< export class PreparedQuery extends PreparedQueryBase< { type: 'sync'; run: RunResult; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { - static readonly [entityKind]: string = 'BetterSQLitePreparedQuery'; + static override readonly [entityKind]: string = 'BetterSQLitePreparedQuery'; constructor( private stmt: Statement, diff --git a/drizzle-orm/src/bun-sqlite/driver.ts b/drizzle-orm/src/bun-sqlite/driver.ts index 0d196ff03..9d2f9415b 100644 --- a/drizzle-orm/src/bun-sqlite/driver.ts +++ b/drizzle-orm/src/bun-sqlite/driver.ts @@ -1,6 +1,7 @@ /// -import type { Database } from 'bun:sqlite'; +import { Database } from 'bun:sqlite'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { createTableRelationsHelpers, @@ -10,18 +11,50 @@ import { } from '~/relations.ts'; import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts'; import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import { SQLiteBunSession } from './session.ts'; -export type BunSQLiteDatabase< +export class BunSQLiteDatabase< TSchema extends Record = Record, -> = BaseSQLiteDatabase<'sync', void, TSchema>; +> extends BaseSQLiteDatabase<'sync', void, TSchema> { + static override readonly [entityKind]: string = 'BunSQLiteDatabase'; +} + +type DrizzleBunSqliteDatabaseOptions = { + /** + * Open the database as read-only (no write operations, no create). + * + * Equivalent to {@link constants.SQLITE_OPEN_READONLY} + */ + readonly?: boolean; + /** + * Allow creating a new database + * + * Equivalent to {@link constants.SQLITE_OPEN_CREATE} + */ + create?: boolean; + /** + * Open the database as read-write + * + * Equivalent to {@link constants.SQLITE_OPEN_READWRITE} + */ + readwrite?: boolean; +}; + +export type DrizzleBunSqliteDatabaseConfig = + | ({ + source?: string; + } & DrizzleBunSqliteDatabaseOptions) + | string + | undefined; -export function drizzle = Record>( +function construct = Record>( client: Database, config: DrizzleConfig = {}, -): BunSQLiteDatabase { - const dialect = new SQLiteSyncDialect(); +): BunSQLiteDatabase & { + $client: Database; +} { + const dialect = new SQLiteSyncDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -43,5 +76,78 @@ export function drizzle = Record; + const db = new BunSQLiteDatabase('sync', dialect, session, schema) as BunSQLiteDatabase; + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends Database = Database, +>( + ...params: + | [] + | [ + TClient | string, + ] + | [ + TClient | string, + DrizzleConfig, + ] + | [ + ( + & DrizzleConfig + & ({ + connection?: DrizzleBunSqliteDatabaseConfig; + } | { + client: TClient; + }) + ), + ] +): BunSQLiteDatabase & { + $client: TClient; +} { + if (params[0] === undefined || typeof params[0] === 'string') { + const instance = params[0] === undefined ? new Database() : new Database(params[0]); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & ({ + connection?: DrizzleBunSqliteDatabaseConfig | string; + client?: TClient; + }) + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + if (typeof connection === 'object') { + const { source, ...opts } = connection; + + const options = Object.values(opts).filter((v) => v !== undefined).length ? opts : undefined; + + const instance = new Database(source, options); + + return construct(instance, drizzleConfig) as any; + } + + const instance = new Database(connection); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as Database, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): BunSQLiteDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/bun-sqlite/migrator.ts b/drizzle-orm/src/bun-sqlite/migrator.ts index ee248fd3e..785dabae9 100644 --- a/drizzle-orm/src/bun-sqlite/migrator.ts +++ b/drizzle-orm/src/bun-sqlite/migrator.ts @@ -4,7 +4,7 @@ import type { BunSQLiteDatabase } from './driver.ts'; export function migrate>( db: BunSQLiteDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/bun-sqlite/session.ts b/drizzle-orm/src/bun-sqlite/session.ts index ff4da3e6e..fd02e4f00 100644 --- a/drizzle-orm/src/bun-sqlite/session.ts +++ b/drizzle-orm/src/bun-sqlite/session.ts @@ -28,7 +28,7 @@ export class SQLiteBunSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteSession<'sync', void, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'SQLiteBunSession'; + static override readonly [entityKind]: string = 'SQLiteBunSession'; private logger: Logger; @@ -83,7 +83,7 @@ export class SQLiteBunTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteTransaction<'sync', void, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'SQLiteBunTransaction'; + static override readonly [entityKind]: string = 'SQLiteBunTransaction'; override transaction(transaction: (tx: SQLiteBunTransaction) => T): T { const savepointName = `sp${this.nestedIndex}`; @@ -103,7 +103,7 @@ export class SQLiteBunTransaction< export class PreparedQuery extends PreparedQueryBase< { type: 'sync'; run: void; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { - static readonly [entityKind]: string = 'SQLiteBunPreparedQuery'; + static override readonly [entityKind]: string = 'SQLiteBunPreparedQuery'; constructor( private stmt: Statement, diff --git a/drizzle-orm/src/casing.ts b/drizzle-orm/src/casing.ts new file mode 100644 index 000000000..8372227b3 --- /dev/null +++ b/drizzle-orm/src/casing.ts @@ -0,0 +1,76 @@ +import type { Column } from '~/column.ts'; +import { entityKind } from './entity.ts'; +import { Table } from './table.ts'; +import type { Casing } from './utils.ts'; + +export function toSnakeCase(input: string) { + const words = input + .replace(/['\u2019]/g, '') + .match(/[\da-z]+|[A-Z]+(?![a-z])|[A-Z][\da-z]+/g) ?? []; + + return words.map((word) => word.toLowerCase()).join('_'); +} + +export function toCamelCase(input: string) { + const words = input + .replace(/['\u2019]/g, '') + .match(/[\da-z]+|[A-Z]+(?![a-z])|[A-Z][\da-z]+/g) ?? []; + + return words.reduce((acc, word, i) => { + const formattedWord = i === 0 ? word.toLowerCase() : `${word[0]!.toUpperCase()}${word.slice(1)}`; + return acc + formattedWord; + }, ''); +} + +function noopCase(input: string) { + return input; +} + +export class CasingCache { + static readonly [entityKind]: string = 'CasingCache'; + + /** @internal */ + cache: Record = {}; + private cachedTables: Record = {}; + private convert: (input: string) => string; + + constructor(casing?: Casing) { + this.convert = casing === 'snake_case' + ? toSnakeCase + : casing === 'camelCase' + ? toCamelCase + : noopCase; + } + + getColumnCasing(column: Column): string { + if (!column.keyAsName) return column.name; + + const schema = column.table[Table.Symbol.Schema] ?? 'public'; + const tableName = column.table[Table.Symbol.OriginalName]; + const key = `${schema}.${tableName}.${column.name}`; + + if (!this.cache[key]) { + this.cacheTable(column.table); + } + return this.cache[key]!; + } + + private cacheTable(table: Table) { + const schema = table[Table.Symbol.Schema] ?? 'public'; + const tableName = table[Table.Symbol.OriginalName]; + const tableKey = `${schema}.${tableName}`; + + if (!this.cachedTables[tableKey]) { + for (const column of Object.values(table[Table.Symbol.Columns])) { + const columnKey = `${tableKey}.${column.name}`; + this.cache[columnKey] = this.convert(column.name); + } + this.cachedTables[tableKey] = true; + } + } + + clearCache() { + this.cache = {}; + this.cachedTables = {}; + } +} diff --git a/drizzle-orm/src/column-builder.ts b/drizzle-orm/src/column-builder.ts index 4a19a79a9..6d6dfeeba 100644 --- a/drizzle-orm/src/column-builder.ts +++ b/drizzle-orm/src/column-builder.ts @@ -2,9 +2,10 @@ import { entityKind } from '~/entity.ts'; import type { Column } from './column.ts'; import type { MySqlColumn } from './mysql-core/index.ts'; import type { ExtraConfigColumn, PgColumn, PgSequenceOptions } from './pg-core/index.ts'; +import type { SingleStoreColumn } from './singlestore-core/index.ts'; import type { SQL } from './sql/sql.ts'; import type { SQLiteColumn } from './sqlite-core/index.ts'; -import type { Simplify } from './utils.ts'; +import type { Assume, Simplify } from './utils.ts'; export type ColumnDataType = | 'string' @@ -17,7 +18,7 @@ export type ColumnDataType = | 'custom' | 'buffer'; -export type Dialect = 'pg' | 'mysql' | 'sqlite' | 'common'; +export type Dialect = 'pg' | 'mysql' | 'sqlite' | 'singlestore' | 'common'; export type GeneratedStorageMode = 'virtual' | 'stored'; @@ -42,7 +43,6 @@ export interface ColumnBuilderBaseConfig | undefined; } export type MakeColumnConfig< @@ -64,7 +64,11 @@ export type MakeColumnConfig< enumValues: T['enumValues']; baseColumn: T extends { baseBuilder: infer U extends ColumnBuilderBase } ? BuildColumn : never; - generated: T['generated'] extends object ? T['generated'] : undefined; + identity: T extends { identity: 'always' } ? 'always' : T extends { identity: 'byDefault' } ? 'byDefault' : undefined; + generated: T extends { generated: infer G } ? unknown extends G ? undefined + : G extends undefined ? undefined + : G + : undefined; } & {}; export type ColumnBuilderTypeConfig< @@ -82,13 +86,15 @@ export type ColumnBuilderTypeConfig< notNull: T extends { notNull: infer U } ? U : boolean; hasDefault: T extends { hasDefault: infer U } ? U : boolean; enumValues: T['enumValues']; - generated: GeneratedColumnConfig | undefined; + identity: T extends { identity: infer U } ? U : unknown; + generated: T extends { generated: infer G } ? G extends undefined ? unknown : G : unknown; } & TTypeConfig >; export type ColumnBuilderRuntimeConfig = { name: string; + keyAsName: boolean; notNull: boolean; default: TData | SQL | undefined; defaultFn: (() => TData | SQL) | undefined; @@ -151,17 +157,16 @@ export type HasGenerated = T & { _: { notNull: true; hasDefault: true; - generated: { as: any; type: TType }; + identity: TType; }; }; - export interface ColumnBuilderBase< T extends ColumnBuilderBaseConfig = ColumnBuilderBaseConfig, TTypeConfig extends object = object, @@ -185,6 +190,7 @@ export abstract class ColumnBuilder< constructor(name: T['name'], dataType: T['dataType'], columnType: T['columnType']) { this.config = { name, + keyAsName: name === '', notNull: false, default: undefined, hasDefault: false, @@ -292,17 +298,64 @@ export abstract class ColumnBuilder< abstract generatedAlwaysAs( as: SQL | T['data'] | (() => SQL), config?: Partial>, - ): HasGenerated; + ): HasGenerated; + + /** @internal Sets the name of the column to the key within the table definition if a name was not given. */ + setName(name: string) { + if (this.config.name !== '') return; + this.config.name = name; + } } export type BuildColumn< TTableName extends string, TBuilder extends ColumnBuilderBase, TDialect extends Dialect, -> = TDialect extends 'pg' ? PgColumn> - : TDialect extends 'mysql' ? MySqlColumn> - : TDialect extends 'sqlite' ? SQLiteColumn> - : TDialect extends 'common' ? Column> +> = TDialect extends 'pg' ? PgColumn< + MakeColumnConfig, + {}, + Simplify | 'brand' | 'dialect'>> + > + : TDialect extends 'mysql' ? MySqlColumn< + MakeColumnConfig, + {}, + Simplify< + Omit< + TBuilder['_'], + | keyof MakeColumnConfig + | 'brand' + | 'dialect' + | 'primaryKeyHasDefault' + | 'mysqlColumnBuilderBrand' + > + > + > + : TDialect extends 'sqlite' ? SQLiteColumn< + MakeColumnConfig, + {}, + Simplify | 'brand' | 'dialect'>> + > + : TDialect extends 'common' ? Column< + MakeColumnConfig, + {}, + Simplify | 'brand' | 'dialect'>> + > + : TDialect extends 'singlestore' ? SingleStoreColumn< + MakeColumnConfig, + {}, + Simplify< + Omit< + TBuilder['_'], + | keyof MakeColumnConfig + | 'brand' + | 'dialect' + | 'primaryKeyHasDefault' + | 'singlestoreColumnBuilderBrand' + > + > + > : never; export type BuildIndexColumn< @@ -322,7 +375,11 @@ export type BuildColumns< TDialect extends Dialect, > = & { - [Key in keyof TConfigMap]: BuildColumn; + [Key in keyof TConfigMap]: BuildColumn + & { name: TConfigMap[Key]['_']['name'] extends '' ? Assume : TConfigMap[Key]['_']['name'] }; + }, TDialect>; } & {}; @@ -339,5 +396,6 @@ export type BuildExtraConfigColumns< export type ChangeColumnTableName = TDialect extends 'pg' ? PgColumn> : TDialect extends 'mysql' ? MySqlColumn> + : TDialect extends 'singlestore' ? SingleStoreColumn> : TDialect extends 'sqlite' ? SQLiteColumn> : never; diff --git a/drizzle-orm/src/column.ts b/drizzle-orm/src/column.ts index e740acaa0..1396e3a61 100644 --- a/drizzle-orm/src/column.ts +++ b/drizzle-orm/src/column.ts @@ -38,6 +38,7 @@ export type ColumnTypeConfig, enumValues: T['enumValues']; baseColumn: T extends { baseColumn: infer U } ? U : unknown; generated: GeneratedColumnConfig | undefined; + identity: undefined | 'always' | 'byDefault'; } & TTypeConfig; export type ColumnRuntimeConfig = ColumnBuilderRuntimeConfig< @@ -69,6 +70,7 @@ export abstract class Column< declare readonly _: ColumnTypeConfig; readonly name: string; + readonly keyAsName: boolean; readonly primary: boolean; readonly notNull: boolean; readonly default: T['data'] | SQL | undefined; @@ -92,6 +94,7 @@ export abstract class Column< ) { this.config = config; this.name = config.name; + this.keyAsName = config.keyAsName; this.notNull = config.notNull; this.default = config.default; this.defaultFn = config.defaultFn; diff --git a/drizzle-orm/src/d1/driver.ts b/drizzle-orm/src/d1/driver.ts index 46fc8ec8e..7b4bbdfb6 100644 --- a/drizzle-orm/src/d1/driver.ts +++ b/drizzle-orm/src/d1/driver.ts @@ -1,4 +1,5 @@ /// +import type { D1Database as MiniflareD1Database } from '@miniflare/d1'; import type { BatchItem, BatchResponse } from '~/batch.ts'; import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; @@ -11,13 +12,19 @@ import { } from '~/relations.ts'; import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts'; import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import type { DrizzleConfig, IfNotImported } from '~/utils.ts'; import { SQLiteD1Session } from './session.ts'; +export type AnyD1Database = IfNotImported< + D1Database, + MiniflareD1Database, + D1Database | IfNotImported +>; + export class DrizzleD1Database< TSchema extends Record = Record, > extends BaseSQLiteDatabase<'async', D1Result, TSchema> { - static readonly [entityKind]: string = 'D1Database'; + static override readonly [entityKind]: string = 'D1Database'; /** @internal */ declare readonly session: SQLiteD1Session>; @@ -29,11 +36,16 @@ export class DrizzleD1Database< } } -export function drizzle = Record>( - client: D1Database, +export function drizzle< + TSchema extends Record = Record, + TClient extends AnyD1Database = AnyD1Database, +>( + client: TClient, config: DrizzleConfig = {}, -): DrizzleD1Database { - const dialect = new SQLiteAsyncDialect(); +): DrizzleD1Database & { + $client: TClient; +} { + const dialect = new SQLiteAsyncDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -54,6 +66,9 @@ export function drizzle = Record; + const session = new SQLiteD1Session(client as D1Database, dialect, schema, { logger }); + const db = new DrizzleD1Database('async', dialect, session, schema) as DrizzleD1Database; + ( db).$client = client; + + return db as any; } diff --git a/drizzle-orm/src/d1/migrator.ts b/drizzle-orm/src/d1/migrator.ts index 9a137136d..2259516bf 100644 --- a/drizzle-orm/src/d1/migrator.ts +++ b/drizzle-orm/src/d1/migrator.ts @@ -5,14 +5,10 @@ import type { DrizzleD1Database } from './driver.ts'; export async function migrate>( db: DrizzleD1Database, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); - const migrationsTable = config === undefined - ? '__drizzle_migrations' - : typeof config === 'string' - ? '__drizzle_migrations' - : config.migrationsTable ?? '__drizzle_migrations'; + const migrationsTable = config.migrationsTable ?? '__drizzle_migrations'; const migrationTableCreate = sql` CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsTable)} ( diff --git a/drizzle-orm/src/d1/session.ts b/drizzle-orm/src/d1/session.ts index 0f2989c12..61ef49315 100644 --- a/drizzle-orm/src/d1/session.ts +++ b/drizzle-orm/src/d1/session.ts @@ -28,7 +28,7 @@ export class SQLiteD1Session< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteSession<'async', D1Result, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'SQLiteD1Session'; + static override readonly [entityKind]: string = 'SQLiteD1Session'; private logger: Logger; @@ -116,7 +116,7 @@ export class D1Transaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteTransaction<'async', D1Result, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'D1Transaction'; + static override readonly [entityKind]: string = 'D1Transaction'; override async transaction(transaction: (tx: D1Transaction) => Promise): Promise { const savepointName = `sp${this.nestedIndex}`; @@ -151,7 +151,7 @@ function d1ToRawMapping(results: any) { export class D1PreparedQuery extends SQLitePreparedQuery< { type: 'async'; run: D1Response; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { - static readonly [entityKind]: string = 'D1PreparedQuery'; + static override readonly [entityKind]: string = 'D1PreparedQuery'; /** @internal */ customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown; diff --git a/drizzle-orm/src/durable-sqlite/driver.ts b/drizzle-orm/src/durable-sqlite/driver.ts new file mode 100644 index 000000000..0be110084 --- /dev/null +++ b/drizzle-orm/src/durable-sqlite/driver.ts @@ -0,0 +1,60 @@ +/// +import { entityKind } from '~/entity.ts'; +import { DefaultLogger } from '~/logger.ts'; +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + type ExtractTablesWithRelations, + type RelationalSchemaConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts'; +import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; +import type { DrizzleConfig } from '~/utils.ts'; +import { SQLiteDOSession } from './session.ts'; + +export class DrizzleSqliteDODatabase< + TSchema extends Record = Record, +> extends BaseSQLiteDatabase<'sync', SqlStorageCursor>, TSchema> { + static override readonly [entityKind]: string = 'DrizzleSqliteDODatabase'; + + /** @internal */ + declare readonly session: SQLiteDOSession>; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends DurableObjectStorage = DurableObjectStorage, +>( + client: TClient, + config: DrizzleConfig = {}, +): DrizzleSqliteDODatabase & { + $client: TClient; +} { + const dialect = new SQLiteSyncDialect({ casing: config.casing }); + let logger; + if (config.logger === true) { + logger = new DefaultLogger(); + } else if (config.logger !== false) { + logger = config.logger; + } + + let schema: RelationalSchemaConfig | undefined; + if (config.schema) { + const tablesConfig = extractTablesRelationalConfig( + config.schema, + createTableRelationsHelpers, + ); + schema = { + fullSchema: config.schema, + schema: tablesConfig.tables, + tableNamesMap: tablesConfig.tableNamesMap, + }; + } + + const session = new SQLiteDOSession(client as DurableObjectStorage, dialect, schema, { logger }); + const db = new DrizzleSqliteDODatabase('sync', dialect, session, schema) as DrizzleSqliteDODatabase; + ( db).$client = client; + + return db as any; +} diff --git a/drizzle-orm/src/durable-sqlite/index.ts b/drizzle-orm/src/durable-sqlite/index.ts new file mode 100644 index 000000000..b1b6a52e7 --- /dev/null +++ b/drizzle-orm/src/durable-sqlite/index.ts @@ -0,0 +1,2 @@ +export * from './driver.ts'; +export * from './session.ts'; diff --git a/drizzle-orm/src/durable-sqlite/migrator.ts b/drizzle-orm/src/durable-sqlite/migrator.ts new file mode 100644 index 000000000..8410b2900 --- /dev/null +++ b/drizzle-orm/src/durable-sqlite/migrator.ts @@ -0,0 +1,85 @@ +import type { MigrationMeta } from '~/migrator.ts'; +import { sql } from '~/sql/index.ts'; +import type { DrizzleSqliteDODatabase } from './driver.ts'; + +interface MigrationConfig { + journal: { + entries: { idx: number; when: number; tag: string; breakpoints: boolean }[]; + }; + migrations: Record; +} + +function readMigrationFiles({ journal, migrations }: MigrationConfig): MigrationMeta[] { + const migrationQueries: MigrationMeta[] = []; + + for (const journalEntry of journal.entries) { + const query = migrations[`m${journalEntry.idx.toString().padStart(4, '0')}`]; + + if (!query) { + throw new Error(`Missing migration: ${journalEntry.tag}`); + } + + try { + const result = query.split('--> statement-breakpoint').map((it) => { + return it; + }); + + migrationQueries.push({ + sql: result, + bps: journalEntry.breakpoints, + folderMillis: journalEntry.when, + hash: '', + }); + } catch { + throw new Error(`Failed to parse migration: ${journalEntry.tag}`); + } + } + + return migrationQueries; +} + +export async function migrate< + TSchema extends Record, +>( + db: DrizzleSqliteDODatabase, + config: MigrationConfig, +): Promise { + const migrations = readMigrationFiles(config); + + db.transaction((tx) => { + try { + const migrationsTable = '__drizzle_migrations'; + + const migrationTableCreate = sql` + CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsTable)} ( + id SERIAL PRIMARY KEY, + hash text NOT NULL, + created_at numeric + ) + `; + db.run(migrationTableCreate); + + const dbMigrations = db.values<[number, string, string]>( + sql`SELECT id, hash, created_at FROM ${sql.identifier(migrationsTable)} ORDER BY created_at DESC LIMIT 1`, + ); + + const lastDbMigration = dbMigrations[0] ?? undefined; + + for (const migration of migrations) { + if (!lastDbMigration || Number(lastDbMigration[2])! < migration.folderMillis) { + for (const stmt of migration.sql) { + db.run(sql.raw(stmt)); + } + db.run( + sql`INSERT INTO ${ + sql.identifier(migrationsTable) + } ("hash", "created_at") VALUES(${migration.hash}, ${migration.folderMillis})`, + ); + } + } + } catch (error: any) { + tx.rollback(); + throw error; + } + }); +} diff --git a/drizzle-orm/src/durable-sqlite/session.ts b/drizzle-orm/src/durable-sqlite/session.ts new file mode 100644 index 000000000..dca5ce7cf --- /dev/null +++ b/drizzle-orm/src/durable-sqlite/session.ts @@ -0,0 +1,181 @@ +import { entityKind } from '~/entity.ts'; +import type { Logger } from '~/logger.ts'; +import { NoopLogger } from '~/logger.ts'; +import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import { fillPlaceholders, type Query } from '~/sql/sql.ts'; +import { type SQLiteSyncDialect, SQLiteTransaction } from '~/sqlite-core/index.ts'; +import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types.ts'; +import { + type PreparedQueryConfig as PreparedQueryConfigBase, + type SQLiteExecuteMethod, + SQLiteSession, + type SQLiteTransactionConfig, +} from '~/sqlite-core/session.ts'; +import { SQLitePreparedQuery as PreparedQueryBase } from '~/sqlite-core/session.ts'; +import { mapResultRow } from '~/utils.ts'; + +export interface SQLiteDOSessionOptions { + logger?: Logger; +} + +type PreparedQueryConfig = Omit; + +export class SQLiteDOSession, TSchema extends TablesRelationalConfig> + extends SQLiteSession< + 'sync', + SqlStorageCursor>, + TFullSchema, + TSchema + > +{ + static override readonly [entityKind]: string = 'SQLiteDOSession'; + + private logger: Logger; + + constructor( + private client: DurableObjectStorage, + dialect: SQLiteSyncDialect, + private schema: RelationalSchemaConfig | undefined, + options: SQLiteDOSessionOptions = {}, + ) { + super(dialect); + this.logger = options.logger ?? new NoopLogger(); + } + + prepareQuery>( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + isResponseInArrayMode: boolean, + customResultMapper?: (rows: unknown[][]) => unknown, + ): SQLiteDOPreparedQuery { + return new SQLiteDOPreparedQuery( + this.client, + query, + this.logger, + fields, + executeMethod, + isResponseInArrayMode, + customResultMapper, + ); + } + + override transaction( + transaction: ( + tx: SQLiteTransaction<'sync', SqlStorageCursor>, TFullSchema, TSchema>, + ) => T, + _config?: SQLiteTransactionConfig, + ): T { + const tx = new SQLiteDOTransaction('sync', this.dialect, this, this.schema); + this.client.transactionSync(() => { + transaction(tx); + }); + return {} as any; + } +} + +export class SQLiteDOTransaction, TSchema extends TablesRelationalConfig> + extends SQLiteTransaction< + 'sync', + SqlStorageCursor>, + TFullSchema, + TSchema + > +{ + static override readonly [entityKind]: string = 'SQLiteDOTransaction'; + + override transaction(transaction: (tx: SQLiteDOTransaction) => T): T { + const tx = new SQLiteDOTransaction('sync', this.dialect, this.session, this.schema, this.nestedIndex + 1); + this.session.transaction(() => transaction(tx)); + + return {} as any; + } +} + +export class SQLiteDOPreparedQuery extends PreparedQueryBase<{ + type: 'sync'; + run: void; + all: T['all']; + get: T['get']; + values: T['values']; + execute: T['execute']; +}> { + static override readonly [entityKind]: string = 'SQLiteDOPreparedQuery'; + + constructor( + private client: DurableObjectStorage, + query: Query, + private logger: Logger, + private fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + private _isResponseInArrayMode: boolean, + private customResultMapper?: (rows: unknown[][]) => unknown, + ) { + super('sync', executeMethod, query); + } + + run(placeholderValues?: Record): void { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); + this.logger.logQuery(this.query.sql, params); + + params.length > 0 ? this.client.sql.exec(this.query.sql, ...params) : this.client.sql.exec(this.query.sql); + } + + all(placeholderValues?: Record): T['all'] { + const { fields, joinsNotNullableMap, query, logger, client, customResultMapper } = this; + if (!fields && !customResultMapper) { + const params = fillPlaceholders(query.params, placeholderValues ?? {}); + logger.logQuery(query.sql, params); + + return params.length > 0 ? client.sql.exec(query.sql, ...params).toArray() : client.sql.exec(query.sql).toArray(); + } + + const rows = this.values(placeholderValues) as unknown[][]; + + if (customResultMapper) { + return customResultMapper(rows) as T['all']; + } + + return rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + } + + get(placeholderValues?: Record): T['get'] { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); + this.logger.logQuery(this.query.sql, params); + + const { fields, client, joinsNotNullableMap, customResultMapper, query } = this; + if (!fields && !customResultMapper) { + return params.length > 0 ? client.sql.exec(query.sql, ...params).one() : client.sql.exec(query.sql).one(); + } + + const rows = this.values(placeholderValues) as unknown[][]; + const row = rows[0]; + + if (!row) { + return undefined; + } + + if (customResultMapper) { + return customResultMapper(rows) as T['get']; + } + + return mapResultRow(fields!, row, joinsNotNullableMap); + } + + values(placeholderValues?: Record): T['values'] { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}); + this.logger.logQuery(this.query.sql, params); + + const res = params.length > 0 + ? this.client.sql.exec(this.query.sql, ...params) + : this.client.sql.exec(this.query.sql); + + // @ts-ignore .raw().toArray() exists + return res.raw().toArray(); + } + + /** @internal */ + isResponseInArrayMode(): boolean { + return this._isResponseInArrayMode; + } +} diff --git a/drizzle-orm/src/entity.ts b/drizzle-orm/src/entity.ts index d9ab3d36a..2b6dfb4de 100644 --- a/drizzle-orm/src/entity.ts +++ b/drizzle-orm/src/entity.ts @@ -26,7 +26,7 @@ export function is>(value: any, type: T): valu ); } - let cls = value.constructor; + let cls = Object.getPrototypeOf(value).constructor; if (cls) { // Traverse the prototype chain to find the entityKind while (cls) { diff --git a/drizzle-orm/src/errors.ts b/drizzle-orm/src/errors.ts index ede6e0a59..a72615c9b 100644 --- a/drizzle-orm/src/errors.ts +++ b/drizzle-orm/src/errors.ts @@ -11,7 +11,7 @@ export class DrizzleError extends Error { } export class TransactionRollbackError extends DrizzleError { - static readonly [entityKind]: string = 'TransactionRollbackError'; + static override readonly [entityKind]: string = 'TransactionRollbackError'; constructor() { super({ message: 'Rollback' }); diff --git a/drizzle-orm/src/expo-sqlite/driver.ts b/drizzle-orm/src/expo-sqlite/driver.ts index ae8ce6577..fce53eed2 100644 --- a/drizzle-orm/src/expo-sqlite/driver.ts +++ b/drizzle-orm/src/expo-sqlite/driver.ts @@ -1,4 +1,5 @@ -import type { SQLiteDatabase, SQLiteRunResult } from 'expo-sqlite/next'; +import type { SQLiteDatabase, SQLiteRunResult } from 'expo-sqlite'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { createTableRelationsHelpers, @@ -11,15 +12,19 @@ import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; import type { DrizzleConfig } from '~/utils.ts'; import { ExpoSQLiteSession } from './session.ts'; -export type ExpoSQLiteDatabase< - TSchema extends Record = Record, -> = BaseSQLiteDatabase<'sync', SQLiteRunResult, TSchema>; +export class ExpoSQLiteDatabase = Record> + extends BaseSQLiteDatabase<'sync', SQLiteRunResult, TSchema> +{ + static override readonly [entityKind]: string = 'ExpoSQLiteDatabase'; +} export function drizzle = Record>( client: SQLiteDatabase, config: DrizzleConfig = {}, -): ExpoSQLiteDatabase { - const dialect = new SQLiteSyncDialect(); +): ExpoSQLiteDatabase & { + $client: SQLiteDatabase; +} { + const dialect = new SQLiteSyncDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -41,5 +46,8 @@ export function drizzle = Record; + const db = new ExpoSQLiteDatabase('sync', dialect, session, schema) as ExpoSQLiteDatabase; + ( db).$client = client; + + return db as any; } diff --git a/drizzle-orm/src/expo-sqlite/query.ts b/drizzle-orm/src/expo-sqlite/query.ts index f687efb54..a38fc3046 100644 --- a/drizzle-orm/src/expo-sqlite/query.ts +++ b/drizzle-orm/src/expo-sqlite/query.ts @@ -1,12 +1,15 @@ -import { addDatabaseChangeListener } from 'expo-sqlite/next'; +import { addDatabaseChangeListener } from 'expo-sqlite'; import { useEffect, useState } from 'react'; -import { is, SQL, Subquery } from '~/index.ts'; +import { is } from '~/entity.ts'; +import { SQL } from '~/sql/sql.ts'; import type { AnySQLiteSelect } from '~/sqlite-core/index.ts'; import { getTableConfig, getViewConfig, SQLiteTable, SQLiteView } from '~/sqlite-core/index.ts'; import { SQLiteRelationalQuery } from '~/sqlite-core/query-builders/query.ts'; +import { Subquery } from '~/subquery.ts'; export const useLiveQuery = | SQLiteRelationalQuery<'sync', unknown>>( query: T, + deps: unknown[] = [], ) => { const [data, setData] = useState>( (is(query, SQLiteRelationalQuery) && query.mode === 'first' ? undefined : []) as Awaited, @@ -43,7 +46,7 @@ export const useLiveQuery = | SQL return () => { listener?.remove(); }; - }, []); + }, deps); return { data, diff --git a/drizzle-orm/src/expo-sqlite/session.ts b/drizzle-orm/src/expo-sqlite/session.ts index d87236bc2..cb880ac6e 100644 --- a/drizzle-orm/src/expo-sqlite/session.ts +++ b/drizzle-orm/src/expo-sqlite/session.ts @@ -1,4 +1,4 @@ -import type { SQLiteDatabase, SQLiteRunResult, SQLiteStatement } from 'expo-sqlite/next'; +import type { SQLiteDatabase, SQLiteRunResult, SQLiteStatement } from 'expo-sqlite'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { NoopLogger } from '~/logger.ts'; @@ -26,7 +26,7 @@ export class ExpoSQLiteSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteSession<'sync', SQLiteRunResult, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'ExpoSQLiteSession'; + static override readonly [entityKind]: string = 'ExpoSQLiteSession'; private logger: Logger; @@ -80,7 +80,7 @@ export class ExpoSQLiteTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteTransaction<'sync', SQLiteRunResult, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'ExpoSQLiteTransaction'; + static override readonly [entityKind]: string = 'ExpoSQLiteTransaction'; override transaction(transaction: (tx: ExpoSQLiteTransaction) => T): T { const savepointName = `sp${this.nestedIndex}`; @@ -100,7 +100,7 @@ export class ExpoSQLiteTransaction< export class ExpoSQLitePreparedQuery extends SQLitePreparedQuery< { type: 'sync'; run: SQLiteRunResult; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { - static readonly [entityKind]: string = 'ExpoSQLitePreparedQuery'; + static override readonly [entityKind]: string = 'ExpoSQLitePreparedQuery'; constructor( private stmt: SQLiteStatement, diff --git a/drizzle-orm/src/libsql/driver-core.ts b/drizzle-orm/src/libsql/driver-core.ts new file mode 100644 index 000000000..1bee47d7b --- /dev/null +++ b/drizzle-orm/src/libsql/driver-core.ts @@ -0,0 +1,64 @@ +import type { Client, ResultSet } from '@libsql/client'; +import type { BatchItem, BatchResponse } from '~/batch.ts'; +import { entityKind } from '~/entity.ts'; +import { DefaultLogger } from '~/logger.ts'; +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + type ExtractTablesWithRelations, + type RelationalSchemaConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts'; +import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts'; +import type { DrizzleConfig } from '~/utils.ts'; +import { LibSQLSession } from './session.ts'; + +export class LibSQLDatabase< + TSchema extends Record = Record, +> extends BaseSQLiteDatabase<'async', ResultSet, TSchema> { + static override readonly [entityKind]: string = 'LibSQLDatabase'; + + /** @internal */ + declare readonly session: LibSQLSession>; + + async batch, T extends Readonly<[U, ...U[]]>>( + batch: T, + ): Promise> { + return this.session.batch(batch) as Promise>; + } +} + +/** @internal */ +export function construct< + TSchema extends Record = Record, +>(client: Client, config: DrizzleConfig = {}): LibSQLDatabase & { + $client: Client; +} { + const dialect = new SQLiteAsyncDialect({ casing: config.casing }); + let logger; + if (config.logger === true) { + logger = new DefaultLogger(); + } else if (config.logger !== false) { + logger = config.logger; + } + + let schema: RelationalSchemaConfig | undefined; + if (config.schema) { + const tablesConfig = extractTablesRelationalConfig( + config.schema, + createTableRelationsHelpers, + ); + schema = { + fullSchema: config.schema, + schema: tablesConfig.tables, + tableNamesMap: tablesConfig.tableNamesMap, + }; + } + + const session = new LibSQLSession(client, dialect, schema, { logger }, undefined); + const db = new LibSQLDatabase('async', dialect, session, schema) as LibSQLDatabase; + ( db).$client = client; + + return db as any; +} diff --git a/drizzle-orm/src/libsql/driver.ts b/drizzle-orm/src/libsql/driver.ts index 3acff2893..b69a5d7bc 100644 --- a/drizzle-orm/src/libsql/driver.ts +++ b/drizzle-orm/src/libsql/driver.ts @@ -1,58 +1,60 @@ -import type { Client, ResultSet } from '@libsql/client'; -import type { BatchItem, BatchResponse } from '~/batch.ts'; -import { entityKind } from '~/entity.ts'; -import { DefaultLogger } from '~/logger.ts'; -import { - createTableRelationsHelpers, - extractTablesRelationalConfig, - type ExtractTablesWithRelations, - type RelationalSchemaConfig, - type TablesRelationalConfig, -} from '~/relations.ts'; -import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts'; -import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts'; -import type { DrizzleConfig } from '~/utils.ts'; -import { LibSQLSession } from './session.ts'; - -export class LibSQLDatabase< - TSchema extends Record = Record, -> extends BaseSQLiteDatabase<'async', ResultSet, TSchema> { - static readonly [entityKind]: string = 'LibSQLDatabase'; - - /** @internal */ - declare readonly session: LibSQLSession>; +import { type Client, type Config, createClient } from '@libsql/client'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; +import { construct as construct, type LibSQLDatabase } from './driver-core.ts'; - async batch, T extends Readonly<[U, ...U[]]>>( - batch: T, - ): Promise> { - return this.session.batch(batch) as Promise>; - } -} +export { LibSQLDatabase } from './driver-core.ts'; export function drizzle< TSchema extends Record = Record, ->(client: Client, config: DrizzleConfig = {}): LibSQLDatabase { - const dialect = new SQLiteAsyncDialect(); - let logger; - if (config.logger === true) { - logger = new DefaultLogger(); - } else if (config.logger !== false) { - logger = config.logger; + TClient extends Client = Client, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; } - let schema: RelationalSchemaConfig | undefined; - if (config.schema) { - const tablesConfig = extractTablesRelationalConfig( - config.schema, - createTableRelationsHelpers, - ); - schema = { - fullSchema: config.schema, - schema: tablesConfig.tables, - tableNamesMap: tablesConfig.tableNamesMap, - }; + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; } - const session = new LibSQLSession(client, dialect, schema, { logger }, undefined); - return new LibSQLDatabase('async', dialect, session, schema) as LibSQLDatabase; + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/libsql/http/index.ts b/drizzle-orm/src/libsql/http/index.ts new file mode 100644 index 000000000..1a07af6a8 --- /dev/null +++ b/drizzle-orm/src/libsql/http/index.ts @@ -0,0 +1,58 @@ +import { type Client, type Config, createClient } from '@libsql/client/http'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/migrator.ts b/drizzle-orm/src/libsql/migrator.ts index 58bcc9e05..373a8aab4 100644 --- a/drizzle-orm/src/libsql/migrator.ts +++ b/drizzle-orm/src/libsql/migrator.ts @@ -8,11 +8,7 @@ export async function migrate>( config: MigrationConfig, ) { const migrations = readMigrationFiles(config); - const migrationsTable = config === undefined - ? '__drizzle_migrations' - : typeof config === 'string' - ? '__drizzle_migrations' - : config.migrationsTable ?? '__drizzle_migrations'; + const migrationsTable = config.migrationsTable ?? '__drizzle_migrations'; const migrationTableCreate = sql` CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsTable)} ( @@ -47,5 +43,5 @@ export async function migrate>( } } - await db.session.batch(statementToBatch); + await db.session.migrate(statementToBatch); } diff --git a/drizzle-orm/src/libsql/node/index.ts b/drizzle-orm/src/libsql/node/index.ts new file mode 100644 index 000000000..bd6d5200d --- /dev/null +++ b/drizzle-orm/src/libsql/node/index.ts @@ -0,0 +1,58 @@ +import { type Client, type Config, createClient } from '@libsql/client/node'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/session.ts b/drizzle-orm/src/libsql/session.ts index 29e4e268f..617ebe342 100644 --- a/drizzle-orm/src/libsql/session.ts +++ b/drizzle-orm/src/libsql/session.ts @@ -27,7 +27,7 @@ export class LibSQLSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteSession<'async', ResultSet, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'LibSQLSession'; + static override readonly [entityKind]: string = 'LibSQLSession'; private logger: Logger; @@ -76,6 +76,21 @@ export class LibSQLSession< return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true)); } + async migrate[] | readonly BatchItem<'sqlite'>[]>(queries: T) { + const preparedQueries: PreparedQuery[] = []; + const builtQueries: InStatement[] = []; + + for (const query of queries) { + const preparedQuery = query._prepare(); + const builtQuery = preparedQuery.getQuery(); + preparedQueries.push(preparedQuery); + builtQueries.push({ sql: builtQuery.sql, args: builtQuery.params as InArgs }); + } + + const batchResults = await this.client.migrate(builtQueries); + return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true)); + } + override async transaction( transaction: (db: LibSQLTransaction) => T | Promise, _config?: SQLiteTransactionConfig, @@ -117,7 +132,7 @@ export class LibSQLTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteTransaction<'async', ResultSet, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'LibSQLTransaction'; + static override readonly [entityKind]: string = 'LibSQLTransaction'; override async transaction(transaction: (tx: LibSQLTransaction) => Promise): Promise { const savepointName = `sp${this.nestedIndex}`; @@ -137,7 +152,7 @@ export class LibSQLTransaction< export class LibSQLPreparedQuery extends SQLitePreparedQuery< { type: 'async'; run: ResultSet; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { - static readonly [entityKind]: string = 'LibSQLPreparedQuery'; + static override readonly [entityKind]: string = 'LibSQLPreparedQuery'; constructor( private client: Client, diff --git a/drizzle-orm/src/libsql/sqlite3/index.ts b/drizzle-orm/src/libsql/sqlite3/index.ts new file mode 100644 index 000000000..afb11f611 --- /dev/null +++ b/drizzle-orm/src/libsql/sqlite3/index.ts @@ -0,0 +1,58 @@ +import { type Client, type Config, createClient } from '@libsql/client/sqlite3'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/wasm/index.ts b/drizzle-orm/src/libsql/wasm/index.ts new file mode 100644 index 000000000..a27a124b5 --- /dev/null +++ b/drizzle-orm/src/libsql/wasm/index.ts @@ -0,0 +1,58 @@ +import { type Client, type Config, createClient } from '@libsql/client-wasm'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/web/index.ts b/drizzle-orm/src/libsql/web/index.ts new file mode 100644 index 000000000..9b1e2c8e0 --- /dev/null +++ b/drizzle-orm/src/libsql/web/index.ts @@ -0,0 +1,58 @@ +import { type Client, type Config, createClient } from '@libsql/client/web'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/ws/index.ts b/drizzle-orm/src/libsql/ws/index.ts new file mode 100644 index 000000000..645ff1a07 --- /dev/null +++ b/drizzle-orm/src/libsql/ws/index.ts @@ -0,0 +1,58 @@ +import { type Client, type Config, createClient } from '@libsql/client/ws'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/migrator.ts b/drizzle-orm/src/migrator.ts index 946f3269d..8b7636a44 100644 --- a/drizzle-orm/src/migrator.ts +++ b/drizzle-orm/src/migrator.ts @@ -1,6 +1,5 @@ import crypto from 'node:crypto'; import fs from 'node:fs'; -import path from 'node:path'; export interface KitConfig { out: string; @@ -20,19 +19,8 @@ export interface MigrationMeta { bps: boolean; } -export function readMigrationFiles(config: string | MigrationConfig): MigrationMeta[] { - let migrationFolderTo: string | undefined; - if (typeof config === 'string') { - const configAsString = fs.readFileSync(path.resolve('.', config), 'utf8'); - const jsonConfig = JSON.parse(configAsString) as KitConfig; - migrationFolderTo = jsonConfig.out; - } else { - migrationFolderTo = config.migrationsFolder; - } - - if (!migrationFolderTo) { - throw new Error('no migration folder defined'); - } +export function readMigrationFiles(config: MigrationConfig): MigrationMeta[] { + const migrationFolderTo = config.migrationsFolder; const migrationQueries: MigrationMeta[] = []; diff --git a/drizzle-orm/src/mysql-core/columns/all.ts b/drizzle-orm/src/mysql-core/columns/all.ts new file mode 100644 index 000000000..44c03eff0 --- /dev/null +++ b/drizzle-orm/src/mysql-core/columns/all.ts @@ -0,0 +1,58 @@ +import { bigint } from './bigint.ts'; +import { binary } from './binary.ts'; +import { boolean } from './boolean.ts'; +import { char } from './char.ts'; +import { customType } from './custom.ts'; +import { date } from './date.ts'; +import { datetime } from './datetime.ts'; +import { decimal } from './decimal.ts'; +import { double } from './double.ts'; +import { mysqlEnum } from './enum.ts'; +import { float } from './float.ts'; +import { int } from './int.ts'; +import { json } from './json.ts'; +import { mediumint } from './mediumint.ts'; +import { real } from './real.ts'; +import { serial } from './serial.ts'; +import { smallint } from './smallint.ts'; +import { longtext, mediumtext, text, tinytext } from './text.ts'; +import { time } from './time.ts'; +import { timestamp } from './timestamp.ts'; +import { tinyint } from './tinyint.ts'; +import { varbinary } from './varbinary.ts'; +import { varchar } from './varchar.ts'; +import { year } from './year.ts'; + +export function getMySqlColumnBuilders() { + return { + bigint, + binary, + boolean, + char, + customType, + date, + datetime, + decimal, + double, + mysqlEnum, + float, + int, + json, + mediumint, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varbinary, + varchar, + year, + longtext, + mediumtext, + tinytext, + }; +} + +export type MySqlColumnBuilders = ReturnType; diff --git a/drizzle-orm/src/mysql-core/columns/bigint.ts b/drizzle-orm/src/mysql-core/columns/bigint.ts index ca1eedb3f..0973a1346 100644 --- a/drizzle-orm/src/mysql-core/columns/bigint.ts +++ b/drizzle-orm/src/mysql-core/columns/bigint.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; export type MySqlBigInt53BuilderInitial = MySqlBigInt53Builder<{ @@ -11,13 +12,12 @@ export type MySqlBigInt53BuilderInitial = MySqlBigInt53Bui data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class MySqlBigInt53Builder> extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlBigInt53Builder'; + static override readonly [entityKind]: string = 'MySqlBigInt53Builder'; constructor(name: T['name'], unsigned: boolean = false) { super(name, 'number', 'MySqlBigInt53'); @@ -38,7 +38,7 @@ export class MySqlBigInt53Builder> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlBigInt53'; + static override readonly [entityKind]: string = 'MySqlBigInt53'; getSQLType(): string { return `bigint${this.config.unsigned ? ' unsigned' : ''}`; @@ -59,13 +59,12 @@ export type MySqlBigInt64BuilderInitial = MySqlBigInt64Bui data: bigint; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class MySqlBigInt64Builder> extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlBigInt64Builder'; + static override readonly [entityKind]: string = 'MySqlBigInt64Builder'; constructor(name: T['name'], unsigned: boolean = false) { super(name, 'bigint', 'MySqlBigInt64'); @@ -86,7 +85,7 @@ export class MySqlBigInt64Builder> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlBigInt64'; + static override readonly [entityKind]: string = 'MySqlBigInt64'; getSQLType(): string { return `bigint${this.config.unsigned ? ' unsigned' : ''}`; @@ -98,16 +97,20 @@ export class MySqlBigInt64 } } -interface MySqlBigIntConfig { +export interface MySqlBigIntConfig { mode: T; unsigned?: boolean; } +export function bigint( + config: MySqlBigIntConfig, +): TMode extends 'number' ? MySqlBigInt53BuilderInitial<''> : MySqlBigInt64BuilderInitial<''>; export function bigint( name: TName, config: MySqlBigIntConfig, ): TMode extends 'number' ? MySqlBigInt53BuilderInitial : MySqlBigInt64BuilderInitial; -export function bigint(name: string, config: MySqlBigIntConfig) { +export function bigint(a?: string | MySqlBigIntConfig, b?: MySqlBigIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); if (config.mode === 'number') { return new MySqlBigInt53Builder(name, config.unsigned); } diff --git a/drizzle-orm/src/mysql-core/columns/binary.ts b/drizzle-orm/src/mysql-core/columns/binary.ts index 87a8e0f8c..e67006653 100644 --- a/drizzle-orm/src/mysql-core/columns/binary.ts +++ b/drizzle-orm/src/mysql-core/columns/binary.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type MySqlBinaryBuilderInitial = MySqlBinaryBuilder<{ @@ -11,14 +12,13 @@ export type MySqlBinaryBuilderInitial = MySqlBinaryBuilder data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class MySqlBinaryBuilder> extends MySqlColumnBuilder< T, MySqlBinaryConfig > { - static readonly [entityKind]: string = 'MySqlBinaryBuilder'; + static override readonly [entityKind]: string = 'MySqlBinaryBuilder'; constructor(name: T['name'], length: number | undefined) { super(name, 'string', 'MySqlBinary'); @@ -37,7 +37,7 @@ export class MySqlBinary> ex T, MySqlBinaryConfig > { - static readonly [entityKind]: string = 'MySqlBinary'; + static override readonly [entityKind]: string = 'MySqlBinary'; length: number | undefined = this.config.length; @@ -50,9 +50,15 @@ export interface MySqlBinaryConfig { length?: number; } +export function binary(): MySqlBinaryBuilderInitial<''>; +export function binary( + config?: MySqlBinaryConfig, +): MySqlBinaryBuilderInitial<''>; export function binary( name: TName, - config: MySqlBinaryConfig = {}, -): MySqlBinaryBuilderInitial { + config?: MySqlBinaryConfig, +): MySqlBinaryBuilderInitial; +export function binary(a?: string | MySqlBinaryConfig, b: MySqlBinaryConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); return new MySqlBinaryBuilder(name, config.length); } diff --git a/drizzle-orm/src/mysql-core/columns/boolean.ts b/drizzle-orm/src/mysql-core/columns/boolean.ts index 3a915e673..2057496b6 100644 --- a/drizzle-orm/src/mysql-core/columns/boolean.ts +++ b/drizzle-orm/src/mysql-core/columns/boolean.ts @@ -11,13 +11,12 @@ export type MySqlBooleanBuilderInitial = MySqlBooleanBuild data: boolean; driverParam: number | boolean; enumValues: undefined; - generated: undefined; }>; export class MySqlBooleanBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlBooleanBuilder'; + static override readonly [entityKind]: string = 'MySqlBooleanBuilder'; constructor(name: T['name']) { super(name, 'boolean', 'MySqlBoolean'); @@ -35,7 +34,7 @@ export class MySqlBooleanBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlBoolean'; + static override readonly [entityKind]: string = 'MySqlBoolean'; getSQLType(): string { return 'boolean'; @@ -49,6 +48,8 @@ export class MySqlBoolean> } } -export function boolean(name: TName): MySqlBooleanBuilderInitial { - return new MySqlBooleanBuilder(name); +export function boolean(): MySqlBooleanBuilderInitial<''>; +export function boolean(name: TName): MySqlBooleanBuilderInitial; +export function boolean(name?: string) { + return new MySqlBooleanBuilder(name ?? ''); } diff --git a/drizzle-orm/src/mysql-core/columns/char.ts b/drizzle-orm/src/mysql-core/columns/char.ts index f871796a5..55743a5d4 100644 --- a/drizzle-orm/src/mysql-core/columns/char.ts +++ b/drizzle-orm/src/mysql-core/columns/char.ts @@ -2,26 +2,33 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; -import type { Writable } from '~/utils.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; -export type MySqlCharBuilderInitial = MySqlCharBuilder<{ +export type MySqlCharBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = MySqlCharBuilder<{ name: TName; dataType: 'string'; columnType: 'MySqlChar'; data: TEnum[number]; driverParam: number | string; enumValues: TEnum; - generated: undefined; + length: TLength; }>; -export class MySqlCharBuilder> extends MySqlColumnBuilder< +export class MySqlCharBuilder< + T extends ColumnBuilderBaseConfig<'string', 'MySqlChar'> & { length?: number | undefined }, +> extends MySqlColumnBuilder< T, - MySqlCharConfig + MySqlCharConfig, + { length: T['length'] } > { - static readonly [entityKind]: string = 'MySqlCharBuilder'; + static override readonly [entityKind]: string = 'MySqlCharBuilder'; - constructor(name: T['name'], config: MySqlCharConfig) { + constructor(name: T['name'], config: MySqlCharConfig) { super(name, 'string', 'MySqlChar'); this.config.length = config.length; this.config.enum = config.enum; @@ -30,20 +37,20 @@ export class MySqlCharBuilder( table: AnyMySqlTable<{ name: TTableName }>, - ): MySqlChar & { enumValues: T['enumValues'] }> { - return new MySqlChar & { enumValues: T['enumValues'] }>( + ): MySqlChar & { length: T['length']; enumValues: T['enumValues'] }> { + return new MySqlChar & { length: T['length']; enumValues: T['enumValues'] }>( table, this.config as ColumnBuilderRuntimeConfig, ); } } -export class MySqlChar> - extends MySqlColumn> +export class MySqlChar & { length?: number | undefined }> + extends MySqlColumn, { length: T['length'] }> { - static readonly [entityKind]: string = 'MySqlChar'; + static override readonly [entityKind]: string = 'MySqlChar'; - readonly length: number | undefined = this.config.length; + readonly length: T['length'] = this.config.length; override readonly enumValues = this.config.enum; getSQLType(): string { @@ -51,14 +58,28 @@ export class MySqlChar> } } -export interface MySqlCharConfig { - length?: number; +export interface MySqlCharConfig< + TEnum extends readonly string[] | string[] | undefined = readonly string[] | string[] | undefined, + TLength extends number | undefined = number | undefined, +> { enum?: TEnum; + length?: TLength; } -export function char>( +export function char(): MySqlCharBuilderInitial<'', [string, ...string[]], undefined>; +export function char, L extends number | undefined>( + config?: MySqlCharConfig, L>, +): MySqlCharBuilderInitial<'', Writable, L>; +export function char< + TName extends string, + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( name: TName, - config: MySqlCharConfig> = {}, -): MySqlCharBuilderInitial> { - return new MySqlCharBuilder(name, config); + config?: MySqlCharConfig, L>, +): MySqlCharBuilderInitial, L>; +export function char(a?: string | MySqlCharConfig, b: MySqlCharConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlCharBuilder(name, config as any); } diff --git a/drizzle-orm/src/mysql-core/columns/common.ts b/drizzle-orm/src/mysql-core/columns/common.ts index a0a192477..289c420ae 100644 --- a/drizzle-orm/src/mysql-core/columns/common.ts +++ b/drizzle-orm/src/mysql-core/columns/common.ts @@ -47,7 +47,7 @@ export abstract class MySqlColumnBuilder< > extends ColumnBuilder implements MySqlColumnBuilderBase { - static readonly [entityKind]: string = 'MySqlColumnBuilder'; + static override readonly [entityKind]: string = 'MySqlColumnBuilder'; private foreignKeyConfigs: ReferenceConfig[] = []; @@ -62,7 +62,9 @@ export abstract class MySqlColumnBuilder< return this; } - generatedAlwaysAs(as: SQL | T['data'] | (() => SQL), config?: MySqlGeneratedColumnConfig): HasGenerated { + generatedAlwaysAs(as: SQL | T['data'] | (() => SQL), config?: MySqlGeneratedColumnConfig): HasGenerated { this.config.generated = { as, type: 'always', @@ -99,9 +101,10 @@ export abstract class MySqlColumnBuilder< // To understand how to use `MySqlColumn` and `AnyMySqlColumn`, see `Column` and `AnyColumn` documentation. export abstract class MySqlColumn< T extends ColumnBaseConfig = ColumnBaseConfig, - TRuntimeConfig extends object = object, -> extends Column { - static readonly [entityKind]: string = 'MySqlColumn'; + TRuntimeConfig extends object = {}, + TTypeConfig extends object = {}, +> extends Column { + static override readonly [entityKind]: string = 'MySqlColumn'; constructor( override readonly table: MySqlTable, @@ -127,7 +130,7 @@ export abstract class MySqlColumnBuilderWithAutoIncrement< TRuntimeConfig extends object = object, TExtraConfig extends ColumnBuilderExtraConfig = ColumnBuilderExtraConfig, > extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlColumnBuilderWithAutoIncrement'; + static override readonly [entityKind]: string = 'MySqlColumnBuilderWithAutoIncrement'; constructor(name: NonNullable, dataType: T['dataType'], columnType: T['columnType']) { super(name, dataType, columnType); @@ -145,7 +148,7 @@ export abstract class MySqlColumnWithAutoIncrement< T extends ColumnBaseConfig = ColumnBaseConfig, TRuntimeConfig extends object = object, > extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlColumnWithAutoIncrement'; + static override readonly [entityKind]: string = 'MySqlColumnWithAutoIncrement'; readonly autoIncrement: boolean = this.config.autoIncrement; } diff --git a/drizzle-orm/src/mysql-core/columns/custom.ts b/drizzle-orm/src/mysql-core/columns/custom.ts index 1c5e2603f..50585bece 100644 --- a/drizzle-orm/src/mysql-core/columns/custom.ts +++ b/drizzle-orm/src/mysql-core/columns/custom.ts @@ -3,7 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; import type { SQL } from '~/sql/sql.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type ConvertCustomConfig> = @@ -14,7 +14,6 @@ export type ConvertCustomConfig { - static readonly [entityKind]: string = 'MySqlCustomColumnBuilder'; + static override readonly [entityKind]: string = 'MySqlCustomColumnBuilder'; constructor( name: T['name'], @@ -59,7 +58,7 @@ export class MySqlCustomColumnBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlCustomColumn'; + static override readonly [entityKind]: string = 'MySqlCustomColumn'; private sqlName: string; private mapTo?: (value: T['data']) => T['driverParam']; @@ -108,7 +107,7 @@ export type CustomTypeValues = { /** * What config type should be used for {@link CustomTypeParams} `dataType` generation */ - config?: unknown; + config?: Record; /** * Whether the config argument should be required or not @@ -203,19 +202,31 @@ export interface CustomTypeParams { */ export function customType( customTypeParams: CustomTypeParams, -): Equal extends true ? ( - dbName: TName, - fieldConfig: T['config'], - ) => MySqlCustomColumnBuilder> - : ( - dbName: TName, - fieldConfig?: T['config'], - ) => MySqlCustomColumnBuilder> +): Equal extends true ? { + & T['config']>( + fieldConfig: TConfig, + ): MySqlCustomColumnBuilder>; + ( + dbName: TName, + fieldConfig: T['config'], + ): MySqlCustomColumnBuilder>; + } + : { + (): MySqlCustomColumnBuilder>; + & T['config']>( + fieldConfig?: TConfig, + ): MySqlCustomColumnBuilder>; + ( + dbName: TName, + fieldConfig?: T['config'], + ): MySqlCustomColumnBuilder>; + } { return ( - dbName: TName, - fieldConfig?: T['config'], + a?: TName | T['config'], + b?: T['config'], ): MySqlCustomColumnBuilder> => { - return new MySqlCustomColumnBuilder(dbName as ConvertCustomConfig['name'], fieldConfig, customTypeParams); + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlCustomColumnBuilder(name as ConvertCustomConfig['name'], config, customTypeParams); }; } diff --git a/drizzle-orm/src/mysql-core/columns/date.common.ts b/drizzle-orm/src/mysql-core/columns/date.common.ts index 3fd8aa612..75faad5b8 100644 --- a/drizzle-orm/src/mysql-core/columns/date.common.ts +++ b/drizzle-orm/src/mysql-core/columns/date.common.ts @@ -18,7 +18,7 @@ export abstract class MySqlDateColumnBaseBuilder< TRuntimeConfig extends object = object, TExtraConfig extends ColumnBuilderExtraConfig = ColumnBuilderExtraConfig, > extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlDateColumnBuilder'; + static override readonly [entityKind]: string = 'MySqlDateColumnBuilder'; defaultNow() { return this.default(sql`(now())`); @@ -36,7 +36,7 @@ export abstract class MySqlDateBaseColumn< T extends ColumnBaseConfig, TRuntimeConfig extends object = object, > extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlDateColumn'; + static override readonly [entityKind]: string = 'MySqlDateColumn'; readonly hasOnUpdateNow: boolean = this.config.hasOnUpdateNow; } diff --git a/drizzle-orm/src/mysql-core/columns/date.ts b/drizzle-orm/src/mysql-core/columns/date.ts index d7e2c409e..f3797ee6e 100644 --- a/drizzle-orm/src/mysql-core/columns/date.ts +++ b/drizzle-orm/src/mysql-core/columns/date.ts @@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type MySqlDateBuilderInitial = MySqlDateBuilder<{ @@ -12,11 +12,10 @@ export type MySqlDateBuilderInitial = MySqlDateBuilder<{ data: Date; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class MySqlDateBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlDateBuilder'; + static override readonly [entityKind]: string = 'MySqlDateBuilder'; constructor(name: T['name']) { super(name, 'date', 'MySqlDate'); @@ -31,7 +30,7 @@ export class MySqlDateBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlDate'; + static override readonly [entityKind]: string = 'MySqlDate'; constructor( table: AnyMySqlTable<{ name: T['tableName'] }>, @@ -56,13 +55,12 @@ export type MySqlDateStringBuilderInitial = MySqlDateStrin data: string; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class MySqlDateStringBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlDateStringBuilder'; + static override readonly [entityKind]: string = 'MySqlDateStringBuilder'; constructor(name: T['name']) { super(name, 'string', 'MySqlDateString'); @@ -80,7 +78,7 @@ export class MySqlDateStringBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlDateString'; + static override readonly [entityKind]: string = 'MySqlDateString'; constructor( table: AnyMySqlTable<{ name: T['tableName'] }>, @@ -98,12 +96,17 @@ export interface MySqlDateConfig; +export function date( + config?: MySqlDateConfig, +): Equal extends true ? MySqlDateStringBuilderInitial<''> : MySqlDateBuilderInitial<''>; export function date( name: TName, config?: MySqlDateConfig, ): Equal extends true ? MySqlDateStringBuilderInitial : MySqlDateBuilderInitial; -export function date(name: string, config: MySqlDateConfig = {}) { - if (config.mode === 'string') { +export function date(a?: string | MySqlDateConfig, b?: MySqlDateConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config?.mode === 'string') { return new MySqlDateStringBuilder(name); } return new MySqlDateBuilder(name); diff --git a/drizzle-orm/src/mysql-core/columns/datetime.ts b/drizzle-orm/src/mysql-core/columns/datetime.ts index 040c57130..ea5b917b9 100644 --- a/drizzle-orm/src/mysql-core/columns/datetime.ts +++ b/drizzle-orm/src/mysql-core/columns/datetime.ts @@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type MySqlDateTimeBuilderInitial = MySqlDateTimeBuilder<{ @@ -12,13 +12,12 @@ export type MySqlDateTimeBuilderInitial = MySqlDateTimeBui data: Date; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class MySqlDateTimeBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlDateTimeBuilder'; + static override readonly [entityKind]: string = 'MySqlDateTimeBuilder'; constructor(name: T['name'], config: MySqlDatetimeConfig | undefined) { super(name, 'date', 'MySqlDateTime'); @@ -37,7 +36,7 @@ export class MySqlDateTimeBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlDateTime'; + static override readonly [entityKind]: string = 'MySqlDateTime'; readonly fsp: number | undefined; @@ -70,13 +69,12 @@ export type MySqlDateTimeStringBuilderInitial = MySqlDateT data: string; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class MySqlDateTimeStringBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlDateTimeStringBuilder'; + static override readonly [entityKind]: string = 'MySqlDateTimeStringBuilder'; constructor(name: T['name'], config: MySqlDatetimeConfig | undefined) { super(name, 'string', 'MySqlDateTimeString'); @@ -95,7 +93,7 @@ export class MySqlDateTimeStringBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlDateTimeString'; + static override readonly [entityKind]: string = 'MySqlDateTimeString'; readonly fsp: number | undefined; @@ -120,12 +118,17 @@ export interface MySqlDatetimeConfig; +export function datetime( + config?: MySqlDatetimeConfig, +): Equal extends true ? MySqlDateTimeStringBuilderInitial<''> : MySqlDateTimeBuilderInitial<''>; export function datetime( name: TName, config?: MySqlDatetimeConfig, ): Equal extends true ? MySqlDateTimeStringBuilderInitial : MySqlDateTimeBuilderInitial; -export function datetime(name: string, config: MySqlDatetimeConfig = {}) { - if (config.mode === 'string') { +export function datetime(a?: string | MySqlDatetimeConfig, b?: MySqlDatetimeConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config?.mode === 'string') { return new MySqlDateTimeStringBuilder(name, config); } return new MySqlDateTimeBuilder(name, config); diff --git a/drizzle-orm/src/mysql-core/columns/decimal.ts b/drizzle-orm/src/mysql-core/columns/decimal.ts index fa25d9cdb..76b0ba8a1 100644 --- a/drizzle-orm/src/mysql-core/columns/decimal.ts +++ b/drizzle-orm/src/mysql-core/columns/decimal.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; export type MySqlDecimalBuilderInitial = MySqlDecimalBuilder<{ @@ -11,18 +12,18 @@ export type MySqlDecimalBuilderInitial = MySqlDecimalBuild data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class MySqlDecimalBuilder< T extends ColumnBuilderBaseConfig<'string', 'MySqlDecimal'>, > extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlDecimalBuilder'; + static override readonly [entityKind]: string = 'MySqlDecimalBuilder'; - constructor(name: T['name'], precision?: number, scale?: number) { + constructor(name: T['name'], config: MySqlDecimalConfig | undefined) { super(name, 'string', 'MySqlDecimal'); - this.config.precision = precision; - this.config.scale = scale; + this.config.precision = config?.precision; + this.config.scale = config?.scale; + this.config.unsigned = config?.unsigned; } /** @internal */ @@ -39,30 +40,41 @@ export class MySqlDecimalBuilder< export class MySqlDecimal> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlDecimal'; + static override readonly [entityKind]: string = 'MySqlDecimal'; readonly precision: number | undefined = this.config.precision; readonly scale: number | undefined = this.config.scale; + readonly unsigned: boolean | undefined = this.config.unsigned; getSQLType(): string { + let type = ''; if (this.precision !== undefined && this.scale !== undefined) { - return `decimal(${this.precision},${this.scale})`; + type += `decimal(${this.precision},${this.scale})`; } else if (this.precision === undefined) { - return 'decimal'; + type += 'decimal'; } else { - return `decimal(${this.precision})`; + type += `decimal(${this.precision})`; } + type = type === 'decimal(10,0)' || type === 'decimal(10)' ? 'decimal' : type; + return this.unsigned ? `${type} unsigned` : type; } } export interface MySqlDecimalConfig { precision?: number; scale?: number; + unsigned?: boolean; } +export function decimal(): MySqlDecimalBuilderInitial<''>; +export function decimal( + config: MySqlDecimalConfig, +): MySqlDecimalBuilderInitial<''>; export function decimal( name: TName, - config: MySqlDecimalConfig = {}, -): MySqlDecimalBuilderInitial { - return new MySqlDecimalBuilder(name, config.precision, config.scale); + config?: MySqlDecimalConfig, +): MySqlDecimalBuilderInitial; +export function decimal(a?: string | MySqlDecimalConfig, b: MySqlDecimalConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlDecimalBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/double.ts b/drizzle-orm/src/mysql-core/columns/double.ts index dd349cf27..bc23fc74c 100644 --- a/drizzle-orm/src/mysql-core/columns/double.ts +++ b/drizzle-orm/src/mysql-core/columns/double.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; export type MySqlDoubleBuilderInitial = MySqlDoubleBuilder<{ @@ -11,18 +12,18 @@ export type MySqlDoubleBuilderInitial = MySqlDoubleBuilder data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class MySqlDoubleBuilder> extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlDoubleBuilder'; + static override readonly [entityKind]: string = 'MySqlDoubleBuilder'; constructor(name: T['name'], config: MySqlDoubleConfig | undefined) { super(name, 'number', 'MySqlDouble'); this.config.precision = config?.precision; this.config.scale = config?.scale; + this.config.unsigned = config?.unsigned; } /** @internal */ @@ -36,30 +37,40 @@ export class MySqlDoubleBuilder> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlDouble'; + static override readonly [entityKind]: string = 'MySqlDouble'; - precision: number | undefined = this.config.precision; - scale: number | undefined = this.config.scale; + readonly precision: number | undefined = this.config.precision; + readonly scale: number | undefined = this.config.scale; + readonly unsigned: boolean | undefined = this.config.unsigned; getSQLType(): string { + let type = ''; if (this.precision !== undefined && this.scale !== undefined) { - return `double(${this.precision},${this.scale})`; + type += `double(${this.precision},${this.scale})`; } else if (this.precision === undefined) { - return 'double'; + type += 'double'; } else { - return `double(${this.precision})`; + type += `double(${this.precision})`; } + return this.unsigned ? `${type} unsigned` : type; } } export interface MySqlDoubleConfig { precision?: number; scale?: number; + unsigned?: boolean; } +export function double(): MySqlDoubleBuilderInitial<''>; +export function double( + config?: MySqlDoubleConfig, +): MySqlDoubleBuilderInitial<''>; export function double( name: TName, config?: MySqlDoubleConfig, -): MySqlDoubleBuilderInitial { +): MySqlDoubleBuilderInitial; +export function double(a?: string | MySqlDoubleConfig, b?: MySqlDoubleConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new MySqlDoubleBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/enum.ts b/drizzle-orm/src/mysql-core/columns/enum.ts index 1d8b4c1f5..384e07d17 100644 --- a/drizzle-orm/src/mysql-core/columns/enum.ts +++ b/drizzle-orm/src/mysql-core/columns/enum.ts @@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; -import type { Writable } from '~/utils.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type MySqlEnumColumnBuilderInitial = @@ -13,13 +13,12 @@ export type MySqlEnumColumnBuilderInitial; export class MySqlEnumColumnBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlEnumColumnBuilder'; + static override readonly [entityKind]: string = 'MySqlEnumColumnBuilder'; constructor(name: T['name'], values: T['enumValues']) { super(name, 'string', 'MySqlEnumColumn'); @@ -40,7 +39,7 @@ export class MySqlEnumColumnBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlEnumColumn'; + static override readonly [entityKind]: string = 'MySqlEnumColumn'; override readonly enumValues = this.config.enumValues; @@ -49,13 +48,22 @@ export class MySqlEnumColumn>( + values: T | Writable, +): MySqlEnumColumnBuilderInitial<'', Writable>; export function mysqlEnum>( name: TName, values: T | Writable, -): MySqlEnumColumnBuilderInitial> { +): MySqlEnumColumnBuilderInitial>; +export function mysqlEnum( + a?: string | readonly [string, ...string[]] | [string, ...string[]], + b?: readonly [string, ...string[]] | [string, ...string[]], +): any { + const { name, config: values } = getColumnNameAndConfig(a, b); + if (values.length === 0) { throw new Error(`You have an empty array for "${name}" enum values`); } - return new MySqlEnumColumnBuilder(name, values); + return new MySqlEnumColumnBuilder(name, values as any); } diff --git a/drizzle-orm/src/mysql-core/columns/float.ts b/drizzle-orm/src/mysql-core/columns/float.ts index b66f1e05a..e368740a0 100644 --- a/drizzle-orm/src/mysql-core/columns/float.ts +++ b/drizzle-orm/src/mysql-core/columns/float.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; export type MySqlFloatBuilderInitial = MySqlFloatBuilder<{ @@ -11,16 +12,18 @@ export type MySqlFloatBuilderInitial = MySqlFloatBuilder<{ data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class MySqlFloatBuilder> - extends MySqlColumnBuilderWithAutoIncrement + extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlFloatBuilder'; + static override readonly [entityKind]: string = 'MySqlFloatBuilder'; - constructor(name: T['name']) { + constructor(name: T['name'], config: MySqlFloatConfig | undefined) { super(name, 'number', 'MySqlFloat'); + this.config.precision = config?.precision; + this.config.scale = config?.scale; + this.config.unsigned = config?.unsigned; } /** @internal */ @@ -31,14 +34,43 @@ export class MySqlFloatBuilder> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlFloat'; +export class MySqlFloat> + extends MySqlColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'MySqlFloat'; + + readonly precision: number | undefined = this.config.precision; + readonly scale: number | undefined = this.config.scale; + readonly unsigned: boolean | undefined = this.config.unsigned; getSQLType(): string { - return 'float'; + let type = ''; + if (this.precision !== undefined && this.scale !== undefined) { + type += `float(${this.precision},${this.scale})`; + } else if (this.precision === undefined) { + type += 'float'; + } else { + type += `float(${this.precision})`; + } + return this.unsigned ? `${type} unsigned` : type; } } -export function float(name: TName): MySqlFloatBuilderInitial { - return new MySqlFloatBuilder(name); +export interface MySqlFloatConfig { + precision?: number; + scale?: number; + unsigned?: boolean; +} + +export function float(): MySqlFloatBuilderInitial<''>; +export function float( + config?: MySqlFloatConfig, +): MySqlFloatBuilderInitial<''>; +export function float( + name: TName, + config?: MySqlFloatConfig, +): MySqlFloatBuilderInitial; +export function float(a?: string | MySqlFloatConfig, b?: MySqlFloatConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlFloatBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/int.ts b/drizzle-orm/src/mysql-core/columns/int.ts index dbfb85760..79b93bdf7 100644 --- a/drizzle-orm/src/mysql-core/columns/int.ts +++ b/drizzle-orm/src/mysql-core/columns/int.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; export type MySqlIntBuilderInitial = MySqlIntBuilder<{ @@ -11,13 +12,12 @@ export type MySqlIntBuilderInitial = MySqlIntBuilder<{ data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class MySqlIntBuilder> extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlIntBuilder'; + static override readonly [entityKind]: string = 'MySqlIntBuilder'; constructor(name: T['name'], config?: MySqlIntConfig) { super(name, 'number', 'MySqlInt'); @@ -35,7 +35,7 @@ export class MySqlIntBuilder> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlInt'; + static override readonly [entityKind]: string = 'MySqlInt'; getSQLType(): string { return `int${this.config.unsigned ? ' unsigned' : ''}`; @@ -53,6 +53,15 @@ export interface MySqlIntConfig { unsigned?: boolean; } -export function int(name: TName, config?: MySqlIntConfig): MySqlIntBuilderInitial { +export function int(): MySqlIntBuilderInitial<''>; +export function int( + config?: MySqlIntConfig, +): MySqlIntBuilderInitial<''>; +export function int( + name: TName, + config?: MySqlIntConfig, +): MySqlIntBuilderInitial; +export function int(a?: string | MySqlIntConfig, b?: MySqlIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new MySqlIntBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/json.ts b/drizzle-orm/src/mysql-core/columns/json.ts index f30ea1534..12d11bd4a 100644 --- a/drizzle-orm/src/mysql-core/columns/json.ts +++ b/drizzle-orm/src/mysql-core/columns/json.ts @@ -11,11 +11,10 @@ export type MySqlJsonBuilderInitial = MySqlJsonBuilder<{ data: unknown; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class MySqlJsonBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlJsonBuilder'; + static override readonly [entityKind]: string = 'MySqlJsonBuilder'; constructor(name: T['name']) { super(name, 'json', 'MySqlJson'); @@ -30,7 +29,7 @@ export class MySqlJsonBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlJson'; + static override readonly [entityKind]: string = 'MySqlJson'; getSQLType(): string { return 'json'; @@ -41,6 +40,8 @@ export class MySqlJson> extends } } -export function json(name: TName): MySqlJsonBuilderInitial { - return new MySqlJsonBuilder(name); +export function json(): MySqlJsonBuilderInitial<''>; +export function json(name: TName): MySqlJsonBuilderInitial; +export function json(name?: string) { + return new MySqlJsonBuilder(name ?? ''); } diff --git a/drizzle-orm/src/mysql-core/columns/mediumint.ts b/drizzle-orm/src/mysql-core/columns/mediumint.ts index 268028b44..7ba5cc944 100644 --- a/drizzle-orm/src/mysql-core/columns/mediumint.ts +++ b/drizzle-orm/src/mysql-core/columns/mediumint.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; import type { MySqlIntConfig } from './int.ts'; @@ -12,13 +13,12 @@ export type MySqlMediumIntBuilderInitial = MySqlMediumIntB data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class MySqlMediumIntBuilder> extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlMediumIntBuilder'; + static override readonly [entityKind]: string = 'MySqlMediumIntBuilder'; constructor(name: T['name'], config?: MySqlIntConfig) { super(name, 'number', 'MySqlMediumInt'); @@ -39,7 +39,7 @@ export class MySqlMediumIntBuilder> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlMediumInt'; + static override readonly [entityKind]: string = 'MySqlMediumInt'; getSQLType(): string { return `mediumint${this.config.unsigned ? ' unsigned' : ''}`; @@ -53,9 +53,15 @@ export class MySqlMediumInt; +export function mediumint( + config?: MySqlIntConfig, +): MySqlMediumIntBuilderInitial<''>; export function mediumint( name: TName, config?: MySqlIntConfig, -): MySqlMediumIntBuilderInitial { +): MySqlMediumIntBuilderInitial; +export function mediumint(a?: string | MySqlIntConfig, b?: MySqlIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new MySqlMediumIntBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/real.ts b/drizzle-orm/src/mysql-core/columns/real.ts index 7dd41dda0..c6b447310 100644 --- a/drizzle-orm/src/mysql-core/columns/real.ts +++ b/drizzle-orm/src/mysql-core/columns/real.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; export type MySqlRealBuilderInitial = MySqlRealBuilder<{ @@ -11,7 +12,6 @@ export type MySqlRealBuilderInitial = MySqlRealBuilder<{ data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class MySqlRealBuilder> @@ -20,7 +20,7 @@ export class MySqlRealBuilder { - static readonly [entityKind]: string = 'MySqlRealBuilder'; + static override readonly [entityKind]: string = 'MySqlRealBuilder'; constructor(name: T['name'], config: MySqlRealConfig | undefined) { super(name, 'number', 'MySqlReal'); @@ -40,7 +40,7 @@ export class MySqlReal> extend T, MySqlRealConfig > { - static readonly [entityKind]: string = 'MySqlReal'; + static override readonly [entityKind]: string = 'MySqlReal'; precision: number | undefined = this.config.precision; scale: number | undefined = this.config.scale; @@ -61,6 +61,15 @@ export interface MySqlRealConfig { scale?: number; } -export function real(name: TName, config: MySqlRealConfig = {}): MySqlRealBuilderInitial { +export function real(): MySqlRealBuilderInitial<''>; +export function real( + config?: MySqlRealConfig, +): MySqlRealBuilderInitial<''>; +export function real( + name: TName, + config?: MySqlRealConfig, +): MySqlRealBuilderInitial; +export function real(a?: string | MySqlRealConfig, b: MySqlRealConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); return new MySqlRealBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/serial.ts b/drizzle-orm/src/mysql-core/columns/serial.ts index 0f87f0bf5..90fd7a2e5 100644 --- a/drizzle-orm/src/mysql-core/columns/serial.ts +++ b/drizzle-orm/src/mysql-core/columns/serial.ts @@ -23,7 +23,6 @@ export type MySqlSerialBuilderInitial = IsAutoincrement< data: number; driverParam: number; enumValues: undefined; - generated: undefined; }> > > @@ -33,7 +32,7 @@ export type MySqlSerialBuilderInitial = IsAutoincrement< export class MySqlSerialBuilder> extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlSerialBuilder'; + static override readonly [entityKind]: string = 'MySqlSerialBuilder'; constructor(name: T['name']) { super(name, 'number', 'MySqlSerial'); @@ -52,7 +51,7 @@ export class MySqlSerialBuilder, > extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlSerial'; + static override readonly [entityKind]: string = 'MySqlSerial'; getSQLType(): string { return 'serial'; @@ -66,6 +65,8 @@ export class MySqlSerial< } } -export function serial(name: TName): MySqlSerialBuilderInitial { - return new MySqlSerialBuilder(name) as MySqlSerialBuilderInitial; +export function serial(): MySqlSerialBuilderInitial<''>; +export function serial(name: TName): MySqlSerialBuilderInitial; +export function serial(name?: string) { + return new MySqlSerialBuilder(name ?? ''); } diff --git a/drizzle-orm/src/mysql-core/columns/smallint.ts b/drizzle-orm/src/mysql-core/columns/smallint.ts index fc1dd0d55..87083f0fa 100644 --- a/drizzle-orm/src/mysql-core/columns/smallint.ts +++ b/drizzle-orm/src/mysql-core/columns/smallint.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; import type { MySqlIntConfig } from './int.ts'; @@ -12,13 +13,12 @@ export type MySqlSmallIntBuilderInitial = MySqlSmallIntBui data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class MySqlSmallIntBuilder> extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlSmallIntBuilder'; + static override readonly [entityKind]: string = 'MySqlSmallIntBuilder'; constructor(name: T['name'], config?: MySqlIntConfig) { super(name, 'number', 'MySqlSmallInt'); @@ -39,7 +39,7 @@ export class MySqlSmallIntBuilder> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlSmallInt'; + static override readonly [entityKind]: string = 'MySqlSmallInt'; getSQLType(): string { return `smallint${this.config.unsigned ? ' unsigned' : ''}`; @@ -53,9 +53,15 @@ export class MySqlSmallInt } } +export function smallint(): MySqlSmallIntBuilderInitial<''>; +export function smallint( + config?: MySqlIntConfig, +): MySqlSmallIntBuilderInitial<''>; export function smallint( name: TName, config?: MySqlIntConfig, -): MySqlSmallIntBuilderInitial { +): MySqlSmallIntBuilderInitial; +export function smallint(a?: string | MySqlIntConfig, b?: MySqlIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new MySqlSmallIntBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/text.ts b/drizzle-orm/src/mysql-core/columns/text.ts index 72c232e16..6106fd45b 100644 --- a/drizzle-orm/src/mysql-core/columns/text.ts +++ b/drizzle-orm/src/mysql-core/columns/text.ts @@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; -import type { Writable } from '~/utils.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type MySqlTextColumnType = 'tinytext' | 'text' | 'mediumtext' | 'longtext'; @@ -14,14 +14,13 @@ export type MySqlTextBuilderInitial; export class MySqlTextBuilder> extends MySqlColumnBuilder< T, { textType: MySqlTextColumnType; enumValues: T['enumValues'] } > { - static readonly [entityKind]: string = 'MySqlTextBuilder'; + static override readonly [entityKind]: string = 'MySqlTextBuilder'; constructor(name: T['name'], textType: MySqlTextColumnType, config: MySqlTextConfig) { super(name, 'string', 'MySqlText'); @@ -40,9 +39,9 @@ export class MySqlTextBuilder> extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlText'; + static override readonly [entityKind]: string = 'MySqlText'; - private textType: MySqlTextColumnType = this.config.textType; + readonly textType: MySqlTextColumnType = this.config.textType; override readonly enumValues = this.config.enumValues; @@ -51,34 +50,60 @@ export class MySqlText> } } -export interface MySqlTextConfig { +export interface MySqlTextConfig< + TEnum extends readonly string[] | string[] | undefined = readonly string[] | string[] | undefined, +> { enum?: TEnum; } +export function text(): MySqlTextBuilderInitial<'', [string, ...string[]]>; +export function text>( + config?: MySqlTextConfig>, +): MySqlTextBuilderInitial<'', Writable>; export function text>( name: TName, - config: MySqlTextConfig> = {}, -): MySqlTextBuilderInitial> { - return new MySqlTextBuilder(name, 'text', config); + config?: MySqlTextConfig>, +): MySqlTextBuilderInitial>; +export function text(a?: string | MySqlTextConfig, b: MySqlTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlTextBuilder(name, 'text', config as any); } +export function tinytext(): MySqlTextBuilderInitial<'', [string, ...string[]]>; +export function tinytext>( + config?: MySqlTextConfig>, +): MySqlTextBuilderInitial<'', Writable>; export function tinytext>( name: TName, - config: MySqlTextConfig> = {}, -): MySqlTextBuilderInitial> { - return new MySqlTextBuilder(name, 'tinytext', config); + config?: MySqlTextConfig>, +): MySqlTextBuilderInitial>; +export function tinytext(a?: string | MySqlTextConfig, b: MySqlTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlTextBuilder(name, 'tinytext', config as any); } +export function mediumtext(): MySqlTextBuilderInitial<'', [string, ...string[]]>; +export function mediumtext>( + config?: MySqlTextConfig>, +): MySqlTextBuilderInitial<'', Writable>; export function mediumtext>( name: TName, - config: MySqlTextConfig> = {}, -): MySqlTextBuilderInitial> { - return new MySqlTextBuilder(name, 'mediumtext', config); + config?: MySqlTextConfig>, +): MySqlTextBuilderInitial>; +export function mediumtext(a?: string | MySqlTextConfig, b: MySqlTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlTextBuilder(name, 'mediumtext', config as any); } +export function longtext(): MySqlTextBuilderInitial<'', [string, ...string[]]>; +export function longtext>( + config?: MySqlTextConfig>, +): MySqlTextBuilderInitial<'', Writable>; export function longtext>( name: TName, - config: MySqlTextConfig> = {}, -): MySqlTextBuilderInitial> { - return new MySqlTextBuilder(name, 'longtext', config); + config?: MySqlTextConfig>, +): MySqlTextBuilderInitial>; +export function longtext(a?: string | MySqlTextConfig, b: MySqlTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlTextBuilder(name, 'longtext', config as any); } diff --git a/drizzle-orm/src/mysql-core/columns/time.ts b/drizzle-orm/src/mysql-core/columns/time.ts index ae2251bda..7ca5426ec 100644 --- a/drizzle-orm/src/mysql-core/columns/time.ts +++ b/drizzle-orm/src/mysql-core/columns/time.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type MySqlTimeBuilderInitial = MySqlTimeBuilder<{ @@ -11,14 +12,13 @@ export type MySqlTimeBuilderInitial = MySqlTimeBuilder<{ data: string; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class MySqlTimeBuilder> extends MySqlColumnBuilder< T, TimeConfig > { - static readonly [entityKind]: string = 'MySqlTimeBuilder'; + static override readonly [entityKind]: string = 'MySqlTimeBuilder'; constructor( name: T['name'], @@ -39,7 +39,7 @@ export class MySqlTimeBuilder, > extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlTime'; + static override readonly [entityKind]: string = 'MySqlTime'; readonly fsp: number | undefined = this.config.fsp; @@ -53,6 +53,15 @@ export type TimeConfig = { fsp?: 0 | 1 | 2 | 3 | 4 | 5 | 6; }; -export function time(name: TName, config?: TimeConfig): MySqlTimeBuilderInitial { +export function time(): MySqlTimeBuilderInitial<''>; +export function time( + config?: TimeConfig, +): MySqlTimeBuilderInitial<''>; +export function time( + name: TName, + config?: TimeConfig, +): MySqlTimeBuilderInitial; +export function time(a?: string | TimeConfig, b?: TimeConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new MySqlTimeBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/timestamp.ts b/drizzle-orm/src/mysql-core/columns/timestamp.ts index 24e3b2650..2ccc2925f 100644 --- a/drizzle-orm/src/mysql-core/columns/timestamp.ts +++ b/drizzle-orm/src/mysql-core/columns/timestamp.ts @@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { MySqlDateBaseColumn, MySqlDateColumnBaseBuilder } from './date.common.ts'; export type MySqlTimestampBuilderInitial = MySqlTimestampBuilder<{ @@ -12,13 +12,12 @@ export type MySqlTimestampBuilderInitial = MySqlTimestampB data: Date; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class MySqlTimestampBuilder> extends MySqlDateColumnBaseBuilder { - static readonly [entityKind]: string = 'MySqlTimestampBuilder'; + static override readonly [entityKind]: string = 'MySqlTimestampBuilder'; constructor(name: T['name'], config: MySqlTimestampConfig | undefined) { super(name, 'date', 'MySqlTimestamp'); @@ -39,7 +38,7 @@ export class MySqlTimestampBuilder> extends MySqlDateBaseColumn { - static readonly [entityKind]: string = 'MySqlTimestamp'; + static override readonly [entityKind]: string = 'MySqlTimestamp'; readonly fsp: number | undefined = this.config.fsp; @@ -64,13 +63,12 @@ export type MySqlTimestampStringBuilderInitial = MySqlTime data: string; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class MySqlTimestampStringBuilder> extends MySqlDateColumnBaseBuilder { - static readonly [entityKind]: string = 'MySqlTimestampStringBuilder'; + static override readonly [entityKind]: string = 'MySqlTimestampStringBuilder'; constructor(name: T['name'], config: MySqlTimestampConfig | undefined) { super(name, 'string', 'MySqlTimestampString'); @@ -91,7 +89,7 @@ export class MySqlTimestampStringBuilder> extends MySqlDateBaseColumn { - static readonly [entityKind]: string = 'MySqlTimestampString'; + static override readonly [entityKind]: string = 'MySqlTimestampString'; readonly fsp: number | undefined = this.config.fsp; @@ -108,13 +106,19 @@ export interface MySqlTimestampConfig; +export function timestamp( + config?: MySqlTimestampConfig, +): Equal extends true ? MySqlTimestampStringBuilderInitial<''> + : MySqlTimestampBuilderInitial<''>; export function timestamp( name: TName, config?: MySqlTimestampConfig, ): Equal extends true ? MySqlTimestampStringBuilderInitial : MySqlTimestampBuilderInitial; -export function timestamp(name: string, config: MySqlTimestampConfig = {}) { - if (config.mode === 'string') { +export function timestamp(a?: string | MySqlTimestampConfig, b: MySqlTimestampConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config?.mode === 'string') { return new MySqlTimestampStringBuilder(name, config); } return new MySqlTimestampBuilder(name, config); diff --git a/drizzle-orm/src/mysql-core/columns/tinyint.ts b/drizzle-orm/src/mysql-core/columns/tinyint.ts index c749e6da8..890f169bd 100644 --- a/drizzle-orm/src/mysql-core/columns/tinyint.ts +++ b/drizzle-orm/src/mysql-core/columns/tinyint.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts'; import type { MySqlIntConfig } from './int.ts'; @@ -12,13 +13,12 @@ export type MySqlTinyIntBuilderInitial = MySqlTinyIntBuild data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class MySqlTinyIntBuilder> extends MySqlColumnBuilderWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlTinyIntBuilder'; + static override readonly [entityKind]: string = 'MySqlTinyIntBuilder'; constructor(name: T['name'], config?: MySqlIntConfig) { super(name, 'number', 'MySqlTinyInt'); @@ -39,7 +39,7 @@ export class MySqlTinyIntBuilder> extends MySqlColumnWithAutoIncrement { - static readonly [entityKind]: string = 'MySqlTinyInt'; + static override readonly [entityKind]: string = 'MySqlTinyInt'; getSQLType(): string { return `tinyint${this.config.unsigned ? ' unsigned' : ''}`; @@ -53,6 +53,15 @@ export class MySqlTinyInt> } } -export function tinyint(name: TName, config?: MySqlIntConfig): MySqlTinyIntBuilderInitial { +export function tinyint(): MySqlTinyIntBuilderInitial<''>; +export function tinyint( + config?: MySqlIntConfig, +): MySqlTinyIntBuilderInitial<''>; +export function tinyint( + name: TName, + config?: MySqlIntConfig, +): MySqlTinyIntBuilderInitial; +export function tinyint(a?: string | MySqlIntConfig, b?: MySqlIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new MySqlTinyIntBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/varbinary.ts b/drizzle-orm/src/mysql-core/columns/varbinary.ts index be0a89cf6..837de8dcb 100644 --- a/drizzle-orm/src/mysql-core/columns/varbinary.ts +++ b/drizzle-orm/src/mysql-core/columns/varbinary.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; export type MySqlVarBinaryBuilderInitial = MySqlVarBinaryBuilder<{ @@ -11,13 +12,12 @@ export type MySqlVarBinaryBuilderInitial = MySqlVarBinaryB data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class MySqlVarBinaryBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlVarBinaryBuilder'; + static override readonly [entityKind]: string = 'MySqlVarBinaryBuilder'; /** @internal */ constructor(name: T['name'], config: MySqlVarbinaryOptions) { @@ -39,7 +39,7 @@ export class MySqlVarBinaryBuilder, > extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlVarBinary'; + static override readonly [entityKind]: string = 'MySqlVarBinary'; length: number | undefined = this.config.length; @@ -52,9 +52,14 @@ export interface MySqlVarbinaryOptions { length: number; } +export function varbinary( + config: MySqlVarbinaryOptions, +): MySqlVarBinaryBuilderInitial<''>; export function varbinary( name: TName, - options: MySqlVarbinaryOptions, -): MySqlVarBinaryBuilderInitial { - return new MySqlVarBinaryBuilder(name, options); + config: MySqlVarbinaryOptions, +): MySqlVarBinaryBuilderInitial; +export function varbinary(a?: string | MySqlVarbinaryOptions, b?: MySqlVarbinaryOptions) { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlVarBinaryBuilder(name, config); } diff --git a/drizzle-orm/src/mysql-core/columns/varchar.ts b/drizzle-orm/src/mysql-core/columns/varchar.ts index b692bf789..0a0bde857 100644 --- a/drizzle-orm/src/mysql-core/columns/varchar.ts +++ b/drizzle-orm/src/mysql-core/columns/varchar.ts @@ -2,10 +2,14 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyMySqlTable } from '~/mysql-core/table.ts'; -import type { Writable } from '~/utils.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { MySqlColumn, MySqlColumnBuilder } from './common.ts'; -export type MySqlVarCharBuilderInitial = MySqlVarCharBuilder< +export type MySqlVarCharBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = MySqlVarCharBuilder< { name: TName; dataType: 'string'; @@ -13,17 +17,17 @@ export type MySqlVarCharBuilderInitial; -export class MySqlVarCharBuilder> - extends MySqlColumnBuilder> -{ - static readonly [entityKind]: string = 'MySqlVarCharBuilder'; +export class MySqlVarCharBuilder< + T extends ColumnBuilderBaseConfig<'string', 'MySqlVarChar'> & { length?: number | undefined }, +> extends MySqlColumnBuilder> { + static override readonly [entityKind]: string = 'MySqlVarCharBuilder'; /** @internal */ - constructor(name: T['name'], config: MySqlVarCharConfig) { + constructor(name: T['name'], config: MySqlVarCharConfig) { super(name, 'string', 'MySqlVarChar'); this.config.length = config.length; this.config.enum = config.enum; @@ -32,18 +36,18 @@ export class MySqlVarCharBuilder( table: AnyMySqlTable<{ name: TTableName }>, - ): MySqlVarChar & { enumValues: T['enumValues'] }> { - return new MySqlVarChar & { enumValues: T['enumValues'] }>( + ): MySqlVarChar & { length: T['length']; enumValues: T['enumValues'] }> { + return new MySqlVarChar & { length: T['length']; enumValues: T['enumValues'] }>( table, this.config as ColumnBuilderRuntimeConfig, ); } } -export class MySqlVarChar> - extends MySqlColumn> +export class MySqlVarChar & { length?: number | undefined }> + extends MySqlColumn, { length: T['length'] }> { - static readonly [entityKind]: string = 'MySqlVarChar'; + static override readonly [entityKind]: string = 'MySqlVarChar'; readonly length: number | undefined = this.config.length; @@ -54,14 +58,27 @@ export class MySqlVarChar> } } -export interface MySqlVarCharConfig { - length: number; +export interface MySqlVarCharConfig< + TEnum extends string[] | readonly string[] | undefined = string[] | readonly string[] | undefined, + TLength extends number | undefined = number | undefined, +> { enum?: TEnum; + length?: TLength; } -export function varchar>( +export function varchar, L extends number | undefined>( + config: MySqlVarCharConfig, L>, +): MySqlVarCharBuilderInitial<'', Writable, L>; +export function varchar< + TName extends string, + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( name: TName, - config: MySqlVarCharConfig>, -): MySqlVarCharBuilderInitial> { - return new MySqlVarCharBuilder(name, config); + config: MySqlVarCharConfig, L>, +): MySqlVarCharBuilderInitial, L>; +export function varchar(a?: string | MySqlVarCharConfig, b?: MySqlVarCharConfig): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new MySqlVarCharBuilder(name, config as any); } diff --git a/drizzle-orm/src/mysql-core/columns/year.ts b/drizzle-orm/src/mysql-core/columns/year.ts index 224de12e9..4e4ae4faf 100644 --- a/drizzle-orm/src/mysql-core/columns/year.ts +++ b/drizzle-orm/src/mysql-core/columns/year.ts @@ -11,11 +11,10 @@ export type MySqlYearBuilderInitial = MySqlYearBuilder<{ data: number; driverParam: number; enumValues: undefined; - generated: undefined; }>; export class MySqlYearBuilder> extends MySqlColumnBuilder { - static readonly [entityKind]: string = 'MySqlYearBuilder'; + static override readonly [entityKind]: string = 'MySqlYearBuilder'; constructor(name: T['name']) { super(name, 'number', 'MySqlYear'); @@ -32,13 +31,15 @@ export class MySqlYearBuilder, > extends MySqlColumn { - static readonly [entityKind]: string = 'MySqlYear'; + static override readonly [entityKind]: string = 'MySqlYear'; getSQLType(): string { return `year`; } } -export function year(name: TName): MySqlYearBuilderInitial { - return new MySqlYearBuilder(name); +export function year(): MySqlYearBuilderInitial<''>; +export function year(name: TName): MySqlYearBuilderInitial; +export function year(name?: string) { + return new MySqlYearBuilder(name ?? ''); } diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts index 8df6ff343..39ca8c77a 100644 --- a/drizzle-orm/src/mysql-core/db.ts +++ b/drizzle-orm/src/mysql-core/db.ts @@ -3,10 +3,11 @@ import { entityKind } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; -import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; +import { type ColumnsSelection, type SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; import { WithSubquery } from '~/subquery.ts'; import type { DrizzleTypeError } from '~/utils.ts'; import type { MySqlDialect } from './dialect.ts'; +import { MySqlCountBuilder } from './query-builders/count.ts'; import { MySqlDeleteBase, MySqlInsertBuilder, @@ -27,6 +28,7 @@ import type { } from './session.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; import type { MySqlTable } from './table.ts'; +import type { MySqlViewBase } from './view-base.ts'; export class MySqlDatabase< TQueryResult extends MySqlQueryResultHKT, @@ -118,12 +120,13 @@ export class MySqlDatabase< * ``` */ $with(alias: TAlias) { + const self = this; return { as( qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), ): WithSubqueryWithSelection { if (typeof qb === 'function') { - qb = qb(new QueryBuilder()); + qb = qb(new QueryBuilder(self.dialect)); } return new Proxy( @@ -134,6 +137,13 @@ export class MySqlDatabase< }; } + $count( + source: MySqlTable | MySqlViewBase | SQL | SQLWrapper, + filters?: SQL, + ) { + return new MySqlCountBuilder({ source, filters, session: this.session }); + } + /** * Incorporates a previously defined CTE (using `$with`) into the main query. * @@ -451,9 +461,9 @@ export class MySqlDatabase< } execute( - query: SQLWrapper, + query: SQLWrapper | string, ): Promise> { - return this.session.execute(query.getSQL()); + return this.session.execute(typeof query === 'string' ? sql.raw(query) : query.getSQL()); } transaction( diff --git a/drizzle-orm/src/mysql-core/dialect.ts b/drizzle-orm/src/mysql-core/dialect.ts index 4a72d9c5f..8661359ab 100644 --- a/drizzle-orm/src/mysql-core/dialect.ts +++ b/drizzle-orm/src/mysql-core/dialect.ts @@ -1,4 +1,5 @@ import { aliasedTable, aliasedTableColumn, mapColumnsInAliasedSQLToAlias, mapColumnsInSQLToAlias } from '~/alias.ts'; +import { CasingCache } from '~/casing.ts'; import { Column } from '~/column.ts'; import { entityKind, is } from '~/entity.ts'; import { DrizzleError } from '~/errors.ts'; @@ -17,23 +18,39 @@ import { type TablesRelationalConfig, } from '~/relations.ts'; import { Param, SQL, sql, View } from '~/sql/sql.ts'; -import type { Name, QueryWithTypings, SQLChunk } from '~/sql/sql.ts'; +import type { Name, Placeholder, QueryWithTypings, SQLChunk } from '~/sql/sql.ts'; import { Subquery } from '~/subquery.ts'; import { getTableName, getTableUniqueName, Table } from '~/table.ts'; -import { orderSelectedFields, type UpdateSet } from '~/utils.ts'; +import { type Casing, orderSelectedFields, type UpdateSet } from '~/utils.ts'; import { ViewBaseConfig } from '~/view-common.ts'; import { MySqlColumn } from './columns/common.ts'; import type { MySqlDeleteConfig } from './query-builders/delete.ts'; import type { MySqlInsertConfig } from './query-builders/insert.ts'; -import type { MySqlSelectConfig, MySqlSelectJoinConfig, SelectedFieldsOrdered } from './query-builders/select.types.ts'; +import type { + AnyMySqlSelectQueryBuilder, + MySqlSelectConfig, + MySqlSelectJoinConfig, + SelectedFieldsOrdered, +} from './query-builders/select.types.ts'; import type { MySqlUpdateConfig } from './query-builders/update.ts'; import type { MySqlSession } from './session.ts'; import { MySqlTable } from './table.ts'; import { MySqlViewBase } from './view-base.ts'; +export interface MySqlDialectConfig { + casing?: Casing; +} + export class MySqlDialect { static readonly [entityKind]: string = 'MySqlDialect'; + /** @internal */ + readonly casing: CasingCache; + + constructor(config?: MySqlDialectConfig) { + this.casing = new CasingCache(config?.casing); + } + async migrate( migrations: MigrationMeta[], session: MySqlSession, @@ -100,7 +117,7 @@ export class MySqlDialect { return sql.join(withSqlChunks); } - buildDeleteQuery({ table, where, returning, withList }: MySqlDeleteConfig): SQL { + buildDeleteQuery({ table, where, returning, withList, limit, orderBy }: MySqlDeleteConfig): SQL { const withSql = this.buildWithCTE(withList); const returningSql = returning @@ -109,7 +126,11 @@ export class MySqlDialect { const whereSql = where ? sql` where ${where}` : undefined; - return sql`${withSql}delete from ${table}${whereSql}${returningSql}`; + const orderBySql = this.buildOrderBy(orderBy); + + const limitSql = this.buildLimit(limit); + + return sql`${withSql}delete from ${table}${whereSql}${orderBySql}${limitSql}${returningSql}`; } buildUpdateSet(table: MySqlTable, set: UpdateSet): SQL { @@ -124,7 +145,7 @@ export class MySqlDialect { const col = tableColumns[colName]!; const value = set[colName] ?? sql.param(col.onUpdateFn!(), col); - const res = sql`${sql.identifier(col.name)} = ${value}`; + const res = sql`${sql.identifier(this.casing.getColumnCasing(col))} = ${value}`; if (i < setSize - 1) { return [res, sql.raw(', ')]; @@ -133,7 +154,7 @@ export class MySqlDialect { })); } - buildUpdateQuery({ table, set, where, returning, withList }: MySqlUpdateConfig): SQL { + buildUpdateQuery({ table, set, where, returning, withList, limit, orderBy }: MySqlUpdateConfig): SQL { const withSql = this.buildWithCTE(withList); const setSql = this.buildUpdateSet(table, set); @@ -144,7 +165,11 @@ export class MySqlDialect { const whereSql = where ? sql` where ${where}` : undefined; - return sql`${withSql}update ${table} set ${setSql}${whereSql}${returningSql}`; + const orderBySql = this.buildOrderBy(orderBy); + + const limitSql = this.buildLimit(limit); + + return sql`${withSql}update ${table} set ${setSql}${whereSql}${orderBySql}${limitSql}${returningSql}`; } /** @@ -178,7 +203,7 @@ export class MySqlDialect { new SQL( query.queryChunks.map((c) => { if (is(c, MySqlColumn)) { - return sql.identifier(c.name); + return sql.identifier(this.casing.getColumnCasing(c)); } return c; }), @@ -193,7 +218,7 @@ export class MySqlDialect { } } else if (is(field, Column)) { if (isSingleTable) { - chunk.push(sql.identifier(field.name)); + chunk.push(sql.identifier(this.casing.getColumnCasing(field))); } else { chunk.push(field); } @@ -209,6 +234,28 @@ export class MySqlDialect { return sql.join(chunks); } + private buildLimit(limit: number | Placeholder | undefined): SQL | undefined { + return typeof limit === 'object' || (typeof limit === 'number' && limit >= 0) + ? sql` limit ${limit}` + : undefined; + } + + private buildOrderBy(orderBy: (MySqlColumn | SQL | SQL.Aliased)[] | undefined): SQL | undefined { + return orderBy && orderBy.length > 0 ? sql` order by ${sql.join(orderBy, sql`, `)}` : undefined; + } + + private buildIndex({ + indexes, + indexFor, + }: { + indexes: string[] | undefined; + indexFor: 'USE' | 'FORCE' | 'IGNORE'; + }): SQL | undefined { + return indexes && indexes.length > 0 + ? sql` ${sql.raw(indexFor)} INDEX (${sql.raw(indexes.join(`, `))})` + : undefined; + } + buildSelectQuery( { withList, @@ -225,6 +272,9 @@ export class MySqlDialect { lockingClause, distinct, setOperators, + useIndex, + forceIndex, + ignoreIndex, }: MySqlSelectConfig, ): SQL { const fieldsList = fieldsFlat ?? orderSelectedFields(fields); @@ -284,10 +334,15 @@ export class MySqlDialect { const tableSchema = table[MySqlTable.Symbol.Schema]; const origTableName = table[MySqlTable.Symbol.OriginalName]; const alias = tableName === origTableName ? undefined : joinMeta.alias; + const useIndexSql = this.buildIndex({ indexes: joinMeta.useIndex, indexFor: 'USE' }); + const forceIndexSql = this.buildIndex({ indexes: joinMeta.forceIndex, indexFor: 'FORCE' }); + const ignoreIndexSql = this.buildIndex({ indexes: joinMeta.ignoreIndex, indexFor: 'IGNORE' }); joinsArray.push( sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ tableSchema ? sql`${sql.identifier(tableSchema)}.` : undefined - }${sql.identifier(origTableName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, + }${sql.identifier(origTableName)}${useIndexSql}${forceIndexSql}${ignoreIndexSql}${ + alias && sql` ${sql.identifier(alias)}` + } on ${joinMeta.on}`, ); } else if (is(table, View)) { const viewName = table[ViewBaseConfig].name; @@ -316,22 +371,20 @@ export class MySqlDialect { const havingSql = having ? sql` having ${having}` : undefined; - let orderBySql; - if (orderBy && orderBy.length > 0) { - orderBySql = sql` order by ${sql.join(orderBy, sql`, `)}`; - } + const orderBySql = this.buildOrderBy(orderBy); - let groupBySql; - if (groupBy && groupBy.length > 0) { - groupBySql = sql` group by ${sql.join(groupBy, sql`, `)}`; - } + const groupBySql = groupBy && groupBy.length > 0 ? sql` group by ${sql.join(groupBy, sql`, `)}` : undefined; - const limitSql = typeof limit === 'object' || (typeof limit === 'number' && limit >= 0) - ? sql` limit ${limit}` - : undefined; + const limitSql = this.buildLimit(limit); const offsetSql = offset ? sql` offset ${offset}` : undefined; + const useIndexSql = this.buildIndex({ indexes: useIndex, indexFor: 'USE' }); + + const forceIndexSql = this.buildIndex({ indexes: forceIndex, indexFor: 'FORCE' }); + + const ignoreIndexSql = this.buildIndex({ indexes: ignoreIndex, indexFor: 'IGNORE' }); + let lockingClausesSql; if (lockingClause) { const { config, strength } = lockingClause; @@ -344,7 +397,7 @@ export class MySqlDialect { } const finalQuery = - sql`${withSql}select${distinctSql} ${selection} from ${tableSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`; + sql`${withSql}select${distinctSql} ${selection} from ${tableSql}${useIndexSql}${forceIndexSql}${ignoreIndexSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`; if (setOperators.length > 0) { return this.buildSetOperations(finalQuery, setOperators); @@ -386,13 +439,13 @@ export class MySqlDialect { // which is invalid MySql syntax, Table from one of the SELECTs cannot be used in global ORDER clause for (const orderByUnit of orderBy) { if (is(orderByUnit, MySqlColumn)) { - orderByValues.push(sql.identifier(orderByUnit.name)); + orderByValues.push(sql.identifier(this.casing.getColumnCasing(orderByUnit))); } else if (is(orderByUnit, SQL)) { for (let i = 0; i < orderByUnit.queryChunks.length; i++) { const chunk = orderByUnit.queryChunks[i]; if (is(chunk, MySqlColumn)) { - orderByUnit.queryChunks[i] = sql.identifier(chunk.name); + orderByUnit.queryChunks[i] = sql.identifier(this.casing.getColumnCasing(chunk)); } } @@ -417,7 +470,7 @@ export class MySqlDialect { } buildInsertQuery( - { table, values, ignore, onConflict }: MySqlInsertConfig, + { table, values: valuesOrSelect, ignore, onConflict, select }: MySqlInsertConfig, ): { sql: SQL; generatedIds: Record[] } { // const isSingleValue = values.length === 1; const valuesSqlList: ((SQLChunk | SQL)[] | SQL)[] = []; @@ -426,42 +479,55 @@ export class MySqlDialect { !col.shouldDisableInsert() ); - const insertOrder = colEntries.map(([, column]) => sql.identifier(column.name)); + const insertOrder = colEntries.map(([, column]) => sql.identifier(this.casing.getColumnCasing(column))); const generatedIdsResponse: Record[] = []; - for (const [valueIndex, value] of values.entries()) { - const generatedIds: Record = {}; - - const valueList: (SQLChunk | SQL)[] = []; - for (const [fieldName, col] of colEntries) { - const colValue = value[fieldName]; - if (colValue === undefined || (is(colValue, Param) && colValue.value === undefined)) { - // eslint-disable-next-line unicorn/no-negated-condition - if (col.defaultFn !== undefined) { - const defaultFnResult = col.defaultFn(); - generatedIds[fieldName] = defaultFnResult; - const defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); - valueList.push(defaultValue); + if (select) { + const select = valuesOrSelect as AnyMySqlSelectQueryBuilder | SQL; + + if (is(select, SQL)) { + valuesSqlList.push(select); + } else { + valuesSqlList.push(select.getSQL()); + } + } else { + const values = valuesOrSelect as Record[]; + valuesSqlList.push(sql.raw('values ')); + + for (const [valueIndex, value] of values.entries()) { + const generatedIds: Record = {}; + + const valueList: (SQLChunk | SQL)[] = []; + for (const [fieldName, col] of colEntries) { + const colValue = value[fieldName]; + if (colValue === undefined || (is(colValue, Param) && colValue.value === undefined)) { // eslint-disable-next-line unicorn/no-negated-condition - } else if (!col.default && col.onUpdateFn !== undefined) { - const onUpdateFnResult = col.onUpdateFn(); - const newValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col); - valueList.push(newValue); + if (col.defaultFn !== undefined) { + const defaultFnResult = col.defaultFn(); + generatedIds[fieldName] = defaultFnResult; + const defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); + valueList.push(defaultValue); + // eslint-disable-next-line unicorn/no-negated-condition + } else if (!col.default && col.onUpdateFn !== undefined) { + const onUpdateFnResult = col.onUpdateFn(); + const newValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col); + valueList.push(newValue); + } else { + valueList.push(sql`default`); + } } else { - valueList.push(sql`default`); - } - } else { - if (col.defaultFn && is(colValue, Param)) { - generatedIds[fieldName] = colValue.value; + if (col.defaultFn && is(colValue, Param)) { + generatedIds[fieldName] = colValue.value; + } + valueList.push(colValue); } - valueList.push(colValue); } - } - generatedIdsResponse.push(generatedIds); - valuesSqlList.push(valueList); - if (valueIndex < values.length - 1) { - valuesSqlList.push(sql`, `); + generatedIdsResponse.push(generatedIds); + valuesSqlList.push(valueList); + if (valueIndex < values.length - 1) { + valuesSqlList.push(sql`, `); + } } } @@ -472,13 +538,14 @@ export class MySqlDialect { const onConflictSql = onConflict ? sql` on duplicate key ${onConflict}` : undefined; return { - sql: sql`insert${ignoreSql} into ${table} ${insertOrder} values ${valuesSql}${onConflictSql}`, + sql: sql`insert${ignoreSql} into ${table} ${insertOrder} ${valuesSql}${onConflictSql}`, generatedIds: generatedIdsResponse, }; } sqlToQuery(sql: SQL, invokeSource?: 'indexes' | undefined): QueryWithTypings { return sql.toQuery({ + casing: this.casing, escapeName: this.escapeName, escapeParam: this.escapeParam, escapeString: this.escapeString, @@ -990,7 +1057,11 @@ export class MySqlDialect { let field = sql`json_array(${ sql.join( selection.map(({ field }) => - is(field, MySqlColumn) ? sql.identifier(field.name) : is(field, SQL.Aliased) ? field.sql : field + is(field, MySqlColumn) + ? sql.identifier(this.casing.getColumnCasing(field)) + : is(field, SQL.Aliased) + ? field.sql + : field ), sql`, `, ) diff --git a/drizzle-orm/src/mysql-core/foreign-keys.ts b/drizzle-orm/src/mysql-core/foreign-keys.ts index 957e1f15c..c8c34d6fd 100644 --- a/drizzle-orm/src/mysql-core/foreign-keys.ts +++ b/drizzle-orm/src/mysql-core/foreign-keys.ts @@ -1,6 +1,7 @@ import { entityKind } from '~/entity.ts'; +import { TableName } from '~/table.utils.ts'; import type { AnyMySqlColumn, MySqlColumn } from './columns/index.ts'; -import { MySqlTable } from './table.ts'; +import type { MySqlTable } from './table.ts'; export type UpdateDeleteAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default'; @@ -80,9 +81,9 @@ export class ForeignKey { const columnNames = columns.map((column) => column.name); const foreignColumnNames = foreignColumns.map((column) => column.name); const chunks = [ - this.table[MySqlTable.Symbol.Name], + this.table[TableName], ...columnNames, - foreignColumns[0]!.table[MySqlTable.Symbol.Name], + foreignColumns[0]!.table[TableName], ...foreignColumnNames, ]; return name ?? `${chunks.join('_')}_fk`; diff --git a/drizzle-orm/src/mysql-core/query-builders/count.ts b/drizzle-orm/src/mysql-core/query-builders/count.ts new file mode 100644 index 000000000..9a0241c70 --- /dev/null +++ b/drizzle-orm/src/mysql-core/query-builders/count.ts @@ -0,0 +1,79 @@ +import { entityKind } from '~/entity.ts'; +import { SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; +import type { MySqlSession } from '../session.ts'; +import type { MySqlTable } from '../table.ts'; +import type { MySqlViewBase } from '../view-base.ts'; + +export class MySqlCountBuilder< + TSession extends MySqlSession, +> extends SQL implements Promise, SQLWrapper { + private sql: SQL; + + static override readonly [entityKind] = 'MySqlCountBuilder'; + [Symbol.toStringTag] = 'MySqlCountBuilder'; + + private session: TSession; + + private static buildEmbeddedCount( + source: MySqlTable | MySqlViewBase | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`; + } + + private static buildCount( + source: MySqlTable | MySqlViewBase | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`select count(*) as count from ${source}${sql.raw(' where ').if(filters)}${filters}`; + } + + constructor( + readonly params: { + source: MySqlTable | MySqlViewBase | SQL | SQLWrapper; + filters?: SQL; + session: TSession; + }, + ) { + super(MySqlCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks); + + this.mapWith(Number); + + this.session = params.session; + + this.sql = MySqlCountBuilder.buildCount( + params.source, + params.filters, + ); + } + + then( + onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined, + ): Promise { + return Promise.resolve(this.session.count(this.sql)) + .then( + onfulfilled, + onrejected, + ); + } + + catch( + onRejected?: ((reason: any) => never | PromiseLike) | null | undefined, + ): Promise { + return this.then(undefined, onRejected); + } + + finally(onFinally?: (() => void) | null | undefined): Promise { + return this.then( + (value) => { + onFinally?.(); + return value; + }, + (reason) => { + onFinally?.(); + throw reason; + }, + ); + } +} diff --git a/drizzle-orm/src/mysql-core/query-builders/delete.ts b/drizzle-orm/src/mysql-core/query-builders/delete.ts index e9a48da8e..22a3e1be3 100644 --- a/drizzle-orm/src/mysql-core/query-builders/delete.ts +++ b/drizzle-orm/src/mysql-core/query-builders/delete.ts @@ -11,8 +11,12 @@ import type { } from '~/mysql-core/session.ts'; import type { MySqlTable } from '~/mysql-core/table.ts'; import { QueryPromise } from '~/query-promise.ts'; -import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts'; import type { Subquery } from '~/subquery.ts'; +import { Table } from '~/table.ts'; +import type { ValueOrArray } from '~/utils.ts'; +import type { MySqlColumn } from '../columns/common.ts'; import type { SelectedFieldsOrdered } from './select.types.ts'; export type MySqlDeleteWithout< @@ -39,6 +43,8 @@ export type MySqlDelete< export interface MySqlDeleteConfig { where?: SQL | undefined; + limit?: number | Placeholder; + orderBy?: (MySqlColumn | SQL | SQL.Aliased)[]; table: MySqlTable; returning?: SelectedFieldsOrdered; withList?: Subquery[]; @@ -86,7 +92,7 @@ export class MySqlDeleteBase< // eslint-disable-next-line @typescript-eslint/no-unused-vars TExcludedMethods extends string = never, > extends QueryPromise> implements SQLWrapper { - static readonly [entityKind]: string = 'MySqlDelete'; + static override readonly [entityKind]: string = 'MySqlDelete'; private config: MySqlDeleteConfig; @@ -134,6 +140,37 @@ export class MySqlDeleteBase< return this as any; } + orderBy( + builder: (deleteTable: TTable) => ValueOrArray, + ): MySqlDeleteWithout; + orderBy(...columns: (MySqlColumn | SQL | SQL.Aliased)[]): MySqlDeleteWithout; + orderBy( + ...columns: + | [(deleteTable: TTable) => ValueOrArray] + | (MySqlColumn | SQL | SQL.Aliased)[] + ): MySqlDeleteWithout { + if (typeof columns[0] === 'function') { + const orderBy = columns[0]( + new Proxy( + this.config.table[Table.Symbol.Columns], + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as any, + ); + + const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; + this.config.orderBy = orderByArray; + } else { + const orderByArray = columns as (MySqlColumn | SQL | SQL.Aliased)[]; + this.config.orderBy = orderByArray; + } + return this as any; + } + + limit(limit: number | Placeholder): MySqlDeleteWithout { + this.config.limit = limit; + return this as any; + } + /** @internal */ getSQL(): SQL { return this.dialect.buildDeleteQuery(this.config); diff --git a/drizzle-orm/src/mysql-core/query-builders/insert.ts b/drizzle-orm/src/mysql-core/query-builders/insert.ts index 97e61de74..f943d0322 100644 --- a/drizzle-orm/src/mysql-core/query-builders/insert.ts +++ b/drizzle-orm/src/mysql-core/query-builders/insert.ts @@ -10,23 +10,26 @@ import type { PreparedQueryKind, } from '~/mysql-core/session.ts'; import type { MySqlTable } from '~/mysql-core/table.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import { QueryPromise } from '~/query-promise.ts'; import type { RunnableQuery } from '~/runnable-query.ts'; import type { Placeholder, Query, SQLWrapper } from '~/sql/sql.ts'; import { Param, SQL, sql } from '~/sql/sql.ts'; import type { InferModelFromColumns } from '~/table.ts'; -import { Table } from '~/table.ts'; -import { mapUpdateSet, orderSelectedFields } from '~/utils.ts'; -import type { AnyMySqlColumn, MySqlColumn } from '../columns/common.ts'; +import { Columns, Table } from '~/table.ts'; +import { haveSameKeys, mapUpdateSet } from '~/utils.ts'; +import type { AnyMySqlColumn } from '../columns/common.ts'; +import { QueryBuilder } from './query-builder.ts'; import type { SelectedFieldsOrdered } from './select.types.ts'; import type { MySqlUpdateSetSource } from './update.ts'; export interface MySqlInsertConfig { table: TTable; - values: Record[]; + values: Record[] | MySqlInsertSelectQueryBuilder | SQL; ignore: boolean; onConflict?: SQL; returning?: SelectedFieldsOrdered; + select?: boolean; } export type AnyMySqlInsertConfig = MySqlInsertConfig; @@ -37,6 +40,10 @@ export type MySqlInsertValue = } & {}; +export type MySqlInsertSelectQueryBuilder = TypedQueryBuilder< + { [K in keyof TTable['$inferInsert']]: AnyMySqlColumn | SQL | SQL.Aliased | TTable['$inferInsert'][K] } +>; + export class MySqlInsertBuilder< TTable extends MySqlTable, TQueryResult extends MySqlQueryResultHKT, @@ -78,6 +85,32 @@ export class MySqlInsertBuilder< return new MySqlInsertBase(this.table, mappedValues, this.shouldIgnore, this.session, this.dialect); } + + select( + selectQuery: (qb: QueryBuilder) => MySqlInsertSelectQueryBuilder, + ): MySqlInsertBase; + select(selectQuery: (qb: QueryBuilder) => SQL): MySqlInsertBase; + select(selectQuery: SQL): MySqlInsertBase; + select(selectQuery: MySqlInsertSelectQueryBuilder): MySqlInsertBase; + select( + selectQuery: + | SQL + | MySqlInsertSelectQueryBuilder + | ((qb: QueryBuilder) => MySqlInsertSelectQueryBuilder | SQL), + ): MySqlInsertBase { + const select = typeof selectQuery === 'function' ? selectQuery(new QueryBuilder()) : selectQuery; + + if ( + !is(select, SQL) + && !haveSameKeys(this.table[Columns], select._.selectedFields) + ) { + throw new Error( + 'Insert select error: selected fields are not the same or are in a different order compared to the table definition', + ); + } + + return new MySqlInsertBase(this.table, select, this.shouldIgnore, this.session, this.dialect, true); + } } export type MySqlInsertWithout = @@ -190,7 +223,7 @@ export class MySqlInsertBase< RunnableQuery : TReturning[], 'mysql'>, SQLWrapper { - static readonly [entityKind]: string = 'MySqlInsert'; + static override readonly [entityKind]: string = 'MySqlInsert'; declare protected $table: TTable; @@ -202,15 +235,16 @@ export class MySqlInsertBase< ignore: boolean, private session: MySqlSession, private dialect: MySqlDialect, + select?: boolean, ) { super(); - this.config = { table, values, ignore }; + this.config = { table, values: values as any, select, ignore }; } /** * Adds an `on duplicate key update` clause to the query. * - * Calling this method will update update the row if any unique index conflicts. MySQL will automatically determine the conflict target based on the primary key and unique indexes. + * Calling this method will update the row if any unique index conflicts. MySQL will automatically determine the conflict target based on the primary key and unique indexes. * * See docs: {@link https://orm.drizzle.team/docs/insert#on-duplicate-key-update} * @@ -252,7 +286,7 @@ export class MySqlInsertBase< returning.push({ field: value, path: [key] }); } } - this.config.returning = orderSelectedFields(this.config.table[Table.Symbol.Columns]); + this.config.returning = returning; return this as any; } diff --git a/drizzle-orm/src/mysql-core/query-builders/query-builder.ts b/drizzle-orm/src/mysql-core/query-builders/query-builder.ts index f5d9cacc4..95b3d6cdd 100644 --- a/drizzle-orm/src/mysql-core/query-builders/query-builder.ts +++ b/drizzle-orm/src/mysql-core/query-builders/query-builder.ts @@ -1,4 +1,5 @@ -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; +import type { MySqlDialectConfig } from '~/mysql-core/dialect.ts'; import { MySqlDialect } from '~/mysql-core/dialect.ts'; import type { WithSubqueryWithSelection } from '~/mysql-core/subquery.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; @@ -12,6 +13,12 @@ export class QueryBuilder { static readonly [entityKind]: string = 'MySqlQueryBuilder'; private dialect: MySqlDialect | undefined; + private dialectConfig: MySqlDialectConfig | undefined; + + constructor(dialect?: MySqlDialect | MySqlDialectConfig) { + this.dialect = is(dialect, MySqlDialect) ? dialect : undefined; + this.dialectConfig = is(dialect, MySqlDialect) ? undefined : dialect; + } $with(alias: TAlias) { const queryBuilder = this; @@ -95,7 +102,7 @@ export class QueryBuilder { // Lazy load dialect to avoid circular dependency private getDialect() { if (!this.dialect) { - this.dialect = new MySqlDialect(); + this.dialect = new MySqlDialect(this.dialectConfig); } return this.dialect; diff --git a/drizzle-orm/src/mysql-core/query-builders/query.ts b/drizzle-orm/src/mysql-core/query-builders/query.ts index 955f73428..16d294598 100644 --- a/drizzle-orm/src/mysql-core/query-builders/query.ts +++ b/drizzle-orm/src/mysql-core/query-builders/query.ts @@ -77,7 +77,7 @@ export class MySqlRelationalQuery< TPreparedQueryHKT extends PreparedQueryHKTBase, TResult, > extends QueryPromise { - static readonly [entityKind]: string = 'MySqlRelationalQuery'; + static override readonly [entityKind]: string = 'MySqlRelationalQuery'; declare protected $brand: 'MySqlRelationalQuery'; diff --git a/drizzle-orm/src/mysql-core/query-builders/select.ts b/drizzle-orm/src/mysql-core/query-builders/select.ts index a5a0ca69a..821199ab4 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.ts @@ -3,7 +3,7 @@ import type { MySqlColumn } from '~/mysql-core/columns/index.ts'; import type { MySqlDialect } from '~/mysql-core/dialect.ts'; import type { MySqlPreparedQueryConfig, MySqlSession, PreparedQueryHKTBase } from '~/mysql-core/session.ts'; import type { SubqueryWithSelection } from '~/mysql-core/subquery.ts'; -import type { MySqlTable } from '~/mysql-core/table.ts'; +import { MySqlTable } from '~/mysql-core/table.ts'; import { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { BuildSubquerySelection, @@ -17,13 +17,15 @@ import type { } from '~/query-builders/select.types.ts'; import { QueryPromise } from '~/query-promise.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; -import type { ColumnsSelection, Query } from '~/sql/sql.ts'; +import type { ColumnsSelection, Placeholder, Query } from '~/sql/sql.ts'; import { SQL, View } from '~/sql/sql.ts'; import { Subquery } from '~/subquery.ts'; import { Table } from '~/table.ts'; -import { applyMixins, getTableColumns, getTableLikeName, haveSameKeys, type ValueOrArray } from '~/utils.ts'; -import { orderSelectedFields } from '~/utils.ts'; +import type { ValueOrArray } from '~/utils.ts'; +import { applyMixins, getTableColumns, getTableLikeName, haveSameKeys, orderSelectedFields } from '~/utils.ts'; import { ViewBaseConfig } from '~/view-common.ts'; +import type { IndexBuilder } from '../indexes.ts'; +import { convertIndexToString, toArray } from '../utils.ts'; import { MySqlViewBase } from '../view-base.ts'; import type { AnyMySqlSelect, @@ -45,6 +47,14 @@ import type { SetOperatorRightSelect, } from './select.types.ts'; +export type IndexForHint = IndexBuilder | string; + +export type IndexConfig = { + useIndex?: IndexForHint | IndexForHint[]; + forceIndex?: IndexForHint | IndexForHint[]; + ignoreIndex?: IndexForHint | IndexForHint[]; +}; + export class MySqlSelectBuilder< TSelection extends SelectedFields | undefined, TPreparedQueryHKT extends PreparedQueryHKTBase, @@ -78,6 +88,8 @@ export class MySqlSelectBuilder< from( source: TFrom, + onIndex?: TFrom extends MySqlTable ? IndexConfig + : 'Index hint configuration is allowed only for MySqlTable and not for subqueries or views', ): CreateMySqlSelectFromBuilderMode< TBuilderMode, GetSelectTableName, @@ -105,6 +117,21 @@ export class MySqlSelectBuilder< fields = getTableColumns(source); } + let useIndex: string[] = []; + let forceIndex: string[] = []; + let ignoreIndex: string[] = []; + if (is(source, MySqlTable) && onIndex && typeof onIndex !== 'string') { + if (onIndex.useIndex) { + useIndex = convertIndexToString(toArray(onIndex.useIndex)); + } + if (onIndex.forceIndex) { + forceIndex = convertIndexToString(toArray(onIndex.forceIndex)); + } + if (onIndex.ignoreIndex) { + ignoreIndex = convertIndexToString(toArray(onIndex.ignoreIndex)); + } + } + return new MySqlSelectBase( { table: source, @@ -114,6 +141,9 @@ export class MySqlSelectBuilder< dialect: this.dialect, withList: this.withList, distinct: this.distinct, + useIndex, + forceIndex, + ignoreIndex, }, ) as any; } @@ -132,7 +162,7 @@ export abstract class MySqlSelectQueryBuilderBase< TResult extends any[] = SelectResult[], TSelectedFields extends ColumnsSelection = BuildSubquerySelection, > extends TypedQueryBuilder { - static readonly [entityKind]: string = 'MySqlSelectQueryBuilder'; + static override readonly [entityKind]: string = 'MySqlSelectQueryBuilder'; override readonly _: { readonly hkt: THKT; @@ -156,7 +186,7 @@ export abstract class MySqlSelectQueryBuilderBase< protected dialect: MySqlDialect; constructor( - { table, fields, isPartialSelect, session, dialect, withList, distinct }: { + { table, fields, isPartialSelect, session, dialect, withList, distinct, useIndex, forceIndex, ignoreIndex }: { table: MySqlSelectConfig['table']; fields: MySqlSelectConfig['fields']; isPartialSelect: boolean; @@ -164,6 +194,9 @@ export abstract class MySqlSelectQueryBuilderBase< dialect: MySqlDialect; withList: Subquery[]; distinct: boolean | undefined; + useIndex?: string[]; + forceIndex?: string[]; + ignoreIndex?: string[]; }, ) { super(); @@ -173,6 +206,9 @@ export abstract class MySqlSelectQueryBuilderBase< fields: { ...fields }, distinct, setOperators: [], + useIndex, + forceIndex, + ignoreIndex, }; this.isPartialSelect = isPartialSelect; this.session = session; @@ -187,9 +223,13 @@ export abstract class MySqlSelectQueryBuilderBase< private createJoin( joinType: TJoinType, ): MySqlJoinFn { - return ( + return < + TJoinedTable extends MySqlTable | Subquery | MySqlViewBase | SQL, + >( table: MySqlTable | Subquery | MySqlViewBase | SQL, on: ((aliases: TSelection) => SQL | undefined) | SQL | undefined, + onIndex?: TJoinedTable extends MySqlTable ? IndexConfig + : 'Index hint configuration is allowed only for MySqlTable and not for subqueries or views', ) => { const baseTableName = this.tableName; const tableName = getTableLikeName(table); @@ -228,7 +268,22 @@ export abstract class MySqlSelectQueryBuilderBase< this.config.joins = []; } - this.config.joins.push({ on, table, joinType, alias: tableName }); + let useIndex: string[] = []; + let forceIndex: string[] = []; + let ignoreIndex: string[] = []; + if (is(table, MySqlTable) && onIndex && typeof onIndex !== 'string') { + if (onIndex.useIndex) { + useIndex = convertIndexToString(toArray(onIndex.useIndex)); + } + if (onIndex.forceIndex) { + forceIndex = convertIndexToString(toArray(onIndex.forceIndex)); + } + if (onIndex.ignoreIndex) { + ignoreIndex = convertIndexToString(toArray(onIndex.ignoreIndex)); + } + } + + this.config.joins.push({ on, table, joinType, alias: tableName, useIndex, forceIndex, ignoreIndex }); if (typeof tableName === 'string') { switch (joinType) { @@ -286,6 +341,16 @@ export abstract class MySqlSelectQueryBuilderBase< * }) * .from(users) * .leftJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId with use index hint + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId), { + * useIndex: ['pets_owner_id_index'] + * }) * ``` */ leftJoin = this.createJoin('left'); @@ -315,6 +380,16 @@ export abstract class MySqlSelectQueryBuilderBase< * }) * .from(users) * .rightJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId with use index hint + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId), { + * useIndex: ['pets_owner_id_index'] + * }) * ``` */ rightJoin = this.createJoin('right'); @@ -344,6 +419,16 @@ export abstract class MySqlSelectQueryBuilderBase< * }) * .from(users) * .innerJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId with use index hint + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId), { + * useIndex: ['pets_owner_id_index'] + * }) * ``` */ innerJoin = this.createJoin('inner'); @@ -373,6 +458,16 @@ export abstract class MySqlSelectQueryBuilderBase< * }) * .from(users) * .fullJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId with use index hint + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId), { + * useIndex: ['pets_owner_id_index'] + * }) * ``` */ fullJoin = this.createJoin('full'); @@ -811,7 +906,7 @@ export abstract class MySqlSelectQueryBuilderBase< * await db.select().from(people).limit(10); * ``` */ - limit(limit: number): MySqlSelectWithout { + limit(limit: number | Placeholder): MySqlSelectWithout { if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1)!.limit = limit; } else { @@ -836,7 +931,7 @@ export abstract class MySqlSelectQueryBuilderBase< * await db.select().from(people).offset(10).limit(10); * ``` */ - offset(offset: number): MySqlSelectWithout { + offset(offset: number | Placeholder): MySqlSelectWithout { if (this.config.setOperators.length > 0) { this.config.setOperators.at(-1)!.offset = offset; } else { @@ -942,7 +1037,7 @@ export class MySqlSelectBase< TResult, TSelectedFields > { - static readonly [entityKind]: string = 'MySqlSelect'; + static override readonly [entityKind]: string = 'MySqlSelect'; prepare(): MySqlSelectPrepare { if (!this.session) { diff --git a/drizzle-orm/src/mysql-core/query-builders/select.types.ts b/drizzle-orm/src/mysql-core/query-builders/select.types.ts index 5f490a2d9..78b6f91a6 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.types.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.types.ts @@ -25,7 +25,7 @@ import type { Assume, ValidateShape } from '~/utils.ts'; import type { MySqlPreparedQueryConfig, PreparedQueryHKTBase, PreparedQueryKind } from '../session.ts'; import type { MySqlViewBase } from '../view-base.ts'; import type { MySqlViewWithSelection } from '../view.ts'; -import type { MySqlSelectBase, MySqlSelectQueryBuilderBase } from './select.ts'; +import type { IndexConfig, MySqlSelectBase, MySqlSelectQueryBuilderBase } from './select.ts'; export interface MySqlSelectJoinConfig { on: SQL | undefined; @@ -33,6 +33,9 @@ export interface MySqlSelectJoinConfig { alias: string | undefined; joinType: JoinType; lateral?: boolean; + useIndex?: string[]; + forceIndex?: string[]; + ignoreIndex?: string[]; } export type BuildAliasTable = TTable extends Table @@ -74,6 +77,9 @@ export interface MySqlSelectConfig { limit?: number | Placeholder; offset?: number | Placeholder; }[]; + useIndex?: string[]; + forceIndex?: string[]; + ignoreIndex?: string[]; } export type MySqlJoin< @@ -116,6 +122,8 @@ export type MySqlJoinFn< >( table: TJoinedTable, on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, + onIndex?: TJoinedTable extends MySqlTable ? IndexConfig + : 'Index hint configuration is allowed only for MySqlTable and not for subqueries or views', ) => MySqlJoin; export type SelectedFieldsFlat = SelectedFieldsFlatBase; diff --git a/drizzle-orm/src/mysql-core/query-builders/update.ts b/drizzle-orm/src/mysql-core/query-builders/update.ts index 7884599cf..7c6fd40ab 100644 --- a/drizzle-orm/src/mysql-core/query-builders/update.ts +++ b/drizzle-orm/src/mysql-core/query-builders/update.ts @@ -12,13 +12,18 @@ import type { } from '~/mysql-core/session.ts'; import type { MySqlTable } from '~/mysql-core/table.ts'; import { QueryPromise } from '~/query-promise.ts'; -import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts'; import type { Subquery } from '~/subquery.ts'; -import { mapUpdateSet, type UpdateSet } from '~/utils.ts'; +import { Table } from '~/table.ts'; +import { mapUpdateSet, type UpdateSet, type ValueOrArray } from '~/utils.ts'; +import type { MySqlColumn } from '../columns/common.ts'; import type { SelectedFieldsOrdered } from './select.types.ts'; export interface MySqlUpdateConfig { where?: SQL | undefined; + limit?: number | Placeholder; + orderBy?: (MySqlColumn | SQL | SQL.Aliased)[]; set: UpdateSet; table: MySqlTable; returning?: SelectedFieldsOrdered; @@ -29,7 +34,8 @@ export type MySqlUpdateSetSource = & { [Key in keyof TTable['$inferInsert']]?: | GetColumnData - | SQL; + | SQL + | undefined; } & {}; @@ -120,7 +126,7 @@ export class MySqlUpdateBase< // eslint-disable-next-line @typescript-eslint/no-unused-vars TExcludedMethods extends string = never, > extends QueryPromise> implements SQLWrapper { - static readonly [entityKind]: string = 'MySqlUpdate'; + static override readonly [entityKind]: string = 'MySqlUpdate'; private config: MySqlUpdateConfig; @@ -173,6 +179,37 @@ export class MySqlUpdateBase< return this as any; } + orderBy( + builder: (updateTable: TTable) => ValueOrArray, + ): MySqlUpdateWithout; + orderBy(...columns: (MySqlColumn | SQL | SQL.Aliased)[]): MySqlUpdateWithout; + orderBy( + ...columns: + | [(updateTable: TTable) => ValueOrArray] + | (MySqlColumn | SQL | SQL.Aliased)[] + ): MySqlUpdateWithout { + if (typeof columns[0] === 'function') { + const orderBy = columns[0]( + new Proxy( + this.config.table[Table.Symbol.Columns], + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as any, + ); + + const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; + this.config.orderBy = orderByArray; + } else { + const orderByArray = columns as (MySqlColumn | SQL | SQL.Aliased)[]; + this.config.orderBy = orderByArray; + } + return this as any; + } + + limit(limit: number | Placeholder): MySqlUpdateWithout { + this.config.limit = limit; + return this as any; + } + /** @internal */ getSQL(): SQL { return this.dialect.buildUpdateQuery(this.config); diff --git a/drizzle-orm/src/mysql-core/session.ts b/drizzle-orm/src/mysql-core/session.ts index 08ad1ebb6..326b0ad61 100644 --- a/drizzle-orm/src/mysql-core/session.ts +++ b/drizzle-orm/src/mysql-core/session.ts @@ -86,6 +86,14 @@ export abstract class MySqlSession< abstract all(query: SQL): Promise; + async count(sql: SQL): Promise { + const res = await this.execute<[[{ count: string }]]>(sql); + + return Number( + res[0][0]['count'], + ); + } + abstract transaction( transaction: (tx: MySqlTransaction) => Promise, config?: MySqlTransactionConfig, @@ -98,7 +106,7 @@ export abstract class MySqlSession< parts.push(`isolation level ${config.isolationLevel}`); } - return parts.length ? sql.join(['set transaction ', parts.join(' ')]) : undefined; + return parts.length ? sql`set transaction ${sql.raw(parts.join(' '))}` : undefined; } protected getStartTransactionSQL(config: MySqlTransactionConfig): SQL | undefined { @@ -112,7 +120,7 @@ export abstract class MySqlSession< parts.push(config.accessMode); } - return parts.length ? sql.join(['start transaction ', parts.join(' ')]) : undefined; + return parts.length ? sql`start transaction ${sql.raw(parts.join(' '))}` : undefined; } } @@ -122,7 +130,7 @@ export abstract class MySqlTransaction< TFullSchema extends Record = Record, TSchema extends TablesRelationalConfig = Record, > extends MySqlDatabase { - static readonly [entityKind]: string = 'MySqlTransaction'; + static override readonly [entityKind]: string = 'MySqlTransaction'; constructor( dialect: MySqlDialect, diff --git a/drizzle-orm/src/mysql-core/table.ts b/drizzle-orm/src/mysql-core/table.ts index 3b1d4c3a3..2616e7159 100644 --- a/drizzle-orm/src/mysql-core/table.ts +++ b/drizzle-orm/src/mysql-core/table.ts @@ -2,19 +2,23 @@ import type { BuildColumns, BuildExtraConfigColumns } from '~/column-builder.ts' import { entityKind } from '~/entity.ts'; import { Table, type TableConfig as TableConfigBase, type UpdateTableConfig } from '~/table.ts'; import type { CheckBuilder } from './checks.ts'; +import { getMySqlColumnBuilders, type MySqlColumnBuilders } from './columns/all.ts'; import type { MySqlColumn, MySqlColumnBuilder, MySqlColumnBuilderBase } from './columns/common.ts'; import type { ForeignKey, ForeignKeyBuilder } from './foreign-keys.ts'; import type { AnyIndexBuilder } from './indexes.ts'; import type { PrimaryKeyBuilder } from './primary-keys.ts'; import type { UniqueConstraintBuilder } from './unique-constraint.ts'; -export type MySqlTableExtraConfig = Record< - string, +export type MySqlTableExtraConfigValue = | AnyIndexBuilder | CheckBuilder | ForeignKeyBuilder | PrimaryKeyBuilder - | UniqueConstraintBuilder + | UniqueConstraintBuilder; + +export type MySqlTableExtraConfig = Record< + string, + MySqlTableExtraConfigValue >; export type TableConfig = TableConfigBase; @@ -23,7 +27,7 @@ export type TableConfig = TableConfigBase; export const InlineForeignKeys = Symbol.for('drizzle:MySqlInlineForeignKeys'); export class MySqlTable extends Table { - static readonly [entityKind]: string = 'MySqlTable'; + static override readonly [entityKind]: string = 'MySqlTable'; declare protected $columns: T['columns']; @@ -60,8 +64,12 @@ export function mysqlTableWithSchema< TColumnsMap extends Record, >( name: TTableName, - columns: TColumnsMap, - extraConfig: ((self: BuildColumns) => MySqlTableExtraConfig) | undefined, + columns: TColumnsMap | ((columnTypes: MySqlColumnBuilders) => TColumnsMap), + extraConfig: + | (( + self: BuildColumns, + ) => MySqlTableExtraConfig | MySqlTableExtraConfigValue[]) + | undefined, schema: TSchemaName, baseName = name, ): MySqlTableWithColumns<{ @@ -77,9 +85,12 @@ export function mysqlTableWithSchema< dialect: 'mysql'; }>(name, schema, baseName); + const parsedColumns: TColumnsMap = typeof columns === 'function' ? columns(getMySqlColumnBuilders()) : columns; + const builtColumns = Object.fromEntries( - Object.entries(columns).map(([name, colBuilderBase]) => { + Object.entries(parsedColumns).map(([name, colBuilderBase]) => { const colBuilder = colBuilderBase as MySqlColumnBuilder; + colBuilder.setName(name); const column = colBuilder.build(rawTable); rawTable[InlineForeignKeys].push(...colBuilder.buildForeignKeys(column, rawTable)); return [name, column]; @@ -111,7 +122,94 @@ export interface MySqlTableFn( name: TTableName, columns: TColumnsMap, - extraConfig?: (self: BuildColumns) => MySqlTableExtraConfig, + extraConfig?: ( + self: BuildColumns, + ) => MySqlTableExtraConfigValue[], + ): MySqlTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'mysql'; + }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: MySqlColumnBuilders) => TColumnsMap, + extraConfig?: (self: BuildColumns) => MySqlTableExtraConfigValue[], + ): MySqlTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'mysql'; + }>; + /** + * @deprecated The third parameter of mysqlTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = mysqlTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = mysqlTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig: (self: BuildColumns) => MySqlTableExtraConfig, + ): MySqlTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'mysql'; + }>; + + /** + * @deprecated The third parameter of mysqlTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = mysqlTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = mysqlTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: MySqlColumnBuilders) => TColumnsMap, + extraConfig: (self: BuildColumns) => MySqlTableExtraConfig, ): MySqlTableWithColumns<{ name: TTableName; schema: TSchemaName; diff --git a/drizzle-orm/src/mysql-core/unique-constraint.ts b/drizzle-orm/src/mysql-core/unique-constraint.ts index 66ed65198..01a3c36c2 100644 --- a/drizzle-orm/src/mysql-core/unique-constraint.ts +++ b/drizzle-orm/src/mysql-core/unique-constraint.ts @@ -1,13 +1,14 @@ import { entityKind } from '~/entity.ts'; +import { TableName } from '~/table.utils.ts'; import type { MySqlColumn } from './columns/index.ts'; -import { MySqlTable } from './table.ts'; +import type { MySqlTable } from './table.ts'; export function unique(name?: string): UniqueOnConstraintBuilder { return new UniqueOnConstraintBuilder(name); } export function uniqueKeyName(table: MySqlTable, columns: string[]) { - return `${table[MySqlTable.Symbol.Name]}_${columns.join('_')}_unique`; + return `${table[TableName]}_${columns.join('_')}_unique`; } export class UniqueConstraintBuilder { diff --git a/drizzle-orm/src/mysql-core/utils.ts b/drizzle-orm/src/mysql-core/utils.ts index f09f65f3e..b49dd0043 100644 --- a/drizzle-orm/src/mysql-core/utils.ts +++ b/drizzle-orm/src/mysql-core/utils.ts @@ -9,6 +9,7 @@ import type { Index } from './indexes.ts'; import { IndexBuilder } from './indexes.ts'; import type { PrimaryKey } from './primary-keys.ts'; import { PrimaryKeyBuilder } from './primary-keys.ts'; +import type { IndexForHint } from './query-builders/select.ts'; import { MySqlTable } from './table.ts'; import { type UniqueConstraint, UniqueConstraintBuilder } from './unique-constraint.ts'; import { MySqlViewConfig } from './view-common.ts'; @@ -29,7 +30,8 @@ export function getTableConfig(table: MySqlTable) { if (extraConfigBuilder !== undefined) { const extraConfig = extraConfigBuilder(table[MySqlTable.Symbol.Columns]); - for (const builder of Object.values(extraConfig)) { + const extraValues = Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); + for (const builder of Object.values(extraValues)) { if (is(builder, IndexBuilder)) { indexes.push(builder.build(table)); } else if (is(builder, CheckBuilder)) { @@ -66,3 +68,13 @@ export function getViewConfig< ...view[MySqlViewConfig], }; } + +export function convertIndexToString(indexes: IndexForHint[]) { + return indexes.map((idx) => { + return typeof idx === 'object' ? idx.config.name : idx; + }); +} + +export function toArray(value: T | T[]): T[] { + return Array.isArray(value) ? value : [value]; +} diff --git a/drizzle-orm/src/mysql-core/view-base.ts b/drizzle-orm/src/mysql-core/view-base.ts index 46b1527d9..fa8a25cfa 100644 --- a/drizzle-orm/src/mysql-core/view-base.ts +++ b/drizzle-orm/src/mysql-core/view-base.ts @@ -7,7 +7,7 @@ export abstract class MySqlViewBase< TExisting extends boolean = boolean, TSelectedFields extends ColumnsSelection = ColumnsSelection, > extends View { - static readonly [entityKind]: string = 'MySqlViewBase'; + static override readonly [entityKind]: string = 'MySqlViewBase'; declare readonly _: View['_'] & { readonly viewBrand: 'MySqlViewBase'; diff --git a/drizzle-orm/src/mysql-core/view.ts b/drizzle-orm/src/mysql-core/view.ts index 4cc7d416c..42d9b3af6 100644 --- a/drizzle-orm/src/mysql-core/view.ts +++ b/drizzle-orm/src/mysql-core/view.ts @@ -7,14 +7,12 @@ import type { ColumnsSelection, SQL } from '~/sql/sql.ts'; import { getTableColumns } from '~/utils.ts'; import type { MySqlColumn, MySqlColumnBuilderBase } from './columns/index.ts'; import { QueryBuilder } from './query-builders/query-builder.ts'; -import type { SelectedFields } from './query-builders/select.types.ts'; import { mysqlTable } from './table.ts'; import { MySqlViewBase } from './view-base.ts'; import { MySqlViewConfig } from './view-common.ts'; export interface ViewBuilderConfig { algorithm?: 'undefined' | 'merge' | 'temptable'; - definer?: string; sqlSecurity?: 'definer' | 'invoker'; withCheckOption?: 'cascaded' | 'local'; } @@ -41,13 +39,6 @@ export class ViewBuilderCore, - ): this { - this.config.definer = definer; - return this; - } - sqlSecurity( sqlSecurity: Exclude, ): this { @@ -64,9 +55,9 @@ export class ViewBuilderCore extends ViewBuilderCore<{ name: TName }> { - static readonly [entityKind]: string = 'MySqlViewBuilder'; + static override readonly [entityKind]: string = 'MySqlViewBuilder'; - as( + as( qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), ): MySqlViewWithSelection> { if (typeof qb === 'function') { @@ -98,7 +89,7 @@ export class ManualViewBuilder< TName extends string = string, TColumns extends Record = Record, > extends ViewBuilderCore<{ name: TName; columns: TColumns }> { - static readonly [entityKind]: string = 'MySqlManualViewBuilder'; + static override readonly [entityKind]: string = 'MySqlManualViewBuilder'; private columns: Record; @@ -157,7 +148,7 @@ export class MySqlView< TExisting extends boolean = boolean, TSelectedFields extends ColumnsSelection = ColumnsSelection, > extends MySqlViewBase { - static readonly [entityKind]: string = 'MySqlView'; + static override readonly [entityKind]: string = 'MySqlView'; declare protected $MySqlViewBrand: 'MySqlView'; @@ -168,7 +159,7 @@ export class MySqlView< config: { name: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: ColumnsSelection; query: SQL | undefined; }; }) { diff --git a/drizzle-orm/src/mysql-proxy/driver.ts b/drizzle-orm/src/mysql-proxy/driver.ts index 574db42c1..bb0c21134 100644 --- a/drizzle-orm/src/mysql-proxy/driver.ts +++ b/drizzle-orm/src/mysql-proxy/driver.ts @@ -1,3 +1,4 @@ +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { MySqlDatabase } from '~/mysql-core/db.ts'; import { MySqlDialect } from '~/mysql-core/dialect.ts'; @@ -10,9 +11,11 @@ import { import type { DrizzleConfig } from '~/utils.ts'; import { type MySqlRemotePreparedQueryHKT, type MySqlRemoteQueryResultHKT, MySqlRemoteSession } from './session.ts'; -export type MySqlRemoteDatabase< +export class MySqlRemoteDatabase< TSchema extends Record = Record, -> = MySqlDatabase; +> extends MySqlDatabase { + static override readonly [entityKind]: string = 'MySqlRemoteDatabase'; +} export type RemoteCallback = ( sql: string, @@ -24,7 +27,7 @@ export function drizzle = Record = {}, ): MySqlRemoteDatabase { - const dialect = new MySqlDialect(); + const dialect = new MySqlDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -46,5 +49,5 @@ export function drizzle = Record; + return new MySqlRemoteDatabase(dialect, session, schema as any, 'default') as MySqlRemoteDatabase; } diff --git a/drizzle-orm/src/mysql-proxy/session.ts b/drizzle-orm/src/mysql-proxy/session.ts index 03039cfb2..e72875e79 100644 --- a/drizzle-orm/src/mysql-proxy/session.ts +++ b/drizzle-orm/src/mysql-proxy/session.ts @@ -30,7 +30,7 @@ export class MySqlRemoteSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends MySqlSession { - static readonly [entityKind]: string = 'MySqlRemoteSession'; + static override readonly [entityKind]: string = 'MySqlRemoteSession'; private logger: Logger; @@ -81,7 +81,7 @@ export class MySqlProxyTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends MySqlTransaction { - static readonly [entityKind]: string = 'MySqlProxyTransaction'; + static override readonly [entityKind]: string = 'MySqlProxyTransaction'; override async transaction( _transaction: (tx: MySqlProxyTransaction) => Promise, @@ -91,7 +91,7 @@ export class MySqlProxyTransaction< } export class PreparedQuery extends PreparedQueryBase { - static readonly [entityKind]: string = 'MySqlProxyPreparedQuery'; + static override readonly [entityKind]: string = 'MySqlProxyPreparedQuery'; constructor( private client: RemoteCallback, diff --git a/drizzle-orm/src/mysql2/driver.ts b/drizzle-orm/src/mysql2/driver.ts index 3b21bf11d..381b4c9bb 100644 --- a/drizzle-orm/src/mysql2/driver.ts +++ b/drizzle-orm/src/mysql2/driver.ts @@ -1,4 +1,5 @@ -import type { Connection as CallbackConnection, Pool as CallbackPool } from 'mysql2'; +import { type Connection as CallbackConnection, createPool, type Pool as CallbackPool, type PoolOptions } from 'mysql2'; +import type { Connection, Pool } from 'mysql2/promise'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; @@ -11,8 +12,8 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; -import { DrizzleError } from '../index.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; +import { DrizzleError } from '../errors.ts'; import type { MySql2Client, MySql2PreparedQueryHKT, MySql2QueryResultHKT } from './session.ts'; import { MySql2Session } from './session.ts'; @@ -40,28 +41,34 @@ export class MySql2Driver { export { MySqlDatabase } from '~/mysql-core/db.ts'; -export type MySql2Database< +export class MySql2Database< TSchema extends Record = Record, -> = MySqlDatabase; +> extends MySqlDatabase { + static override readonly [entityKind]: string = 'MySql2Database'; +} export type MySql2DrizzleConfig = Record> = & Omit, 'schema'> & ({ schema: TSchema; mode: Mode } | { schema?: undefined; mode?: Mode }); -export function drizzle = Record>( - client: MySql2Client | CallbackConnection | CallbackPool, +function construct< + TSchema extends Record = Record, + TClient extends Pool | Connection | CallbackPool | CallbackConnection = CallbackPool, +>( + client: TClient, config: MySql2DrizzleConfig = {}, -): MySql2Database { - const dialect = new MySqlDialect(); +): MySql2Database & { + $client: TClient; +} { + const dialect = new MySqlDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); } else if (config.logger !== false) { logger = config.logger; } - if (isCallbackClient(client)) { - client = client.promise(); - } + + const clientForInstance = isCallbackClient(client) ? client.promise() : client; let schema: RelationalSchemaConfig | undefined; if (config.schema) { @@ -85,9 +92,12 @@ export function drizzle = Record; + const db = new MySql2Database(dialect, session, schema as any, mode) as MySql2Database; + ( db).$client = client; + + return db as any; } interface CallbackClient { @@ -97,3 +107,66 @@ interface CallbackClient { function isCallbackClient(client: any): client is CallbackClient { return typeof client.promise === 'function'; } + +export type AnyMySql2Connection = Pool | Connection | CallbackPool | CallbackConnection; + +export function drizzle< + TSchema extends Record = Record, + TClient extends AnyMySql2Connection = CallbackPool, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + MySql2DrizzleConfig, + ] | [ + ( + & MySql2DrizzleConfig + & ({ + connection: string | PoolOptions; + } | { + client: TClient; + }) + ), + ] +): MySql2Database & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const connectionString = params[0]!; + const instance = createPool({ + uri: connectionString, + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: PoolOptions | string; client?: TClient } + & MySql2DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' + ? createPool({ + uri: connection, + }) + : createPool(connection!); + const db = construct(instance, drizzleConfig); + + return db as any; + } + + return construct(params[0] as TClient, params[1] as MySql2DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: MySql2DrizzleConfig, + ): MySql2Database & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/mysql2/session.ts b/drizzle-orm/src/mysql2/session.ts index ab11d1f17..7ca21c4a6 100644 --- a/drizzle-orm/src/mysql2/session.ts +++ b/drizzle-orm/src/mysql2/session.ts @@ -41,7 +41,7 @@ export type MySqlQueryResult< > = [T extends ResultSetHeader ? T : T[], FieldPacket[]]; export class MySql2PreparedQuery extends MySqlPreparedQuery { - static readonly [entityKind]: string = 'MySql2PreparedQuery'; + static override readonly [entityKind]: string = 'MySql2PreparedQuery'; private rawQuery: QueryOptions; private query: QueryOptions; @@ -190,7 +190,7 @@ export class MySql2Session< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends MySqlSession { - static readonly [entityKind]: string = 'MySql2Session'; + static override readonly [entityKind]: string = 'MySql2Session'; private logger: Logger; private mode: Mode; @@ -301,7 +301,7 @@ export class MySql2Transaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends MySqlTransaction { - static readonly [entityKind]: string = 'MySql2Transaction'; + static override readonly [entityKind]: string = 'MySql2Transaction'; override async transaction(transaction: (tx: MySql2Transaction) => Promise): Promise { const savepointName = `sp${this.nestedIndex + 1}`; diff --git a/drizzle-orm/src/neon-http/driver.ts b/drizzle-orm/src/neon-http/driver.ts index 81a66c69b..209e41963 100644 --- a/drizzle-orm/src/neon-http/driver.ts +++ b/drizzle-orm/src/neon-http/driver.ts @@ -1,5 +1,5 @@ -import type { NeonQueryFunction } from '@neondatabase/serverless'; -import { types } from '@neondatabase/serverless'; +import type { HTTPQueryOptions, HTTPTransactionOptions, NeonQueryFunction } from '@neondatabase/serverless'; +import { neon, types } from '@neondatabase/serverless'; import type { BatchItem, BatchResponse } from '~/batch.ts'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; @@ -8,7 +8,7 @@ import { PgDatabase } from '~/pg-core/db.ts'; import { PgDialect } from '~/pg-core/dialect.ts'; import { createTableRelationsHelpers, extractTablesRelationalConfig } from '~/relations.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import { type NeonHttpClient, type NeonHttpQueryResultHKT, NeonHttpSession } from './session.ts'; export interface NeonDriverOptions { @@ -16,7 +16,7 @@ export interface NeonDriverOptions { } export class NeonHttpDriver { - static readonly [entityKind]: string = 'NeonDriver'; + static readonly [entityKind]: string = 'NeonHttpDriver'; constructor( private client: NeonHttpClient, @@ -40,10 +40,66 @@ export class NeonHttpDriver { } } +function wrap( + target: T, + token: Exclude['authToken'], undefined>, + cb: (target: any, p: string | symbol, res: any) => any, + deep?: boolean, +) { + return new Proxy(target, { + get(target, p) { + const element = target[p as keyof typeof p]; + if (typeof element !== 'function' && (typeof element !== 'object' || element === null)) return element; + + if (deep) return wrap(element, token, cb); + if (p === 'query') return wrap(element, token, cb, true); + + return new Proxy(element as any, { + apply(target, thisArg, argArray) { + const res = target.call(thisArg, ...argArray); + if (typeof res === 'object' && res !== null && 'setToken' in res && typeof res.setToken === 'function') { + res.setToken(token); + } + return cb(target, p, res); + }, + }); + }, + }); +} + export class NeonHttpDatabase< TSchema extends Record = Record, > extends PgDatabase { - static readonly [entityKind]: string = 'NeonHttpDatabase'; + static override readonly [entityKind]: string = 'NeonHttpDatabase'; + + $withAuth( + token: Exclude['authToken'], undefined>, + ): Omit< + this, + Exclude< + keyof this, + | '$count' + | 'delete' + | 'select' + | 'selectDistinct' + | 'selectDistinctOn' + | 'update' + | 'insert' + | 'with' + | 'query' + | 'execute' + | 'refreshMaterializedView' + > + > { + this.authToken = token; + + return wrap(this, token, (target, p, res) => { + if (p === 'with') { + return wrap(res, token, (_, __, res) => res); + } + return res; + }); + } /** @internal */ declare readonly session: NeonHttpSession>; @@ -55,11 +111,16 @@ export class NeonHttpDatabase< } } -export function drizzle = Record>( - client: NeonQueryFunction, +function construct< + TSchema extends Record = Record, + TClient extends NeonQueryFunction = NeonQueryFunction, +>( + client: TClient, config: DrizzleConfig = {}, -): NeonHttpDatabase { - const dialect = new PgDialect(); +): NeonHttpDatabase & { + $client: TClient; +} { + const dialect = new PgDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -83,9 +144,79 @@ export function drizzle = Record> | undefined, ); + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends NeonQueryFunction = NeonQueryFunction, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | ({ connectionString: string } & HTTPTransactionOptions); + } | { + client: TClient; + }) + ), + ] +): NeonHttpDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = neon(params[0] as string); + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { + connection?: + | ({ + connectionString: string; + } & HTTPTransactionOptions) + | string; + client?: TClient; + } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig); + + if (typeof connection === 'object') { + const { connectionString, ...options } = connection; + + const instance = neon(connectionString, options); + + return construct(instance, drizzleConfig) as any; + } + + const instance = neon(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): NeonHttpDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/neon-http/migrator.ts b/drizzle-orm/src/neon-http/migrator.ts index b5c6d363a..db8784248 100644 --- a/drizzle-orm/src/neon-http/migrator.ts +++ b/drizzle-orm/src/neon-http/migrator.ts @@ -13,13 +13,11 @@ import type { NeonHttpDatabase } from './driver.ts'; */ export async function migrate>( db: NeonHttpDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); - const migrationsTable = typeof config === 'string' - ? '__drizzle_migrations' - : config.migrationsTable ?? '__drizzle_migrations'; - const migrationsSchema = typeof config === 'string' ? 'drizzle' : config.migrationsSchema ?? 'drizzle'; + const migrationsTable = config.migrationsTable ?? '__drizzle_migrations'; + const migrationsSchema = config.migrationsSchema ?? 'drizzle'; const migrationTableCreate = sql` CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsSchema)}.${sql.identifier(migrationsTable)} ( id SERIAL PRIMARY KEY, diff --git a/drizzle-orm/src/neon-http/session.ts b/drizzle-orm/src/neon-http/session.ts index 6d7685116..00ffe26e7 100644 --- a/drizzle-orm/src/neon-http/session.ts +++ b/drizzle-orm/src/neon-http/session.ts @@ -10,8 +10,8 @@ import type { PgQueryResultHKT, PgTransactionConfig, PreparedQueryConfig } from import { PgPreparedQuery as PgPreparedQuery, PgSession } from '~/pg-core/session.ts'; import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; import type { PreparedQuery } from '~/session.ts'; -import { fillPlaceholders, type Query } from '~/sql/sql.ts'; -import { mapResultRow } from '~/utils.ts'; +import { fillPlaceholders, type Query, type SQL } from '~/sql/sql.ts'; +import { mapResultRow, type NeonAuthToken } from '~/utils.ts'; export type NeonHttpClient = NeonQueryFunction; @@ -25,7 +25,7 @@ const queryConfig = { } as const; export class NeonHttpPreparedQuery extends PgPreparedQuery { - static readonly [entityKind]: string = 'NeonHttpPreparedQuery'; + static override readonly [entityKind]: string = 'NeonHttpPreparedQuery'; constructor( private client: NeonHttpClient, @@ -38,7 +38,14 @@ export class NeonHttpPreparedQuery extends PgPrep super(query); } - async execute(placeholderValues: Record | undefined = {}): Promise { + async execute(placeholderValues: Record | undefined): Promise; + /** @internal */ + async execute(placeholderValues: Record | undefined, token?: NeonAuthToken): Promise; + /** @internal */ + async execute( + placeholderValues: Record | undefined = {}, + token: NeonAuthToken | undefined = this.authToken, + ): Promise { const params = fillPlaceholders(this.query.params, placeholderValues); this.logger.logQuery(this.query.sql, params); @@ -46,10 +53,28 @@ export class NeonHttpPreparedQuery extends PgPrep const { fields, client, query, customResultMapper } = this; if (!fields && !customResultMapper) { - return client(query.sql, params, rawQueryConfig); + return client( + query.sql, + params, + token === undefined + ? rawQueryConfig + : { + ...rawQueryConfig, + authToken: token, + }, + ); } - const result = await client(query.sql, params, queryConfig); + const result = await client( + query.sql, + params, + token === undefined + ? queryConfig + : { + ...queryConfig, + authToken: token, + }, + ); return this.mapResult(result); } @@ -71,13 +96,26 @@ export class NeonHttpPreparedQuery extends PgPrep all(placeholderValues: Record | undefined = {}): Promise { const params = fillPlaceholders(this.query.params, placeholderValues); this.logger.logQuery(this.query.sql, params); - return this.client(this.query.sql, params, rawQueryConfig).then((result) => result.rows); + return this.client( + this.query.sql, + params, + this.authToken === undefined ? rawQueryConfig : { + ...rawQueryConfig, + authToken: this.authToken, + }, + ).then((result) => result.rows); } - values(placeholderValues: Record | undefined = {}): Promise { + values(placeholderValues: Record | undefined): Promise; + /** @internal */ + values(placeholderValues: Record | undefined, token?: NeonAuthToken): Promise; + /** @internal */ + values(placeholderValues: Record | undefined = {}, token?: NeonAuthToken): Promise { const params = fillPlaceholders(this.query.params, placeholderValues); this.logger.logQuery(this.query.sql, params); - return this.client(this.query.sql, params, { arrayMode: true, fullResults: true }).then((result) => result.rows); + return this.client(this.query.sql, params, { arrayMode: true, fullResults: true, authToken: token }).then(( + result, + ) => result.rows); } /** @internal */ @@ -94,7 +132,7 @@ export class NeonHttpSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgSession { - static readonly [entityKind]: string = 'NeonHttpSession'; + static override readonly [entityKind]: string = 'NeonHttpSession'; private logger: Logger; @@ -125,7 +163,9 @@ export class NeonHttpSession< ); } - async batch, T extends Readonly<[U, ...U[]]>>(queries: T) { + async batch, T extends Readonly<[U, ...U[]]>>( + queries: T, + ) { const preparedQueries: PreparedQuery[] = []; const builtQueries: NeonQueryPromise[] = []; @@ -143,7 +183,7 @@ export class NeonHttpSession< const batchResults = await this.client.transaction(builtQueries, queryConfig); - return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true)); + return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true)) as any; } // change return type to QueryRows @@ -161,6 +201,18 @@ export class NeonHttpSession< return this.client(query, params, { arrayMode: false, fullResults: true }); } + override async count(sql: SQL): Promise; + /** @internal */ + override async count(sql: SQL, token?: NeonAuthToken): Promise; + /** @internal */ + override async count(sql: SQL, token?: NeonAuthToken): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql, token); + + return Number( + res['rows'][0]['count'], + ); + } + override async transaction( _transaction: (tx: NeonTransaction) => Promise, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -174,7 +226,7 @@ export class NeonTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgTransaction { - static readonly [entityKind]: string = 'NeonHttpTransaction'; + static override readonly [entityKind]: string = 'NeonHttpTransaction'; override async transaction(_transaction: (tx: NeonTransaction) => Promise): Promise { throw new Error('No transactions support in neon-http driver'); diff --git a/drizzle-orm/src/neon-serverless/driver.ts b/drizzle-orm/src/neon-serverless/driver.ts index 8a15dd678..35d24d568 100644 --- a/drizzle-orm/src/neon-serverless/driver.ts +++ b/drizzle-orm/src/neon-serverless/driver.ts @@ -1,4 +1,4 @@ -import { types } from '@neondatabase/serverless'; +import { neonConfig, Pool, type PoolConfig } from '@neondatabase/serverless'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; @@ -10,7 +10,7 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import type { NeonClient, NeonQueryResultHKT } from './session.ts'; import { NeonSession } from './session.ts'; @@ -26,7 +26,6 @@ export class NeonDriver { private dialect: PgDialect, private options: NeonDriverOptions = {}, ) { - this.initMappers(); } createSession( @@ -34,24 +33,24 @@ export class NeonDriver { ): NeonSession, TablesRelationalConfig> { return new NeonSession(this.client, this.dialect, schema, { logger: this.options.logger }); } - - initMappers() { - types.setTypeParser(types.builtins.TIMESTAMPTZ, (val) => val); - types.setTypeParser(types.builtins.TIMESTAMP, (val) => val); - types.setTypeParser(types.builtins.DATE, (val) => val); - types.setTypeParser(types.builtins.INTERVAL, (val) => val); - } } -export type NeonDatabase< +export class NeonDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static override readonly [entityKind]: string = 'NeonServerlessDatabase'; +} -export function drizzle = Record>( - client: NeonClient, +function construct< + TSchema extends Record = Record, + TClient extends NeonClient = NeonClient, +>( + client: TClient, config: DrizzleConfig = {}, -): NeonDatabase { - const dialect = new PgDialect(); +): NeonDatabase & { + $client: TClient; +} { + const dialect = new PgDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -74,5 +73,76 @@ export function drizzle = Record; + const db = new NeonDatabase(dialect, session, schema as any) as NeonDatabase; + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends NeonClient = Pool, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | PoolConfig; + } | { + client: TClient; + }) + & { + ws?: any; + } + ), + ] +): NeonDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = new Pool({ + connectionString: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ws, ...drizzleConfig } = params[0] as { + connection?: PoolConfig | string; + ws?: any; + client?: TClient; + } & DrizzleConfig; + + if (ws) { + neonConfig.webSocketConstructor = ws; + } + + if (client) return construct(client, drizzleConfig); + + const instance = typeof connection === 'string' + ? new Pool({ + connectionString: connection, + }) + : new Pool(connection); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): NeonDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/neon-serverless/migrator.ts b/drizzle-orm/src/neon-serverless/migrator.ts index 63b675563..9948f245c 100644 --- a/drizzle-orm/src/neon-serverless/migrator.ts +++ b/drizzle-orm/src/neon-serverless/migrator.ts @@ -4,7 +4,7 @@ import type { NeonDatabase } from './driver.ts'; export async function migrate>( db: NeonDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); await db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/neon-serverless/session.ts b/drizzle-orm/src/neon-serverless/session.ts index 82c405333..6f144e3fb 100644 --- a/drizzle-orm/src/neon-serverless/session.ts +++ b/drizzle-orm/src/neon-serverless/session.ts @@ -6,6 +6,7 @@ import { type QueryConfig, type QueryResult, type QueryResultRow, + types, } from '@neondatabase/serverless'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; @@ -16,13 +17,13 @@ import type { SelectedFieldsOrdered } from '~/pg-core/query-builders/select.type import type { PgQueryResultHKT, PgTransactionConfig, PreparedQueryConfig } from '~/pg-core/session.ts'; import { PgPreparedQuery, PgSession } from '~/pg-core/session.ts'; import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; -import { fillPlaceholders, type Query, sql } from '~/sql/sql.ts'; +import { fillPlaceholders, type Query, type SQL, sql } from '~/sql/sql.ts'; import { type Assume, mapResultRow } from '~/utils.ts'; export type NeonClient = Pool | PoolClient | Client; export class NeonPreparedQuery extends PgPreparedQuery { - static readonly [entityKind]: string = 'NeonPreparedQuery'; + static override readonly [entityKind]: string = 'NeonPreparedQuery'; private rawQueryConfig: QueryConfig; private queryConfig: QueryArrayConfig; @@ -41,11 +42,49 @@ export class NeonPreparedQuery extends PgPrepared this.rawQueryConfig = { name, text: queryString, + types: { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === types.builtins.TIMESTAMPTZ) { + return (val: any) => val; + } + if (typeId === types.builtins.TIMESTAMP) { + return (val: any) => val; + } + if (typeId === types.builtins.DATE) { + return (val: any) => val; + } + if (typeId === types.builtins.INTERVAL) { + return (val: any) => val; + } + // @ts-ignore + return types.getTypeParser(typeId, format); + }, + }, }; this.queryConfig = { name, text: queryString, rowMode: 'array', + types: { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === types.builtins.TIMESTAMPTZ) { + return (val: any) => val; + } + if (typeId === types.builtins.TIMESTAMP) { + return (val: any) => val; + } + if (typeId === types.builtins.DATE) { + return (val: any) => val; + } + if (typeId === types.builtins.INTERVAL) { + return (val: any) => val; + } + // @ts-ignore + return types.getTypeParser(typeId, format); + }, + }, }; } @@ -93,7 +132,7 @@ export class NeonSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgSession { - static readonly [entityKind]: string = 'NeonSession'; + static override readonly [entityKind]: string = 'NeonSession'; private logger: Logger; @@ -143,6 +182,14 @@ export class NeonSession< return this.client.query(query, params); } + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + + return Number( + res['rows'][0]['count'], + ); + } + override async transaction( transaction: (tx: NeonTransaction) => Promise, config: PgTransactionConfig = {}, @@ -171,7 +218,7 @@ export class NeonTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgTransaction { - static readonly [entityKind]: string = 'NeonTransaction'; + static override readonly [entityKind]: string = 'NeonTransaction'; override async transaction(transaction: (tx: NeonTransaction) => Promise): Promise { const savepointName = `sp${this.nestedIndex + 1}`; diff --git a/drizzle-orm/src/neon/index.ts b/drizzle-orm/src/neon/index.ts new file mode 100644 index 000000000..ee201ff1c --- /dev/null +++ b/drizzle-orm/src/neon/index.ts @@ -0,0 +1 @@ +export * from './rls.ts'; diff --git a/drizzle-orm/src/neon/rls.ts b/drizzle-orm/src/neon/rls.ts new file mode 100644 index 000000000..16e22f449 --- /dev/null +++ b/drizzle-orm/src/neon/rls.ts @@ -0,0 +1,93 @@ +import { is } from '~/entity.ts'; +import { type AnyPgColumn, pgPolicy, type PgPolicyToOption } from '~/pg-core/index.ts'; +import { PgRole, pgRole } from '~/pg-core/roles.ts'; +import { type SQL, sql } from '~/sql/sql.ts'; + +/** + * Generates a set of PostgreSQL row-level security (RLS) policies for CRUD operations based on the provided options. + * + * @param options - An object containing the policy configuration. + * @param options.role - The PostgreSQL role(s) to apply the policy to. Can be a single `PgRole` instance or an array of `PgRole` instances or role names. + * @param options.read - The SQL expression or boolean value that defines the read policy. Set to `true` to allow all reads, `false` to deny all reads, or provide a custom SQL expression. Set to `null` to prevent the policy from being generated. + * @param options.modify - The SQL expression or boolean value that defines the modify (insert, update, delete) policies. Set to `true` to allow all modifications, `false` to deny all modifications, or provide a custom SQL expression. Set to `null` to prevent policies from being generated. + * @returns An array of PostgreSQL policy definitions, one for each CRUD operation. + */ +export const crudPolicy = (options: { + role: PgPolicyToOption; + read: SQL | boolean | null; + modify: SQL | boolean | null; +}) => { + if (options.read === undefined) { + throw new Error('crudPolicy requires a read policy'); + } + + if (options.modify === undefined) { + throw new Error('crudPolicy requires a modify policy'); + } + + let read: SQL | undefined; + if (options.read === true) { + read = sql`true`; + } else if (options.read === false) { + read = sql`false`; + } else if (options.read !== null) { + read = options.read; + } + + let modify: SQL | undefined; + if (options.modify === true) { + modify = sql`true`; + } else if (options.modify === false) { + modify = sql`false`; + } else if (options.modify !== null) { + modify = options.modify; + } + + let rolesName = ''; + if (Array.isArray(options.role)) { + rolesName = options.role + .map((it) => { + return is(it, PgRole) ? it.name : (it as string); + }) + .join('-'); + } else { + rolesName = is(options.role, PgRole) + ? options.role.name + : (options.role as string); + } + + return [ + read + && pgPolicy(`crud-${rolesName}-policy-select`, { + for: 'select', + to: options.role, + using: read, + }), + + modify + && pgPolicy(`crud-${rolesName}-policy-insert`, { + for: 'insert', + to: options.role, + withCheck: modify, + }), + modify + && pgPolicy(`crud-${rolesName}-policy-update`, { + for: 'update', + to: options.role, + using: modify, + withCheck: modify, + }), + modify + && pgPolicy(`crud-${rolesName}-policy-delete`, { + for: 'delete', + to: options.role, + using: modify, + }), + ].filter(Boolean); +}; + +// These are default roles that Neon will set up. +export const authenticatedRole = pgRole('authenticated').existing(); +export const anonymousRole = pgRole('anonymous').existing(); + +export const authUid = (userIdColumn: AnyPgColumn) => sql`(select auth.user_id() = ${userIdColumn})`; diff --git a/drizzle-orm/src/node-postgres/driver.ts b/drizzle-orm/src/node-postgres/driver.ts index 4c233f891..e48d0e177 100644 --- a/drizzle-orm/src/node-postgres/driver.ts +++ b/drizzle-orm/src/node-postgres/driver.ts @@ -1,4 +1,4 @@ -import pg from 'pg'; +import pg, { type Pool, type PoolConfig } from 'pg'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; @@ -10,12 +10,10 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import type { NodePgClient, NodePgQueryResultHKT } from './session.ts'; import { NodePgSession } from './session.ts'; -const { types } = pg; - export interface PgDriverOptions { logger?: Logger; } @@ -28,7 +26,6 @@ export class NodePgDriver { private dialect: PgDialect, private options: PgDriverOptions = {}, ) { - this.initMappers(); } createSession( @@ -36,24 +33,24 @@ export class NodePgDriver { ): NodePgSession, TablesRelationalConfig> { return new NodePgSession(this.client, this.dialect, schema, { logger: this.options.logger }); } - - initMappers() { - types.setTypeParser(types.builtins.TIMESTAMPTZ, (val) => val); - types.setTypeParser(types.builtins.TIMESTAMP, (val) => val); - types.setTypeParser(types.builtins.DATE, (val) => val); - types.setTypeParser(types.builtins.INTERVAL, (val) => val); - } } -export type NodePgDatabase< +export class NodePgDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static override readonly [entityKind]: string = 'NodePgDatabase'; +} -export function drizzle = Record>( - client: NodePgClient, +function construct< + TSchema extends Record = Record, + TClient extends NodePgClient = NodePgClient, +>( + client: TClient, config: DrizzleConfig = {}, -): NodePgDatabase { - const dialect = new PgDialect(); +): NodePgDatabase & { + $client: TClient; +} { + const dialect = new PgDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -76,5 +73,71 @@ export function drizzle = Record; + const db = new NodePgDatabase(dialect, session, schema as any) as NodePgDatabase; + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends NodePgClient = Pool, +>( + ...params: + | [ + TClient | string, + ] + | [ + TClient | string, + DrizzleConfig, + ] + | [ + ( + & DrizzleConfig + & ({ + connection: string | PoolConfig; + } | { + client: TClient; + }) + ), + ] +): NodePgDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = new pg.Pool({ + connectionString: params[0], + }); + + return construct(instance, params[1] as DrizzleConfig | undefined) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as ( + & ({ connection?: PoolConfig | string; client?: TClient }) + & DrizzleConfig + ); + + if (client) return construct(client, drizzleConfig); + + const instance = typeof connection === 'string' + ? new pg.Pool({ + connectionString: connection, + }) + : new pg.Pool(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): NodePgDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/node-postgres/migrator.ts b/drizzle-orm/src/node-postgres/migrator.ts index fa6d508d0..933e5a486 100644 --- a/drizzle-orm/src/node-postgres/migrator.ts +++ b/drizzle-orm/src/node-postgres/migrator.ts @@ -4,7 +4,7 @@ import type { NodePgDatabase } from './driver.ts'; export async function migrate>( db: NodePgDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); await db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/node-postgres/session.ts b/drizzle-orm/src/node-postgres/session.ts index 91a21312a..a925d7018 100644 --- a/drizzle-orm/src/node-postgres/session.ts +++ b/drizzle-orm/src/node-postgres/session.ts @@ -8,16 +8,16 @@ import type { SelectedFieldsOrdered } from '~/pg-core/query-builders/select.type import type { PgQueryResultHKT, PgTransactionConfig, PreparedQueryConfig } from '~/pg-core/session.ts'; import { PgPreparedQuery, PgSession } from '~/pg-core/session.ts'; import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; -import { fillPlaceholders, type Query, sql } from '~/sql/sql.ts'; +import { fillPlaceholders, type Query, type SQL, sql } from '~/sql/sql.ts'; import { tracer } from '~/tracing.ts'; import { type Assume, mapResultRow } from '~/utils.ts'; -const { Pool } = pg; +const { Pool, types } = pg; export type NodePgClient = pg.Pool | PoolClient | Client; export class NodePgPreparedQuery extends PgPreparedQuery { - static readonly [entityKind]: string = 'NodePgPreparedQuery'; + static override readonly [entityKind]: string = 'NodePgPreparedQuery'; private rawQueryConfig: QueryConfig; private queryConfig: QueryArrayConfig; @@ -36,11 +36,49 @@ export class NodePgPreparedQuery extends PgPrepar this.rawQueryConfig = { name, text: queryString, + types: { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === types.builtins.TIMESTAMPTZ) { + return (val) => val; + } + if (typeId === types.builtins.TIMESTAMP) { + return (val) => val; + } + if (typeId === types.builtins.DATE) { + return (val) => val; + } + if (typeId === types.builtins.INTERVAL) { + return (val) => val; + } + // @ts-ignore + return types.getTypeParser(typeId, format); + }, + }, }; this.queryConfig = { name, text: queryString, rowMode: 'array', + types: { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === types.builtins.TIMESTAMPTZ) { + return (val) => val; + } + if (typeId === types.builtins.TIMESTAMP) { + return (val) => val; + } + if (typeId === types.builtins.DATE) { + return (val) => val; + } + if (typeId === types.builtins.INTERVAL) { + return (val) => val; + } + // @ts-ignore + return types.getTypeParser(typeId, format); + }, + }, }; } @@ -109,7 +147,7 @@ export class NodePgSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgSession { - static readonly [entityKind]: string = 'NodePgSession'; + static override readonly [entityKind]: string = 'NodePgSession'; private logger: Logger; @@ -164,13 +202,20 @@ export class NodePgSession< } } } + + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + return Number( + res['rows'][0]['count'], + ); + } } export class NodePgTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgTransaction { - static readonly [entityKind]: string = 'NodePgTransaction'; + static override readonly [entityKind]: string = 'NodePgTransaction'; override async transaction(transaction: (tx: NodePgTransaction) => Promise): Promise { const savepointName = `sp${this.nestedIndex + 1}`; diff --git a/drizzle-orm/src/op-sqlite/driver.ts b/drizzle-orm/src/op-sqlite/driver.ts index 24c663abf..06b9d57f4 100644 --- a/drizzle-orm/src/op-sqlite/driver.ts +++ b/drizzle-orm/src/op-sqlite/driver.ts @@ -1,4 +1,5 @@ import type { OPSQLiteConnection, QueryResult } from '@op-engineering/op-sqlite'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { createTableRelationsHelpers, @@ -11,15 +12,19 @@ import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts'; import type { DrizzleConfig } from '~/utils.ts'; import { OPSQLiteSession } from './session.ts'; -export type OPSQLiteDatabase< +export class OPSQLiteDatabase< TSchema extends Record = Record, -> = BaseSQLiteDatabase<'async', QueryResult, TSchema>; +> extends BaseSQLiteDatabase<'async', QueryResult, TSchema> { + static override readonly [entityKind]: string = 'OPSQLiteDatabase'; +} export function drizzle = Record>( client: OPSQLiteConnection, config: DrizzleConfig = {}, -): OPSQLiteDatabase { - const dialect = new SQLiteAsyncDialect(); +): OPSQLiteDatabase & { + $client: OPSQLiteConnection; +} { + const dialect = new SQLiteAsyncDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -41,5 +46,8 @@ export function drizzle = Record; + const db = new OPSQLiteDatabase('async', dialect, session, schema) as OPSQLiteDatabase; + ( db).$client = client; + + return db as any; } diff --git a/drizzle-orm/src/op-sqlite/session.ts b/drizzle-orm/src/op-sqlite/session.ts index f7a08a56b..c1ac63071 100644 --- a/drizzle-orm/src/op-sqlite/session.ts +++ b/drizzle-orm/src/op-sqlite/session.ts @@ -26,7 +26,7 @@ export class OPSQLiteSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteSession<'async', QueryResult, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'OPSQLiteSession'; + static override readonly [entityKind]: string = 'OPSQLiteSession'; private logger: Logger; @@ -79,7 +79,7 @@ export class OPSQLiteTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteTransaction<'async', QueryResult, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'OPSQLiteTransaction'; + static override readonly [entityKind]: string = 'OPSQLiteTransaction'; override transaction(transaction: (tx: OPSQLiteTransaction) => T): T { const savepointName = `sp${this.nestedIndex}`; @@ -99,7 +99,7 @@ export class OPSQLiteTransaction< export class OPSQLitePreparedQuery extends SQLitePreparedQuery< { type: 'async'; run: QueryResult; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { - static readonly [entityKind]: string = 'OPSQLitePreparedQuery'; + static override readonly [entityKind]: string = 'OPSQLitePreparedQuery'; constructor( private client: OPSQLiteConnection, diff --git a/drizzle-orm/src/operations.ts b/drizzle-orm/src/operations.ts index 492bb3f2a..5f7704fd6 100644 --- a/drizzle-orm/src/operations.ts +++ b/drizzle-orm/src/operations.ts @@ -8,19 +8,22 @@ export type RequiredKeyOnly = T extends A }> ? TKey : never; -export type NotGenerated = T extends AnyColumn<{ - generated: undefined; -}> ? TKey - : never; - export type OptionalKeyOnly< TKey extends string, T extends Column, + OverrideT extends boolean | undefined = false, > = TKey extends RequiredKeyOnly ? never - : TKey extends NotGenerated ? TKey - : T['_']['generated'] extends object ? T['_']['generated']['type'] extends 'byDefault' ? TKey : never + : T extends { + _: { + generated: undefined; + }; + } ? ( + T['_']['identity'] extends 'always' ? OverrideT extends true ? TKey : never + : TKey + ) : never; +// TODO: SQL -> SQLWrapper export type SelectedFieldsFlat = Record< string, TColumn | SQL | SQL.Aliased diff --git a/drizzle-orm/src/pg-core/columns/all.ts b/drizzle-orm/src/pg-core/columns/all.ts new file mode 100644 index 000000000..e45828067 --- /dev/null +++ b/drizzle-orm/src/pg-core/columns/all.ts @@ -0,0 +1,71 @@ +import { bigint } from './bigint.ts'; +import { bigserial } from './bigserial.ts'; +import { boolean } from './boolean.ts'; +import { char } from './char.ts'; +import { cidr } from './cidr.ts'; +import { customType } from './custom.ts'; +import { date } from './date.ts'; +import { doublePrecision } from './double-precision.ts'; +import { inet } from './inet.ts'; +import { integer } from './integer.ts'; +import { interval } from './interval.ts'; +import { json } from './json.ts'; +import { jsonb } from './jsonb.ts'; +import { line } from './line.ts'; +import { macaddr } from './macaddr.ts'; +import { macaddr8 } from './macaddr8.ts'; +import { numeric } from './numeric.ts'; +import { point } from './point.ts'; +import { geometry } from './postgis_extension/geometry.ts'; +import { real } from './real.ts'; +import { serial } from './serial.ts'; +import { smallint } from './smallint.ts'; +import { smallserial } from './smallserial.ts'; +import { text } from './text.ts'; +import { time } from './time.ts'; +import { timestamp } from './timestamp.ts'; +import { uuid } from './uuid.ts'; +import { varchar } from './varchar.ts'; +import { bit } from './vector_extension/bit.ts'; +import { halfvec } from './vector_extension/halfvec.ts'; +import { sparsevec } from './vector_extension/sparsevec.ts'; +import { vector } from './vector_extension/vector.ts'; + +export function getPgColumnBuilders() { + return { + bigint, + bigserial, + boolean, + char, + cidr, + customType, + date, + doublePrecision, + inet, + integer, + interval, + json, + jsonb, + line, + macaddr, + macaddr8, + numeric, + point, + geometry, + real, + serial, + smallint, + smallserial, + text, + time, + timestamp, + uuid, + varchar, + bit, + halfvec, + sparsevec, + vector, + }; +} + +export type PgColumnsBuilders = ReturnType; diff --git a/drizzle-orm/src/pg-core/columns/bigint.ts b/drizzle-orm/src/pg-core/columns/bigint.ts index 81f40d4e9..17ac0798b 100644 --- a/drizzle-orm/src/pg-core/columns/bigint.ts +++ b/drizzle-orm/src/pg-core/columns/bigint.ts @@ -3,6 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn } from './common.ts'; import { PgIntColumnBaseBuilder } from './int.common.ts'; @@ -13,13 +14,12 @@ export type PgBigInt53BuilderInitial = PgBigInt53Builder<{ data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class PgBigInt53Builder> extends PgIntColumnBaseBuilder { - static readonly [entityKind]: string = 'PgBigInt53Builder'; + static override readonly [entityKind]: string = 'PgBigInt53Builder'; constructor(name: T['name']) { super(name, 'number', 'PgBigInt53'); @@ -34,7 +34,7 @@ export class PgBigInt53Builder> extends PgColumn { - static readonly [entityKind]: string = 'PgBigInt53'; + static override readonly [entityKind]: string = 'PgBigInt53'; getSQLType(): string { return 'bigint'; @@ -55,13 +55,12 @@ export type PgBigInt64BuilderInitial = PgBigInt64Builder<{ data: bigint; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgBigInt64Builder> extends PgIntColumnBaseBuilder { - static readonly [entityKind]: string = 'PgBigInt64Builder'; + static override readonly [entityKind]: string = 'PgBigInt64Builder'; constructor(name: T['name']) { super(name, 'bigint', 'PgBigInt64'); @@ -79,7 +78,7 @@ export class PgBigInt64Builder> extends PgColumn { - static readonly [entityKind]: string = 'PgBigInt64'; + static override readonly [entityKind]: string = 'PgBigInt64'; getSQLType(): string { return 'bigint'; @@ -91,15 +90,19 @@ export class PgBigInt64> exte } } -interface PgBigIntConfig { +export interface PgBigIntConfig { mode: T; } +export function bigint( + config: PgBigIntConfig, +): TMode extends 'number' ? PgBigInt53BuilderInitial<''> : PgBigInt64BuilderInitial<''>; export function bigint( name: TName, config: PgBigIntConfig, ): TMode extends 'number' ? PgBigInt53BuilderInitial : PgBigInt64BuilderInitial; -export function bigint(name: string, config: PgBigIntConfig) { +export function bigint(a: string | PgBigIntConfig, b?: PgBigIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); if (config.mode === 'number') { return new PgBigInt53Builder(name); } diff --git a/drizzle-orm/src/pg-core/columns/bigserial.ts b/drizzle-orm/src/pg-core/columns/bigserial.ts index 0cca21577..0aa7e7a7f 100644 --- a/drizzle-orm/src/pg-core/columns/bigserial.ts +++ b/drizzle-orm/src/pg-core/columns/bigserial.ts @@ -7,6 +7,7 @@ import type { } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import type { AnyPgTable } from '../table.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; @@ -19,7 +20,6 @@ export type PgBigSerial53BuilderInitial = NotNull< data: number; driverParam: number; enumValues: undefined; - generated: undefined; }> > >; @@ -27,7 +27,7 @@ export type PgBigSerial53BuilderInitial = NotNull< export class PgBigSerial53Builder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgBigSerial53Builder'; + static override readonly [entityKind]: string = 'PgBigSerial53Builder'; constructor(name: string) { super(name, 'number', 'PgBigSerial53'); @@ -47,7 +47,7 @@ export class PgBigSerial53Builder> extends PgColumn { - static readonly [entityKind]: string = 'PgBigSerial53'; + static override readonly [entityKind]: string = 'PgBigSerial53'; getSQLType(): string { return 'bigserial'; @@ -70,7 +70,6 @@ export type PgBigSerial64BuilderInitial = NotNull< data: bigint; driverParam: string; enumValues: undefined; - generated: undefined; }> > >; @@ -78,7 +77,7 @@ export type PgBigSerial64BuilderInitial = NotNull< export class PgBigSerial64Builder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgBigSerial64Builder'; + static override readonly [entityKind]: string = 'PgBigSerial64Builder'; constructor(name: string) { super(name, 'bigint', 'PgBigSerial64'); @@ -97,7 +96,7 @@ export class PgBigSerial64Builder> extends PgColumn { - static readonly [entityKind]: string = 'PgBigSerial64'; + static override readonly [entityKind]: string = 'PgBigSerial64'; getSQLType(): string { return 'bigserial'; @@ -109,16 +108,20 @@ export class PgBigSerial64 } } -interface PgBigSerialConfig { +export interface PgBigSerialConfig { mode: T; } +export function bigserial( + config: PgBigSerialConfig, +): TMode extends 'number' ? PgBigSerial53BuilderInitial<''> : PgBigSerial64BuilderInitial<''>; export function bigserial( name: TName, config: PgBigSerialConfig, ): TMode extends 'number' ? PgBigSerial53BuilderInitial : PgBigSerial64BuilderInitial; -export function bigserial(name: string, { mode }: PgBigSerialConfig) { - if (mode === 'number') { +export function bigserial(a: string | PgBigSerialConfig, b?: PgBigSerialConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config.mode === 'number') { return new PgBigSerial53Builder(name); } return new PgBigSerial64Builder(name); diff --git a/drizzle-orm/src/pg-core/columns/boolean.ts b/drizzle-orm/src/pg-core/columns/boolean.ts index f4670f1a6..e2d514864 100644 --- a/drizzle-orm/src/pg-core/columns/boolean.ts +++ b/drizzle-orm/src/pg-core/columns/boolean.ts @@ -11,11 +11,10 @@ export type PgBooleanBuilderInitial = PgBooleanBuilder<{ data: boolean; driverParam: boolean; enumValues: undefined; - generated: undefined; }>; export class PgBooleanBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgBooleanBuilder'; + static override readonly [entityKind]: string = 'PgBooleanBuilder'; constructor(name: T['name']) { super(name, 'boolean', 'PgBoolean'); @@ -30,13 +29,15 @@ export class PgBooleanBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgBoolean'; + static override readonly [entityKind]: string = 'PgBoolean'; getSQLType(): string { return 'boolean'; } } -export function boolean(name: TName): PgBooleanBuilderInitial { - return new PgBooleanBuilder(name); +export function boolean(): PgBooleanBuilderInitial<''>; +export function boolean(name: TName): PgBooleanBuilderInitial; +export function boolean(name?: string) { + return new PgBooleanBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/char.ts b/drizzle-orm/src/pg-core/columns/char.ts index 9f33de4ae..e362e2f42 100644 --- a/drizzle-orm/src/pg-core/columns/char.ts +++ b/drizzle-orm/src/pg-core/columns/char.ts @@ -2,26 +2,33 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; -import type { Writable } from '~/utils.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; -export type PgCharBuilderInitial = PgCharBuilder<{ +export type PgCharBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = PgCharBuilder<{ name: TName; dataType: 'string'; columnType: 'PgChar'; data: TEnum[number]; enumValues: TEnum; driverParam: string; - generated: undefined; + length: TLength; }>; -export class PgCharBuilder> extends PgColumnBuilder< - T, - { length: number | undefined; enumValues: T['enumValues'] } -> { - static readonly [entityKind]: string = 'PgCharBuilder'; +export class PgCharBuilder & { length?: number | undefined }> + extends PgColumnBuilder< + T, + { length: T['length']; enumValues: T['enumValues'] }, + { length: T['length'] } + > +{ + static override readonly [entityKind]: string = 'PgCharBuilder'; - constructor(name: string, config: PgCharConfig) { + constructor(name: T['name'], config: PgCharConfig) { super(name, 'string', 'PgChar'); this.config.length = config.length; this.config.enumValues = config.enum; @@ -30,15 +37,18 @@ export class PgCharBuilder /** @internal */ override build( table: AnyPgTable<{ name: TTableName }>, - ): PgChar> { - return new PgChar>(table, this.config as ColumnBuilderRuntimeConfig); + ): PgChar & { length: T['length'] }> { + return new PgChar & { length: T['length'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); } } -export class PgChar> - extends PgColumn +export class PgChar & { length?: number | undefined }> + extends PgColumn { - static readonly [entityKind]: string = 'PgChar'; + static override readonly [entityKind]: string = 'PgChar'; readonly length = this.config.length; override readonly enumValues = this.config.enumValues; @@ -48,14 +58,28 @@ export class PgChar> } } -export interface PgCharConfig { - length?: number; +export interface PgCharConfig< + TEnum extends readonly string[] | string[] | undefined = readonly string[] | string[] | undefined, + TLength extends number | undefined = number | undefined, +> { enum?: TEnum; + length?: TLength; } -export function char>( +export function char(): PgCharBuilderInitial<'', [string, ...string[]], undefined>; +export function char, L extends number | undefined>( + config?: PgCharConfig, L>, +): PgCharBuilderInitial<'', Writable, L>; +export function char< + TName extends string, + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( name: TName, - config: PgCharConfig> = {}, -): PgCharBuilderInitial> { - return new PgCharBuilder(name, config); + config?: PgCharConfig, L>, +): PgCharBuilderInitial, L>; +export function char(a?: string | PgCharConfig, b: PgCharConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new PgCharBuilder(name, config as any); } diff --git a/drizzle-orm/src/pg-core/columns/cidr.ts b/drizzle-orm/src/pg-core/columns/cidr.ts index 9c2e9e19f..ab97536ee 100644 --- a/drizzle-orm/src/pg-core/columns/cidr.ts +++ b/drizzle-orm/src/pg-core/columns/cidr.ts @@ -11,11 +11,10 @@ export type PgCidrBuilderInitial = PgCidrBuilder<{ data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgCidrBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgCidrBuilder'; + static override readonly [entityKind]: string = 'PgCidrBuilder'; constructor(name: T['name']) { super(name, 'string', 'PgCidr'); @@ -30,13 +29,15 @@ export class PgCidrBuilder } export class PgCidr> extends PgColumn { - static readonly [entityKind]: string = 'PgCidr'; + static override readonly [entityKind]: string = 'PgCidr'; getSQLType(): string { return 'cidr'; } } -export function cidr(name: TName): PgCidrBuilderInitial { - return new PgCidrBuilder(name); +export function cidr(): PgCidrBuilderInitial<''>; +export function cidr(name: TName): PgCidrBuilderInitial; +export function cidr(name?: string) { + return new PgCidrBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/common.ts b/drizzle-orm/src/pg-core/columns/common.ts index a7440e24c..aa1d6c1f2 100644 --- a/drizzle-orm/src/pg-core/columns/common.ts +++ b/drizzle-orm/src/pg-core/columns/common.ts @@ -4,7 +4,6 @@ import type { ColumnBuilderExtraConfig, ColumnBuilderRuntimeConfig, ColumnDataType, - GeneratedColumnConfig, HasGenerated, MakeColumnConfig, } from '~/column-builder.ts'; @@ -12,12 +11,12 @@ import { ColumnBuilder } from '~/column-builder.ts'; import type { ColumnBaseConfig } from '~/column.ts'; import { Column } from '~/column.ts'; import { entityKind, is } from '~/entity.ts'; -import type { Update } from '~/utils.ts'; +import type { Simplify, Update } from '~/utils.ts'; -import type { SQL } from '~/index.ts'; import type { ForeignKey, UpdateDeleteAction } from '~/pg-core/foreign-keys.ts'; import { ForeignKeyBuilder } from '~/pg-core/foreign-keys.ts'; import type { AnyPgTable, PgTable } from '~/pg-core/table.ts'; +import type { SQL } from '~/sql/sql.ts'; import { iife } from '~/tracing-utils.ts'; import type { PgIndexOpClass } from '../indexes.ts'; import { uniqueKeyName } from '../unique-constraint.ts'; @@ -46,9 +45,9 @@ export abstract class PgColumnBuilder< { private foreignKeyConfigs: ReferenceConfig[] = []; - static readonly [entityKind]: string = 'PgColumnBuilder'; + static override readonly [entityKind]: string = 'PgColumnBuilder'; - array(size?: number): PgArrayBuilder< + array(size?: TSize): PgArrayBuilder< & { name: T['name']; dataType: 'array'; @@ -56,13 +55,14 @@ export abstract class PgColumnBuilder< data: T['data'][]; driverParam: T['driverParam'][] | string; enumValues: T['enumValues']; - generated: GeneratedColumnConfig; + size: TSize; + baseBuilder: T; } & (T extends { notNull: true } ? { notNull: true } : {}) & (T extends { hasDefault: true } ? { hasDefault: true } : {}), T > { - return new PgArrayBuilder(this.config.name, this as PgColumnBuilder, size); + return new PgArrayBuilder(this.config.name, this as PgColumnBuilder, size as any); } references( @@ -83,13 +83,17 @@ export abstract class PgColumnBuilder< return this; } - generatedAlwaysAs(as: SQL | T['data'] | (() => SQL)): HasGenerated { + generatedAlwaysAs(as: SQL | T['data'] | (() => SQL)): HasGenerated { this.config.generated = { as, type: 'always', mode: 'stored', }; - return this as any; + return this as HasGenerated; } /** @internal */ @@ -134,7 +138,7 @@ export abstract class PgColumn< TRuntimeConfig extends object = {}, TTypeConfig extends object = {}, > extends Column { - static readonly [entityKind]: string = 'PgColumn'; + static override readonly [entityKind]: string = 'PgColumn'; constructor( override readonly table: PgTable, @@ -152,7 +156,7 @@ export type IndexedExtraConfigType = { order?: 'asc' | 'desc'; nulls?: 'first' | export class ExtraConfigColumn< T extends ColumnBaseConfig = ColumnBaseConfig, > extends PgColumn { - static readonly [entityKind]: string = 'ExtraConfigColumn'; + static override readonly [entityKind]: string = 'ExtraConfigColumn'; override getSQLType(): string { return this.getSQLType(); @@ -228,15 +232,18 @@ export class IndexedColumn { static readonly [entityKind]: string = 'IndexedColumn'; constructor( name: string | undefined, + keyAsName: boolean, type: string, indexConfig: IndexedExtraConfigType, ) { this.name = name; + this.keyAsName = keyAsName; this.type = type; this.indexConfig = indexConfig; } name: string | undefined; + keyAsName: boolean; type: string; indexConfig: IndexedExtraConfigType; } @@ -245,17 +252,33 @@ export type AnyPgColumn, TPartial>> >; +export type PgArrayColumnBuilderBaseConfig = ColumnBuilderBaseConfig<'array', 'PgArray'> & { + size: number | undefined; + baseBuilder: ColumnBuilderBaseConfig; +}; + export class PgArrayBuilder< - T extends ColumnBuilderBaseConfig<'array', 'PgArray'>, - TBase extends ColumnBuilderBaseConfig, + T extends PgArrayColumnBuilderBaseConfig, + TBase extends ColumnBuilderBaseConfig | PgArrayColumnBuilderBaseConfig, > extends PgColumnBuilder< T, { - baseBuilder: PgColumnBuilder; - size: number | undefined; + baseBuilder: TBase extends PgArrayColumnBuilderBaseConfig ? PgArrayBuilder< + TBase, + TBase extends { baseBuilder: infer TBaseBuilder extends ColumnBuilderBaseConfig } ? TBaseBuilder + : never + > + : PgColumnBuilder>>>; + size: T['size']; }, { - baseBuilder: PgColumnBuilder; + baseBuilder: TBase extends PgArrayColumnBuilderBaseConfig ? PgArrayBuilder< + TBase, + TBase extends { baseBuilder: infer TBaseBuilder extends ColumnBuilderBaseConfig } ? TBaseBuilder + : never + > + : PgColumnBuilder>>>; + size: T['size']; } > { static override readonly [entityKind] = 'PgArrayBuilder'; @@ -263,7 +286,7 @@ export class PgArrayBuilder< constructor( name: string, baseBuilder: PgArrayBuilder['config']['baseBuilder'], - size: number | undefined, + size: T['size'], ) { super(name, 'array', 'PgArray'); this.config.baseBuilder = baseBuilder; @@ -273,9 +296,9 @@ export class PgArrayBuilder< /** @internal */ override build( table: AnyPgTable<{ name: TTableName }>, - ): PgArray, TBase> { + ): PgArray & { size: T['size']; baseBuilder: T['baseBuilder'] }, TBase> { const baseColumn = this.config.baseBuilder.build(table); - return new PgArray, TBase>( + return new PgArray & { size: T['size']; baseBuilder: T['baseBuilder'] }, TBase>( table as AnyPgTable<{ name: MakeColumnConfig['tableName'] }>, this.config as ColumnBuilderRuntimeConfig, baseColumn, @@ -284,12 +307,15 @@ export class PgArrayBuilder< } export class PgArray< - T extends ColumnBaseConfig<'array', 'PgArray'>, + T extends ColumnBaseConfig<'array', 'PgArray'> & { + size: number | undefined; + baseBuilder: ColumnBuilderBaseConfig; + }, TBase extends ColumnBuilderBaseConfig, -> extends PgColumn { - readonly size: number | undefined; +> extends PgColumn { + readonly size: T['size']; - static readonly [entityKind]: string = 'PgArray'; + static override readonly [entityKind]: string = 'PgArray'; constructor( table: AnyPgTable<{ name: T['tableName'] }>, diff --git a/drizzle-orm/src/pg-core/columns/custom.ts b/drizzle-orm/src/pg-core/columns/custom.ts index 4249e326c..f4f622ff6 100644 --- a/drizzle-orm/src/pg-core/columns/custom.ts +++ b/drizzle-orm/src/pg-core/columns/custom.ts @@ -3,7 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; import type { SQL } from '~/sql/sql.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; export type ConvertCustomConfig> = @@ -14,7 +14,6 @@ export type ConvertCustomConfig { - static readonly [entityKind]: string = 'PgCustomColumnBuilder'; + static override readonly [entityKind]: string = 'PgCustomColumnBuilder'; constructor( name: T['name'], @@ -59,7 +58,7 @@ export class PgCustomColumnBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgCustomColumn'; + static override readonly [entityKind]: string = 'PgCustomColumn'; private sqlName: string; private mapTo?: (value: T['data']) => T['driverParam']; @@ -108,7 +107,7 @@ export type CustomTypeValues = { /** * What config type should be used for {@link CustomTypeParams} `dataType` generation */ - config?: unknown; + config?: Record; /** * Whether the config argument should be required or not @@ -203,19 +202,31 @@ export interface CustomTypeParams { */ export function customType( customTypeParams: CustomTypeParams, -): Equal extends true ? ( - dbName: TName, - fieldConfig: T['config'], - ) => PgCustomColumnBuilder> - : ( - dbName: TName, - fieldConfig?: T['config'], - ) => PgCustomColumnBuilder> +): Equal extends true ? { + & T['config']>( + fieldConfig: TConfig, + ): PgCustomColumnBuilder>; + ( + dbName: TName, + fieldConfig: T['config'], + ): PgCustomColumnBuilder>; + } + : { + (): PgCustomColumnBuilder>; + & T['config']>( + fieldConfig?: TConfig, + ): PgCustomColumnBuilder>; + ( + dbName: TName, + fieldConfig?: T['config'], + ): PgCustomColumnBuilder>; + } { return ( - dbName: TName, - fieldConfig?: T['config'], + a?: TName | T['config'], + b?: T['config'], ): PgCustomColumnBuilder> => { - return new PgCustomColumnBuilder(dbName as ConvertCustomConfig['name'], fieldConfig, customTypeParams); + const { name, config } = getColumnNameAndConfig(a, b); + return new PgCustomColumnBuilder(name as ConvertCustomConfig['name'], config, customTypeParams); }; } diff --git a/drizzle-orm/src/pg-core/columns/date.common.ts b/drizzle-orm/src/pg-core/columns/date.common.ts index c2a46d1ce..e8bfbf2b1 100644 --- a/drizzle-orm/src/pg-core/columns/date.common.ts +++ b/drizzle-orm/src/pg-core/columns/date.common.ts @@ -7,7 +7,7 @@ export abstract class PgDateColumnBaseBuilder< T extends ColumnBuilderBaseConfig, TRuntimeConfig extends object = object, > extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgDateColumnBaseBuilder'; + static override readonly [entityKind]: string = 'PgDateColumnBaseBuilder'; defaultNow() { return this.default(sql`now()`); diff --git a/drizzle-orm/src/pg-core/columns/date.ts b/drizzle-orm/src/pg-core/columns/date.ts index 3c0107c3c..54f3d1a0e 100644 --- a/drizzle-orm/src/pg-core/columns/date.ts +++ b/drizzle-orm/src/pg-core/columns/date.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn } from './common.ts'; import { PgDateColumnBaseBuilder } from './date.common.ts'; @@ -12,11 +13,10 @@ export type PgDateBuilderInitial = PgDateBuilder<{ data: Date; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgDateBuilder> extends PgDateColumnBaseBuilder { - static readonly [entityKind]: string = 'PgDateBuilder'; + static override readonly [entityKind]: string = 'PgDateBuilder'; constructor(name: T['name']) { super(name, 'date', 'PgDate'); @@ -31,7 +31,7 @@ export class PgDateBuilder> } export class PgDate> extends PgColumn { - static readonly [entityKind]: string = 'PgDate'; + static override readonly [entityKind]: string = 'PgDate'; getSQLType(): string { return 'date'; @@ -53,13 +53,12 @@ export type PgDateStringBuilderInitial = PgDateStringBuild data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgDateStringBuilder> extends PgDateColumnBaseBuilder { - static readonly [entityKind]: string = 'PgDateStringBuilder'; + static override readonly [entityKind]: string = 'PgDateStringBuilder'; constructor(name: T['name']) { super(name, 'string', 'PgDateString'); @@ -77,19 +76,27 @@ export class PgDateStringBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgDateString'; + static override readonly [entityKind]: string = 'PgDateString'; getSQLType(): string { return 'date'; } } -export function date( +export interface PgDateConfig { + mode: T; +} + +export function date(): PgDateStringBuilderInitial<''>; +export function date( + config?: PgDateConfig, +): Equal extends true ? PgDateBuilderInitial<''> : PgDateStringBuilderInitial<''>; +export function date( name: TName, - config?: { mode: 'string' }, -): PgDateStringBuilderInitial; -export function date(TName: TName, config?: { mode: 'date' }): PgDateBuilderInitial; -export function date(name: TName, config?: { mode: 'date' | 'string' }) { + config?: PgDateConfig, +): Equal extends true ? PgDateBuilderInitial : PgDateStringBuilderInitial; +export function date(a?: string | PgDateConfig, b?: PgDateConfig) { + const { name, config } = getColumnNameAndConfig(a, b); if (config?.mode === 'date') { return new PgDateBuilder(name); } diff --git a/drizzle-orm/src/pg-core/columns/double-precision.ts b/drizzle-orm/src/pg-core/columns/double-precision.ts index 879c25ccb..942085bc1 100644 --- a/drizzle-orm/src/pg-core/columns/double-precision.ts +++ b/drizzle-orm/src/pg-core/columns/double-precision.ts @@ -11,13 +11,12 @@ export type PgDoublePrecisionBuilderInitial = PgDoublePrec data: number; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class PgDoublePrecisionBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgDoublePrecisionBuilder'; + static override readonly [entityKind]: string = 'PgDoublePrecisionBuilder'; constructor(name: T['name']) { super(name, 'number', 'PgDoublePrecision'); @@ -35,7 +34,7 @@ export class PgDoublePrecisionBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgDoublePrecision'; + static override readonly [entityKind]: string = 'PgDoublePrecision'; getSQLType(): string { return 'double precision'; @@ -49,6 +48,8 @@ export class PgDoublePrecision(name: TName): PgDoublePrecisionBuilderInitial { - return new PgDoublePrecisionBuilder(name); +export function doublePrecision(): PgDoublePrecisionBuilderInitial<''>; +export function doublePrecision(name: TName): PgDoublePrecisionBuilderInitial; +export function doublePrecision(name?: string) { + return new PgDoublePrecisionBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/enum.ts b/drizzle-orm/src/pg-core/columns/enum.ts index 9fbdc15c1..db7905b39 100644 --- a/drizzle-orm/src/pg-core/columns/enum.ts +++ b/drizzle-orm/src/pg-core/columns/enum.ts @@ -13,12 +13,13 @@ export type PgEnumColumnBuilderInitial; const isPgEnumSym = Symbol.for('drizzle:isPgEnum'); export interface PgEnum { + (): PgEnumColumnBuilderInitial<'', TValues>; (name: TName): PgEnumColumnBuilderInitial; + (name?: TName): PgEnumColumnBuilderInitial; readonly enumName: string; readonly enumValues: TValues; @@ -34,9 +35,9 @@ export function isPgEnum(obj: unknown): obj is PgEnum<[string, ...string[]]> { export class PgEnumColumnBuilder< T extends ColumnBuilderBaseConfig<'string', 'PgEnumColumn'> & { enumValues: [string, ...string[]] }, > extends PgColumnBuilder }> { - static readonly [entityKind]: string = 'PgEnumColumnBuilder'; + static override readonly [entityKind]: string = 'PgEnumColumnBuilder'; - constructor(name: string, enumInstance: PgEnum) { + constructor(name: T['name'], enumInstance: PgEnum) { super(name, 'string', 'PgEnumColumn'); this.config.enum = enumInstance; } @@ -55,7 +56,7 @@ export class PgEnumColumnBuilder< export class PgEnumColumn & { enumValues: [string, ...string[]] }> extends PgColumn }> { - static readonly [entityKind]: string = 'PgEnumColumn'; + static override readonly [entityKind]: string = 'PgEnumColumn'; readonly enum = this.config.enum; override readonly enumValues = this.config.enum.enumValues; @@ -88,8 +89,8 @@ export function pgEnumWithSchema> { const enumInstance: PgEnum> = Object.assign( - (name: TName): PgEnumColumnBuilderInitial> => - new PgEnumColumnBuilder(name, enumInstance), + (name?: TName): PgEnumColumnBuilderInitial> => + new PgEnumColumnBuilder(name ?? '' as TName, enumInstance), { enumName, enumValues: values, diff --git a/drizzle-orm/src/pg-core/columns/index.ts b/drizzle-orm/src/pg-core/columns/index.ts index 881f53e33..79afc74c6 100644 --- a/drizzle-orm/src/pg-core/columns/index.ts +++ b/drizzle-orm/src/pg-core/columns/index.ts @@ -9,6 +9,7 @@ export * from './date.ts'; export * from './double-precision.ts'; export * from './enum.ts'; export * from './inet.ts'; +export * from './int.common.ts'; export * from './integer.ts'; export * from './interval.ts'; export * from './json.ts'; diff --git a/drizzle-orm/src/pg-core/columns/inet.ts b/drizzle-orm/src/pg-core/columns/inet.ts index f8e473de7..741bbdf86 100644 --- a/drizzle-orm/src/pg-core/columns/inet.ts +++ b/drizzle-orm/src/pg-core/columns/inet.ts @@ -11,11 +11,10 @@ export type PgInetBuilderInitial = PgInetBuilder<{ data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgInetBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgInetBuilder'; + static override readonly [entityKind]: string = 'PgInetBuilder'; constructor(name: T['name']) { super(name, 'string', 'PgInet'); @@ -30,13 +29,15 @@ export class PgInetBuilder } export class PgInet> extends PgColumn { - static readonly [entityKind]: string = 'PgInet'; + static override readonly [entityKind]: string = 'PgInet'; getSQLType(): string { return 'inet'; } } -export function inet(name: TName): PgInetBuilderInitial { - return new PgInetBuilder(name); +export function inet(): PgInetBuilderInitial<''>; +export function inet(name: TName): PgInetBuilderInitial; +export function inet(name?: string) { + return new PgInetBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/int.common.ts b/drizzle-orm/src/pg-core/columns/int.common.ts index 07c26ba9e..32d5d12cd 100644 --- a/drizzle-orm/src/pg-core/columns/int.common.ts +++ b/drizzle-orm/src/pg-core/columns/int.common.ts @@ -1,9 +1,4 @@ -import type { - ColumnBuilderBaseConfig, - ColumnDataType, - GeneratedIdentityConfig, - IsIdentityByDefault, -} from '~/column-builder.ts'; +import type { ColumnBuilderBaseConfig, ColumnDataType, GeneratedIdentityConfig, IsIdentity } from '~/column-builder.ts'; import { entityKind } from '~/entity.ts'; import type { PgSequenceOptions } from '../sequence.ts'; import { PgColumnBuilder } from './common.ts'; @@ -14,11 +9,11 @@ export abstract class PgIntColumnBaseBuilder< T, { generatedIdentity: GeneratedIdentityConfig } > { - static readonly [entityKind]: string = 'PgIntColumnBaseBuilder'; + static override readonly [entityKind]: string = 'PgIntColumnBaseBuilder'; generatedAlwaysAsIdentity( sequence?: PgSequenceOptions & { name?: string }, - ): IsIdentityByDefault { + ): IsIdentity { if (sequence) { const { name, ...options } = sequence; this.config.generatedIdentity = { @@ -35,12 +30,12 @@ export abstract class PgIntColumnBaseBuilder< this.config.hasDefault = true; this.config.notNull = true; - return this as any; + return this as IsIdentity; } generatedByDefaultAsIdentity( sequence?: PgSequenceOptions & { name?: string }, - ): IsIdentityByDefault { + ): IsIdentity { if (sequence) { const { name, ...options } = sequence; this.config.generatedIdentity = { @@ -57,6 +52,6 @@ export abstract class PgIntColumnBaseBuilder< this.config.hasDefault = true; this.config.notNull = true; - return this as any; + return this as IsIdentity; } } diff --git a/drizzle-orm/src/pg-core/columns/integer.ts b/drizzle-orm/src/pg-core/columns/integer.ts index 2c35c1e29..9be92bb34 100644 --- a/drizzle-orm/src/pg-core/columns/integer.ts +++ b/drizzle-orm/src/pg-core/columns/integer.ts @@ -5,20 +5,19 @@ import type { AnyPgTable } from '../table.ts'; import { PgColumn } from './common.ts'; import { PgIntColumnBaseBuilder } from './int.common.ts'; -type PgIntegerBuilderInitial = PgIntegerBuilder<{ +export type PgIntegerBuilderInitial = PgIntegerBuilder<{ name: TName; dataType: 'number'; columnType: 'PgInteger'; data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class PgIntegerBuilder> extends PgIntColumnBaseBuilder { - static readonly [entityKind]: string = 'PgIntegerBuilder'; + static override readonly [entityKind]: string = 'PgIntegerBuilder'; constructor(name: T['name']) { super(name, 'number', 'PgInteger'); @@ -33,7 +32,7 @@ export class PgIntegerBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgInteger'; + static override readonly [entityKind]: string = 'PgInteger'; getSQLType(): string { return 'integer'; @@ -47,6 +46,8 @@ export class PgInteger> extend } } -export function integer(name: TName): PgIntegerBuilderInitial { - return new PgIntegerBuilder(name); +export function integer(): PgIntegerBuilderInitial<''>; +export function integer(name: TName): PgIntegerBuilderInitial; +export function integer(name?: string) { + return new PgIntegerBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/interval.ts b/drizzle-orm/src/pg-core/columns/interval.ts index c70dd0c04..8f81e9867 100644 --- a/drizzle-orm/src/pg-core/columns/interval.ts +++ b/drizzle-orm/src/pg-core/columns/interval.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; import type { Precision } from './timestamp.ts'; @@ -12,13 +13,12 @@ export type PgIntervalBuilderInitial = PgIntervalBuilder<{ data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgIntervalBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgIntervalBuilder'; + static override readonly [entityKind]: string = 'PgIntervalBuilder'; constructor( name: T['name'], @@ -39,7 +39,7 @@ export class PgIntervalBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgInterval'; + static override readonly [entityKind]: string = 'PgInterval'; readonly fields: IntervalConfig['fields'] = this.config.intervalConfig.fields; readonly precision: IntervalConfig['precision'] = this.config.intervalConfig.precision; @@ -69,9 +69,15 @@ export interface IntervalConfig { precision?: Precision; } +export function interval(): PgIntervalBuilderInitial<''>; +export function interval( + config?: IntervalConfig, +): PgIntervalBuilderInitial<''>; export function interval( name: TName, - config: IntervalConfig = {}, -): PgIntervalBuilderInitial { + config?: IntervalConfig, +): PgIntervalBuilderInitial; +export function interval(a?: string | IntervalConfig, b: IntervalConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); return new PgIntervalBuilder(name, config); } diff --git a/drizzle-orm/src/pg-core/columns/json.ts b/drizzle-orm/src/pg-core/columns/json.ts index ddb97b67e..deed256b9 100644 --- a/drizzle-orm/src/pg-core/columns/json.ts +++ b/drizzle-orm/src/pg-core/columns/json.ts @@ -11,13 +11,12 @@ export type PgJsonBuilderInitial = PgJsonBuilder<{ data: unknown; driverParam: unknown; enumValues: undefined; - generated: undefined; }>; export class PgJsonBuilder> extends PgColumnBuilder< T > { - static readonly [entityKind]: string = 'PgJsonBuilder'; + static override readonly [entityKind]: string = 'PgJsonBuilder'; constructor(name: T['name']) { super(name, 'json', 'PgJson'); @@ -32,7 +31,7 @@ export class PgJsonBuilder> } export class PgJson> extends PgColumn { - static readonly [entityKind]: string = 'PgJson'; + static override readonly [entityKind]: string = 'PgJson'; constructor(table: AnyPgTable<{ name: T['tableName'] }>, config: PgJsonBuilder['config']) { super(table, config); @@ -58,6 +57,8 @@ export class PgJson> extends PgColu } } -export function json(name: TName): PgJsonBuilderInitial { - return new PgJsonBuilder(name); +export function json(): PgJsonBuilderInitial<''>; +export function json(name: TName): PgJsonBuilderInitial; +export function json(name?: string) { + return new PgJsonBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/jsonb.ts b/drizzle-orm/src/pg-core/columns/jsonb.ts index a44b26fd0..14af7c9e9 100644 --- a/drizzle-orm/src/pg-core/columns/jsonb.ts +++ b/drizzle-orm/src/pg-core/columns/jsonb.ts @@ -11,11 +11,10 @@ export type PgJsonbBuilderInitial = PgJsonbBuilder<{ data: unknown; driverParam: unknown; enumValues: undefined; - generated: undefined; }>; export class PgJsonbBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgJsonbBuilder'; + static override readonly [entityKind]: string = 'PgJsonbBuilder'; constructor(name: T['name']) { super(name, 'json', 'PgJsonb'); @@ -30,7 +29,7 @@ export class PgJsonbBuilder } export class PgJsonb> extends PgColumn { - static readonly [entityKind]: string = 'PgJsonb'; + static override readonly [entityKind]: string = 'PgJsonb'; constructor(table: AnyPgTable<{ name: T['tableName'] }>, config: PgJsonbBuilder['config']) { super(table, config); @@ -56,6 +55,8 @@ export class PgJsonb> extends PgCo } } -export function jsonb(name: TName): PgJsonbBuilderInitial { - return new PgJsonbBuilder(name); +export function jsonb(): PgJsonbBuilderInitial<''>; +export function jsonb(name: TName): PgJsonbBuilderInitial; +export function jsonb(name?: string) { + return new PgJsonbBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/line.ts b/drizzle-orm/src/pg-core/columns/line.ts index bf4e653ad..9378d1aa1 100644 --- a/drizzle-orm/src/pg-core/columns/line.ts +++ b/drizzle-orm/src/pg-core/columns/line.ts @@ -3,7 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; export type PgLineBuilderInitial = PgLineBuilder<{ @@ -13,11 +13,10 @@ export type PgLineBuilderInitial = PgLineBuilder<{ data: [number, number, number]; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class PgLineBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgLineBuilder'; + static override readonly [entityKind]: string = 'PgLineBuilder'; constructor(name: T['name']) { super(name, 'array', 'PgLine'); @@ -35,7 +34,7 @@ export class PgLineBuilder> } export class PgLineTuple> extends PgColumn { - static readonly [entityKind]: string = 'PgLine'; + static override readonly [entityKind]: string = 'PgLine'; getSQLType(): string { return 'line'; @@ -58,11 +57,10 @@ export type PgLineABCBuilderInitial = PgLineABCBuilder<{ data: { a: number; b: number; c: number }; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgLineABCBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgLineABCBuilder'; + static override readonly [entityKind]: string = 'PgLineABCBuilder'; constructor(name: T['name']) { super(name, 'json', 'PgLineABC'); @@ -80,7 +78,7 @@ export class PgLineABCBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgLineABC'; + static override readonly [entityKind]: string = 'PgLineABC'; getSQLType(): string { return 'line'; @@ -100,15 +98,20 @@ export interface PgLineTypeConfig { mode?: T; } +export function line(): PgLineBuilderInitial<''>; +export function line( + config?: PgLineTypeConfig, +): Equal extends true ? PgLineABCBuilderInitial<''> + : PgLineBuilderInitial<''>; export function line( name: TName, config?: PgLineTypeConfig, ): Equal extends true ? PgLineABCBuilderInitial : PgLineBuilderInitial; -export function line(name: string, config?: PgLineTypeConfig) { +export function line(a?: string | PgLineTypeConfig, b?: PgLineTypeConfig) { + const { name, config } = getColumnNameAndConfig(a, b); if (!config?.mode || config.mode === 'tuple') { return new PgLineBuilder(name); } - return new PgLineABCBuilder(name); } diff --git a/drizzle-orm/src/pg-core/columns/macaddr.ts b/drizzle-orm/src/pg-core/columns/macaddr.ts index edc27f7c0..bcd8b02bc 100644 --- a/drizzle-orm/src/pg-core/columns/macaddr.ts +++ b/drizzle-orm/src/pg-core/columns/macaddr.ts @@ -11,11 +11,10 @@ export type PgMacaddrBuilderInitial = PgMacaddrBuilder<{ data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgMacaddrBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgMacaddrBuilder'; + static override readonly [entityKind]: string = 'PgMacaddrBuilder'; constructor(name: T['name']) { super(name, 'string', 'PgMacaddr'); @@ -30,13 +29,15 @@ export class PgMacaddrBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgMacaddr'; + static override readonly [entityKind]: string = 'PgMacaddr'; getSQLType(): string { return 'macaddr'; } } -export function macaddr(name: TName): PgMacaddrBuilderInitial { - return new PgMacaddrBuilder(name); +export function macaddr(): PgMacaddrBuilderInitial<''>; +export function macaddr(name: TName): PgMacaddrBuilderInitial; +export function macaddr(name?: string) { + return new PgMacaddrBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/macaddr8.ts b/drizzle-orm/src/pg-core/columns/macaddr8.ts index fc611d063..e9e3e4bef 100644 --- a/drizzle-orm/src/pg-core/columns/macaddr8.ts +++ b/drizzle-orm/src/pg-core/columns/macaddr8.ts @@ -11,11 +11,10 @@ export type PgMacaddr8BuilderInitial = PgMacaddr8Builder<{ data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgMacaddr8Builder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgMacaddr8Builder'; + static override readonly [entityKind]: string = 'PgMacaddr8Builder'; constructor(name: T['name']) { super(name, 'string', 'PgMacaddr8'); @@ -30,13 +29,15 @@ export class PgMacaddr8Builder> extends PgColumn { - static readonly [entityKind]: string = 'PgMacaddr8'; + static override readonly [entityKind]: string = 'PgMacaddr8'; getSQLType(): string { return 'macaddr8'; } } -export function macaddr8(name: TName): PgMacaddr8BuilderInitial { - return new PgMacaddr8Builder(name); +export function macaddr8(): PgMacaddr8BuilderInitial<''>; +export function macaddr8(name: TName): PgMacaddr8BuilderInitial; +export function macaddr8(name?: string) { + return new PgMacaddr8Builder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/numeric.ts b/drizzle-orm/src/pg-core/columns/numeric.ts index d740c1a4a..fc58d9859 100644 --- a/drizzle-orm/src/pg-core/columns/numeric.ts +++ b/drizzle-orm/src/pg-core/columns/numeric.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; export type PgNumericBuilderInitial = PgNumericBuilder<{ @@ -11,7 +12,6 @@ export type PgNumericBuilderInitial = PgNumericBuilder<{ data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgNumericBuilder> extends PgColumnBuilder< @@ -21,9 +21,9 @@ export class PgNumericBuilder { - static readonly [entityKind]: string = 'PgNumericBuilder'; + static override readonly [entityKind]: string = 'PgNumericBuilder'; - constructor(name: string, precision?: number, scale?: number) { + constructor(name: T['name'], precision?: number, scale?: number) { super(name, 'string', 'PgNumeric'); this.config.precision = precision; this.config.scale = scale; @@ -38,7 +38,7 @@ export class PgNumericBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgNumeric'; + static override readonly [entityKind]: string = 'PgNumeric'; readonly precision: number | undefined; readonly scale: number | undefined; @@ -60,13 +60,21 @@ export class PgNumeric> extend } } +export type PgNumericConfig = + | { precision: number; scale?: number } + | { precision?: number; scale: number } + | { precision: number; scale: number }; + +export function numeric(): PgNumericBuilderInitial<''>; +export function numeric( + config?: PgNumericConfig, +): PgNumericBuilderInitial<''>; export function numeric( name: TName, - config?: - | { precision: number; scale?: number } - | { precision?: number; scale: number } - | { precision: number; scale: number }, -): PgNumericBuilderInitial { + config?: PgNumericConfig, +): PgNumericBuilderInitial; +export function numeric(a?: string | PgNumericConfig, b?: PgNumericConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new PgNumericBuilder(name, config?.precision, config?.scale); } diff --git a/drizzle-orm/src/pg-core/columns/point.ts b/drizzle-orm/src/pg-core/columns/point.ts index 7bff25e55..c204aedea 100644 --- a/drizzle-orm/src/pg-core/columns/point.ts +++ b/drizzle-orm/src/pg-core/columns/point.ts @@ -3,7 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; export type PgPointTupleBuilderInitial = PgPointTupleBuilder<{ @@ -13,15 +13,14 @@ export type PgPointTupleBuilderInitial = PgPointTupleBuild data: [number, number]; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class PgPointTupleBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgPointTupleBuilder'; + static override readonly [entityKind]: string = 'PgPointTupleBuilder'; - constructor(name: T['name']) { + constructor(name: string) { super(name, 'array', 'PgPointTuple'); } @@ -37,7 +36,7 @@ export class PgPointTupleBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgPointTuple'; + static override readonly [entityKind]: string = 'PgPointTuple'; getSQLType(): string { return 'point'; @@ -63,15 +62,14 @@ export type PgPointObjectBuilderInitial = PgPointObjectBui data: { x: number; y: number }; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgPointObjectBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgPointObjectBuilder'; + static override readonly [entityKind]: string = 'PgPointObjectBuilder'; - constructor(name: T['name']) { + constructor(name: string) { super(name, 'json', 'PgPointObject'); } @@ -87,7 +85,7 @@ export class PgPointObjectBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgPointObject'; + static override readonly [entityKind]: string = 'PgPointObject'; getSQLType(): string { return 'point'; @@ -110,12 +108,18 @@ export interface PgPointConfig { mode?: T; } +export function point(): PgPointTupleBuilderInitial<''>; +export function point( + config?: PgPointConfig, +): Equal extends true ? PgPointObjectBuilderInitial<''> + : PgPointTupleBuilderInitial<''>; export function point( name: TName, config?: PgPointConfig, ): Equal extends true ? PgPointObjectBuilderInitial : PgPointTupleBuilderInitial; -export function point(name: string, config?: PgPointConfig) { +export function point(a?: string | PgPointConfig, b?: PgPointConfig) { + const { name, config } = getColumnNameAndConfig(a, b); if (!config?.mode || config.mode === 'tuple') { return new PgPointTupleBuilder(name); } diff --git a/drizzle-orm/src/pg-core/columns/postgis_extension/geometry.ts b/drizzle-orm/src/pg-core/columns/postgis_extension/geometry.ts index 5dc2b8955..93632d31c 100644 --- a/drizzle-orm/src/pg-core/columns/postgis_extension/geometry.ts +++ b/drizzle-orm/src/pg-core/columns/postgis_extension/geometry.ts @@ -3,7 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from '../common.ts'; import { parseEWKB } from './utils.ts'; @@ -14,11 +14,10 @@ export type PgGeometryBuilderInitial = PgGeometryBuilder<{ data: [number, number]; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgGeometryBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgGeometryBuilder'; + static override readonly [entityKind]: string = 'PgGeometryBuilder'; constructor(name: T['name']) { super(name, 'array', 'PgGeometry'); @@ -36,7 +35,7 @@ export class PgGeometryBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgGeometry'; + static override readonly [entityKind]: string = 'PgGeometry'; getSQLType(): string { return 'geometry(point)'; @@ -58,13 +57,12 @@ export type PgGeometryObjectBuilderInitial = PgGeometryObj data: { x: number; y: number }; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgGeometryObjectBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgGeometryObjectBuilder'; + static override readonly [entityKind]: string = 'PgGeometryObjectBuilder'; constructor(name: T['name']) { super(name, 'json', 'PgGeometryObject'); @@ -82,7 +80,7 @@ export class PgGeometryObjectBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgGeometryObject'; + static override readonly [entityKind]: string = 'PgGeometryObject'; getSQLType(): string { return 'geometry(point)'; @@ -98,18 +96,22 @@ export class PgGeometryObject { +export interface PgGeometryConfig { mode?: T; type?: 'point' | (string & {}); srid?: number; } +export function geometry(): PgGeometryBuilderInitial<''>; +export function geometry( + config?: PgGeometryConfig, +): Equal extends true ? PgGeometryObjectBuilderInitial<''> : PgGeometryBuilderInitial<''>; export function geometry( name: TName, config?: PgGeometryConfig, -): Equal extends true ? PgGeometryObjectBuilderInitial - : PgGeometryBuilderInitial; -export function geometry(name: string, config?: PgGeometryConfig) { +): Equal extends true ? PgGeometryObjectBuilderInitial : PgGeometryBuilderInitial; +export function geometry(a?: string | PgGeometryConfig, b?: PgGeometryConfig) { + const { name, config } = getColumnNameAndConfig(a, b); if (!config?.mode || config.mode === 'tuple') { return new PgGeometryBuilder(name); } diff --git a/drizzle-orm/src/pg-core/columns/real.ts b/drizzle-orm/src/pg-core/columns/real.ts index 6abe81441..b3c339c9b 100644 --- a/drizzle-orm/src/pg-core/columns/real.ts +++ b/drizzle-orm/src/pg-core/columns/real.ts @@ -11,16 +11,15 @@ export type PgRealBuilderInitial = PgRealBuilder<{ data: number; driverParam: string | number; enumValues: undefined; - generated: undefined; }>; export class PgRealBuilder> extends PgColumnBuilder< T, { length: number | undefined } > { - static readonly [entityKind]: string = 'PgRealBuilder'; + static override readonly [entityKind]: string = 'PgRealBuilder'; - constructor(name: string, length?: number) { + constructor(name: T['name'], length?: number) { super(name, 'number', 'PgReal'); this.config.length = length; } @@ -34,7 +33,7 @@ export class PgRealBuilder } export class PgReal> extends PgColumn { - static readonly [entityKind]: string = 'PgReal'; + static override readonly [entityKind]: string = 'PgReal'; constructor(table: AnyPgTable<{ name: T['tableName'] }>, config: PgRealBuilder['config']) { super(table, config); @@ -52,6 +51,8 @@ export class PgReal> extends PgCo }; } -export function real(name: TName): PgRealBuilderInitial { - return new PgRealBuilder(name); +export function real(): PgRealBuilderInitial<''>; +export function real(name: TName): PgRealBuilderInitial; +export function real(name?: string) { + return new PgRealBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/serial.ts b/drizzle-orm/src/pg-core/columns/serial.ts index a15619a87..261ef2af9 100644 --- a/drizzle-orm/src/pg-core/columns/serial.ts +++ b/drizzle-orm/src/pg-core/columns/serial.ts @@ -19,15 +19,14 @@ export type PgSerialBuilderInitial = NotNull< data: number; driverParam: number; enumValues: undefined; - generated: undefined; }> > >; export class PgSerialBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgSerialBuilder'; + static override readonly [entityKind]: string = 'PgSerialBuilder'; - constructor(name: string) { + constructor(name: T['name']) { super(name, 'number', 'PgSerial'); this.config.hasDefault = true; this.config.notNull = true; @@ -42,13 +41,15 @@ export class PgSerialBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgSerial'; + static override readonly [entityKind]: string = 'PgSerial'; getSQLType(): string { return 'serial'; } } -export function serial(name: TName): PgSerialBuilderInitial { - return new PgSerialBuilder(name) as PgSerialBuilderInitial; +export function serial(): PgSerialBuilderInitial<''>; +export function serial(name: TName): PgSerialBuilderInitial; +export function serial(name?: string) { + return new PgSerialBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/smallint.ts b/drizzle-orm/src/pg-core/columns/smallint.ts index 4a500bf5c..e662904b5 100644 --- a/drizzle-orm/src/pg-core/columns/smallint.ts +++ b/drizzle-orm/src/pg-core/columns/smallint.ts @@ -12,13 +12,12 @@ export type PgSmallIntBuilderInitial = PgSmallIntBuilder<{ data: number; driverParam: number | string; enumValues: undefined; - generated: undefined; }>; export class PgSmallIntBuilder> extends PgIntColumnBaseBuilder { - static readonly [entityKind]: string = 'PgSmallIntBuilder'; + static override readonly [entityKind]: string = 'PgSmallIntBuilder'; constructor(name: T['name']) { super(name, 'number', 'PgSmallInt'); @@ -33,7 +32,7 @@ export class PgSmallIntBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgSmallInt'; + static override readonly [entityKind]: string = 'PgSmallInt'; getSQLType(): string { return 'smallint'; @@ -47,6 +46,8 @@ export class PgSmallInt> exte }; } -export function smallint(name: TName): PgSmallIntBuilderInitial { - return new PgSmallIntBuilder(name); +export function smallint(): PgSmallIntBuilderInitial<''>; +export function smallint(name: TName): PgSmallIntBuilderInitial; +export function smallint(name?: string) { + return new PgSmallIntBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/smallserial.ts b/drizzle-orm/src/pg-core/columns/smallserial.ts index 02acbb87f..c92fc8350 100644 --- a/drizzle-orm/src/pg-core/columns/smallserial.ts +++ b/drizzle-orm/src/pg-core/columns/smallserial.ts @@ -19,7 +19,6 @@ export type PgSmallSerialBuilderInitial = NotNull< data: number; driverParam: number; enumValues: undefined; - generated: undefined; }> > >; @@ -27,9 +26,9 @@ export type PgSmallSerialBuilderInitial = NotNull< export class PgSmallSerialBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgSmallSerialBuilder'; + static override readonly [entityKind]: string = 'PgSmallSerialBuilder'; - constructor(name: string) { + constructor(name: T['name']) { super(name, 'number', 'PgSmallSerial'); this.config.hasDefault = true; this.config.notNull = true; @@ -47,13 +46,15 @@ export class PgSmallSerialBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgSmallSerial'; + static override readonly [entityKind]: string = 'PgSmallSerial'; getSQLType(): string { return 'smallserial'; } } -export function smallserial(name: TName): PgSmallSerialBuilderInitial { - return new PgSmallSerialBuilder(name) as PgSmallSerialBuilderInitial; +export function smallserial(): PgSmallSerialBuilderInitial<''>; +export function smallserial(name: TName): PgSmallSerialBuilderInitial; +export function smallserial(name?: string) { + return new PgSmallSerialBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/text.ts b/drizzle-orm/src/pg-core/columns/text.ts index 47c3c9045..980f0ec2f 100644 --- a/drizzle-orm/src/pg-core/columns/text.ts +++ b/drizzle-orm/src/pg-core/columns/text.ts @@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; -import type { Writable } from '~/utils.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; type PgTextBuilderInitial = PgTextBuilder<{ @@ -12,13 +12,12 @@ type PgTextBuilderInitial; export class PgTextBuilder< T extends ColumnBuilderBaseConfig<'string', 'PgText'>, > extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgTextBuilder'; + static override readonly [entityKind]: string = 'PgTextBuilder'; constructor( name: T['name'], @@ -39,7 +38,7 @@ export class PgTextBuilder< export class PgText> extends PgColumn { - static readonly [entityKind]: string = 'PgText'; + static override readonly [entityKind]: string = 'PgText'; override readonly enumValues = this.config.enumValues; @@ -48,13 +47,21 @@ export class PgText> } } -export interface PgTextConfig { +export interface PgTextConfig< + TEnum extends readonly string[] | string[] | undefined = readonly string[] | string[] | undefined, +> { enum?: TEnum; } +export function text(): PgTextBuilderInitial<'', [string, ...string[]]>; +export function text>( + config?: PgTextConfig>, +): PgTextBuilderInitial<'', Writable>; export function text>( name: TName, - config: PgTextConfig> = {}, -): PgTextBuilderInitial> { - return new PgTextBuilder(name, config); + config?: PgTextConfig>, +): PgTextBuilderInitial>; +export function text(a?: string | PgTextConfig, b: PgTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new PgTextBuilder(name, config as any); } diff --git a/drizzle-orm/src/pg-core/columns/time.ts b/drizzle-orm/src/pg-core/columns/time.ts index fe82c9142..b9b52fd79 100644 --- a/drizzle-orm/src/pg-core/columns/time.ts +++ b/drizzle-orm/src/pg-core/columns/time.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn } from './common.ts'; import { PgDateColumnBaseBuilder } from './date.common.ts'; import type { Precision } from './timestamp.ts'; @@ -13,14 +14,13 @@ export type PgTimeBuilderInitial = PgTimeBuilder<{ data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgTimeBuilder> extends PgDateColumnBaseBuilder< T, { withTimezone: boolean; precision: number | undefined } > { - static readonly [entityKind]: string = 'PgTimeBuilder'; + static override readonly [entityKind]: string = 'PgTimeBuilder'; constructor( name: T['name'], @@ -41,7 +41,7 @@ export class PgTimeBuilder } export class PgTime> extends PgColumn { - static readonly [entityKind]: string = 'PgTime'; + static override readonly [entityKind]: string = 'PgTime'; readonly withTimezone: boolean; readonly precision: number | undefined; @@ -63,6 +63,10 @@ export interface TimeConfig { withTimezone?: boolean; } -export function time(name: TName, config: TimeConfig = {}): PgTimeBuilderInitial { +export function time(): PgTimeBuilderInitial<''>; +export function time(config?: TimeConfig): PgTimeBuilderInitial<''>; +export function time(name: TName, config?: TimeConfig): PgTimeBuilderInitial; +export function time(a?: string | TimeConfig, b: TimeConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); return new PgTimeBuilder(name, config.withTimezone ?? false, config.precision); } diff --git a/drizzle-orm/src/pg-core/columns/timestamp.ts b/drizzle-orm/src/pg-core/columns/timestamp.ts index 8293f9472..f5a5cb0a6 100644 --- a/drizzle-orm/src/pg-core/columns/timestamp.ts +++ b/drizzle-orm/src/pg-core/columns/timestamp.ts @@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn } from './common.ts'; import { PgDateColumnBaseBuilder } from './date.common.ts'; @@ -13,7 +13,6 @@ export type PgTimestampBuilderInitial = PgTimestampBuilder data: Date; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgTimestampBuilder> @@ -22,10 +21,10 @@ export class PgTimestampBuilder { - static readonly [entityKind]: string = 'PgTimestampBuilder'; + static override readonly [entityKind]: string = 'PgTimestampBuilder'; constructor( - name: string, + name: T['name'], withTimezone: boolean, precision: number | undefined, ) { @@ -43,7 +42,7 @@ export class PgTimestampBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgTimestamp'; + static override readonly [entityKind]: string = 'PgTimestamp'; readonly withTimezone: boolean; readonly precision: number | undefined; @@ -75,7 +74,6 @@ export type PgTimestampStringBuilderInitial = PgTimestampS data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgTimestampStringBuilder> @@ -84,10 +82,10 @@ export class PgTimestampStringBuilder { - static readonly [entityKind]: string = 'PgTimestampStringBuilder'; + static override readonly [entityKind]: string = 'PgTimestampStringBuilder'; constructor( - name: string, + name: T['name'], withTimezone: boolean, precision: number | undefined, ) { @@ -108,7 +106,7 @@ export class PgTimestampStringBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgTimestampString'; + static override readonly [entityKind]: string = 'PgTimestampString'; readonly withTimezone: boolean; readonly precision: number | undefined; @@ -133,16 +131,18 @@ export interface PgTimestampConfig; +export function timestamp( + config?: PgTimestampConfig, +): Equal extends true ? PgTimestampStringBuilderInitial<''> : PgTimestampBuilderInitial<''>; export function timestamp( name: TName, config?: PgTimestampConfig, ): Equal extends true ? PgTimestampStringBuilderInitial : PgTimestampBuilderInitial; -export function timestamp( - name: string, - config: PgTimestampConfig = {}, -) { - if (config.mode === 'string') { +export function timestamp(a?: string | PgTimestampConfig, b: PgTimestampConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config?.mode === 'string') { return new PgTimestampStringBuilder(name, config.withTimezone ?? false, config.precision); } - return new PgTimestampBuilder(name, config.withTimezone ?? false, config.precision); + return new PgTimestampBuilder(name, config?.withTimezone ?? false, config?.precision); } diff --git a/drizzle-orm/src/pg-core/columns/uuid.ts b/drizzle-orm/src/pg-core/columns/uuid.ts index 24907ce99..c8455d372 100644 --- a/drizzle-orm/src/pg-core/columns/uuid.ts +++ b/drizzle-orm/src/pg-core/columns/uuid.ts @@ -12,11 +12,10 @@ export type PgUUIDBuilderInitial = PgUUIDBuilder<{ data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgUUIDBuilder> extends PgColumnBuilder { - static readonly [entityKind]: string = 'PgUUIDBuilder'; + static override readonly [entityKind]: string = 'PgUUIDBuilder'; constructor(name: T['name']) { super(name, 'string', 'PgUUID'); @@ -38,13 +37,15 @@ export class PgUUIDBuilder } export class PgUUID> extends PgColumn { - static readonly [entityKind]: string = 'PgUUID'; + static override readonly [entityKind]: string = 'PgUUID'; getSQLType(): string { return 'uuid'; } } -export function uuid(name: TName): PgUUIDBuilderInitial { - return new PgUUIDBuilder(name); +export function uuid(): PgUUIDBuilderInitial<''>; +export function uuid(name: TName): PgUUIDBuilderInitial; +export function uuid(name?: string) { + return new PgUUIDBuilder(name ?? ''); } diff --git a/drizzle-orm/src/pg-core/columns/varchar.ts b/drizzle-orm/src/pg-core/columns/varchar.ts index 84283d40e..6c2042ae4 100644 --- a/drizzle-orm/src/pg-core/columns/varchar.ts +++ b/drizzle-orm/src/pg-core/columns/varchar.ts @@ -2,26 +2,33 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; -import type { Writable } from '~/utils.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from './common.ts'; -export type PgVarcharBuilderInitial = PgVarcharBuilder<{ +export type PgVarcharBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = PgVarcharBuilder<{ name: TName; dataType: 'string'; columnType: 'PgVarchar'; data: TEnum[number]; driverParam: string; enumValues: TEnum; - generated: undefined; + length: TLength; }>; -export class PgVarcharBuilder> extends PgColumnBuilder< +export class PgVarcharBuilder< + T extends ColumnBuilderBaseConfig<'string', 'PgVarchar'> & { length?: number | undefined }, +> extends PgColumnBuilder< T, - { length: number | undefined; enumValues: T['enumValues'] } + { length: T['length']; enumValues: T['enumValues'] }, + { length: T['length'] } > { - static readonly [entityKind]: string = 'PgVarcharBuilder'; + static override readonly [entityKind]: string = 'PgVarcharBuilder'; - constructor(name: string, config: PgVarcharConfig) { + constructor(name: T['name'], config: PgVarcharConfig) { super(name, 'string', 'PgVarchar'); this.config.length = config.length; this.config.enumValues = config.enum; @@ -30,15 +37,18 @@ export class PgVarcharBuilder( table: AnyPgTable<{ name: TTableName }>, - ): PgVarchar> { - return new PgVarchar>(table, this.config as ColumnBuilderRuntimeConfig); + ): PgVarchar & { length: T['length'] }> { + return new PgVarchar & { length: T['length'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); } } -export class PgVarchar> - extends PgColumn +export class PgVarchar & { length?: number | undefined }> + extends PgColumn { - static readonly [entityKind]: string = 'PgVarchar'; + static override readonly [entityKind]: string = 'PgVarchar'; readonly length = this.config.length; override readonly enumValues = this.config.enumValues; @@ -48,14 +58,32 @@ export class PgVarchar> } } -export interface PgVarcharConfig { - length?: number; +export interface PgVarcharConfig< + TEnum extends readonly string[] | string[] | undefined = readonly string[] | string[] | undefined, + TLength extends number | undefined = number | undefined, +> { enum?: TEnum; + length?: TLength; } -export function varchar>( +export function varchar(): PgVarcharBuilderInitial<'', [string, ...string[]], undefined>; +export function varchar< + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( + config?: PgVarcharConfig, L>, +): PgVarcharBuilderInitial<'', Writable, L>; +export function varchar< + TName extends string, + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( name: TName, - config: PgVarcharConfig> = {}, -): PgVarcharBuilderInitial> { - return new PgVarcharBuilder(name, config); + config?: PgVarcharConfig, L>, +): PgVarcharBuilderInitial, L>; +export function varchar(a?: string | PgVarcharConfig, b: PgVarcharConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new PgVarcharBuilder(name, config as any); } diff --git a/drizzle-orm/src/pg-core/columns/vector_extension/bit.ts b/drizzle-orm/src/pg-core/columns/vector_extension/bit.ts index 95d60e7d8..a3bdd6bdf 100644 --- a/drizzle-orm/src/pg-core/columns/vector_extension/bit.ts +++ b/drizzle-orm/src/pg-core/columns/vector_extension/bit.ts @@ -2,27 +2,28 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from '../common.ts'; -export type PgBinaryVectorBuilderInitial = PgBinaryVectorBuilder<{ +export type PgBinaryVectorBuilderInitial = PgBinaryVectorBuilder<{ name: TName; dataType: 'string'; columnType: 'PgBinaryVector'; data: string; driverParam: string; enumValues: undefined; - generated: undefined; + dimensions: TDimensions; }>; -export class PgBinaryVectorBuilder> - extends PgColumnBuilder< - T, - { dimensions: number | undefined } - > -{ - static readonly [entityKind]: string = 'PgBinaryVectorBuilder'; +export class PgBinaryVectorBuilder< + T extends ColumnBuilderBaseConfig<'string', 'PgBinaryVector'> & { dimensions: number }, +> extends PgColumnBuilder< + T, + { dimensions: T['dimensions'] } +> { + static override readonly [entityKind]: string = 'PgBinaryVectorBuilder'; - constructor(name: string, config: PgBinaryVectorConfig) { + constructor(name: string, config: PgBinaryVectorConfig) { super(name, 'string', 'PgBinaryVector'); this.config.dimensions = config.dimensions; } @@ -30,18 +31,18 @@ export class PgBinaryVectorBuilder( table: AnyPgTable<{ name: TTableName }>, - ): PgBinaryVector> { - return new PgBinaryVector>( + ): PgBinaryVector & { dimensions: T['dimensions'] }> { + return new PgBinaryVector & { dimensions: T['dimensions'] }>( table, this.config as ColumnBuilderRuntimeConfig, ); } } -export class PgBinaryVector> - extends PgColumn +export class PgBinaryVector & { dimensions: number }> + extends PgColumn { - static readonly [entityKind]: string = 'PgBinaryVector'; + static override readonly [entityKind]: string = 'PgBinaryVector'; readonly dimensions = this.config.dimensions; @@ -50,13 +51,18 @@ export class PgBinaryVector { + dimensions: TDimensions; } -export function bit( +export function bit( + config: PgBinaryVectorConfig, +): PgBinaryVectorBuilderInitial<'', D>; +export function bit( name: TName, - config: PgBinaryVectorConfig, -): PgBinaryVectorBuilderInitial { + config: PgBinaryVectorConfig, +): PgBinaryVectorBuilderInitial; +export function bit(a: string | PgBinaryVectorConfig, b?: PgBinaryVectorConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new PgBinaryVectorBuilder(name, config); } diff --git a/drizzle-orm/src/pg-core/columns/vector_extension/halfvec.ts b/drizzle-orm/src/pg-core/columns/vector_extension/halfvec.ts index 182beda7e..5f52f5f16 100644 --- a/drizzle-orm/src/pg-core/columns/vector_extension/halfvec.ts +++ b/drizzle-orm/src/pg-core/columns/vector_extension/halfvec.ts @@ -2,25 +2,29 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from '../common.ts'; -export type PgHalfVectorBuilderInitial = PgHalfVectorBuilder<{ +export type PgHalfVectorBuilderInitial = PgHalfVectorBuilder<{ name: TName; dataType: 'array'; columnType: 'PgHalfVector'; data: number[]; driverParam: string; enumValues: undefined; - generated: undefined; + dimensions: TDimensions; }>; -export class PgHalfVectorBuilder> extends PgColumnBuilder< - T, - { dimensions: number | undefined } -> { - static readonly [entityKind]: string = 'PgHalfVectorBuilder'; +export class PgHalfVectorBuilder & { dimensions: number }> + extends PgColumnBuilder< + T, + { dimensions: T['dimensions'] }, + { dimensions: T['dimensions'] } + > +{ + static override readonly [entityKind]: string = 'PgHalfVectorBuilder'; - constructor(name: string, config: PgHalfVectorConfig) { + constructor(name: string, config: PgHalfVectorConfig) { super(name, 'array', 'PgHalfVector'); this.config.dimensions = config.dimensions; } @@ -28,20 +32,20 @@ export class PgHalfVectorBuilder( table: AnyPgTable<{ name: TTableName }>, - ): PgHalfVector> { - return new PgHalfVector>( + ): PgHalfVector & { dimensions: T['dimensions'] }> { + return new PgHalfVector & { dimensions: T['dimensions'] }>( table, this.config as ColumnBuilderRuntimeConfig, ); } } -export class PgHalfVector> - extends PgColumn +export class PgHalfVector & { dimensions: number }> + extends PgColumn { - static readonly [entityKind]: string = 'PgHalfVector'; + static override readonly [entityKind]: string = 'PgHalfVector'; - readonly dimensions = this.config.dimensions; + readonly dimensions: T['dimensions'] = this.config.dimensions; getSQLType(): string { return `halfvec(${this.dimensions})`; @@ -59,13 +63,18 @@ export class PgHalfVector> } } -export interface PgHalfVectorConfig { - dimensions: number; +export interface PgHalfVectorConfig { + dimensions: TDimensions; } -export function halfvec( +export function halfvec( + config: PgHalfVectorConfig, +): PgHalfVectorBuilderInitial<'', D>; +export function halfvec( name: TName, config: PgHalfVectorConfig, -): PgHalfVectorBuilderInitial { +): PgHalfVectorBuilderInitial; +export function halfvec(a: string | PgHalfVectorConfig, b?: PgHalfVectorConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new PgHalfVectorBuilder(name, config); } diff --git a/drizzle-orm/src/pg-core/columns/vector_extension/sparsevec.ts b/drizzle-orm/src/pg-core/columns/vector_extension/sparsevec.ts index 060003bc6..4817fa51f 100644 --- a/drizzle-orm/src/pg-core/columns/vector_extension/sparsevec.ts +++ b/drizzle-orm/src/pg-core/columns/vector_extension/sparsevec.ts @@ -2,6 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from '../common.ts'; export type PgSparseVectorBuilderInitial = PgSparseVectorBuilder<{ @@ -11,7 +12,6 @@ export type PgSparseVectorBuilderInitial = PgSparseVectorB data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class PgSparseVectorBuilder> @@ -20,7 +20,7 @@ export class PgSparseVectorBuilder { - static readonly [entityKind]: string = 'PgSparseVectorBuilder'; + static override readonly [entityKind]: string = 'PgSparseVectorBuilder'; constructor(name: string, config: PgSparseVectorConfig) { super(name, 'string', 'PgSparseVector'); @@ -41,7 +41,7 @@ export class PgSparseVectorBuilder> extends PgColumn { - static readonly [entityKind]: string = 'PgSparseVector'; + static override readonly [entityKind]: string = 'PgSparseVector'; readonly dimensions = this.config.dimensions; @@ -54,9 +54,14 @@ export interface PgSparseVectorConfig { dimensions: number; } +export function sparsevec( + config: PgSparseVectorConfig, +): PgSparseVectorBuilderInitial<''>; export function sparsevec( name: TName, config: PgSparseVectorConfig, -): PgSparseVectorBuilderInitial { +): PgSparseVectorBuilderInitial; +export function sparsevec(a: string | PgSparseVectorConfig, b?: PgSparseVectorConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new PgSparseVectorBuilder(name, config); } diff --git a/drizzle-orm/src/pg-core/columns/vector_extension/vector.ts b/drizzle-orm/src/pg-core/columns/vector_extension/vector.ts index c7099b5dc..f36a9b1ca 100644 --- a/drizzle-orm/src/pg-core/columns/vector_extension/vector.ts +++ b/drizzle-orm/src/pg-core/columns/vector_extension/vector.ts @@ -2,25 +2,29 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnyPgTable } from '~/pg-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; import { PgColumn, PgColumnBuilder } from '../common.ts'; -export type PgVectorBuilderInitial = PgVectorBuilder<{ +export type PgVectorBuilderInitial = PgVectorBuilder<{ name: TName; dataType: 'array'; columnType: 'PgVector'; data: number[]; driverParam: string; enumValues: undefined; - generated: undefined; + dimensions: TDimensions; }>; -export class PgVectorBuilder> extends PgColumnBuilder< - T, - { dimensions: number | undefined } -> { - static readonly [entityKind]: string = 'PgVectorBuilder'; +export class PgVectorBuilder & { dimensions: number }> + extends PgColumnBuilder< + T, + { dimensions: T['dimensions'] }, + { dimensions: T['dimensions'] } + > +{ + static override readonly [entityKind]: string = 'PgVectorBuilder'; - constructor(name: string, config: PgVectorConfig) { + constructor(name: string, config: PgVectorConfig) { super(name, 'array', 'PgVector'); this.config.dimensions = config.dimensions; } @@ -28,17 +32,20 @@ export class PgVectorBuilder( table: AnyPgTable<{ name: TTableName }>, - ): PgVector> { - return new PgVector>(table, this.config as ColumnBuilderRuntimeConfig); + ): PgVector & { dimensions: T['dimensions'] }> { + return new PgVector & { dimensions: T['dimensions'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); } } -export class PgVector> - extends PgColumn +export class PgVector & { dimensions: number | undefined }> + extends PgColumn { - static readonly [entityKind]: string = 'PgVector'; + static override readonly [entityKind]: string = 'PgVector'; - readonly dimensions = this.config.dimensions; + readonly dimensions: T['dimensions'] = this.config.dimensions; getSQLType(): string { return `vector(${this.dimensions})`; @@ -56,13 +63,18 @@ export class PgVector> } } -export interface PgVectorConfig { - dimensions: number; +export interface PgVectorConfig { + dimensions: TDimensions; } -export function vector( +export function vector( + config: PgVectorConfig, +): PgVectorBuilderInitial<'', D>; +export function vector( name: TName, - config: PgVectorConfig, -): PgVectorBuilderInitial { + config: PgVectorConfig, +): PgVectorBuilderInitial; +export function vector(a: string | PgVectorConfig, b?: PgVectorConfig) { + const { name, config } = getColumnNameAndConfig(a, b); return new PgVectorBuilder(name, config); } diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index 4e8d2f354..29dc4f166 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -19,15 +19,17 @@ import type { PgTable } from '~/pg-core/table.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; -import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; +import { type ColumnsSelection, type SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; import { WithSubquery } from '~/subquery.ts'; -import type { DrizzleTypeError } from '~/utils.ts'; +import type { DrizzleTypeError, NeonAuthToken } from '~/utils.ts'; import type { PgColumn } from './columns/index.ts'; +import { PgCountBuilder } from './query-builders/count.ts'; import { RelationalQueryBuilder } from './query-builders/query.ts'; import { PgRaw } from './query-builders/raw.ts'; import { PgRefreshMaterializedView } from './query-builders/refresh-materialized-view.ts'; import type { SelectedFields } from './query-builders/select.types.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; +import type { PgViewBase } from './view-base.ts'; import type { PgMaterializedView } from './view.ts'; export class PgDatabase< @@ -119,12 +121,13 @@ export class PgDatabase< * ``` */ $with(alias: TAlias) { + const self = this; return { as( qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), ): WithSubqueryWithSelection { if (typeof qb === 'function') { - qb = qb(new QueryBuilder()); + qb = qb(new QueryBuilder(self.dialect)); } return new Proxy( @@ -135,6 +138,13 @@ export class PgDatabase< }; } + $count( + source: PgTable | PgViewBase | SQL | SQLWrapper, + filters?: SQL, + ) { + return new PgCountBuilder({ source, filters, session: this.session }); + } + /** * Incorporates a previously defined CTE (using `$with`) into the main query. * @@ -587,11 +597,13 @@ export class PgDatabase< return new PgRefreshMaterializedView(view, this.session, this.dialect); } + protected authToken?: NeonAuthToken; + execute = Record>( - query: SQLWrapper, + query: SQLWrapper | string, ): PgRaw> { - const sql = query.getSQL(); - const builtQuery = this.dialect.sqlToQuery(sql); + const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); + const builtQuery = this.dialect.sqlToQuery(sequel); const prepared = this.session.prepareQuery< PreparedQueryConfig & { execute: PgQueryResultKind } >( @@ -601,8 +613,8 @@ export class PgDatabase< false, ); return new PgRaw( - () => prepared.execute(), - sql, + () => prepared.execute(undefined, this.authToken), + sequel, builtQuery, (result) => prepared.mapResult(result, true), ); @@ -622,7 +634,11 @@ export const withReplicas = < HKT extends PgQueryResultHKT, TFullSchema extends Record, TSchema extends TablesRelationalConfig, - Q extends PgDatabase, + Q extends PgDatabase< + HKT, + TFullSchema, + TSchema extends Record ? ExtractTablesWithRelations : TSchema + >, >( primary: Q, replicas: [Q, ...Q[]], @@ -631,7 +647,8 @@ export const withReplicas = < const select: Q['select'] = (...args: []) => getReplica(replicas).select(...args); const selectDistinct: Q['selectDistinct'] = (...args: []) => getReplica(replicas).selectDistinct(...args); const selectDistinctOn: Q['selectDistinctOn'] = (...args: [any]) => getReplica(replicas).selectDistinctOn(...args); - const $with: Q['with'] = (...args: any) => getReplica(replicas).with(...args); + const _with: Q['with'] = (...args: any) => getReplica(replicas).with(...args); + const $with: Q['$with'] = (arg: any) => getReplica(replicas).$with(arg); const update: Q['update'] = (...args: [any]) => primary.update(...args); const insert: Q['insert'] = (...args: [any]) => primary.insert(...args); @@ -653,7 +670,8 @@ export const withReplicas = < select, selectDistinct, selectDistinctOn, - with: $with, + $with, + with: _with, get query() { return getReplica(replicas).query; }, diff --git a/drizzle-orm/src/pg-core/dialect.ts b/drizzle-orm/src/pg-core/dialect.ts index fff2ce65a..52bd10ed5 100644 --- a/drizzle-orm/src/pg-core/dialect.ts +++ b/drizzle-orm/src/pg-core/dialect.ts @@ -1,4 +1,5 @@ import { aliasedTable, aliasedTableColumn, mapColumnsInAliasedSQLToAlias, mapColumnsInSQLToAlias } from '~/alias.ts'; +import { CasingCache } from '~/casing.ts'; import { Column } from '~/column.ts'; import { entityKind, is } from '~/entity.ts'; import { DrizzleError } from '~/errors.ts'; @@ -16,6 +17,7 @@ import { PgUUID, } from '~/pg-core/columns/index.ts'; import type { + AnyPgSelectQueryBuilder, PgDeleteConfig, PgInsertConfig, PgSelectJoinConfig, @@ -48,15 +50,26 @@ import { } from '~/sql/sql.ts'; import { Subquery } from '~/subquery.ts'; import { getTableName, getTableUniqueName, Table } from '~/table.ts'; -import { orderSelectedFields, type UpdateSet } from '~/utils.ts'; +import { type Casing, orderSelectedFields, type UpdateSet } from '~/utils.ts'; import { ViewBaseConfig } from '~/view-common.ts'; import type { PgSession } from './session.ts'; import { PgViewBase } from './view-base.ts'; import type { PgMaterializedView } from './view.ts'; +export interface PgDialectConfig { + casing?: Casing; +} + export class PgDialect { static readonly [entityKind]: string = 'PgDialect'; + /** @internal */ + readonly casing: CasingCache; + + constructor(config?: PgDialectConfig) { + this.casing = new CasingCache(config?.casing); + } + async migrate(migrations: MigrationMeta[], session: PgSession, config: string | MigrationConfig): Promise { const migrationsTable = typeof config === 'string' ? '__drizzle_migrations' @@ -148,7 +161,7 @@ export class PgDialect { const col = tableColumns[colName]!; const value = set[colName] ?? sql.param(col.onUpdateFn!(), col); - const res = sql`${sql.identifier(col.name)} = ${value}`; + const res = sql`${sql.identifier(this.casing.getColumnCasing(col))} = ${value}`; if (i < setSize - 1) { return [res, sql.raw(', ')]; @@ -157,18 +170,30 @@ export class PgDialect { })); } - buildUpdateQuery({ table, set, where, returning, withList }: PgUpdateConfig): SQL { + buildUpdateQuery({ table, set, where, returning, withList, from, joins }: PgUpdateConfig): SQL { const withSql = this.buildWithCTE(withList); + const tableName = table[PgTable.Symbol.Name]; + const tableSchema = table[PgTable.Symbol.Schema]; + const origTableName = table[PgTable.Symbol.OriginalName]; + const alias = tableName === origTableName ? undefined : tableName; + const tableSql = sql`${tableSchema ? sql`${sql.identifier(tableSchema)}.` : undefined}${ + sql.identifier(origTableName) + }${alias && sql` ${sql.identifier(alias)}`}`; + const setSql = this.buildUpdateSet(table, set); + const fromSql = from && sql.join([sql.raw(' from '), this.buildFromTable(from)]); + + const joinsSql = this.buildJoins(joins); + const returningSql = returning - ? sql` returning ${this.buildSelection(returning, { isSingleTable: true })}` + ? sql` returning ${this.buildSelection(returning, { isSingleTable: !from })}` : undefined; const whereSql = where ? sql` where ${where}` : undefined; - return sql`${withSql}update ${table} set ${setSql}${whereSql}${returningSql}`; + return sql`${withSql}update ${tableSql} set ${setSql}${fromSql}${joinsSql}${whereSql}${returningSql}`; } /** @@ -202,7 +227,7 @@ export class PgDialect { new SQL( query.queryChunks.map((c) => { if (is(c, PgColumn)) { - return sql.identifier(c.name); + return sql.identifier(this.casing.getColumnCasing(c)); } return c; }), @@ -217,7 +242,7 @@ export class PgDialect { } } else if (is(field, Column)) { if (isSingleTable) { - chunk.push(sql.identifier(field.name)); + chunk.push(sql.identifier(this.casing.getColumnCasing(field))); } else { chunk.push(field); } @@ -233,6 +258,67 @@ export class PgDialect { return sql.join(chunks); } + private buildJoins(joins: PgSelectJoinConfig[] | undefined): SQL | undefined { + if (!joins || joins.length === 0) { + return undefined; + } + + const joinsArray: SQL[] = []; + + for (const [index, joinMeta] of joins.entries()) { + if (index === 0) { + joinsArray.push(sql` `); + } + const table = joinMeta.table; + const lateralSql = joinMeta.lateral ? sql` lateral` : undefined; + + if (is(table, PgTable)) { + const tableName = table[PgTable.Symbol.Name]; + const tableSchema = table[PgTable.Symbol.Schema]; + const origTableName = table[PgTable.Symbol.OriginalName]; + const alias = tableName === origTableName ? undefined : joinMeta.alias; + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ + tableSchema ? sql`${sql.identifier(tableSchema)}.` : undefined + }${sql.identifier(origTableName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, + ); + } else if (is(table, View)) { + const viewName = table[ViewBaseConfig].name; + const viewSchema = table[ViewBaseConfig].schema; + const origViewName = table[ViewBaseConfig].originalName; + const alias = viewName === origViewName ? undefined : joinMeta.alias; + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ + viewSchema ? sql`${sql.identifier(viewSchema)}.` : undefined + }${sql.identifier(origViewName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, + ); + } else { + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${table} on ${joinMeta.on}`, + ); + } + if (index < joins.length - 1) { + joinsArray.push(sql` `); + } + } + + return sql.join(joinsArray); + } + + private buildFromTable( + table: SQL | Subquery | PgViewBase | PgTable | undefined, + ): SQL | Subquery | PgViewBase | PgTable | undefined { + if (is(table, Table) && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) { + let fullName = sql`${sql.identifier(table[Table.Symbol.OriginalName])}`; + if (table[Table.Symbol.Schema]) { + fullName = sql`${sql.identifier(table[Table.Symbol.Schema]!)}.${fullName}`; + } + return sql`${fullName} ${sql.identifier(table[Table.Symbol.Name])}`; + } + + return table; + } + buildSelectQuery( { withList, @@ -288,60 +374,9 @@ export class PgDialect { const selection = this.buildSelection(fieldsList, { isSingleTable }); - const tableSql = (() => { - if (is(table, Table) && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) { - let fullName = sql`${sql.identifier(table[Table.Symbol.OriginalName])}`; - if (table[Table.Symbol.Schema]) { - fullName = sql`${sql.identifier(table[Table.Symbol.Schema]!)}.${fullName}`; - } - return sql`${fullName} ${sql.identifier(table[Table.Symbol.Name])}`; - } - - return table; - })(); - - const joinsArray: SQL[] = []; - - if (joins) { - for (const [index, joinMeta] of joins.entries()) { - if (index === 0) { - joinsArray.push(sql` `); - } - const table = joinMeta.table; - const lateralSql = joinMeta.lateral ? sql` lateral` : undefined; - - if (is(table, PgTable)) { - const tableName = table[PgTable.Symbol.Name]; - const tableSchema = table[PgTable.Symbol.Schema]; - const origTableName = table[PgTable.Symbol.OriginalName]; - const alias = tableName === origTableName ? undefined : joinMeta.alias; - joinsArray.push( - sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ - tableSchema ? sql`${sql.identifier(tableSchema)}.` : undefined - }${sql.identifier(origTableName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, - ); - } else if (is(table, View)) { - const viewName = table[ViewBaseConfig].name; - const viewSchema = table[ViewBaseConfig].schema; - const origViewName = table[ViewBaseConfig].originalName; - const alias = viewName === origViewName ? undefined : joinMeta.alias; - joinsArray.push( - sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ - viewSchema ? sql`${sql.identifier(viewSchema)}.` : undefined - }${sql.identifier(origViewName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, - ); - } else { - joinsArray.push( - sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${table} on ${joinMeta.on}`, - ); - } - if (index < joins.length - 1) { - joinsArray.push(sql` `); - } - } - } + const tableSql = this.buildFromTable(table); - const joinsSql = sql.join(joinsArray); + const joinsSql = this.buildJoins(joins); const whereSql = where ? sql` where ${where}` : undefined; @@ -456,40 +491,57 @@ export class PgDialect { return sql`${leftChunk}${operatorChunk}${rightChunk}${orderBySql}${limitSql}${offsetSql}`; } - buildInsertQuery({ table, values, onConflict, returning, withList }: PgInsertConfig): SQL { + buildInsertQuery( + { table, values: valuesOrSelect, onConflict, returning, withList, select, overridingSystemValue_ }: PgInsertConfig, + ): SQL { const valuesSqlList: ((SQLChunk | SQL)[] | SQL)[] = []; const columns: Record = table[Table.Symbol.Columns]; const colEntries: [string, PgColumn][] = Object.entries(columns).filter(([_, col]) => !col.shouldDisableInsert()); - const insertOrder = colEntries.map(([, column]) => sql.identifier(column.name)); - - for (const [valueIndex, value] of values.entries()) { - const valueList: (SQLChunk | SQL)[] = []; - for (const [fieldName, col] of colEntries) { - const colValue = value[fieldName]; - if (colValue === undefined || (is(colValue, Param) && colValue.value === undefined)) { - // eslint-disable-next-line unicorn/no-negated-condition - if (col.defaultFn !== undefined) { - const defaultFnResult = col.defaultFn(); - const defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); - valueList.push(defaultValue); + const insertOrder = colEntries.map( + ([, column]) => sql.identifier(this.casing.getColumnCasing(column)), + ); + + if (select) { + const select = valuesOrSelect as AnyPgSelectQueryBuilder | SQL; + + if (is(select, SQL)) { + valuesSqlList.push(select); + } else { + valuesSqlList.push(select.getSQL()); + } + } else { + const values = valuesOrSelect as Record[]; + valuesSqlList.push(sql.raw('values ')); + + for (const [valueIndex, value] of values.entries()) { + const valueList: (SQLChunk | SQL)[] = []; + for (const [fieldName, col] of colEntries) { + const colValue = value[fieldName]; + if (colValue === undefined || (is(colValue, Param) && colValue.value === undefined)) { // eslint-disable-next-line unicorn/no-negated-condition - } else if (!col.default && col.onUpdateFn !== undefined) { - const onUpdateFnResult = col.onUpdateFn(); - const newValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col); - valueList.push(newValue); + if (col.defaultFn !== undefined) { + const defaultFnResult = col.defaultFn(); + const defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); + valueList.push(defaultValue); + // eslint-disable-next-line unicorn/no-negated-condition + } else if (!col.default && col.onUpdateFn !== undefined) { + const onUpdateFnResult = col.onUpdateFn(); + const newValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col); + valueList.push(newValue); + } else { + valueList.push(sql`default`); + } } else { - valueList.push(sql`default`); + valueList.push(colValue); } - } else { - valueList.push(colValue); } - } - valuesSqlList.push(valueList); - if (valueIndex < values.length - 1) { - valuesSqlList.push(sql`, `); + valuesSqlList.push(valueList); + if (valueIndex < values.length - 1) { + valuesSqlList.push(sql`, `); + } } } @@ -503,7 +555,9 @@ export class PgDialect { const onConflictSql = onConflict ? sql` on conflict ${onConflict}` : undefined; - return sql`${withSql}insert into ${table} ${insertOrder} values ${valuesSql}${onConflictSql}${returningSql}`; + const overridingSql = overridingSystemValue_ === true ? sql`overriding system value ` : undefined; + + return sql`${withSql}insert into ${table} ${insertOrder} ${overridingSql}${valuesSql}${onConflictSql}${returningSql}`; } buildRefreshMaterializedViewQuery( @@ -535,6 +589,7 @@ export class PgDialect { sqlToQuery(sql: SQL, invokeSource?: 'indexes' | undefined): QueryWithTypings { return sql.toQuery({ + casing: this.casing, escapeName: this.escapeName, escapeParam: this.escapeParam, escapeString: this.escapeString, @@ -1112,7 +1167,9 @@ export class PgDialect { })); } else { const aliasedColumns = Object.fromEntries( - Object.entries(tableConfig.columns).map(([key, value]) => [key, aliasedTableColumn(value, tableAlias)]), + Object.entries(tableConfig.columns).map(( + [key, value], + ) => [key, aliasedTableColumn(value, tableAlias)]), ); if (config.where) { diff --git a/drizzle-orm/src/pg-core/foreign-keys.ts b/drizzle-orm/src/pg-core/foreign-keys.ts index 841ac4aa8..f8ba0b862 100644 --- a/drizzle-orm/src/pg-core/foreign-keys.ts +++ b/drizzle-orm/src/pg-core/foreign-keys.ts @@ -1,6 +1,7 @@ import { entityKind } from '~/entity.ts'; +import { TableName } from '~/table.utils.ts'; import type { AnyPgColumn, PgColumn } from './columns/index.ts'; -import { PgTable } from './table.ts'; +import type { PgTable } from './table.ts'; export type UpdateDeleteAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default'; @@ -80,9 +81,9 @@ export class ForeignKey { const columnNames = columns.map((column) => column.name); const foreignColumnNames = foreignColumns.map((column) => column.name); const chunks = [ - this.table[PgTable.Symbol.Name], + this.table[TableName], ...columnNames, - foreignColumns[0]!.table[PgTable.Symbol.Name], + foreignColumns[0]!.table[TableName], ...foreignColumnNames, ]; return name ?? `${chunks.join('_')}_fk`; diff --git a/drizzle-orm/src/pg-core/index.ts b/drizzle-orm/src/pg-core/index.ts index 084633c4a..ebc436bc1 100644 --- a/drizzle-orm/src/pg-core/index.ts +++ b/drizzle-orm/src/pg-core/index.ts @@ -5,8 +5,10 @@ export * from './db.ts'; export * from './dialect.ts'; export * from './foreign-keys.ts'; export * from './indexes.ts'; +export * from './policies.ts'; export * from './primary-keys.ts'; export * from './query-builders/index.ts'; +export * from './roles.ts'; export * from './schema.ts'; export * from './sequence.ts'; export * from './session.ts'; diff --git a/drizzle-orm/src/pg-core/indexes.ts b/drizzle-orm/src/pg-core/indexes.ts index c0e0888c9..bf53a56ce 100644 --- a/drizzle-orm/src/pg-core/indexes.ts +++ b/drizzle-orm/src/pg-core/indexes.ts @@ -118,7 +118,7 @@ export class IndexBuilderOn { return it; } it = it as ExtraConfigColumn; - const clonedIndexedColumn = new IndexedColumn(it.name, it.columnType!, it.indexConfig!); + const clonedIndexedColumn = new IndexedColumn(it.name, !!it.keyAsName, it.columnType!, it.indexConfig!); it.indexConfig = JSON.parse(JSON.stringify(it.defaultConfig)); return clonedIndexedColumn; }), @@ -135,7 +135,7 @@ export class IndexBuilderOn { return it; } it = it as ExtraConfigColumn; - const clonedIndexedColumn = new IndexedColumn(it.name, it.columnType!, it.indexConfig!); + const clonedIndexedColumn = new IndexedColumn(it.name, !!it.keyAsName, it.columnType!, it.indexConfig!); it.indexConfig = it.defaultConfig; return clonedIndexedColumn; }), @@ -166,7 +166,7 @@ export class IndexBuilderOn { return it; } it = it as ExtraConfigColumn; - const clonedIndexedColumn = new IndexedColumn(it.name, it.columnType!, it.indexConfig!); + const clonedIndexedColumn = new IndexedColumn(it.name, !!it.keyAsName, it.columnType!, it.indexConfig!); it.indexConfig = JSON.parse(JSON.stringify(it.defaultConfig)); return clonedIndexedColumn; }), diff --git a/drizzle-orm/src/pg-core/policies.ts b/drizzle-orm/src/pg-core/policies.ts new file mode 100644 index 000000000..9c2f94f28 --- /dev/null +++ b/drizzle-orm/src/pg-core/policies.ts @@ -0,0 +1,56 @@ +import { entityKind } from '~/entity.ts'; +import type { SQL } from '~/sql/sql.ts'; +import type { PgRole } from './roles.ts'; +import type { PgTable } from './table.ts'; + +export type PgPolicyToOption = + | 'public' + | 'current_role' + | 'current_user' + | 'session_user' + | (string & {}) + | PgPolicyToOption[] + | PgRole; + +export interface PgPolicyConfig { + as?: 'permissive' | 'restrictive'; + for?: 'all' | 'select' | 'insert' | 'update' | 'delete'; + to?: PgPolicyToOption; + using?: SQL; + withCheck?: SQL; +} + +export class PgPolicy implements PgPolicyConfig { + static readonly [entityKind]: string = 'PgPolicy'; + + readonly as: PgPolicyConfig['as']; + readonly for: PgPolicyConfig['for']; + readonly to: PgPolicyConfig['to']; + readonly using: PgPolicyConfig['using']; + readonly withCheck: PgPolicyConfig['withCheck']; + + /** @internal */ + _linkedTable?: PgTable; + + constructor( + readonly name: string, + config?: PgPolicyConfig, + ) { + if (config) { + this.as = config.as; + this.for = config.for; + this.to = config.to; + this.using = config.using; + this.withCheck = config.withCheck; + } + } + + link(table: PgTable): this { + this._linkedTable = table; + return this; + } +} + +export function pgPolicy(name: string, config?: PgPolicyConfig) { + return new PgPolicy(name, config); +} diff --git a/drizzle-orm/src/pg-core/query-builders/count.ts b/drizzle-orm/src/pg-core/query-builders/count.ts new file mode 100644 index 000000000..a86ffd745 --- /dev/null +++ b/drizzle-orm/src/pg-core/query-builders/count.ts @@ -0,0 +1,86 @@ +import { entityKind } from '~/entity.ts'; +import { SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; +import type { NeonAuthToken } from '~/utils.ts'; +import type { PgSession } from '../session.ts'; +import type { PgTable } from '../table.ts'; + +export class PgCountBuilder< + TSession extends PgSession, +> extends SQL implements Promise, SQLWrapper { + private sql: SQL; + private token?: NeonAuthToken; + + static override readonly [entityKind] = 'PgCountBuilder'; + [Symbol.toStringTag] = 'PgCountBuilder'; + + private session: TSession; + + private static buildEmbeddedCount( + source: PgTable | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`; + } + + private static buildCount( + source: PgTable | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`select count(*) as count from ${source}${sql.raw(' where ').if(filters)}${filters};`; + } + + constructor( + readonly params: { + source: PgTable | SQL | SQLWrapper; + filters?: SQL; + session: TSession; + }, + ) { + super(PgCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks); + + this.mapWith(Number); + + this.session = params.session; + + this.sql = PgCountBuilder.buildCount( + params.source, + params.filters, + ); + } + + /** @intrnal */ + setToken(token?: NeonAuthToken) { + this.token = token; + return this; + } + + then( + onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined, + ): Promise { + return Promise.resolve(this.session.count(this.sql, this.token)) + .then( + onfulfilled, + onrejected, + ); + } + + catch( + onRejected?: ((reason: any) => any) | null | undefined, + ): Promise { + return this.then(undefined, onRejected); + } + + finally(onFinally?: (() => void) | null | undefined): Promise { + return this.then( + (value) => { + onFinally?.(); + return value; + }, + (reason) => { + onFinally?.(); + throw reason; + }, + ); + } +} diff --git a/drizzle-orm/src/pg-core/query-builders/delete.ts b/drizzle-orm/src/pg-core/query-builders/delete.ts index dc127f167..682e52e2d 100644 --- a/drizzle-orm/src/pg-core/query-builders/delete.ts +++ b/drizzle-orm/src/pg-core/query-builders/delete.ts @@ -15,7 +15,7 @@ import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; import type { Subquery } from '~/subquery.ts'; import { Table } from '~/table.ts'; import { tracer } from '~/tracing.ts'; -import { orderSelectedFields } from '~/utils.ts'; +import { type NeonAuthToken, orderSelectedFields } from '~/utils.ts'; import type { PgColumn } from '../columns/common.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; @@ -128,7 +128,7 @@ export class PgDeleteBase< RunnableQuery : TReturning[], 'pg'>, SQLWrapper { - static readonly [entityKind]: string = 'PgDelete'; + static override readonly [entityKind]: string = 'PgDelete'; private config: PgDeleteConfig; @@ -232,9 +232,16 @@ export class PgDeleteBase< return this._prepare(name); } + private authToken?: NeonAuthToken; + /** @internal */ + setToken(token?: NeonAuthToken) { + this.authToken = token; + return this; + } + override execute: ReturnType['execute'] = (placeholderValues) => { return tracer.startActiveSpan('drizzle.operation', () => { - return this._prepare().execute(placeholderValues); + return this._prepare().execute(placeholderValues, this.authToken); }); }; diff --git a/drizzle-orm/src/pg-core/query-builders/insert.ts b/drizzle-orm/src/pg-core/query-builders/insert.ts index c27f8ce9b..2cf266be4 100644 --- a/drizzle-orm/src/pg-core/query-builders/insert.ts +++ b/drizzle-orm/src/pg-core/query-builders/insert.ts @@ -8,35 +8,51 @@ import type { PgSession, PreparedQueryConfig, } from '~/pg-core/session.ts'; -import type { PgTable } from '~/pg-core/table.ts'; +import type { PgTable, TableConfig } from '~/pg-core/table.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { SelectResultFields } from '~/query-builders/select.types.ts'; import { QueryPromise } from '~/query-promise.ts'; import type { RunnableQuery } from '~/runnable-query.ts'; import type { Placeholder, Query, SQLWrapper } from '~/sql/sql.ts'; import { Param, SQL, sql } from '~/sql/sql.ts'; import type { Subquery } from '~/subquery.ts'; -import { Table } from '~/table.ts'; +import type { InferInsertModel } from '~/table.ts'; +import { Columns, Table } from '~/table.ts'; import { tracer } from '~/tracing.ts'; -import { mapUpdateSet, orderSelectedFields } from '~/utils.ts'; -import type { PgColumn } from '../columns/common.ts'; +import { haveSameKeys, mapUpdateSet, type NeonAuthToken, orderSelectedFields } from '~/utils.ts'; +import type { AnyPgColumn, PgColumn } from '../columns/common.ts'; +import { QueryBuilder } from './query-builder.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; import type { PgUpdateSetSource } from './update.ts'; export interface PgInsertConfig { table: TTable; - values: Record[]; + values: Record[] | PgInsertSelectQueryBuilder | SQL; withList?: Subquery[]; onConflict?: SQL; returning?: SelectedFieldsOrdered; + select?: boolean; + overridingSystemValue_?: boolean; } -export type PgInsertValue = +export type PgInsertValue, OverrideT extends boolean = false> = & { - [Key in keyof TTable['$inferInsert']]: TTable['$inferInsert'][Key] | SQL | Placeholder; + [Key in keyof InferInsertModel]: + | InferInsertModel[Key] + | SQL + | Placeholder; } & {}; -export class PgInsertBuilder { +export type PgInsertSelectQueryBuilder = TypedQueryBuilder< + { [K in keyof TTable['$inferInsert']]: AnyPgColumn | SQL | SQL.Aliased | TTable['$inferInsert'][K] } +>; + +export class PgInsertBuilder< + TTable extends PgTable, + TQueryResult extends PgQueryResultHKT, + OverrideT extends boolean = false, +> { static readonly [entityKind]: string = 'PgInsertBuilder'; constructor( @@ -44,11 +60,26 @@ export class PgInsertBuilder): PgInsertBase; - values(values: PgInsertValue[]): PgInsertBase; - values(values: PgInsertValue | PgInsertValue[]): PgInsertBase { + private authToken?: NeonAuthToken; + /** @internal */ + setToken(token?: NeonAuthToken) { + this.authToken = token; + return this; + } + + overridingSystemValue(): Omit, 'overridingSystemValue'> { + this.overridingSystemValue_ = true; + return this as any; + } + + values(value: PgInsertValue): PgInsertBase; + values(values: PgInsertValue[]): PgInsertBase; + values( + values: PgInsertValue | PgInsertValue[], + ): PgInsertBase { values = Array.isArray(values) ? values : [values]; if (values.length === 0) { throw new Error('values() must be called with at least one value'); @@ -63,7 +94,39 @@ export class PgInsertBuilder PgInsertSelectQueryBuilder): PgInsertBase; + select(selectQuery: (qb: QueryBuilder) => SQL): PgInsertBase; + select(selectQuery: SQL): PgInsertBase; + select(selectQuery: PgInsertSelectQueryBuilder): PgInsertBase; + select( + selectQuery: + | SQL + | PgInsertSelectQueryBuilder + | ((qb: QueryBuilder) => PgInsertSelectQueryBuilder | SQL), + ): PgInsertBase { + const select = typeof selectQuery === 'function' ? selectQuery(new QueryBuilder()) : selectQuery; + + if ( + !is(select, SQL) + && !haveSameKeys(this.table[Columns], select._.selectedFields) + ) { + throw new Error( + 'Insert select error: selected fields are not the same or are in a different order compared to the table definition', + ); + } + + return new PgInsertBase(this.table, select, this.session, this.dialect, this.withList, true); } } @@ -166,7 +229,7 @@ export class PgInsertBase< RunnableQuery : TReturning[], 'pg'>, SQLWrapper { - static readonly [entityKind]: string = 'PgInsert'; + static override readonly [entityKind]: string = 'PgInsert'; private config: PgInsertConfig; @@ -176,9 +239,11 @@ export class PgInsertBase< private session: PgSession, private dialect: PgDialect, withList?: Subquery[], + select?: boolean, + overridingSystemValue_?: boolean, ) { super(); - this.config = { table, values, withList }; + this.config = { table, values: values as any, withList, select, overridingSystemValue_ }; } /** @@ -242,8 +307,8 @@ export class PgInsertBase< } else { let targetColumn = ''; targetColumn = Array.isArray(config.target) - ? config.target.map((it) => this.dialect.escapeName(it.name)).join(',') - : this.dialect.escapeName(config.target.name); + ? config.target.map((it) => this.dialect.escapeName(this.dialect.casing.getColumnCasing(it))).join(',') + : this.dialect.escapeName(this.dialect.casing.getColumnCasing(config.target)); const whereSql = config.where ? sql` where ${config.where}` : undefined; this.config.onConflict = sql`(${sql.raw(targetColumn)})${whereSql} do nothing`; @@ -294,8 +359,8 @@ export class PgInsertBase< const setSql = this.dialect.buildUpdateSet(this.config.table, mapUpdateSet(this.config.table, config.set)); let targetColumn = ''; targetColumn = Array.isArray(config.target) - ? config.target.map((it) => this.dialect.escapeName(it.name)).join(',') - : this.dialect.escapeName(config.target.name); + ? config.target.map((it) => this.dialect.escapeName(this.dialect.casing.getColumnCasing(it))).join(',') + : this.dialect.escapeName(this.dialect.casing.getColumnCasing(config.target)); this.config.onConflict = sql`(${ sql.raw(targetColumn) })${targetWhereSql} do update set ${setSql}${whereSql}${setWhereSql}`; @@ -327,9 +392,16 @@ export class PgInsertBase< return this._prepare(name); } + private authToken?: NeonAuthToken; + /** @internal */ + setToken(token?: NeonAuthToken) { + this.authToken = token; + return this; + } + override execute: ReturnType['execute'] = (placeholderValues) => { return tracer.startActiveSpan('drizzle.operation', () => { - return this._prepare().execute(placeholderValues); + return this._prepare().execute(placeholderValues, this.authToken); }); }; diff --git a/drizzle-orm/src/pg-core/query-builders/query-builder.ts b/drizzle-orm/src/pg-core/query-builders/query-builder.ts index fd29ee10d..9f08f642d 100644 --- a/drizzle-orm/src/pg-core/query-builders/query-builder.ts +++ b/drizzle-orm/src/pg-core/query-builders/query-builder.ts @@ -1,4 +1,5 @@ -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; +import type { PgDialectConfig } from '~/pg-core/dialect.ts'; import { PgDialect } from '~/pg-core/dialect.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; @@ -13,6 +14,12 @@ export class QueryBuilder { static readonly [entityKind]: string = 'PgQueryBuilder'; private dialect: PgDialect | undefined; + private dialectConfig: PgDialectConfig | undefined; + + constructor(dialect?: PgDialect | PgDialectConfig) { + this.dialect = is(dialect, PgDialect) ? dialect : undefined; + this.dialectConfig = is(dialect, PgDialect) ? undefined : dialect; + } $with(alias: TAlias) { const queryBuilder = this; @@ -121,7 +128,7 @@ export class QueryBuilder { // Lazy load dialect to avoid circular dependency private getDialect() { if (!this.dialect) { - this.dialect = new PgDialect(); + this.dialect = new PgDialect(this.dialectConfig); } return this.dialect; diff --git a/drizzle-orm/src/pg-core/query-builders/query.ts b/drizzle-orm/src/pg-core/query-builders/query.ts index 17cea6cff..816b60500 100644 --- a/drizzle-orm/src/pg-core/query-builders/query.ts +++ b/drizzle-orm/src/pg-core/query-builders/query.ts @@ -11,7 +11,7 @@ import { import type { RunnableQuery } from '~/runnable-query.ts'; import type { Query, QueryWithTypings, SQL, SQLWrapper } from '~/sql/sql.ts'; import { tracer } from '~/tracing.ts'; -import type { KnownKeysOnly } from '~/utils.ts'; +import type { KnownKeysOnly, NeonAuthToken } from '~/utils.ts'; import type { PgDialect } from '../dialect.ts'; import type { PgPreparedQuery, PgSession, PreparedQueryConfig } from '../session.ts'; import type { PgTable } from '../table.ts'; @@ -65,7 +65,7 @@ export class RelationalQueryBuilder extends QueryPromise implements RunnableQuery, SQLWrapper { - static readonly [entityKind]: string = 'PgRelationalQuery'; + static override readonly [entityKind]: string = 'PgRelationalQuery'; declare readonly _: { readonly dialect: 'pg'; @@ -142,9 +142,16 @@ export class PgRelationalQuery extends QueryPromise return this._toSQL().builtQuery; } + private authToken?: NeonAuthToken; + /** @internal */ + setToken(token?: NeonAuthToken) { + this.authToken = token; + return this; + } + override execute(): Promise { return tracer.startActiveSpan('drizzle.operation', () => { - return this._prepare().execute(); + return this._prepare().execute(undefined, this.authToken); }); } } diff --git a/drizzle-orm/src/pg-core/query-builders/raw.ts b/drizzle-orm/src/pg-core/query-builders/raw.ts index 871581dfb..d08c1ee14 100644 --- a/drizzle-orm/src/pg-core/query-builders/raw.ts +++ b/drizzle-orm/src/pg-core/query-builders/raw.ts @@ -9,7 +9,7 @@ export interface PgRaw extends QueryPromise, RunnableQuery extends QueryPromise implements RunnableQuery, SQLWrapper, PreparedQuery { - static readonly [entityKind]: string = 'PgRaw'; + static override readonly [entityKind]: string = 'PgRaw'; declare readonly _: { readonly dialect: 'pg'; diff --git a/drizzle-orm/src/pg-core/query-builders/refresh-materialized-view.ts b/drizzle-orm/src/pg-core/query-builders/refresh-materialized-view.ts index d2bedac68..c20baaf5a 100644 --- a/drizzle-orm/src/pg-core/query-builders/refresh-materialized-view.ts +++ b/drizzle-orm/src/pg-core/query-builders/refresh-materialized-view.ts @@ -12,6 +12,7 @@ import { QueryPromise } from '~/query-promise.ts'; import type { RunnableQuery } from '~/runnable-query.ts'; import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; import { tracer } from '~/tracing.ts'; +import type { NeonAuthToken } from '~/utils'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PgRefreshMaterializedView @@ -30,7 +31,7 @@ export class PgRefreshMaterializedView extends QueryPromise> implements RunnableQuery, 'pg'>, SQLWrapper { - static readonly [entityKind]: string = 'PgRefreshMaterializedView'; + static override readonly [entityKind]: string = 'PgRefreshMaterializedView'; private config: { view: PgMaterializedView; @@ -92,9 +93,16 @@ export class PgRefreshMaterializedView return this._prepare(name); } + private authToken?: NeonAuthToken; + /** @internal */ + setToken(token: NeonAuthToken) { + this.authToken = token; + return this; + } + execute: ReturnType['execute'] = (placeholderValues) => { return tracer.startActiveSpan('drizzle.operation', () => { - return this._prepare().execute(placeholderValues); + return this._prepare().execute(placeholderValues, this.authToken); }); }; } diff --git a/drizzle-orm/src/pg-core/query-builders/select.ts b/drizzle-orm/src/pg-core/query-builders/select.ts index d2406995b..597991f79 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.ts @@ -24,7 +24,14 @@ import type { ColumnsSelection, Placeholder, Query, SQLWrapper } from '~/sql/sql import { Subquery } from '~/subquery.ts'; import { Table } from '~/table.ts'; import { tracer } from '~/tracing.ts'; -import { applyMixins, getTableColumns, getTableLikeName, haveSameKeys, type ValueOrArray } from '~/utils.ts'; +import { + applyMixins, + getTableColumns, + getTableLikeName, + haveSameKeys, + type NeonAuthToken, + type ValueOrArray, +} from '~/utils.ts'; import { orderSelectedFields } from '~/utils.ts'; import { ViewBaseConfig } from '~/view-common.ts'; import type { @@ -34,11 +41,11 @@ import type { LockConfig, LockStrength, PgCreateSetOperatorFn, - PgJoinFn, PgSelectConfig, PgSelectDynamic, PgSelectHKT, PgSelectHKTBase, + PgSelectJoinFn, PgSelectPrepare, PgSelectWithout, PgSetOperatorExcludedMethods, @@ -81,6 +88,13 @@ export class PgSelectBuilder< this.distinct = config.distinct; } + private authToken?: NeonAuthToken; + /** @internal */ + setToken(token?: NeonAuthToken) { + this.authToken = token; + return this; + } + /** * Specify the table, subquery, or other target that you're * building a select query against. @@ -115,7 +129,7 @@ export class PgSelectBuilder< fields = getTableColumns(source); } - return new PgSelectBase({ + return (new PgSelectBase({ table: source, fields, isPartialSelect, @@ -123,7 +137,7 @@ export class PgSelectBuilder< dialect: this.dialect, withList: this.withList, distinct: this.distinct, - }) as any; + }).setToken(this.authToken)) as any; } } @@ -139,7 +153,7 @@ export abstract class PgSelectQueryBuilderBase< TResult extends any[] = SelectResult[], TSelectedFields extends ColumnsSelection = BuildSubquerySelection, > extends TypedQueryBuilder { - static readonly [entityKind]: string = 'PgSelectQueryBuilder'; + static override readonly [entityKind]: string = 'PgSelectQueryBuilder'; override readonly _: { readonly dialect: 'pg'; @@ -194,7 +208,7 @@ export abstract class PgSelectQueryBuilderBase< private createJoin( joinType: TJoinType, - ): PgJoinFn { + ): PgSelectJoinFn { return ( table: PgTable | Subquery | PgViewBase | SQL, on: ((aliases: TSelection) => SQL | undefined) | SQL | undefined, @@ -947,11 +961,11 @@ export class PgSelectBase< TResult, TSelectedFields > implements RunnableQuery, SQLWrapper { - static readonly [entityKind]: string = 'PgSelect'; + static override readonly [entityKind]: string = 'PgSelect'; /** @internal */ _prepare(name?: string): PgSelectPrepare { - const { session, config, dialect, joinsNotNullableMap } = this; + const { session, config, dialect, joinsNotNullableMap, authToken } = this; if (!session) { throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); } @@ -961,7 +975,8 @@ export class PgSelectBase< PreparedQueryConfig & { execute: TResult } >(dialect.sqlToQuery(this.getSQL()), fieldsList, name, true); query.joinsNotNullableMap = joinsNotNullableMap; - return query; + + return query.setToken(authToken); }); } @@ -976,9 +991,16 @@ export class PgSelectBase< return this._prepare(name); } + private authToken?: NeonAuthToken; + /** @internal */ + setToken(token?: NeonAuthToken) { + this.authToken = token; + return this; + } + execute: ReturnType['execute'] = (placeholderValues) => { return tracer.startActiveSpan('drizzle.operation', () => { - return this._prepare().execute(placeholderValues); + return this._prepare().execute(placeholderValues, this.authToken); }); }; } diff --git a/drizzle-orm/src/pg-core/query-builders/select.types.ts b/drizzle-orm/src/pg-core/query-builders/select.types.ts index 005a2d924..9c5a538aa 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.types.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.types.ts @@ -79,7 +79,7 @@ export interface PgSelectConfig { }[]; } -export type PgJoin< +export type PgSelectJoin< T extends AnyPgSelectQueryBuilder, TDynamic extends boolean, TJoinType extends JoinType, @@ -108,7 +108,7 @@ export type PgJoin< > : never; -export type PgJoinFn< +export type PgSelectJoinFn< T extends AnyPgSelectQueryBuilder, TDynamic extends boolean, TJoinType extends JoinType, @@ -118,7 +118,7 @@ export type PgJoinFn< >( table: TJoinedTable, on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, -) => PgJoin; +) => PgSelectJoin; export type SelectedFieldsFlat = SelectedFieldsFlatBase; diff --git a/drizzle-orm/src/pg-core/query-builders/update.ts b/drizzle-orm/src/pg-core/query-builders/update.ts index ec404ac22..911916381 100644 --- a/drizzle-orm/src/pg-core/query-builders/update.ts +++ b/drizzle-orm/src/pg-core/query-builders/update.ts @@ -1,5 +1,5 @@ import type { GetColumnData } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { PgDialect } from '~/pg-core/dialect.ts'; import type { PgPreparedQuery, @@ -8,21 +8,42 @@ import type { PgSession, PreparedQueryConfig, } from '~/pg-core/session.ts'; -import type { PgTable } from '~/pg-core/table.ts'; -import type { SelectResultFields } from '~/query-builders/select.types.ts'; +import { PgTable } from '~/pg-core/table.ts'; +import type { + AppendToNullabilityMap, + AppendToResult, + GetSelectTableName, + GetSelectTableSelection, + JoinNullability, + JoinType, + SelectMode, + SelectResult, +} from '~/query-builders/select.types.ts'; import { QueryPromise } from '~/query-promise.ts'; import type { RunnableQuery } from '~/runnable-query.ts'; -import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; -import type { Subquery } from '~/subquery.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import { type ColumnsSelection, type Query, SQL, type SQLWrapper } from '~/sql/sql.ts'; +import { Subquery } from '~/subquery.ts'; import { Table } from '~/table.ts'; -import { mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils.ts'; +import { + type Assume, + getTableLikeName, + mapUpdateSet, + type NeonAuthToken, + orderSelectedFields, + type UpdateSet, +} from '~/utils.ts'; +import { ViewBaseConfig } from '~/view-common.ts'; import type { PgColumn } from '../columns/common.ts'; -import type { SelectedFields, SelectedFieldsOrdered } from './select.types.ts'; +import type { PgViewBase } from '../view-base.ts'; +import type { PgSelectJoinConfig, SelectedFields, SelectedFieldsOrdered } from './select.types.ts'; export interface PgUpdateConfig { where?: SQL | undefined; set: UpdateSet; table: PgTable; + from?: PgTable | Subquery | PgViewBase | SQL; + joins: PgSelectJoinConfig[]; returning?: SelectedFieldsOrdered; withList?: Subquery[]; } @@ -31,7 +52,9 @@ export type PgUpdateSetSource = & { [Key in keyof TTable['$inferInsert']]?: | GetColumnData - | SQL; + | SQL + | PgColumn + | undefined; } & {}; @@ -49,14 +72,22 @@ export class PgUpdateBuilder): PgUpdateBase { + private authToken?: NeonAuthToken; + setToken(token: NeonAuthToken) { + this.authToken = token; + return this; + } + + set( + values: PgUpdateSetSource, + ): PgUpdateWithout, false, 'leftJoin' | 'rightJoin' | 'innerJoin' | 'fullJoin'> { return new PgUpdateBase( this.table, mapUpdateSet(this.table, values), this.session, this.dialect, this.withList, - ); + ).setToken(this.authToken); } } @@ -68,18 +99,123 @@ export type PgUpdateWithout< PgUpdateBase< T['_']['table'], T['_']['queryResult'], + T['_']['from'], T['_']['returning'], + T['_']['nullabilityMap'], + T['_']['joins'], TDynamic, T['_']['excludedMethods'] | K >, T['_']['excludedMethods'] | K >; +export type PgUpdateWithJoins< + T extends AnyPgUpdate, + TDynamic extends boolean, + TFrom extends PgTable | Subquery | PgViewBase | SQL, +> = TDynamic extends true ? T : Omit< + PgUpdateBase< + T['_']['table'], + T['_']['queryResult'], + TFrom, + T['_']['returning'], + AppendToNullabilityMap, 'inner'>, + [...T['_']['joins'], { + name: GetSelectTableName; + joinType: 'inner'; + table: TFrom; + }], + TDynamic, + Exclude + >, + Exclude +>; + +export type PgUpdateJoinFn< + T extends AnyPgUpdate, + TDynamic extends boolean, + TJoinType extends JoinType, +> = < + TJoinedTable extends PgTable | Subquery | PgViewBase | SQL, +>( + table: TJoinedTable, + on: + | ( + ( + updateTable: T['_']['table']['_']['columns'], + from: T['_']['from'] extends PgTable ? T['_']['from']['_']['columns'] + : T['_']['from'] extends Subquery | PgViewBase ? T['_']['from']['_']['selectedFields'] + : never, + ) => SQL | undefined + ) + | SQL + | undefined, +) => PgUpdateJoin; + +export type PgUpdateJoin< + T extends AnyPgUpdate, + TDynamic extends boolean, + TJoinType extends JoinType, + TJoinedTable extends PgTable | Subquery | PgViewBase | SQL, +> = TDynamic extends true ? T : PgUpdateBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['from'], + T['_']['returning'], + AppendToNullabilityMap, TJoinType>, + [...T['_']['joins'], { + name: GetSelectTableName; + joinType: TJoinType; + table: TJoinedTable; + }], + TDynamic, + T['_']['excludedMethods'] +>; + +type Join = { + name: string | undefined; + joinType: JoinType; + table: PgTable | Subquery | PgViewBase | SQL; +}; + +type AccumulateToResult< + T extends AnyPgUpdate, + TSelectMode extends SelectMode, + TJoins extends Join[], + TSelectedFields extends ColumnsSelection, +> = TJoins extends [infer TJoin extends Join, ...infer TRest extends Join[]] ? AccumulateToResult< + T, + TSelectMode extends 'partial' ? TSelectMode : 'multiple', + TRest, + AppendToResult< + T['_']['table']['_']['name'], + TSelectedFields, + TJoin['name'], + TJoin['table'] extends Table ? TJoin['table']['_']['columns'] + : TJoin['table'] extends Subquery ? Assume + : never, + TSelectMode extends 'partial' ? TSelectMode : 'multiple' + > + > + : TSelectedFields; + export type PgUpdateReturningAll = PgUpdateWithout< PgUpdateBase< T['_']['table'], T['_']['queryResult'], - T['_']['table']['$inferSelect'], + T['_']['from'], + SelectResult< + AccumulateToResult< + T, + 'single', + T['_']['joins'], + GetSelectTableSelection + >, + 'partial', + T['_']['nullabilityMap'] + >, + T['_']['nullabilityMap'], + T['_']['joins'], TDynamic, T['_']['excludedMethods'] >, @@ -95,7 +231,19 @@ export type PgUpdateReturning< PgUpdateBase< T['_']['table'], T['_']['queryResult'], - SelectResultFields, + T['_']['from'], + SelectResult< + AccumulateToResult< + T, + 'partial', + T['_']['joins'], + TSelectedFields + >, + 'partial', + T['_']['nullabilityMap'] + >, + T['_']['nullabilityMap'], + T['_']['joins'], TDynamic, T['_']['excludedMethods'] >, @@ -113,21 +261,29 @@ export type PgUpdatePrepare = PgPreparedQuery< export type PgUpdateDynamic = PgUpdate< T['_']['table'], T['_']['queryResult'], - T['_']['returning'] + T['_']['from'], + T['_']['returning'], + T['_']['nullabilityMap'] >; export type PgUpdate< TTable extends PgTable = PgTable, TQueryResult extends PgQueryResultHKT = PgQueryResultHKT, + TFrom extends PgTable | Subquery | PgViewBase | SQL | undefined = undefined, TReturning extends Record | undefined = Record | undefined, -> = PgUpdateBase; + TNullabilityMap extends Record = Record, + TJoins extends Join[] = [], +> = PgUpdateBase; -type AnyPgUpdate = PgUpdateBase; +export type AnyPgUpdate = PgUpdateBase; export interface PgUpdateBase< TTable extends PgTable, TQueryResult extends PgQueryResultHKT, + TFrom extends PgTable | Subquery | PgViewBase | SQL | undefined = undefined, TReturning extends Record | undefined = undefined, + TNullabilityMap extends Record = Record, + TJoins extends Join[] = [], TDynamic extends boolean = false, TExcludedMethods extends string = never, > extends @@ -138,7 +294,10 @@ export interface PgUpdateBase< readonly _: { readonly dialect: 'pg'; readonly table: TTable; + readonly joins: TJoins; + readonly nullabilityMap: TNullabilityMap; readonly queryResult: TQueryResult; + readonly from: TFrom; readonly returning: TReturning; readonly dynamic: TDynamic; readonly excludedMethods: TExcludedMethods; @@ -149,8 +308,13 @@ export interface PgUpdateBase< export class PgUpdateBase< TTable extends PgTable, TQueryResult extends PgQueryResultHKT, + TFrom extends PgTable | Subquery | PgViewBase | SQL | undefined = undefined, TReturning extends Record | undefined = undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars + TNullabilityMap extends Record = Record, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TJoins extends Join[] = [], + // eslint-disable-next-line @typescript-eslint/no-unused-vars TDynamic extends boolean = false, // eslint-disable-next-line @typescript-eslint/no-unused-vars TExcludedMethods extends string = never, @@ -159,9 +323,11 @@ export class PgUpdateBase< RunnableQuery : TReturning[], 'pg'>, SQLWrapper { - static readonly [entityKind]: string = 'PgUpdate'; + static override readonly [entityKind]: string = 'PgUpdate'; private config: PgUpdateConfig; + private tableName: string | undefined; + private joinsNotNullableMap: Record; constructor( table: TTable, @@ -171,9 +337,101 @@ export class PgUpdateBase< withList?: Subquery[], ) { super(); - this.config = { set, table, withList }; + this.config = { set, table, withList, joins: [] }; + this.tableName = getTableLikeName(table); + this.joinsNotNullableMap = typeof this.tableName === 'string' ? { [this.tableName]: true } : {}; + } + + from( + source: TFrom, + ): PgUpdateWithJoins { + const tableName = getTableLikeName(source); + if (typeof tableName === 'string') { + this.joinsNotNullableMap[tableName] = true; + } + this.config.from = source; + return this as any; + } + + private getTableLikeFields(table: PgTable | Subquery | PgViewBase): Record { + if (is(table, PgTable)) { + return table[Table.Symbol.Columns]; + } else if (is(table, Subquery)) { + return table._.selectedFields; + } + return table[ViewBaseConfig].selectedFields; + } + + private createJoin( + joinType: TJoinType, + ): PgUpdateJoinFn { + return (( + table: PgTable | Subquery | PgViewBase | SQL, + on: ((updateTable: TTable, from: TFrom) => SQL | undefined) | SQL | undefined, + ) => { + const tableName = getTableLikeName(table); + + if (typeof tableName === 'string' && this.config.joins.some((join) => join.alias === tableName)) { + throw new Error(`Alias "${tableName}" is already used in this query`); + } + + if (typeof on === 'function') { + const from = this.config.from && !is(this.config.from, SQL) + ? this.getTableLikeFields(this.config.from) + : undefined; + on = on( + new Proxy( + this.config.table[Table.Symbol.Columns], + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as any, + from && new Proxy( + from, + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as any, + ); + } + + this.config.joins.push({ on, table, joinType, alias: tableName }); + + if (typeof tableName === 'string') { + switch (joinType) { + case 'left': { + this.joinsNotNullableMap[tableName] = false; + break; + } + case 'right': { + this.joinsNotNullableMap = Object.fromEntries( + Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false]), + ); + this.joinsNotNullableMap[tableName] = true; + break; + } + case 'inner': { + this.joinsNotNullableMap[tableName] = true; + break; + } + case 'full': { + this.joinsNotNullableMap = Object.fromEntries( + Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false]), + ); + this.joinsNotNullableMap[tableName] = false; + break; + } + } + } + + return this as any; + }) as any; } + leftJoin = this.createJoin('left'); + + rightJoin = this.createJoin('right'); + + innerJoin = this.createJoin('inner'); + + fullJoin = this.createJoin('full'); + /** * Adds a 'where' clause to the query. * @@ -239,8 +497,30 @@ export class PgUpdateBase< fields: TSelectedFields, ): PgUpdateReturning; returning( - fields: SelectedFields = this.config.table[Table.Symbol.Columns], + fields?: SelectedFields, ): PgUpdateWithout { + if (!fields) { + fields = Object.assign({}, this.config.table[Table.Symbol.Columns]); + + if (this.config.from) { + const tableName = getTableLikeName(this.config.from); + + if (typeof tableName === 'string' && this.config.from && !is(this.config.from, SQL)) { + const fromFields = this.getTableLikeFields(this.config.from); + fields[tableName] = fromFields as any; + } + + for (const join of this.config.joins) { + const tableName = getTableLikeName(join.table); + + if (typeof tableName === 'string' && !is(join.table, SQL)) { + const fromFields = this.getTableLikeFields(join.table); + fields[tableName] = fromFields as any; + } + } + } + } + this.config.returning = orderSelectedFields(fields); return this as any; } @@ -257,15 +537,26 @@ export class PgUpdateBase< /** @internal */ _prepare(name?: string): PgUpdatePrepare { - return this.session.prepareQuery(this.dialect.sqlToQuery(this.getSQL()), this.config.returning, name, true); + const query = this.session.prepareQuery< + PreparedQueryConfig & { execute: TReturning[] } + >(this.dialect.sqlToQuery(this.getSQL()), this.config.returning, name, true); + query.joinsNotNullableMap = this.joinsNotNullableMap; + return query; } prepare(name: string): PgUpdatePrepare { return this._prepare(name); } + private authToken?: NeonAuthToken; + /** @internal */ + setToken(token?: NeonAuthToken) { + this.authToken = token; + return this; + } + override execute: ReturnType['execute'] = (placeholderValues) => { - return this._prepare().execute(placeholderValues); + return this._prepare().execute(placeholderValues, this.authToken); }; $dynamic(): PgUpdateDynamic { diff --git a/drizzle-orm/src/pg-core/roles.ts b/drizzle-orm/src/pg-core/roles.ts new file mode 100644 index 000000000..a2c77c303 --- /dev/null +++ b/drizzle-orm/src/pg-core/roles.ts @@ -0,0 +1,41 @@ +import { entityKind } from '~/entity.ts'; + +export interface PgRoleConfig { + createDb?: boolean; + createRole?: boolean; + inherit?: boolean; +} + +export class PgRole implements PgRoleConfig { + static readonly [entityKind]: string = 'PgRole'; + + /** @internal */ + _existing?: boolean; + + /** @internal */ + readonly createDb: PgRoleConfig['createDb']; + /** @internal */ + readonly createRole: PgRoleConfig['createRole']; + /** @internal */ + readonly inherit: PgRoleConfig['inherit']; + + constructor( + readonly name: string, + config?: PgRoleConfig, + ) { + if (config) { + this.createDb = config.createDb; + this.createRole = config.createRole; + this.inherit = config.inherit; + } + } + + existing(): this { + this._existing = true; + return this; + } +} + +export function pgRole(name: string, config?: PgRoleConfig) { + return new PgRole(name, config); +} diff --git a/drizzle-orm/src/pg-core/session.ts b/drizzle-orm/src/pg-core/session.ts index 434ebc086..d77f2c4db 100644 --- a/drizzle-orm/src/pg-core/session.ts +++ b/drizzle-orm/src/pg-core/session.ts @@ -4,6 +4,7 @@ import type { TablesRelationalConfig } from '~/relations.ts'; import type { PreparedQuery } from '~/session.ts'; import { type Query, type SQL, sql } from '~/sql/index.ts'; import { tracer } from '~/tracing.ts'; +import type { NeonAuthToken } from '~/utils.ts'; import { PgDatabase } from './db.ts'; import type { PgDialect } from './dialect.ts'; import type { SelectedFieldsOrdered } from './query-builders/select.types.ts'; @@ -17,6 +18,8 @@ export interface PreparedQueryConfig { export abstract class PgPreparedQuery implements PreparedQuery { constructor(protected query: Query) {} + protected authToken?: NeonAuthToken; + getQuery(): Query { return this.query; } @@ -25,12 +28,22 @@ export abstract class PgPreparedQuery implements return response; } + /** @internal */ + setToken(token?: NeonAuthToken) { + this.authToken = token; + return this; + } + static readonly [entityKind]: string = 'PgPreparedQuery'; /** @internal */ joinsNotNullableMap?: Record; abstract execute(placeholderValues?: Record): Promise; + /** @internal */ + abstract execute(placeholderValues?: Record, token?: NeonAuthToken): Promise; + /** @internal */ + abstract execute(placeholderValues?: Record, token?: NeonAuthToken): Promise; /** @internal */ abstract all(placeholderValues?: Record): Promise; @@ -62,7 +75,11 @@ export abstract class PgSession< customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => T['execute'], ): PgPreparedQuery; - execute(query: SQL): Promise { + execute(query: SQL): Promise; + /** @internal */ + execute(query: SQL, token?: NeonAuthToken): Promise; + /** @internal */ + execute(query: SQL, token?: NeonAuthToken): Promise { return tracer.startActiveSpan('drizzle.operation', () => { const prepared = tracer.startActiveSpan('drizzle.prepareQuery', () => { return this.prepareQuery( @@ -73,7 +90,7 @@ export abstract class PgSession< ); }); - return prepared.execute(); + return prepared.setToken(token).execute(undefined, token); }); } @@ -86,6 +103,18 @@ export abstract class PgSession< ).all(); } + async count(sql: SQL): Promise; + /** @internal */ + async count(sql: SQL, token?: NeonAuthToken): Promise; + /** @internal */ + async count(sql: SQL, token?: NeonAuthToken): Promise { + const res = await this.execute<[{ count: string }]>(sql, token); + + return Number( + res[0]['count'], + ); + } + abstract transaction( transaction: (tx: PgTransaction) => Promise, config?: PgTransactionConfig, @@ -97,7 +126,7 @@ export abstract class PgTransaction< TFullSchema extends Record = Record, TSchema extends TablesRelationalConfig = Record, > extends PgDatabase { - static readonly [entityKind]: string = 'PgTransaction'; + static override readonly [entityKind]: string = 'PgTransaction'; constructor( dialect: PgDialect, diff --git a/drizzle-orm/src/pg-core/table.ts b/drizzle-orm/src/pg-core/table.ts index 78cd52019..b5a60e91a 100644 --- a/drizzle-orm/src/pg-core/table.ts +++ b/drizzle-orm/src/pg-core/table.ts @@ -2,37 +2,49 @@ import type { BuildColumns, BuildExtraConfigColumns } from '~/column-builder.ts' import { entityKind } from '~/entity.ts'; import { Table, type TableConfig as TableConfigBase, type UpdateTableConfig } from '~/table.ts'; import type { CheckBuilder } from './checks.ts'; +import { getPgColumnBuilders, type PgColumnsBuilders } from './columns/all.ts'; import type { PgColumn, PgColumnBuilder, PgColumnBuilderBase } from './columns/common.ts'; import type { ForeignKey, ForeignKeyBuilder } from './foreign-keys.ts'; import type { AnyIndexBuilder } from './indexes.ts'; +import type { PgPolicy } from './policies.ts'; import type { PrimaryKeyBuilder } from './primary-keys.ts'; import type { UniqueConstraintBuilder } from './unique-constraint.ts'; -export type PgTableExtraConfig = Record< - string, +export type PgTableExtraConfigValue = | AnyIndexBuilder | CheckBuilder | ForeignKeyBuilder | PrimaryKeyBuilder | UniqueConstraintBuilder + | PgPolicy; + +export type PgTableExtraConfig = Record< + string, + PgTableExtraConfigValue >; export type TableConfig = TableConfigBase; /** @internal */ export const InlineForeignKeys = Symbol.for('drizzle:PgInlineForeignKeys'); +/** @internal */ +export const EnableRLS = Symbol.for('drizzle:EnableRLS'); export class PgTable extends Table { - static readonly [entityKind]: string = 'PgTable'; + static override readonly [entityKind]: string = 'PgTable'; /** @internal */ static override readonly Symbol = Object.assign({}, Table.Symbol, { InlineForeignKeys: InlineForeignKeys as typeof InlineForeignKeys, + EnableRLS: EnableRLS as typeof EnableRLS, }); /**@internal */ [InlineForeignKeys]: ForeignKey[] = []; + /** @internal */ + [EnableRLS]: boolean = false; + /** @internal */ override [Table.Symbol.ExtraConfigBuilder]: ((self: Record) => PgTableExtraConfig) | undefined = undefined; @@ -44,6 +56,12 @@ export type PgTableWithColumns = & PgTable & { [Key in keyof T['columns']]: T['columns'][Key]; + } + & { + enableRLS: () => Omit< + PgTableWithColumns, + 'enableRLS' + >; }; /** @internal */ @@ -53,8 +71,10 @@ export function pgTableWithSchema< TColumnsMap extends Record, >( name: TTableName, - columns: TColumnsMap, - extraConfig: ((self: BuildExtraConfigColumns) => PgTableExtraConfig) | undefined, + columns: TColumnsMap | ((columnTypes: PgColumnsBuilders) => TColumnsMap), + extraConfig: + | ((self: BuildExtraConfigColumns) => PgTableExtraConfig | PgTableExtraConfigValue[]) + | undefined, schema: TSchemaName, baseName = name, ): PgTableWithColumns<{ @@ -70,9 +90,12 @@ export function pgTableWithSchema< dialect: 'pg'; }>(name, schema, baseName); + const parsedColumns: TColumnsMap = typeof columns === 'function' ? columns(getPgColumnBuilders()) : columns; + const builtColumns = Object.fromEntries( - Object.entries(columns).map(([name, colBuilderBase]) => { + Object.entries(parsedColumns).map(([name, colBuilderBase]) => { const colBuilder = colBuilderBase as PgColumnBuilder; + colBuilder.setName(name); const column = colBuilder.build(rawTable); rawTable[InlineForeignKeys].push(...colBuilder.buildForeignKeys(column, rawTable)); return [name, column]; @@ -80,8 +103,9 @@ export function pgTableWithSchema< ) as unknown as BuildColumns; const builtColumnsForExtraConfig = Object.fromEntries( - Object.entries(columns).map(([name, colBuilderBase]) => { + Object.entries(parsedColumns).map(([name, colBuilderBase]) => { const colBuilder = colBuilderBase as PgColumnBuilder; + colBuilder.setName(name); const column = colBuilder.buildExtraConfigColumn(rawTable); return [name, column]; }), @@ -96,7 +120,17 @@ export function pgTableWithSchema< table[PgTable.Symbol.ExtraConfigBuilder] = extraConfig as any; } - return table; + return Object.assign(table, { + enableRLS: () => { + table[PgTable.Symbol.EnableRLS] = true; + return table as PgTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'pg'; + }>; + }, + }); } export interface PgTableFn { @@ -106,7 +140,96 @@ export interface PgTableFn { >( name: TTableName, columns: TColumnsMap, - extraConfig?: (self: BuildExtraConfigColumns) => PgTableExtraConfig, + extraConfig?: ( + self: BuildExtraConfigColumns, + ) => PgTableExtraConfigValue[], + ): PgTableWithColumns<{ + name: TTableName; + schema: TSchema; + columns: BuildColumns; + dialect: 'pg'; + }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: PgColumnsBuilders) => TColumnsMap, + extraConfig?: (self: BuildExtraConfigColumns) => PgTableExtraConfigValue[], + ): PgTableWithColumns<{ + name: TTableName; + schema: TSchema; + columns: BuildColumns; + dialect: 'pg'; + }>; + /** + * @deprecated The third parameter of pgTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = pgTable("users", { + * id: integer(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = pgTable("users", { + * id: integer(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig: ( + self: BuildExtraConfigColumns, + ) => PgTableExtraConfig, + ): PgTableWithColumns<{ + name: TTableName; + schema: TSchema; + columns: BuildColumns; + dialect: 'pg'; + }>; + + /** + * @deprecated The third parameter of pgTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = pgTable("users", { + * id: integer(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = pgTable("users", { + * id: integer(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: PgColumnsBuilders) => TColumnsMap, + extraConfig: (self: BuildExtraConfigColumns) => PgTableExtraConfig, ): PgTableWithColumns<{ name: TTableName; schema: TSchema; diff --git a/drizzle-orm/src/pg-core/unique-constraint.ts b/drizzle-orm/src/pg-core/unique-constraint.ts index 339fa604e..ceb860b6f 100644 --- a/drizzle-orm/src/pg-core/unique-constraint.ts +++ b/drizzle-orm/src/pg-core/unique-constraint.ts @@ -1,13 +1,14 @@ import { entityKind } from '~/entity.ts'; +import { TableName } from '~/table.utils.ts'; import type { PgColumn } from './columns/index.ts'; -import { PgTable } from './table.ts'; +import type { PgTable } from './table.ts'; export function unique(name?: string): UniqueOnConstraintBuilder { return new UniqueOnConstraintBuilder(name); } export function uniqueKeyName(table: PgTable, columns: string[]) { - return `${table[PgTable.Symbol.Name]}_${columns.join('_')}_unique`; + return `${table[TableName]}_${columns.join('_')}_unique`; } export class UniqueConstraintBuilder { diff --git a/drizzle-orm/src/pg-core/utils.ts b/drizzle-orm/src/pg-core/utils.ts index 90378f249..0191c2439 100644 --- a/drizzle-orm/src/pg-core/utils.ts +++ b/drizzle-orm/src/pg-core/utils.ts @@ -7,6 +7,7 @@ import type { AnyPgColumn } from './columns/index.ts'; import { type ForeignKey, ForeignKeyBuilder } from './foreign-keys.ts'; import type { Index } from './indexes.ts'; import { IndexBuilder } from './indexes.ts'; +import { PgPolicy } from './policies.ts'; import { type PrimaryKey, PrimaryKeyBuilder } from './primary-keys.ts'; import { type UniqueConstraint, UniqueConstraintBuilder } from './unique-constraint.ts'; import { PgViewConfig } from './view-common.ts'; @@ -21,12 +22,15 @@ export function getTableConfig(table: TTable) { const uniqueConstraints: UniqueConstraint[] = []; const name = table[Table.Symbol.Name]; const schema = table[Table.Symbol.Schema]; + const policies: PgPolicy[] = []; + const enableRLS: boolean = table[PgTable.Symbol.EnableRLS]; const extraConfigBuilder = table[PgTable.Symbol.ExtraConfigBuilder]; if (extraConfigBuilder !== undefined) { const extraConfig = extraConfigBuilder(table[Table.Symbol.ExtraConfigColumns]); - for (const builder of Object.values(extraConfig)) { + const extraValues = Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); + for (const builder of extraValues) { if (is(builder, IndexBuilder)) { indexes.push(builder.build(table)); } else if (is(builder, CheckBuilder)) { @@ -37,6 +41,8 @@ export function getTableConfig(table: TTable) { primaryKeys.push(builder.build(table)); } else if (is(builder, ForeignKeyBuilder)) { foreignKeys.push(builder.build(table)); + } else if (is(builder, PgPolicy)) { + policies.push(builder); } } } @@ -50,6 +56,8 @@ export function getTableConfig(table: TTable) { uniqueConstraints, name, schema, + policies, + enableRLS, }; } diff --git a/drizzle-orm/src/pg-core/view-base.ts b/drizzle-orm/src/pg-core/view-base.ts index 87f76ac24..d3f52a501 100644 --- a/drizzle-orm/src/pg-core/view-base.ts +++ b/drizzle-orm/src/pg-core/view-base.ts @@ -6,7 +6,7 @@ export abstract class PgViewBase< TExisting extends boolean = boolean, TSelectedFields extends ColumnsSelection = ColumnsSelection, > extends View { - static readonly [entityKind]: string = 'PgViewBase'; + static override readonly [entityKind]: string = 'PgViewBase'; declare readonly _: View['_'] & { readonly viewBrand: 'PgViewBase'; diff --git a/drizzle-orm/src/pg-core/view.ts b/drizzle-orm/src/pg-core/view.ts index 22c510dce..f8e916a31 100644 --- a/drizzle-orm/src/pg-core/view.ts +++ b/drizzle-orm/src/pg-core/view.ts @@ -1,22 +1,22 @@ import type { BuildColumns } from '~/column-builder.ts'; -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { AddAliasToSelection } from '~/query-builders/select.types.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; import type { ColumnsSelection, SQL } from '~/sql/sql.ts'; import { getTableColumns } from '~/utils.ts'; +import type { RequireAtLeastOne } from '~/utils.ts'; import type { PgColumn, PgColumnBuilderBase } from './columns/common.ts'; import { QueryBuilder } from './query-builders/query-builder.ts'; -import type { SelectedFields } from './query-builders/select.types.ts'; import { pgTable } from './table.ts'; import { PgViewBase } from './view-base.ts'; import { PgViewConfig } from './view-common.ts'; -export interface ViewWithConfig { +export type ViewWithConfig = RequireAtLeastOne<{ checkOption: 'local' | 'cascaded'; securityBarrier: boolean; securityInvoker: boolean; -} +}>; export class DefaultViewBuilderCore { static readonly [entityKind]: string = 'PgDefaultViewBuilderCore'; @@ -42,9 +42,9 @@ export class DefaultViewBuilderCore extends DefaultViewBuilderCore<{ name: TName }> { - static readonly [entityKind]: string = 'PgViewBuilder'; + static override readonly [entityKind]: string = 'PgViewBuilder'; - as( + as( qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), ): PgViewWithSelection> { if (typeof qb === 'function') { @@ -76,7 +76,7 @@ export class ManualViewBuilder< TName extends string = string, TColumns extends Record = Record, > extends DefaultViewBuilderCore<{ name: TName; columns: TColumns }> { - static readonly [entityKind]: string = 'PgManualViewBuilder'; + static override readonly [entityKind]: string = 'PgManualViewBuilder'; private columns: Record; @@ -130,9 +130,26 @@ export class ManualViewBuilder< } } -export interface PgMaterializedViewWithConfig { - [Key: string]: string | number | boolean | SQL; -} +export type PgMaterializedViewWithConfig = RequireAtLeastOne<{ + fillfactor: number; + toastTupleTarget: number; + parallelWorkers: number; + autovacuumEnabled: boolean; + vacuumIndexCleanup: 'auto' | 'off' | 'on'; + vacuumTruncate: boolean; + autovacuumVacuumThreshold: number; + autovacuumVacuumScaleFactor: number; + autovacuumVacuumCostDelay: number; + autovacuumVacuumCostLimit: number; + autovacuumFreezeMinAge: number; + autovacuumFreezeMaxAge: number; + autovacuumFreezeTableAge: number; + autovacuumMultixactFreezeMinAge: number; + autovacuumMultixactFreezeMaxAge: number; + autovacuumMultixactFreezeTableAge: number; + logAutovacuumMinDuration: number; + userCatalogTable: boolean; +}>; export class MaterializedViewBuilderCore { static readonly [entityKind]: string = 'PgMaterializedViewBuilderCore'; @@ -178,9 +195,9 @@ export class MaterializedViewBuilderCore extends MaterializedViewBuilderCore<{ name: TName }> { - static readonly [entityKind]: string = 'PgMaterializedViewBuilder'; + static override readonly [entityKind]: string = 'PgMaterializedViewBuilder'; - as( + as( qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), ): PgMaterializedViewWithSelection> { if (typeof qb === 'function') { @@ -217,7 +234,7 @@ export class ManualMaterializedViewBuilder< TName extends string = string, TColumns extends Record = Record, > extends MaterializedViewBuilderCore<{ name: TName; columns: TColumns }> { - static readonly [entityKind]: string = 'PgManualMaterializedViewBuilder'; + static override readonly [entityKind]: string = 'PgManualMaterializedViewBuilder'; private columns: Record; @@ -233,7 +250,12 @@ export class ManualMaterializedViewBuilder< existing(): PgMaterializedViewWithSelection> { return new Proxy( new PgMaterializedView({ - pgConfig: undefined, + pgConfig: { + tablespace: this.config.tablespace, + using: this.config.using, + with: this.config.with, + withNoData: this.config.withNoData, + }, config: { name: this.name, schema: this.schema, @@ -253,7 +275,12 @@ export class ManualMaterializedViewBuilder< as(query: SQL): PgMaterializedViewWithSelection> { return new Proxy( new PgMaterializedView({ - pgConfig: undefined, + pgConfig: { + tablespace: this.config.tablespace, + using: this.config.using, + with: this.config.with, + withNoData: this.config.withNoData, + }, config: { name: this.name, schema: this.schema, @@ -276,7 +303,7 @@ export class PgView< TExisting extends boolean = boolean, TSelectedFields extends ColumnsSelection = ColumnsSelection, > extends PgViewBase { - static readonly [entityKind]: string = 'PgView'; + static override readonly [entityKind]: string = 'PgView'; [PgViewConfig]: { with?: ViewWithConfig; @@ -289,7 +316,7 @@ export class PgView< config: { name: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: ColumnsSelection; query: SQL | undefined; }; }) { @@ -315,7 +342,7 @@ export class PgMaterializedView< TExisting extends boolean = boolean, TSelectedFields extends ColumnsSelection = ColumnsSelection, > extends PgViewBase { - static readonly [entityKind]: string = 'PgMaterializedView'; + static override readonly [entityKind]: string = 'PgMaterializedView'; readonly [PgMaterializedViewConfig]: { readonly with?: PgMaterializedViewWithConfig; @@ -334,7 +361,7 @@ export class PgMaterializedView< config: { name: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: ColumnsSelection; query: SQL | undefined; }; }) { @@ -398,3 +425,11 @@ export function pgMaterializedView( ): MaterializedViewBuilder | ManualMaterializedViewBuilder { return pgMaterializedViewWithSchema(name, columns, undefined); } + +export function isPgView(obj: unknown): obj is PgView { + return is(obj, PgView); +} + +export function isPgMaterializedView(obj: unknown): obj is PgMaterializedView { + return is(obj, PgMaterializedView); +} diff --git a/drizzle-orm/src/pg-proxy/driver.ts b/drizzle-orm/src/pg-proxy/driver.ts index cdffa15c1..955dc2bb4 100644 --- a/drizzle-orm/src/pg-proxy/driver.ts +++ b/drizzle-orm/src/pg-proxy/driver.ts @@ -1,3 +1,4 @@ +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { PgDatabase } from '~/pg-core/db.ts'; import { PgDialect } from '~/pg-core/dialect.ts'; @@ -10,9 +11,11 @@ import { import type { DrizzleConfig } from '~/utils.ts'; import { type PgRemoteQueryResultHKT, PgRemoteSession } from './session.ts'; -export type PgRemoteDatabase< +export class PgRemoteDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static override readonly [entityKind]: string = 'PgRemoteDatabase'; +} export type RemoteCallback = ( sql: string, @@ -24,7 +27,7 @@ export type RemoteCallback = ( export function drizzle = Record>( callback: RemoteCallback, config: DrizzleConfig = {}, - _dialect: () => PgDialect = () => new PgDialect(), + _dialect: () => PgDialect = () => new PgDialect({ casing: config.casing }), ): PgRemoteDatabase { const dialect = _dialect(); let logger; @@ -48,5 +51,5 @@ export function drizzle = Record; + return new PgRemoteDatabase(dialect, session, schema as any) as PgRemoteDatabase; } diff --git a/drizzle-orm/src/pg-proxy/migrator.ts b/drizzle-orm/src/pg-proxy/migrator.ts index 5ca49ca43..68214ca58 100644 --- a/drizzle-orm/src/pg-proxy/migrator.ts +++ b/drizzle-orm/src/pg-proxy/migrator.ts @@ -8,7 +8,7 @@ export type ProxyMigrator = (migrationQueries: string[]) => Promise; export async function migrate>( db: PgRemoteDatabase, callback: ProxyMigrator, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); diff --git a/drizzle-orm/src/pg-proxy/session.ts b/drizzle-orm/src/pg-proxy/session.ts index eb6a1b1a3..9d433502c 100644 --- a/drizzle-orm/src/pg-proxy/session.ts +++ b/drizzle-orm/src/pg-proxy/session.ts @@ -21,7 +21,7 @@ export class PgRemoteSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgSession { - static readonly [entityKind]: string = 'PgRemoteSession'; + static override readonly [entityKind]: string = 'PgRemoteSession'; private logger: Logger; @@ -66,7 +66,7 @@ export class PgProxyTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgTransaction { - static readonly [entityKind]: string = 'PgProxyTransaction'; + static override readonly [entityKind]: string = 'PgProxyTransaction'; override async transaction( _transaction: (tx: PgProxyTransaction) => Promise, @@ -76,7 +76,7 @@ export class PgProxyTransaction< } export class PreparedQuery extends PreparedQueryBase { - static readonly [entityKind]: string = 'PgProxyPreparedQuery'; + static override readonly [entityKind]: string = 'PgProxyPreparedQuery'; constructor( private client: RemoteCallback, @@ -130,7 +130,8 @@ export class PreparedQuery extends PreparedQueryB }); } - async all() {} + async all() { + } /** @internal */ isResponseInArrayMode(): boolean { diff --git a/drizzle-orm/src/pglite/driver.ts b/drizzle-orm/src/pglite/driver.ts index 7de2ce110..7f35b2779 100644 --- a/drizzle-orm/src/pglite/driver.ts +++ b/drizzle-orm/src/pglite/driver.ts @@ -1,3 +1,4 @@ +import { PGlite, type PGliteOptions } from '@electric-sql/pglite'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; @@ -9,7 +10,7 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import type { PgliteClient, PgliteQueryResultHKT } from './session.ts'; import { PgliteSession } from './session.ts'; @@ -34,15 +35,19 @@ export class PgliteDriver { } } -export type PgliteDatabase< +export class PgliteDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static override readonly [entityKind]: string = 'PgliteDatabase'; +} -export function drizzle = Record>( +function construct = Record>( client: PgliteClient, config: DrizzleConfig = {}, -): PgliteDatabase { - const dialect = new PgDialect(); +): PgliteDatabase & { + $client: PgliteClient; +} { + const dialect = new PgDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -65,5 +70,73 @@ export function drizzle = Record; + const db = new PgliteDatabase(dialect, session, schema as any) as PgliteDatabase; + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends PGlite = PGlite, +>( + ...params: + | [] + | [ + TClient | string, + ] + | [ + TClient | string, + DrizzleConfig, + ] + | [ + ( + & DrizzleConfig + & ({ + connection?: (PGliteOptions & { dataDir?: string }) | string; + } | { + client: TClient; + }) + ), + ] +): PgliteDatabase & { + $client: TClient; +} { + if (params[0] === undefined || typeof params[0] === 'string') { + const instance = new PGlite(params[0]); + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as { + connection?: PGliteOptions & { dataDir: string }; + client?: TClient; + } & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + if (typeof connection === 'object') { + const { dataDir, ...options } = connection; + + const instance = new PGlite(dataDir, options); + + return construct(instance, drizzleConfig) as any; + } + + const instance = new PGlite(connection); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): PgliteDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/pglite/migrator.ts b/drizzle-orm/src/pglite/migrator.ts index 83c26cf3d..f43d9f618 100644 --- a/drizzle-orm/src/pglite/migrator.ts +++ b/drizzle-orm/src/pglite/migrator.ts @@ -4,7 +4,7 @@ import type { PgliteDatabase } from './driver.ts'; export async function migrate>( db: PgliteDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); await db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/pglite/session.ts b/drizzle-orm/src/pglite/session.ts index c7a1dbb5d..18c341fc6 100644 --- a/drizzle-orm/src/pglite/session.ts +++ b/drizzle-orm/src/pglite/session.ts @@ -7,7 +7,7 @@ import type { SelectedFieldsOrdered } from '~/pg-core/query-builders/select.type import type { PgQueryResultHKT, PgTransactionConfig, PreparedQueryConfig } from '~/pg-core/session.ts'; import { PgPreparedQuery, PgSession } from '~/pg-core/session.ts'; import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; -import { fillPlaceholders, type Query, sql } from '~/sql/sql.ts'; +import { fillPlaceholders, type Query, type SQL, sql } from '~/sql/sql.ts'; import { type Assume, mapResultRow } from '~/utils.ts'; import { types } from '@electric-sql/pglite'; @@ -15,7 +15,7 @@ import { types } from '@electric-sql/pglite'; export type PgliteClient = PGlite; export class PglitePreparedQuery extends PgPreparedQuery { - static readonly [entityKind]: string = 'PglitePreparedQuery'; + static override readonly [entityKind]: string = 'PglitePreparedQuery'; private rawQueryConfig: QueryOptions; private queryConfig: QueryOptions; @@ -89,7 +89,7 @@ export class PgliteSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgSession { - static readonly [entityKind]: string = 'PgliteSession'; + static override readonly [entityKind]: string = 'PgliteSession'; private logger: Logger; @@ -140,13 +140,20 @@ export class PgliteSession< return transaction(tx); }) as Promise; } + + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + return Number( + res['rows'][0]['count'], + ); + } } export class PgliteTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgTransaction { - static readonly [entityKind]: string = 'PgliteTransaction'; + static override readonly [entityKind]: string = 'PgliteTransaction'; override async transaction(transaction: (tx: PgliteTransaction) => Promise): Promise { const savepointName = `sp${this.nestedIndex + 1}`; diff --git a/drizzle-orm/src/planetscale-serverless/driver.ts b/drizzle-orm/src/planetscale-serverless/driver.ts index b1d2d6e6f..1ea8825cb 100644 --- a/drizzle-orm/src/planetscale-serverless/driver.ts +++ b/drizzle-orm/src/planetscale-serverless/driver.ts @@ -1,5 +1,6 @@ -import type { Connection } from '@planetscale/database'; +import type { Config } from '@planetscale/database'; import { Client } from '@planetscale/database'; +import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; import { MySqlDatabase } from '~/mysql-core/db.ts'; @@ -10,7 +11,7 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import type { PlanetScalePreparedQueryHKT, PlanetscaleQueryResultHKT } from './session.ts'; import { PlanetscaleSession } from './session.ts'; @@ -18,33 +19,25 @@ export interface PlanetscaleSDriverOptions { logger?: Logger; } -export type PlanetScaleDatabase< +export class PlanetScaleDatabase< TSchema extends Record = Record, -> = MySqlDatabase; +> extends MySqlDatabase { + static override readonly [entityKind]: string = 'PlanetScaleDatabase'; +} -export function drizzle = Record>( - client: Client | Connection, +function construct< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + client: TClient, config: DrizzleConfig = {}, -): PlanetScaleDatabase { +): PlanetScaleDatabase & { + $client: TClient; +} { // Client is not Drizzle Object, so we can ignore this rule here // eslint-disable-next-line no-instanceof/no-instanceof if (!(client instanceof Client)) { - // Should use error on 0.30.0 release - // throw new DrizzleError({ - // message: `You need to pass an instance of Client: - - // import { Client } from "@planetscale/database"; - - // const client = new Client({ - // host: process.env["DATABASE_HOST"], - // username: process.env["DATABASE_USERNAME"], - // password: process.env["DATABASE_PASSWORD"], - // }); - - // const db = drizzle(client); - // `, - // }); - console.log(`Warning: You need to pass an instance of Client: + throw new Error(`Warning: You need to pass an instance of Client: import { Client } from "@planetscale/database"; @@ -55,12 +48,10 @@ const client = new Client({ }); const db = drizzle(client); - -Starting from version 0.30.0, you will encounter an error if you attempt to use anything other than a Client instance.\nPlease make the necessary changes now to prevent any runtime errors in the future `); } - const dialect = new MySqlDialect(); + const dialect = new MySqlDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -82,5 +73,69 @@ Starting from version 0.30.0, you will encounter an error if you attempt to use } const session = new PlanetscaleSession(client, dialect, undefined, schema, { logger }); - return new MySqlDatabase(dialect, session, schema, 'planetscale') as PlanetScaleDatabase; + const db = new PlanetScaleDatabase(dialect, session, schema as any, 'planetscale') as PlanetScaleDatabase; + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] +): PlanetScaleDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = new Client({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config | string; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' + ? new Client({ + url: connection, + }) + : new Client( + connection!, + ); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): PlanetScaleDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/planetscale-serverless/migrator.ts b/drizzle-orm/src/planetscale-serverless/migrator.ts index 5a668ae01..c6b964051 100644 --- a/drizzle-orm/src/planetscale-serverless/migrator.ts +++ b/drizzle-orm/src/planetscale-serverless/migrator.ts @@ -7,5 +7,6 @@ export async function migrate>( config: MigrationConfig, ) { const migrations = readMigrationFiles(config); + await db.dialect.migrate(migrations, db.session, config); } diff --git a/drizzle-orm/src/planetscale-serverless/session.ts b/drizzle-orm/src/planetscale-serverless/session.ts index f2275b7f2..4e6a0f432 100644 --- a/drizzle-orm/src/planetscale-serverless/session.ts +++ b/drizzle-orm/src/planetscale-serverless/session.ts @@ -18,7 +18,7 @@ import { fillPlaceholders, type Query, type SQL, sql } from '~/sql/sql.ts'; import { type Assume, mapResultRow } from '~/utils.ts'; export class PlanetScalePreparedQuery extends MySqlPreparedQuery { - static readonly [entityKind]: string = 'PlanetScalePreparedQuery'; + static override readonly [entityKind]: string = 'PlanetScalePreparedQuery'; private rawQuery = { as: 'object' } as const; private query = { as: 'array' } as const; @@ -106,7 +106,7 @@ export class PlanetscaleSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends MySqlSession { - static readonly [entityKind]: string = 'PlanetscaleSession'; + static override readonly [entityKind]: string = 'PlanetscaleSession'; private logger: Logger; private client: Client | Transaction | Connection; @@ -164,6 +164,14 @@ export class PlanetscaleSession< ) => eQuery.rows as T[]); } + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + + return Number( + res['rows'][0]['count'], + ); + } + override transaction( transaction: (tx: PlanetScaleTransaction) => Promise, ): Promise { @@ -183,7 +191,7 @@ export class PlanetScaleTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends MySqlTransaction { - static readonly [entityKind]: string = 'PlanetScaleTransaction'; + static override readonly [entityKind]: string = 'PlanetScaleTransaction'; constructor( dialect: MySqlDialect, diff --git a/drizzle-orm/src/postgres-js/driver.ts b/drizzle-orm/src/postgres-js/driver.ts index ae1b48a21..69d5a126d 100644 --- a/drizzle-orm/src/postgres-js/driver.ts +++ b/drizzle-orm/src/postgres-js/driver.ts @@ -1,4 +1,5 @@ -import type { Sql } from 'postgres'; +import pgClient, { type Options, type PostgresType, type Sql } from 'postgres'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { PgDatabase } from '~/pg-core/db.ts'; import { PgDialect } from '~/pg-core/dialect.ts'; @@ -8,18 +9,22 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import type { PostgresJsQueryResultHKT } from './session.ts'; import { PostgresJsSession } from './session.ts'; -export type PostgresJsDatabase< +export class PostgresJsDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static override readonly [entityKind]: string = 'PostgresJsDatabase'; +} -export function drizzle = Record>( +function construct = Record>( client: Sql, config: DrizzleConfig = {}, -): PostgresJsDatabase { +): PostgresJsDatabase & { + $client: Sql; +} { const transparentParser = (val: any) => val; // Override postgres.js default date parsers: https://github.com/porsager/postgres/discussions/761 @@ -27,8 +32,10 @@ export function drizzle = Record = Record; + const db = new PostgresJsDatabase(dialect, session, schema as any) as PostgresJsDatabase; + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends Sql = Sql, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | ({ url?: string } & Options>); + } | { + client: TClient; + }) + ), + ] +): PostgresJsDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = pgClient(params[0] as string); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as { + connection?: { url?: string } & Options>; + client?: TClient; + } & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + if (typeof connection === 'object' && connection.url !== undefined) { + const { url, ...config } = connection; + + const instance = pgClient(url, config); + return construct(instance, drizzleConfig) as any; + } + + const instance = pgClient(connection); + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): PostgresJsDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({ + options: { + parsers: {}, + serializers: {}, + }, + } as any, config) as any; + } } diff --git a/drizzle-orm/src/postgres-js/migrator.ts b/drizzle-orm/src/postgres-js/migrator.ts index 881381e83..7930129f4 100644 --- a/drizzle-orm/src/postgres-js/migrator.ts +++ b/drizzle-orm/src/postgres-js/migrator.ts @@ -4,7 +4,7 @@ import type { PostgresJsDatabase } from './driver.ts'; export async function migrate>( db: PostgresJsDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); await db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/postgres-js/session.ts b/drizzle-orm/src/postgres-js/session.ts index 05179ebdb..7509e2a00 100644 --- a/drizzle-orm/src/postgres-js/session.ts +++ b/drizzle-orm/src/postgres-js/session.ts @@ -13,7 +13,7 @@ import { tracer } from '~/tracing.ts'; import { type Assume, mapResultRow } from '~/utils.ts'; export class PostgresJsPreparedQuery extends PgPreparedQuery { - static readonly [entityKind]: string = 'PostgresJsPreparedQuery'; + static override readonly [entityKind]: string = 'PostgresJsPreparedQuery'; constructor( private client: Sql, @@ -95,7 +95,7 @@ export class PostgresJsSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgSession { - static readonly [entityKind]: string = 'PostgresJsSession'; + static override readonly [entityKind]: string = 'PostgresJsSession'; logger: Logger; @@ -164,7 +164,7 @@ export class PostgresJsTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgTransaction { - static readonly [entityKind]: string = 'PostgresJsTransaction'; + static override readonly [entityKind]: string = 'PostgresJsTransaction'; constructor( dialect: PgDialect, diff --git a/drizzle-orm/src/prisma/mysql/driver.ts b/drizzle-orm/src/prisma/mysql/driver.ts index 586832948..c6ca143df 100644 --- a/drizzle-orm/src/prisma/mysql/driver.ts +++ b/drizzle-orm/src/prisma/mysql/driver.ts @@ -13,7 +13,7 @@ import { PrismaMySqlSession } from './session.ts'; export class PrismaMySqlDatabase extends MySqlDatabase> { - static readonly [entityKind]: string = 'PrismaMySqlDatabase'; + static override readonly [entityKind]: string = 'PrismaMySqlDatabase'; constructor(client: PrismaClient, logger: Logger | undefined) { const dialect = new MySqlDialect(); diff --git a/drizzle-orm/src/prisma/mysql/session.ts b/drizzle-orm/src/prisma/mysql/session.ts index a6b12a0c3..fc3807bc5 100644 --- a/drizzle-orm/src/prisma/mysql/session.ts +++ b/drizzle-orm/src/prisma/mysql/session.ts @@ -19,7 +19,7 @@ export class PrismaMySqlPreparedQuery extends MySqlPreparedQuery | undefined): AsyncGenerator { throw new Error('Method not implemented.'); } - static readonly [entityKind]: string = 'PrismaMySqlPreparedQuery'; + static override readonly [entityKind]: string = 'PrismaMySqlPreparedQuery'; constructor( private readonly prisma: PrismaClient, @@ -41,7 +41,7 @@ export interface PrismaMySqlSessionOptions { } export class PrismaMySqlSession extends MySqlSession { - static readonly [entityKind]: string = 'PrismaMySqlSession'; + static override readonly [entityKind]: string = 'PrismaMySqlSession'; private readonly logger: Logger; diff --git a/drizzle-orm/src/prisma/pg/driver.ts b/drizzle-orm/src/prisma/pg/driver.ts index 23678f09f..f9038d8a1 100644 --- a/drizzle-orm/src/prisma/pg/driver.ts +++ b/drizzle-orm/src/prisma/pg/driver.ts @@ -11,7 +11,7 @@ import type { PrismaPgQueryResultHKT } from './session.ts'; import { PrismaPgSession } from './session.ts'; export class PrismaPgDatabase extends PgDatabase> { - static readonly [entityKind]: string = 'PrismaPgDatabase'; + static override readonly [entityKind]: string = 'PrismaPgDatabase'; constructor(client: PrismaClient, logger: Logger | undefined) { const dialect = new PgDialect(); diff --git a/drizzle-orm/src/prisma/pg/session.ts b/drizzle-orm/src/prisma/pg/session.ts index 077326ef3..b93f6f14b 100644 --- a/drizzle-orm/src/prisma/pg/session.ts +++ b/drizzle-orm/src/prisma/pg/session.ts @@ -14,7 +14,7 @@ import type { Query, SQL } from '~/sql/sql.ts'; import { fillPlaceholders } from '~/sql/sql.ts'; export class PrismaPgPreparedQuery extends PgPreparedQuery { - static readonly [entityKind]: string = 'PrismaPgPreparedQuery'; + static override readonly [entityKind]: string = 'PrismaPgPreparedQuery'; constructor( private readonly prisma: PrismaClient, @@ -44,7 +44,7 @@ export interface PrismaPgSessionOptions { } export class PrismaPgSession extends PgSession { - static readonly [entityKind]: string = 'PrismaPgSession'; + static override readonly [entityKind]: string = 'PrismaPgSession'; private readonly logger: Logger; diff --git a/drizzle-orm/src/prisma/sqlite/session.ts b/drizzle-orm/src/prisma/sqlite/session.ts index 3dbdc6f1a..3a10fddbd 100644 --- a/drizzle-orm/src/prisma/sqlite/session.ts +++ b/drizzle-orm/src/prisma/sqlite/session.ts @@ -19,7 +19,7 @@ type PreparedQueryConfig = Omit; export class PrismaSQLitePreparedQuery extends SQLitePreparedQuery< { type: 'async'; run: []; all: T['all']; get: T['get']; values: never; execute: T['execute'] } > { - static readonly [entityKind]: string = 'PrismaSQLitePreparedQuery'; + static override readonly [entityKind]: string = 'PrismaSQLitePreparedQuery'; constructor( private readonly prisma: PrismaClient, @@ -60,7 +60,7 @@ export interface PrismaSQLiteSessionOptions { } export class PrismaSQLiteSession extends SQLiteSession<'async', unknown, Record, Record> { - static readonly [entityKind]: string = 'PrismaSQLiteSession'; + static override readonly [entityKind]: string = 'PrismaSQLiteSession'; private readonly logger: Logger; diff --git a/drizzle-orm/src/query-builders/select.types.ts b/drizzle-orm/src/query-builders/select.types.ts index 07579662f..e7975af65 100644 --- a/drizzle-orm/src/query-builders/select.types.ts +++ b/drizzle-orm/src/query-builders/select.types.ts @@ -93,6 +93,7 @@ export type AddAliasToSelection< : { [Key in keyof TSelection]: TSelection[Key] extends Column ? ChangeColumnTableName + : TSelection[Key] extends Table ? AddAliasToSelection : TSelection[Key] extends SQL | SQL.Aliased ? TSelection[Key] : TSelection[Key] extends ColumnsSelection ? MapColumnsToTableAlias : never; @@ -120,6 +121,7 @@ export type BuildSubquerySelection< [Key in keyof TSelection]: TSelection[Key] extends SQL ? DrizzleTypeError<'You cannot reference this field without assigning it an alias first - use `.as()`'> : TSelection[Key] extends SQL.Aliased ? TSelection[Key] + : TSelection[Key] extends Table ? BuildSubquerySelection : TSelection[Key] extends Column ? ApplyNullabilityToColumn : TSelection[Key] extends ColumnsSelection ? BuildSubquerySelection diff --git a/drizzle-orm/src/relations.ts b/drizzle-orm/src/relations.ts index 99780897e..adaee8076 100644 --- a/drizzle-orm/src/relations.ts +++ b/drizzle-orm/src/relations.ts @@ -66,7 +66,7 @@ export class One< TTableName extends string = string, TIsNullable extends boolean = boolean, > extends Relation { - static readonly [entityKind]: string = 'One'; + static override readonly [entityKind]: string = 'One'; declare protected $relationBrand: 'One'; @@ -98,7 +98,7 @@ export class One< } export class Many extends Relation { - static readonly [entityKind]: string = 'Many'; + static override readonly [entityKind]: string = 'Many'; declare protected $relationBrand: 'Many'; @@ -214,22 +214,27 @@ export type DBQueryConfig< TTableConfig extends TableRelationalConfig = TableRelationalConfig, > = & { - columns?: { - [K in keyof TTableConfig['columns']]?: boolean; - }; - with?: { - [K in keyof TTableConfig['relations']]?: - | true - | DBQueryConfig< - TTableConfig['relations'][K] extends One ? 'one' : 'many', - false, - TSchema, - FindTableByDBName< + columns?: + | { + [K in keyof TTableConfig['columns']]?: boolean; + } + | undefined; + with?: + | { + [K in keyof TTableConfig['relations']]?: + | true + | DBQueryConfig< + TTableConfig['relations'][K] extends One ? 'one' : 'many', + false, TSchema, - TTableConfig['relations'][K]['referencedTableName'] + FindTableByDBName< + TSchema, + TTableConfig['relations'][K]['referencedTableName'] + > > - >; - }; + | undefined; + } + | undefined; extras?: | Record | (( @@ -238,7 +243,8 @@ export type DBQueryConfig< : TTableConfig['columns'] >, operators: { sql: Operators['sql'] }, - ) => Record); + ) => Record) + | undefined; } & (TRelationType extends 'many' ? & { @@ -260,11 +266,12 @@ export type DBQueryConfig< : TTableConfig['columns'] >, operators: OrderByOperators, - ) => ValueOrArray); - limit?: number | Placeholder; + ) => ValueOrArray) + | undefined; + limit?: number | Placeholder | undefined; } & (TIsRoot extends true ? { - offset?: number | Placeholder; + offset?: number | Placeholder | undefined; } : {}) : {}); diff --git a/drizzle-orm/src/singlestore-core/alias.ts b/drizzle-orm/src/singlestore-core/alias.ts new file mode 100644 index 000000000..6c08bdff3 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/alias.ts @@ -0,0 +1,10 @@ +import { TableAliasProxyHandler } from '~/alias.ts'; +import type { BuildAliasTable } from './query-builders/select.types.ts'; +import type { SingleStoreTable } from './table.ts'; + +export function alias( // | SingleStoreViewBase + table: TTable, + alias: TAlias, +): BuildAliasTable { + return new Proxy(table, new TableAliasProxyHandler(alias, false)) as any; +} diff --git a/drizzle-orm/src/singlestore-core/columns/all.ts b/drizzle-orm/src/singlestore-core/columns/all.ts new file mode 100644 index 000000000..7bb704a8b --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/all.ts @@ -0,0 +1,60 @@ +import { bigint } from './bigint.ts'; +import { binary } from './binary.ts'; +import { boolean } from './boolean.ts'; +import { char } from './char.ts'; +import { customType } from './custom.ts'; +import { date } from './date.ts'; +import { datetime } from './datetime.ts'; +import { decimal } from './decimal.ts'; +import { double } from './double.ts'; +import { singlestoreEnum } from './enum.ts'; +import { float } from './float.ts'; +import { int } from './int.ts'; +import { json } from './json.ts'; +import { mediumint } from './mediumint.ts'; +import { real } from './real.ts'; +import { serial } from './serial.ts'; +import { smallint } from './smallint.ts'; +import { longtext, mediumtext, text, tinytext } from './text.ts'; +import { time } from './time.ts'; +import { timestamp } from './timestamp.ts'; +import { tinyint } from './tinyint.ts'; +import { varbinary } from './varbinary.ts'; +import { varchar } from './varchar.ts'; +import { vector } from './vector.ts'; +import { year } from './year.ts'; + +export function getSingleStoreColumnBuilders() { + return { + bigint, + binary, + boolean, + char, + customType, + date, + datetime, + decimal, + double, + singlestoreEnum, + float, + int, + json, + mediumint, + real, + serial, + smallint, + longtext, + mediumtext, + text, + tinytext, + time, + timestamp, + tinyint, + varbinary, + varchar, + vector, + year, + }; +} + +export type SingleStoreColumnBuilders = ReturnType; diff --git a/drizzle-orm/src/singlestore-core/columns/bigint.ts b/drizzle-orm/src/singlestore-core/columns/bigint.ts new file mode 100644 index 000000000..fed436993 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/bigint.ts @@ -0,0 +1,119 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreBigInt53BuilderInitial = SingleStoreBigInt53Builder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreBigInt53'; + data: number; + driverParam: number | string; + enumValues: undefined; +}>; + +export class SingleStoreBigInt53Builder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreBigInt53Builder'; + + constructor(name: T['name'], unsigned: boolean = false) { + super(name, 'number', 'SingleStoreBigInt53'); + this.config.unsigned = unsigned; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBigInt53> { + return new SingleStoreBigInt53>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBigInt53> + extends SingleStoreColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreBigInt53'; + + getSQLType(): string { + return `bigint${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'number') { + return value; + } + return Number(value); + } +} + +export type SingleStoreBigInt64BuilderInitial = SingleStoreBigInt64Builder<{ + name: TName; + dataType: 'bigint'; + columnType: 'SingleStoreBigInt64'; + data: bigint; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBigInt64Builder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreBigInt64Builder'; + + constructor(name: T['name'], unsigned: boolean = false) { + super(name, 'bigint', 'SingleStoreBigInt64'); + this.config.unsigned = unsigned; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBigInt64> { + return new SingleStoreBigInt64>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBigInt64> + extends SingleStoreColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreBigInt64'; + + getSQLType(): string { + return `bigint${this.config.unsigned ? ' unsigned' : ''}`; + } + + // eslint-disable-next-line unicorn/prefer-native-coercion-functions + override mapFromDriverValue(value: string): bigint { + return BigInt(value); + } +} + +export interface SingleStoreBigIntConfig { + mode: T; + unsigned?: boolean; +} + +export function bigint( + config: SingleStoreBigIntConfig, +): TMode extends 'number' ? SingleStoreBigInt53BuilderInitial<''> : SingleStoreBigInt64BuilderInitial<''>; +export function bigint( + name: TName, + config: SingleStoreBigIntConfig, +): TMode extends 'number' ? SingleStoreBigInt53BuilderInitial : SingleStoreBigInt64BuilderInitial; +export function bigint(a?: string | SingleStoreBigIntConfig, b?: SingleStoreBigIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config.mode === 'number') { + return new SingleStoreBigInt53Builder(name, config.unsigned); + } + return new SingleStoreBigInt64Builder(name, config.unsigned); +} diff --git a/drizzle-orm/src/singlestore-core/columns/binary.ts b/drizzle-orm/src/singlestore-core/columns/binary.ts new file mode 100644 index 000000000..153456447 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/binary.ts @@ -0,0 +1,70 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreBinaryBuilderInitial = SingleStoreBinaryBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreBinary'; + data: string; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBinaryBuilder> + extends SingleStoreColumnBuilder< + T, + SingleStoreBinaryConfig + > +{ + static override readonly [entityKind]: string = 'SingleStoreBinaryBuilder'; + + constructor(name: T['name'], length: number | undefined) { + super(name, 'string', 'SingleStoreBinary'); + this.config.length = length; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBinary> { + return new SingleStoreBinary>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBinary> extends SingleStoreColumn< + T, + SingleStoreBinaryConfig +> { + static override readonly [entityKind]: string = 'SingleStoreBinary'; + + length: number | undefined = this.config.length; + + getSQLType(): string { + return this.length === undefined ? `binary` : `binary(${this.length})`; + } +} + +export interface SingleStoreBinaryConfig { + length?: number; +} + +export function binary(): SingleStoreBinaryBuilderInitial<''>; +export function binary( + config?: SingleStoreBinaryConfig, +): SingleStoreBinaryBuilderInitial<''>; +export function binary( + name: TName, + config?: SingleStoreBinaryConfig, +): SingleStoreBinaryBuilderInitial; +export function binary(a?: string | SingleStoreBinaryConfig, b: SingleStoreBinaryConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreBinaryBuilder(name, config.length); +} diff --git a/drizzle-orm/src/singlestore-core/columns/boolean.ts b/drizzle-orm/src/singlestore-core/columns/boolean.ts new file mode 100644 index 000000000..bf48ff1da --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/boolean.ts @@ -0,0 +1,58 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreBooleanBuilderInitial = SingleStoreBooleanBuilder<{ + name: TName; + dataType: 'boolean'; + columnType: 'SingleStoreBoolean'; + data: boolean; + driverParam: number | boolean; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBooleanBuilder> + extends SingleStoreColumnBuilder +{ + static override readonly [entityKind]: string = 'SingleStoreBooleanBuilder'; + + constructor(name: T['name']) { + super(name, 'boolean', 'SingleStoreBoolean'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBoolean> { + return new SingleStoreBoolean>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBoolean> + extends SingleStoreColumn +{ + static override readonly [entityKind]: string = 'SingleStoreBoolean'; + + getSQLType(): string { + return 'boolean'; + } + + override mapFromDriverValue(value: number | boolean): boolean { + if (typeof value === 'boolean') { + return value; + } + return value === 1; + } +} + +export function boolean(): SingleStoreBooleanBuilderInitial<''>; +export function boolean(name: TName): SingleStoreBooleanBuilderInitial; +export function boolean(name?: string) { + return new SingleStoreBooleanBuilder(name ?? ''); +} diff --git a/drizzle-orm/src/singlestore-core/columns/char.ts b/drizzle-orm/src/singlestore-core/columns/char.ts new file mode 100644 index 000000000..903946688 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/char.ts @@ -0,0 +1,86 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreCharBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = SingleStoreCharBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreChar'; + data: TEnum[number]; + driverParam: number | string; + enumValues: TEnum; + generated: undefined; + length: TLength; +}>; + +export class SingleStoreCharBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SingleStoreChar'> & { length?: number | undefined }, +> extends SingleStoreColumnBuilder< + T, + SingleStoreCharConfig, + { length: T['length'] } +> { + static override readonly [entityKind]: string = 'SingleStoreCharBuilder'; + + constructor(name: T['name'], config: SingleStoreCharConfig) { + super(name, 'string', 'SingleStoreChar'); + this.config.length = config.length; + this.config.enum = config.enum; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreChar & { length: T['length']; enumValues: T['enumValues'] }> { + return new SingleStoreChar & { length: T['length']; enumValues: T['enumValues'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreChar & { length?: number | undefined }> + extends SingleStoreColumn, { length: T['length'] }> +{ + static override readonly [entityKind]: string = 'SingleStoreChar'; + + readonly length: T['length'] = this.config.length; + override readonly enumValues = this.config.enum; + + getSQLType(): string { + return this.length === undefined ? `char` : `char(${this.length})`; + } +} + +export interface SingleStoreCharConfig< + TEnum extends readonly string[] | string[] | undefined = readonly string[] | string[] | undefined, + TLength extends number | undefined = number | undefined, +> { + enum?: TEnum; + length?: TLength; +} + +export function char(): SingleStoreCharBuilderInitial<'', [string, ...string[]], undefined>; +export function char, L extends number | undefined>( + config?: SingleStoreCharConfig, L>, +): SingleStoreCharBuilderInitial<'', Writable, L>; +export function char< + TName extends string, + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( + name: TName, + config?: SingleStoreCharConfig, L>, +): SingleStoreCharBuilderInitial, L>; +export function char(a?: string | SingleStoreCharConfig, b: SingleStoreCharConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreCharBuilder(name, config as any); +} diff --git a/drizzle-orm/src/singlestore-core/columns/common.ts b/drizzle-orm/src/singlestore-core/columns/common.ts new file mode 100644 index 000000000..7f2a521e5 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/common.ts @@ -0,0 +1,118 @@ +import type { + ColumnBuilderBase, + ColumnBuilderBaseConfig, + ColumnBuilderExtraConfig, + ColumnBuilderRuntimeConfig, + ColumnDataType, + HasDefault, + IsAutoincrement, + MakeColumnConfig, +} from '~/column-builder.ts'; +import { ColumnBuilder } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { Column } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable, SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { SQL } from '~/sql/sql.ts'; +import type { Update } from '~/utils.ts'; +import { uniqueKeyName } from '../unique-constraint.ts'; + +export interface SingleStoreColumnBuilderBase< + T extends ColumnBuilderBaseConfig = ColumnBuilderBaseConfig, + TTypeConfig extends object = object, +> extends ColumnBuilderBase {} + +export interface SingleStoreGeneratedColumnConfig { + mode?: 'virtual' | 'stored'; +} + +export abstract class SingleStoreColumnBuilder< + T extends ColumnBuilderBaseConfig = ColumnBuilderBaseConfig & { + data: any; + }, + TRuntimeConfig extends object = object, + TTypeConfig extends object = object, + TExtraConfig extends ColumnBuilderExtraConfig = ColumnBuilderExtraConfig, +> extends ColumnBuilder + implements SingleStoreColumnBuilderBase +{ + static override readonly [entityKind]: string = 'SingleStoreColumnBuilder'; + + unique(name?: string): this { + this.config.isUnique = true; + this.config.uniqueName = name; + return this; + } + + // TODO: Implement generated columns for SingleStore (https://docs.singlestore.com/cloud/create-a-database/using-persistent-computed-columns/) + /** @internal */ + generatedAlwaysAs(as: SQL | T['data'] | (() => SQL), config?: SingleStoreGeneratedColumnConfig) { + this.config.generated = { + as, + type: 'always', + mode: config?.mode ?? 'virtual', + }; + return this as any; + } + + /** @internal */ + abstract build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreColumn>; +} + +// To understand how to use `SingleStoreColumn` and `AnySingleStoreColumn`, see `Column` and `AnyColumn` documentation. +export abstract class SingleStoreColumn< + T extends ColumnBaseConfig = ColumnBaseConfig, + TRuntimeConfig extends object = {}, + TTypeConfig extends object = {}, +> extends Column { + static override readonly [entityKind]: string = 'SingleStoreColumn'; + + constructor( + override readonly table: SingleStoreTable, + config: ColumnBuilderRuntimeConfig, + ) { + if (!config.uniqueName) { + config.uniqueName = uniqueKeyName(table, [config.name]); + } + super(table, config); + } +} + +export type AnySingleStoreColumn> = {}> = + SingleStoreColumn< + Required, TPartial>> + >; + +export interface SingleStoreColumnWithAutoIncrementConfig { + autoIncrement: boolean; +} + +export abstract class SingleStoreColumnBuilderWithAutoIncrement< + T extends ColumnBuilderBaseConfig = ColumnBuilderBaseConfig, + TRuntimeConfig extends object = object, + TExtraConfig extends ColumnBuilderExtraConfig = ColumnBuilderExtraConfig, +> extends SingleStoreColumnBuilder { + static override readonly [entityKind]: string = 'SingleStoreColumnBuilderWithAutoIncrement'; + + constructor(name: NonNullable, dataType: T['dataType'], columnType: T['columnType']) { + super(name, dataType, columnType); + this.config.autoIncrement = false; + } + + autoincrement(): IsAutoincrement> { + this.config.autoIncrement = true; + this.config.hasDefault = true; + return this as IsAutoincrement>; + } +} + +export abstract class SingleStoreColumnWithAutoIncrement< + T extends ColumnBaseConfig = ColumnBaseConfig, + TRuntimeConfig extends object = object, +> extends SingleStoreColumn { + static override readonly [entityKind]: string = 'SingleStoreColumnWithAutoIncrement'; + + readonly autoIncrement: boolean = this.config.autoIncrement; +} diff --git a/drizzle-orm/src/singlestore-core/columns/custom.ts b/drizzle-orm/src/singlestore-core/columns/custom.ts new file mode 100644 index 000000000..964e077d7 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/custom.ts @@ -0,0 +1,235 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { SQL } from '~/sql/sql.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type ConvertCustomConfig> = + & { + name: TName; + dataType: 'custom'; + columnType: 'SingleStoreCustomColumn'; + data: T['data']; + driverParam: T['driverData']; + enumValues: undefined; + generated: undefined; + } + & (T['notNull'] extends true ? { notNull: true } : {}) + & (T['default'] extends true ? { hasDefault: true } : {}); + +export interface SingleStoreCustomColumnInnerConfig { + customTypeValues: CustomTypeValues; +} + +export class SingleStoreCustomColumnBuilder> + extends SingleStoreColumnBuilder< + T, + { + fieldConfig: CustomTypeValues['config']; + customTypeParams: CustomTypeParams; + }, + { + singlestoreColumnBuilderBrand: 'SingleStoreCustomColumnBuilderBrand'; + } + > +{ + static override readonly [entityKind]: string = 'SingleStoreCustomColumnBuilder'; + + constructor( + name: T['name'], + fieldConfig: CustomTypeValues['config'], + customTypeParams: CustomTypeParams, + ) { + super(name, 'custom', 'SingleStoreCustomColumn'); + this.config.fieldConfig = fieldConfig; + this.config.customTypeParams = customTypeParams; + } + + /** @internal */ + build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreCustomColumn> { + return new SingleStoreCustomColumn>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreCustomColumn> + extends SingleStoreColumn +{ + static override readonly [entityKind]: string = 'SingleStoreCustomColumn'; + + private sqlName: string; + private mapTo?: (value: T['data']) => T['driverParam']; + private mapFrom?: (value: T['driverParam']) => T['data']; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreCustomColumnBuilder['config'], + ) { + super(table, config); + this.sqlName = config.customTypeParams.dataType(config.fieldConfig); + this.mapTo = config.customTypeParams.toDriver; + this.mapFrom = config.customTypeParams.fromDriver; + } + + getSQLType(): string { + return this.sqlName; + } + + override mapFromDriverValue(value: T['driverParam']): T['data'] { + return typeof this.mapFrom === 'function' ? this.mapFrom(value) : value as T['data']; + } + + override mapToDriverValue(value: T['data']): T['driverParam'] { + return typeof this.mapTo === 'function' ? this.mapTo(value) : value as T['data']; + } +} + +export type CustomTypeValues = { + /** + * Required type for custom column, that will infer proper type model + * + * Examples: + * + * If you want your column to be `string` type after selecting/or on inserting - use `data: string`. Like `text`, `varchar` + * + * If you want your column to be `number` type after selecting/or on inserting - use `data: number`. Like `integer` + */ + data: unknown; + + /** + * Type helper, that represents what type database driver is accepting for specific database data type + */ + driverData?: unknown; + + /** + * What config type should be used for {@link CustomTypeParams} `dataType` generation + */ + config?: Record; + + /** + * Whether the config argument should be required or not + * @default false + */ + configRequired?: boolean; + + /** + * If your custom data type should be notNull by default you can use `notNull: true` + * + * @example + * const customSerial = customType<{ data: number, notNull: true, default: true }>({ + * dataType() { + * return 'serial'; + * }, + * }); + */ + notNull?: boolean; + + /** + * If your custom data type has default you can use `default: true` + * + * @example + * const customSerial = customType<{ data: number, notNull: true, default: true }>({ + * dataType() { + * return 'serial'; + * }, + * }); + */ + default?: boolean; +}; + +export interface CustomTypeParams { + /** + * Database data type string representation, that is used for migrations + * @example + * ``` + * `jsonb`, `text` + * ``` + * + * If database data type needs additional params you can use them from `config` param + * @example + * ``` + * `varchar(256)`, `numeric(2,3)` + * ``` + * + * To make `config` be of specific type please use config generic in {@link CustomTypeValues} + * + * @example + * Usage example + * ``` + * dataType() { + * return 'boolean'; + * }, + * ``` + * Or + * ``` + * dataType(config) { + * return typeof config.length !== 'undefined' ? `varchar(${config.length})` : `varchar`; + * } + * ``` + */ + dataType: (config: T['config'] | (Equal extends true ? never : undefined)) => string; + + /** + * Optional mapping function, between user input and driver + * @example + * For example, when using jsonb we need to map JS/TS object to string before writing to database + * ``` + * toDriver(value: TData): string { + * return JSON.stringify(value); + * } + * ``` + */ + toDriver?: (value: T['data']) => T['driverData'] | SQL; + + /** + * Optional mapping function, that is responsible for data mapping from database to JS/TS code + * @example + * For example, when using timestamp we need to map string Date representation to JS Date + * ``` + * fromDriver(value: string): Date { + * return new Date(value); + * }, + * ``` + */ + fromDriver?: (value: T['driverData']) => T['data']; +} + +/** + * Custom singlestore database data type generator + */ +export function customType( + customTypeParams: CustomTypeParams, +): Equal extends true ? { + & T['config']>( + fieldConfig: TConfig, + ): SingleStoreCustomColumnBuilder>; + ( + dbName: TName, + fieldConfig: T['config'], + ): SingleStoreCustomColumnBuilder>; + } + : { + (): SingleStoreCustomColumnBuilder>; + & T['config']>( + fieldConfig?: TConfig, + ): SingleStoreCustomColumnBuilder>; + ( + dbName: TName, + fieldConfig?: T['config'], + ): SingleStoreCustomColumnBuilder>; + } +{ + return ( + a?: TName | T['config'], + b?: T['config'], + ): SingleStoreCustomColumnBuilder> => { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreCustomColumnBuilder(name as ConvertCustomConfig['name'], config, customTypeParams); + }; +} diff --git a/drizzle-orm/src/singlestore-core/columns/date.common.ts b/drizzle-orm/src/singlestore-core/columns/date.common.ts new file mode 100644 index 000000000..39b2507eb --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/date.common.ts @@ -0,0 +1,41 @@ +import type { + ColumnBuilderBaseConfig, + ColumnBuilderExtraConfig, + ColumnDataType, + HasDefault, +} from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import { sql } from '~/sql/sql.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export interface SingleStoreDateColumnBaseConfig { + hasOnUpdateNow: boolean; +} + +export abstract class SingleStoreDateColumnBaseBuilder< + T extends ColumnBuilderBaseConfig, + TRuntimeConfig extends object = object, + TExtraConfig extends ColumnBuilderExtraConfig = ColumnBuilderExtraConfig, +> extends SingleStoreColumnBuilder { + static override readonly [entityKind]: string = 'SingleStoreDateColumnBuilder'; + + defaultNow() { + return this.default(sql`now()`); + } + + onUpdateNow(): HasDefault { + this.config.hasOnUpdateNow = true; + this.config.hasDefault = true; + return this as HasDefault; + } +} + +export abstract class SingleStoreDateBaseColumn< + T extends ColumnBaseConfig, + TRuntimeConfig extends object = object, +> extends SingleStoreColumn { + static override readonly [entityKind]: string = 'SingleStoreDateColumn'; + + readonly hasOnUpdateNow: boolean = this.config.hasOnUpdateNow; +} diff --git a/drizzle-orm/src/singlestore-core/columns/date.ts b/drizzle-orm/src/singlestore-core/columns/date.ts new file mode 100644 index 000000000..70da74f3a --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/date.ts @@ -0,0 +1,123 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreDateBuilderInitial = SingleStoreDateBuilder<{ + name: TName; + dataType: 'date'; + columnType: 'SingleStoreDate'; + data: Date; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDateBuilder> + extends SingleStoreColumnBuilder +{ + static override readonly [entityKind]: string = 'SingleStoreDateBuilder'; + + constructor(name: T['name']) { + super(name, 'date', 'SingleStoreDate'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDate> { + return new SingleStoreDate>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDate> extends SingleStoreColumn { + static override readonly [entityKind]: string = 'SingleStoreDate'; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreDateBuilder['config'], + ) { + super(table, config); + } + + getSQLType(): string { + return `date`; + } + + override mapFromDriverValue(value: string): Date { + return new Date(value); + } +} + +export type SingleStoreDateStringBuilderInitial = SingleStoreDateStringBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreDateString'; + data: string; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDateStringBuilder> + extends SingleStoreColumnBuilder +{ + static override readonly [entityKind]: string = 'SingleStoreDateStringBuilder'; + + constructor(name: T['name']) { + super(name, 'string', 'SingleStoreDateString'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDateString> { + return new SingleStoreDateString>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDateString> + extends SingleStoreColumn +{ + static override readonly [entityKind]: string = 'SingleStoreDateString'; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreDateStringBuilder['config'], + ) { + super(table, config); + } + + getSQLType(): string { + return `date`; + } +} + +export interface SingleStoreDateConfig { + mode?: TMode; +} + +export function date(): SingleStoreDateBuilderInitial<''>; +export function date( + config?: SingleStoreDateConfig, +): Equal extends true ? SingleStoreDateStringBuilderInitial<''> : SingleStoreDateBuilderInitial<''>; +export function date( + name: TName, + config?: SingleStoreDateConfig, +): Equal extends true ? SingleStoreDateStringBuilderInitial + : SingleStoreDateBuilderInitial; +export function date(a?: string | SingleStoreDateConfig, b?: SingleStoreDateConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config?.mode === 'string') { + return new SingleStoreDateStringBuilder(name); + } + return new SingleStoreDateBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/columns/datetime.ts b/drizzle-orm/src/singlestore-core/columns/datetime.ts new file mode 100644 index 000000000..16f137901 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/datetime.ts @@ -0,0 +1,153 @@ +import type { + ColumnBuilderBaseConfig, + ColumnBuilderRuntimeConfig, + GeneratedColumnConfig, + HasGenerated, + MakeColumnConfig, +} from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { SQL } from '~/sql/index.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreDateTimeBuilderInitial = SingleStoreDateTimeBuilder<{ + name: TName; + dataType: 'date'; + columnType: 'SingleStoreDateTime'; + data: Date; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDateTimeBuilder> + extends SingleStoreColumnBuilder +{ + /** @internal */ + // TODO: we need to add a proper support for SingleStore + override generatedAlwaysAs( + _as: SQL | (() => SQL) | T['data'], + _config?: Partial>, + ): HasGenerated { + throw new Error('Method not implemented.'); + } + static override readonly [entityKind]: string = 'SingleStoreDateTimeBuilder'; + + constructor(name: T['name']) { + super(name, 'date', 'SingleStoreDateTime'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDateTime> { + return new SingleStoreDateTime>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDateTime> + extends SingleStoreColumn +{ + static override readonly [entityKind]: string = 'SingleStoreDateTime'; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreDateTimeBuilder['config'], + ) { + super(table, config); + } + + getSQLType(): string { + return `datetime`; + } + + override mapToDriverValue(value: Date): unknown { + return value.toISOString().replace('T', ' ').replace('Z', ''); + } + + override mapFromDriverValue(value: string): Date { + return new Date(value.replace(' ', 'T') + 'Z'); + } +} + +export type SingleStoreDateTimeStringBuilderInitial = SingleStoreDateTimeStringBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreDateTimeString'; + data: string; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDateTimeStringBuilder> + extends SingleStoreColumnBuilder +{ + /** @internal */ + // TODO: we need to add a proper support for SingleStore + override generatedAlwaysAs( + _as: SQL | (() => SQL) | T['data'], + _config?: Partial>, + ): HasGenerated { + throw new Error('Method not implemented.'); + } + static override readonly [entityKind]: string = 'SingleStoreDateTimeStringBuilder'; + + constructor(name: T['name']) { + super(name, 'string', 'SingleStoreDateTimeString'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDateTimeString> { + return new SingleStoreDateTimeString>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDateTimeString> + extends SingleStoreColumn +{ + static override readonly [entityKind]: string = 'SingleStoreDateTimeString'; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreDateTimeStringBuilder['config'], + ) { + super(table, config); + } + + getSQLType(): string { + return `datetime`; + } +} + +export interface SingleStoreDatetimeConfig { + mode?: TMode; +} + +export function datetime(): SingleStoreDateTimeBuilderInitial<''>; +export function datetime( + config?: SingleStoreDatetimeConfig, +): Equal extends true ? SingleStoreDateTimeStringBuilderInitial<''> + : SingleStoreDateTimeBuilderInitial<''>; +export function datetime( + name: TName, + config?: SingleStoreDatetimeConfig, +): Equal extends true ? SingleStoreDateTimeStringBuilderInitial + : SingleStoreDateTimeBuilderInitial; +export function datetime(a?: string | SingleStoreDatetimeConfig, b?: SingleStoreDatetimeConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config?.mode === 'string') { + return new SingleStoreDateTimeStringBuilder(name); + } + return new SingleStoreDateTimeBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/columns/decimal.ts b/drizzle-orm/src/singlestore-core/columns/decimal.ts new file mode 100644 index 000000000..d0c61732c --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/decimal.ts @@ -0,0 +1,90 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreDecimalBuilderInitial = SingleStoreDecimalBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreDecimal'; + data: string; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDecimalBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SingleStoreDecimal'>, +> extends SingleStoreColumnBuilderWithAutoIncrement< + T, + SingleStoreDecimalConfig +> { + static override readonly [entityKind]: string = 'SingleStoreDecimalBuilder'; + + constructor(name: T['name'], config: SingleStoreDecimalConfig | undefined) { + super(name, 'string', 'SingleStoreDecimal'); + this.config.precision = config?.precision; + this.config.scale = config?.scale; + this.config.unsigned = config?.unsigned; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDecimal> { + return new SingleStoreDecimal>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDecimal< + T extends ColumnBaseConfig<'string', 'SingleStoreDecimal'>, +> extends SingleStoreColumnWithAutoIncrement { + static override readonly [entityKind]: string = 'SingleStoreDecimal'; + + readonly precision: number | undefined = this.config.precision; + readonly scale: number | undefined = this.config.scale; + readonly unsigned: boolean | undefined = this.config.unsigned; + + getSQLType(): string { + let type = ''; + if (this.precision !== undefined && this.scale !== undefined) { + type += `decimal(${this.precision},${this.scale})`; + } else if (this.precision === undefined) { + type += 'decimal'; + } else { + type += `decimal(${this.precision})`; + } + type = type === 'decimal(10,0)' || type === 'decimal(10)' ? 'decimal' : type; + return this.unsigned ? `${type} unsigned` : type; + } +} + +export interface SingleStoreDecimalConfig { + precision?: number; + scale?: number; + unsigned?: boolean; +} + +export function decimal(): SingleStoreDecimalBuilderInitial<''>; +export function decimal( + config: SingleStoreDecimalConfig, +): SingleStoreDecimalBuilderInitial<''>; +export function decimal( + name: TName, + config?: SingleStoreDecimalConfig, +): SingleStoreDecimalBuilderInitial; +export function decimal( + a?: string | SingleStoreDecimalConfig, + b: SingleStoreDecimalConfig = {}, +) { + const { name, config } = getColumnNameAndConfig( + a, + b, + ); + return new SingleStoreDecimalBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/double.ts b/drizzle-orm/src/singlestore-core/columns/double.ts new file mode 100644 index 000000000..103731eab --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/double.ts @@ -0,0 +1,80 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreDoubleBuilderInitial = SingleStoreDoubleBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreDouble'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDoubleBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreDoubleBuilder'; + + constructor(name: T['name'], config: SingleStoreDoubleConfig | undefined) { + super(name, 'number', 'SingleStoreDouble'); + this.config.precision = config?.precision; + this.config.scale = config?.scale; + this.config.unsigned = config?.unsigned; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDouble> { + return new SingleStoreDouble>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDouble> + extends SingleStoreColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreDouble'; + + readonly precision: number | undefined = this.config.precision; + readonly scale: number | undefined = this.config.scale; + readonly unsigned: boolean | undefined = this.config.unsigned; + + getSQLType(): string { + let type = ''; + if (this.precision !== undefined && this.scale !== undefined) { + type += `double(${this.precision},${this.scale})`; + } else if (this.precision === undefined) { + type += 'double'; + } else { + type += `double(${this.precision})`; + } + return this.unsigned ? `${type} unsigned` : type; + } +} + +export interface SingleStoreDoubleConfig { + precision?: number; + scale?: number; + unsigned?: boolean; +} + +export function double(): SingleStoreDoubleBuilderInitial<''>; +export function double( + config?: SingleStoreDoubleConfig, +): SingleStoreDoubleBuilderInitial<''>; +export function double( + name: TName, + config?: SingleStoreDoubleConfig, +): SingleStoreDoubleBuilderInitial; +export function double(a?: string | SingleStoreDoubleConfig, b?: SingleStoreDoubleConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreDoubleBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/enum.ts b/drizzle-orm/src/singlestore-core/columns/enum.ts new file mode 100644 index 000000000..98f3d78e3 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/enum.ts @@ -0,0 +1,84 @@ +import type { + ColumnBuilderBaseConfig, + ColumnBuilderRuntimeConfig, + GeneratedColumnConfig, + HasGenerated, + MakeColumnConfig, +} from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { SQL } from '~/sql/index.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreEnumColumnBuilderInitial = + SingleStoreEnumColumnBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreEnumColumn'; + data: TEnum[number]; + driverParam: string; + enumValues: TEnum; + generated: undefined; + }>; + +export class SingleStoreEnumColumnBuilder> + extends SingleStoreColumnBuilder +{ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + override generatedAlwaysAs( + as: SQL | (() => SQL) | T['data'], + config?: Partial>, + ): HasGenerated { + throw new Error('Method not implemented.'); + } + static override readonly [entityKind]: string = 'SingleStoreEnumColumnBuilder'; + + constructor(name: T['name'], values: T['enumValues']) { + super(name, 'string', 'SingleStoreEnumColumn'); + this.config.enumValues = values; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreEnumColumn & { enumValues: T['enumValues'] }> { + return new SingleStoreEnumColumn & { enumValues: T['enumValues'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreEnumColumn> + extends SingleStoreColumn +{ + static override readonly [entityKind]: string = 'SingleStoreEnumColumn'; + + override readonly enumValues = this.config.enumValues; + + getSQLType(): string { + return `enum(${this.enumValues!.map((value) => `'${value}'`).join(',')})`; + } +} + +export function singlestoreEnum>( + values: T | Writable, +): SingleStoreEnumColumnBuilderInitial<'', Writable>; +export function singlestoreEnum>( + name: TName, + values: T | Writable, +): SingleStoreEnumColumnBuilderInitial>; +export function singlestoreEnum( + a?: string | readonly [string, ...string[]] | [string, ...string[]], + b?: readonly [string, ...string[]] | [string, ...string[]], +): any { + const { name, config: values } = getColumnNameAndConfig(a, b); + + if (values.length === 0) { + throw new Error(`You have an empty array for "${name}" enum values`); + } + + return new SingleStoreEnumColumnBuilder(name, values as any); +} diff --git a/drizzle-orm/src/singlestore-core/columns/float.ts b/drizzle-orm/src/singlestore-core/columns/float.ts new file mode 100644 index 000000000..9cfed6131 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/float.ts @@ -0,0 +1,80 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreFloatBuilderInitial = SingleStoreFloatBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreFloat'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreFloatBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreFloatBuilder'; + + constructor(name: T['name'], config: SingleStoreFloatConfig | undefined) { + super(name, 'number', 'SingleStoreFloat'); + this.config.precision = config?.precision; + this.config.scale = config?.scale; + this.config.unsigned = config?.unsigned; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreFloat> { + return new SingleStoreFloat>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreFloat> + extends SingleStoreColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreFloat'; + + readonly precision: number | undefined = this.config.precision; + readonly scale: number | undefined = this.config.scale; + readonly unsigned: boolean | undefined = this.config.unsigned; + + getSQLType(): string { + let type = ''; + if (this.precision !== undefined && this.scale !== undefined) { + type += `float(${this.precision},${this.scale})`; + } else if (this.precision === undefined) { + type += 'float'; + } else { + type += `float(${this.precision},0)`; + } + return this.unsigned ? `${type} unsigned` : type; + } +} + +export interface SingleStoreFloatConfig { + precision?: number; + scale?: number; + unsigned?: boolean; +} + +export function float(): SingleStoreFloatBuilderInitial<''>; +export function float( + config?: SingleStoreFloatConfig, +): SingleStoreFloatBuilderInitial<''>; +export function float( + name: TName, + config?: SingleStoreFloatConfig, +): SingleStoreFloatBuilderInitial; +export function float(a?: string | SingleStoreFloatConfig, b?: SingleStoreFloatConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreFloatBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/index.ts b/drizzle-orm/src/singlestore-core/columns/index.ts new file mode 100644 index 000000000..ec17fa21a --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/index.ts @@ -0,0 +1,26 @@ +export * from './bigint.ts'; +export * from './binary.ts'; +export * from './boolean.ts'; +export * from './char.ts'; +export * from './common.ts'; +export * from './custom.ts'; +export * from './date.ts'; +export * from './datetime.ts'; +export * from './decimal.ts'; +export * from './double.ts'; +export * from './enum.ts'; +export * from './float.ts'; +export * from './int.ts'; +export * from './json.ts'; +export * from './mediumint.ts'; +export * from './real.ts'; +export * from './serial.ts'; +export * from './smallint.ts'; +export * from './text.ts'; +export * from './time.ts'; +export * from './timestamp.ts'; +export * from './tinyint.ts'; +export * from './varbinary.ts'; +export * from './varchar.ts'; +export * from './vector.ts'; +export * from './year.ts'; diff --git a/drizzle-orm/src/singlestore-core/columns/int.ts b/drizzle-orm/src/singlestore-core/columns/int.ts new file mode 100644 index 000000000..b6a661f66 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/int.ts @@ -0,0 +1,71 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreIntBuilderInitial = SingleStoreIntBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreIntBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreIntBuilder'; + + constructor(name: T['name'], config?: SingleStoreIntConfig) { + super(name, 'number', 'SingleStoreInt'); + this.config.unsigned = config ? config.unsigned : false; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreInt> { + return new SingleStoreInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreInt> + extends SingleStoreColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreInt'; + + getSQLType(): string { + return `int${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export interface SingleStoreIntConfig { + unsigned?: boolean; +} + +export function int(): SingleStoreIntBuilderInitial<''>; +export function int( + config?: SingleStoreIntConfig, +): SingleStoreIntBuilderInitial<''>; +export function int( + name: TName, + config?: SingleStoreIntConfig, +): SingleStoreIntBuilderInitial; +export function int(a?: string | SingleStoreIntConfig, b?: SingleStoreIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreIntBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/json.ts b/drizzle-orm/src/singlestore-core/columns/json.ts new file mode 100644 index 000000000..97ff759d1 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/json.ts @@ -0,0 +1,53 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreJsonBuilderInitial = SingleStoreJsonBuilder<{ + name: TName; + dataType: 'json'; + columnType: 'SingleStoreJson'; + data: unknown; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreJsonBuilder> + extends SingleStoreColumnBuilder +{ + static override readonly [entityKind]: string = 'SingleStoreJsonBuilder'; + + constructor(name: T['name']) { + super(name, 'json', 'SingleStoreJson'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreJson> { + return new SingleStoreJson>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreJson> extends SingleStoreColumn { + static override readonly [entityKind]: string = 'SingleStoreJson'; + + getSQLType(): string { + return 'json'; + } + + override mapToDriverValue(value: T['data']): string { + return JSON.stringify(value); + } +} + +export function json(): SingleStoreJsonBuilderInitial<''>; +export function json(name: TName): SingleStoreJsonBuilderInitial; +export function json(name?: string) { + return new SingleStoreJsonBuilder(name ?? ''); +} diff --git a/drizzle-orm/src/singlestore-core/columns/mediumint.ts b/drizzle-orm/src/singlestore-core/columns/mediumint.ts new file mode 100644 index 000000000..4a5fa80f9 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/mediumint.ts @@ -0,0 +1,68 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; +import type { SingleStoreIntConfig } from './int.ts'; + +export type SingleStoreMediumIntBuilderInitial = SingleStoreMediumIntBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreMediumInt'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreMediumIntBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreMediumIntBuilder'; + + constructor(name: T['name'], config?: SingleStoreIntConfig) { + super(name, 'number', 'SingleStoreMediumInt'); + this.config.unsigned = config ? config.unsigned : false; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreMediumInt> { + return new SingleStoreMediumInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreMediumInt> + extends SingleStoreColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreMediumInt'; + + getSQLType(): string { + return `mediumint${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export function mediumint(): SingleStoreMediumIntBuilderInitial<''>; +export function mediumint( + config?: SingleStoreIntConfig, +): SingleStoreMediumIntBuilderInitial<''>; +export function mediumint( + name: TName, + config?: SingleStoreIntConfig, +): SingleStoreMediumIntBuilderInitial; +export function mediumint(a?: string | SingleStoreIntConfig, b?: SingleStoreIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreMediumIntBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/real.ts b/drizzle-orm/src/singlestore-core/columns/real.ts new file mode 100644 index 000000000..53d15345c --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/real.ts @@ -0,0 +1,81 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreRealBuilderInitial = SingleStoreRealBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreReal'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreRealBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement< + T, + SingleStoreRealConfig + > +{ + static override readonly [entityKind]: string = 'SingleStoreRealBuilder'; + + constructor(name: T['name'], config: SingleStoreRealConfig | undefined) { + super(name, 'number', 'SingleStoreReal'); + this.config.precision = config?.precision; + this.config.scale = config?.scale; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreReal> { + return new SingleStoreReal>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreReal> + extends SingleStoreColumnWithAutoIncrement< + T, + SingleStoreRealConfig + > +{ + static override readonly [entityKind]: string = 'SingleStoreReal'; + + precision: number | undefined = this.config.precision; + scale: number | undefined = this.config.scale; + + getSQLType(): string { + if (this.precision !== undefined && this.scale !== undefined) { + return `real(${this.precision}, ${this.scale})`; + } else if (this.precision === undefined) { + return 'real'; + } else { + return `real(${this.precision})`; + } + } +} + +export interface SingleStoreRealConfig { + precision?: number; + scale?: number; +} + +export function real(): SingleStoreRealBuilderInitial<''>; +export function real( + config?: SingleStoreRealConfig, +): SingleStoreRealBuilderInitial<''>; +export function real( + name: TName, + config?: SingleStoreRealConfig, +): SingleStoreRealBuilderInitial; +export function real(a?: string | SingleStoreRealConfig, b: SingleStoreRealConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreRealBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/serial.ts b/drizzle-orm/src/singlestore-core/columns/serial.ts new file mode 100644 index 000000000..df415d47e --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/serial.ts @@ -0,0 +1,76 @@ +import type { + ColumnBuilderBaseConfig, + ColumnBuilderRuntimeConfig, + HasDefault, + IsAutoincrement, + IsPrimaryKey, + MakeColumnConfig, + NotNull, +} from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreSerialBuilderInitial = IsAutoincrement< + IsPrimaryKey< + NotNull< + HasDefault< + SingleStoreSerialBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + enumValues: undefined; + generated: undefined; + }> + > + > + > +>; + +export class SingleStoreSerialBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreSerialBuilder'; + + constructor(name: T['name']) { + super(name, 'number', 'SingleStoreSerial'); + this.config.hasDefault = true; + this.config.autoIncrement = true; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreSerial> { + return new SingleStoreSerial>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreSerial< + T extends ColumnBaseConfig<'number', 'SingleStoreSerial'>, +> extends SingleStoreColumnWithAutoIncrement { + static override readonly [entityKind]: string = 'SingleStoreSerial'; + + getSQLType(): string { + return 'serial'; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export function serial(): SingleStoreSerialBuilderInitial<''>; +export function serial(name: TName): SingleStoreSerialBuilderInitial; +export function serial(name?: string) { + return new SingleStoreSerialBuilder(name ?? ''); +} diff --git a/drizzle-orm/src/singlestore-core/columns/smallint.ts b/drizzle-orm/src/singlestore-core/columns/smallint.ts new file mode 100644 index 000000000..3f504b68c --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/smallint.ts @@ -0,0 +1,68 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; +import type { SingleStoreIntConfig } from './int.ts'; + +export type SingleStoreSmallIntBuilderInitial = SingleStoreSmallIntBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreSmallInt'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreSmallIntBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreSmallIntBuilder'; + + constructor(name: T['name'], config?: SingleStoreIntConfig) { + super(name, 'number', 'SingleStoreSmallInt'); + this.config.unsigned = config ? config.unsigned : false; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreSmallInt> { + return new SingleStoreSmallInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreSmallInt> + extends SingleStoreColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreSmallInt'; + + getSQLType(): string { + return `smallint${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export function smallint(): SingleStoreSmallIntBuilderInitial<''>; +export function smallint( + config?: SingleStoreIntConfig, +): SingleStoreSmallIntBuilderInitial<''>; +export function smallint( + name: TName, + config?: SingleStoreIntConfig, +): SingleStoreSmallIntBuilderInitial; +export function smallint(a?: string | SingleStoreIntConfig, b?: SingleStoreIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreSmallIntBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/text.ts b/drizzle-orm/src/singlestore-core/columns/text.ts new file mode 100644 index 000000000..d4d46ab2c --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/text.ts @@ -0,0 +1,116 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreTextColumnType = 'tinytext' | 'text' | 'mediumtext' | 'longtext'; + +export type SingleStoreTextBuilderInitial = + SingleStoreTextBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreText'; + data: TEnum[number]; + driverParam: string; + enumValues: TEnum; + generated: undefined; + }>; + +export class SingleStoreTextBuilder> + extends SingleStoreColumnBuilder< + T, + { textType: SingleStoreTextColumnType; enumValues: T['enumValues'] } + > +{ + static override readonly [entityKind]: string = 'SingleStoreTextBuilder'; + + constructor(name: T['name'], textType: SingleStoreTextColumnType, config: SingleStoreTextConfig) { + super(name, 'string', 'SingleStoreText'); + this.config.textType = textType; + this.config.enumValues = config.enum; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreText> { + return new SingleStoreText>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreText> + extends SingleStoreColumn +{ + static override readonly [entityKind]: string = 'SingleStoreText'; + + readonly textType: SingleStoreTextColumnType = this.config.textType; + + override readonly enumValues = this.config.enumValues; + + getSQLType(): string { + return this.textType; + } +} + +export interface SingleStoreTextConfig< + TEnum extends readonly string[] | string[] | undefined = readonly string[] | string[] | undefined, +> { + enum?: TEnum; +} + +export function text(): SingleStoreTextBuilderInitial<'', [string, ...string[]]>; +export function text>( + config?: SingleStoreTextConfig>, +): SingleStoreTextBuilderInitial<'', Writable>; +export function text>( + name: TName, + config?: SingleStoreTextConfig>, +): SingleStoreTextBuilderInitial>; +export function text(a?: string | SingleStoreTextConfig, b: SingleStoreTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreTextBuilder(name, 'text', config as any); +} + +export function tinytext(): SingleStoreTextBuilderInitial<'', [string, ...string[]]>; +export function tinytext>( + config?: SingleStoreTextConfig>, +): SingleStoreTextBuilderInitial<'', Writable>; +export function tinytext>( + name: TName, + config?: SingleStoreTextConfig>, +): SingleStoreTextBuilderInitial>; +export function tinytext(a?: string | SingleStoreTextConfig, b: SingleStoreTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreTextBuilder(name, 'tinytext', config as any); +} + +export function mediumtext(): SingleStoreTextBuilderInitial<'', [string, ...string[]]>; +export function mediumtext>( + config?: SingleStoreTextConfig>, +): SingleStoreTextBuilderInitial<'', Writable>; +export function mediumtext>( + name: TName, + config?: SingleStoreTextConfig>, +): SingleStoreTextBuilderInitial>; +export function mediumtext(a?: string | SingleStoreTextConfig, b: SingleStoreTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreTextBuilder(name, 'mediumtext', config as any); +} + +export function longtext(): SingleStoreTextBuilderInitial<'', [string, ...string[]]>; +export function longtext>( + config?: SingleStoreTextConfig>, +): SingleStoreTextBuilderInitial<'', Writable>; +export function longtext>( + name: TName, + config?: SingleStoreTextConfig>, +): SingleStoreTextBuilderInitial>; +export function longtext(a?: string | SingleStoreTextConfig, b: SingleStoreTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreTextBuilder(name, 'longtext', config as any); +} diff --git a/drizzle-orm/src/singlestore-core/columns/time.ts b/drizzle-orm/src/singlestore-core/columns/time.ts new file mode 100644 index 000000000..b7605a4ee --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/time.ts @@ -0,0 +1,55 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreTimeBuilderInitial = SingleStoreTimeBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreTime'; + data: string; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreTimeBuilder> + extends SingleStoreColumnBuilder< + T + > +{ + static override readonly [entityKind]: string = 'SingleStoreTimeBuilder'; + + constructor( + name: T['name'], + ) { + super(name, 'string', 'SingleStoreTime'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreTime> { + return new SingleStoreTime>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreTime< + T extends ColumnBaseConfig<'string', 'SingleStoreTime'>, +> extends SingleStoreColumn { + static override readonly [entityKind]: string = 'SingleStoreTime'; + + getSQLType(): string { + return `time`; + } +} + +export function time(): SingleStoreTimeBuilderInitial<''>; +export function time(name: TName): SingleStoreTimeBuilderInitial; +export function time(name?: string) { + return new SingleStoreTimeBuilder(name ?? ''); +} diff --git a/drizzle-orm/src/singlestore-core/columns/timestamp.ts b/drizzle-orm/src/singlestore-core/columns/timestamp.ts new file mode 100644 index 000000000..ea7342fd7 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/timestamp.ts @@ -0,0 +1,125 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { sql } from '~/sql/sql.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreDateBaseColumn, SingleStoreDateColumnBaseBuilder } from './date.common.ts'; + +export type SingleStoreTimestampBuilderInitial = SingleStoreTimestampBuilder<{ + name: TName; + dataType: 'date'; + columnType: 'SingleStoreTimestamp'; + data: Date; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreTimestampBuilder> + extends SingleStoreDateColumnBaseBuilder +{ + static override readonly [entityKind]: string = 'SingleStoreTimestampBuilder'; + + constructor(name: T['name']) { + super(name, 'date', 'SingleStoreTimestamp'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreTimestamp> { + return new SingleStoreTimestamp>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } + + override defaultNow() { + return this.default(sql`CURRENT_TIMESTAMP`); + } +} + +export class SingleStoreTimestamp> + extends SingleStoreDateBaseColumn +{ + static override readonly [entityKind]: string = 'SingleStoreTimestamp'; + + getSQLType(): string { + return `timestamp`; + } + + override mapFromDriverValue(value: string): Date { + return new Date(value + '+0000'); + } + + override mapToDriverValue(value: Date): string { + return value.toISOString().slice(0, -1).replace('T', ' '); + } +} + +export type SingleStoreTimestampStringBuilderInitial = SingleStoreTimestampStringBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreTimestampString'; + data: string; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreTimestampStringBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SingleStoreTimestampString'>, +> extends SingleStoreDateColumnBaseBuilder { + static override readonly [entityKind]: string = 'SingleStoreTimestampStringBuilder'; + + constructor(name: T['name']) { + super(name, 'string', 'SingleStoreTimestampString'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreTimestampString> { + return new SingleStoreTimestampString>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } + + override defaultNow() { + return this.default(sql`CURRENT_TIMESTAMP`); + } +} + +export class SingleStoreTimestampString> + extends SingleStoreDateBaseColumn +{ + static override readonly [entityKind]: string = 'SingleStoreTimestampString'; + + getSQLType(): string { + return `timestamp`; + } +} + +export interface SingleStoreTimestampConfig { + mode?: TMode; +} + +export function timestamp(): SingleStoreTimestampBuilderInitial<''>; +export function timestamp( + config?: SingleStoreTimestampConfig, +): Equal extends true ? SingleStoreTimestampStringBuilderInitial<''> + : SingleStoreTimestampBuilderInitial<''>; +export function timestamp( + name: TName, + config?: SingleStoreTimestampConfig, +): Equal extends true ? SingleStoreTimestampStringBuilderInitial + : SingleStoreTimestampBuilderInitial; +export function timestamp(a?: string | SingleStoreTimestampConfig, b: SingleStoreTimestampConfig = {}) { + const { name, config } = getColumnNameAndConfig(a, b); + if (config?.mode === 'string') { + return new SingleStoreTimestampStringBuilder(name); + } + return new SingleStoreTimestampBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/columns/tinyint.ts b/drizzle-orm/src/singlestore-core/columns/tinyint.ts new file mode 100644 index 000000000..090619a6d --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/tinyint.ts @@ -0,0 +1,68 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; +import type { SingleStoreIntConfig } from './int.ts'; + +export type SingleStoreTinyIntBuilderInitial = SingleStoreTinyIntBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreTinyInt'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreTinyIntBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreTinyIntBuilder'; + + constructor(name: T['name'], config?: SingleStoreIntConfig) { + super(name, 'number', 'SingleStoreTinyInt'); + this.config.unsigned = config ? config.unsigned : false; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreTinyInt> { + return new SingleStoreTinyInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreTinyInt> + extends SingleStoreColumnWithAutoIncrement +{ + static override readonly [entityKind]: string = 'SingleStoreTinyInt'; + + getSQLType(): string { + return `tinyint${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export function tinyint(): SingleStoreTinyIntBuilderInitial<''>; +export function tinyint( + config?: SingleStoreIntConfig, +): SingleStoreTinyIntBuilderInitial<''>; +export function tinyint( + name: TName, + config?: SingleStoreIntConfig, +): SingleStoreTinyIntBuilderInitial; +export function tinyint(a?: string | SingleStoreIntConfig, b?: SingleStoreIntConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreTinyIntBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/varbinary.ts b/drizzle-orm/src/singlestore-core/columns/varbinary.ts new file mode 100644 index 000000000..c55aa8071 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/varbinary.ts @@ -0,0 +1,66 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreVarBinaryBuilderInitial = SingleStoreVarBinaryBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreVarBinary'; + data: string; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreVarBinaryBuilder> + extends SingleStoreColumnBuilder +{ + static override readonly [entityKind]: string = 'SingleStoreVarBinaryBuilder'; + + /** @internal */ + constructor(name: T['name'], config: SingleStoreVarbinaryOptions) { + super(name, 'string', 'SingleStoreVarBinary'); + this.config.length = config?.length; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreVarBinary> { + return new SingleStoreVarBinary>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreVarBinary< + T extends ColumnBaseConfig<'string', 'SingleStoreVarBinary'>, +> extends SingleStoreColumn { + static override readonly [entityKind]: string = 'SingleStoreVarBinary'; + + length: number | undefined = this.config.length; + + getSQLType(): string { + return this.length === undefined ? `varbinary` : `varbinary(${this.length})`; + } +} + +export interface SingleStoreVarbinaryOptions { + length: number; +} + +export function varbinary( + config: SingleStoreVarbinaryOptions, +): SingleStoreVarBinaryBuilderInitial<''>; +export function varbinary( + name: TName, + config: SingleStoreVarbinaryOptions, +): SingleStoreVarBinaryBuilderInitial; +export function varbinary(a?: string | SingleStoreVarbinaryOptions, b?: SingleStoreVarbinaryOptions) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreVarBinaryBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/varchar.ts b/drizzle-orm/src/singlestore-core/columns/varchar.ts new file mode 100644 index 000000000..d9a21ba27 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/varchar.ts @@ -0,0 +1,86 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreVarCharBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = SingleStoreVarCharBuilder< + { + name: TName; + dataType: 'string'; + columnType: 'SingleStoreVarChar'; + data: TEnum[number]; + driverParam: number | string; + enumValues: TEnum; + generated: undefined; + length: TLength; + } +>; + +export class SingleStoreVarCharBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SingleStoreVarChar'> & { length?: number | undefined }, +> extends SingleStoreColumnBuilder, { length: T['length'] }> { + static override readonly [entityKind]: string = 'SingleStoreVarCharBuilder'; + + /** @internal */ + constructor(name: T['name'], config: SingleStoreVarCharConfig) { + super(name, 'string', 'SingleStoreVarChar'); + this.config.length = config.length; + this.config.enum = config.enum; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreVarChar & { length: T['length']; enumValues: T['enumValues'] }> { + return new SingleStoreVarChar< + MakeColumnConfig & { length: T['length']; enumValues: T['enumValues'] } + >( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreVarChar< + T extends ColumnBaseConfig<'string', 'SingleStoreVarChar'> & { length?: number | undefined }, +> extends SingleStoreColumn, { length: T['length'] }> { + static override readonly [entityKind]: string = 'SingleStoreVarChar'; + + readonly length: T['length'] = this.config.length; + override readonly enumValues = this.config.enum; + + getSQLType(): string { + return this.length === undefined ? `varchar` : `varchar(${this.length})`; + } +} + +export interface SingleStoreVarCharConfig< + TEnum extends string[] | readonly string[] | undefined = string[] | readonly string[] | undefined, + TLength extends number | undefined = number | undefined, +> { + enum?: TEnum; + length?: TLength; +} + +export function varchar, L extends number | undefined>( + config: SingleStoreVarCharConfig, L>, +): SingleStoreVarCharBuilderInitial<'', Writable, L>; +export function varchar< + TName extends string, + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( + name: TName, + config: SingleStoreVarCharConfig, L>, +): SingleStoreVarCharBuilderInitial, L>; +export function varchar(a?: string | SingleStoreVarCharConfig, b?: SingleStoreVarCharConfig): any { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreVarCharBuilder(name, config as any); +} diff --git a/drizzle-orm/src/singlestore-core/columns/vector.ts b/drizzle-orm/src/singlestore-core/columns/vector.ts new file mode 100644 index 000000000..d33b819b9 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/vector.ts @@ -0,0 +1,83 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SQL } from '~/sql/index.ts'; +import { getColumnNameAndConfig } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder, SingleStoreGeneratedColumnConfig } from './common.ts'; + +export type SingleStoreVectorBuilderInitial = SingleStoreVectorBuilder<{ + name: TName; + dataType: 'array'; + columnType: 'SingleStoreVector'; + data: Array; + driverParam: string; + enumValues: undefined; +}>; + +export class SingleStoreVectorBuilder> + extends SingleStoreColumnBuilder +{ + static override readonly [entityKind]: string = 'SingleStoreVectorBuilder'; + + constructor(name: T['name'], config: SingleStoreVectorConfig) { + super(name, 'array', 'SingleStoreVector'); + this.config.dimensions = config.dimensions; + this.config.elementType = config.elementType; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreVector> { + return new SingleStoreVector>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } + + /** @internal */ + override generatedAlwaysAs(as: SQL | (() => SQL) | T['data'], config?: SingleStoreGeneratedColumnConfig) { + throw new Error('not implemented'); + } +} + +export class SingleStoreVector> + extends SingleStoreColumn +{ + static override readonly [entityKind]: string = 'SingleStoreVector'; + + dimensions: number = this.config.dimensions; + elementType: ElementType | undefined = this.config.elementType; + + getSQLType(): string { + return `vector(${this.dimensions}, ${this.elementType || 'F32'})`; + } + + override mapToDriverValue(value: Array) { + return JSON.stringify(value); + } + + override mapFromDriverValue(value: string): Array { + return JSON.parse(value); + } +} + +type ElementType = 'I8' | 'I16' | 'I32' | 'I64' | 'F32' | 'F64'; + +export interface SingleStoreVectorConfig { + dimensions: number; + elementType?: ElementType; +} + +export function vector( + config: SingleStoreVectorConfig, +): SingleStoreVectorBuilderInitial<''>; +export function vector( + name: TName, + config: SingleStoreVectorConfig, +): SingleStoreVectorBuilderInitial; +export function vector(a: string | SingleStoreVectorConfig, b?: SingleStoreVectorConfig) { + const { name, config } = getColumnNameAndConfig(a, b); + return new SingleStoreVectorBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/year.ts b/drizzle-orm/src/singlestore-core/columns/year.ts new file mode 100644 index 000000000..37f3d55a3 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/year.ts @@ -0,0 +1,51 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreYearBuilderInitial = SingleStoreYearBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreYear'; + data: number; + driverParam: number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreYearBuilder> + extends SingleStoreColumnBuilder +{ + static override readonly [entityKind]: string = 'SingleStoreYearBuilder'; + + constructor(name: T['name']) { + super(name, 'number', 'SingleStoreYear'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreYear> { + return new SingleStoreYear>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreYear< + T extends ColumnBaseConfig<'number', 'SingleStoreYear'>, +> extends SingleStoreColumn { + static override readonly [entityKind]: string = 'SingleStoreYear'; + + getSQLType(): string { + return `year`; + } +} + +export function year(): SingleStoreYearBuilderInitial<''>; +export function year(name: TName): SingleStoreYearBuilderInitial; +export function year(name?: string) { + return new SingleStoreYearBuilder(name ?? ''); +} diff --git a/drizzle-orm/src/singlestore-core/db.ts b/drizzle-orm/src/singlestore-core/db.ts new file mode 100644 index 000000000..1d64448da --- /dev/null +++ b/drizzle-orm/src/singlestore-core/db.ts @@ -0,0 +1,516 @@ +import type { ResultSetHeader } from 'mysql2/promise'; +import { entityKind } from '~/entity.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { SingleStoreDriverDatabase } from '~/singlestore/driver.ts'; +import { type ColumnsSelection, type SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; +import { WithSubquery } from '~/subquery.ts'; +import type { SingleStoreDialect } from './dialect.ts'; +import { SingleStoreCountBuilder } from './query-builders/count.ts'; +import { + QueryBuilder, + SingleStoreDeleteBase, + SingleStoreInsertBuilder, + SingleStoreSelectBuilder, + SingleStoreUpdateBuilder, +} from './query-builders/index.ts'; +import type { SelectedFields } from './query-builders/select.types.ts'; +import type { + PreparedQueryHKTBase, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, + SingleStoreTransaction, + SingleStoreTransactionConfig, +} from './session.ts'; +import type { WithSubqueryWithSelection } from './subquery.ts'; +import type { SingleStoreTable } from './table.ts'; + +export class SingleStoreDatabase< + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TFullSchema extends Record = {}, + TSchema extends TablesRelationalConfig = ExtractTablesWithRelations, +> { + static readonly [entityKind]: string = 'SingleStoreDatabase'; + + declare readonly _: { + readonly schema: TSchema | undefined; + readonly fullSchema: TFullSchema; + readonly tableNamesMap: Record; + }; + + // We are waiting for SingleStore support for `json_array` function + /**@inrernal */ + query: unknown; + + constructor( + /** @internal */ + readonly dialect: SingleStoreDialect, + /** @internal */ + readonly session: SingleStoreSession, + schema: RelationalSchemaConfig | undefined, + ) { + this._ = schema + ? { + schema: schema.schema, + fullSchema: schema.fullSchema as TFullSchema, + tableNamesMap: schema.tableNamesMap, + } + : { + schema: undefined, + fullSchema: {} as TFullSchema, + tableNamesMap: {}, + }; + this.query = {} as typeof this['query']; + // this.queryNotSupported = true; + // if (this._.schema) { + // for (const [tableName, columns] of Object.entries(this._.schema)) { + // (this.query as SingleStoreDatabase>['query'])[tableName] = + // new RelationalQueryBuilder( + // schema!.fullSchema, + // this._.schema, + // this._.tableNamesMap, + // schema!.fullSchema[tableName] as SingleStoreTable, + // columns, + // dialect, + // session, + // ); + // } + // } + } + + /** + * Creates a subquery that defines a temporary named result set as a CTE. + * + * It is useful for breaking down complex queries into simpler parts and for reusing the result set in subsequent parts of the query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param alias The alias for the subquery. + * + * Failure to provide an alias will result in a DrizzleTypeError, preventing the subquery from being referenced in other queries. + * + * @example + * + * ```ts + * // Create a subquery with alias 'sq' and use it in the select query + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * const result = await db.with(sq).select().from(sq); + * ``` + * + * To select arbitrary SQL values as fields in a CTE and reference them in other CTEs or in the main query, you need to add aliases to them: + * + * ```ts + * // Select an arbitrary SQL value as a field in a CTE and reference it in the main query + * const sq = db.$with('sq').as(db.select({ + * name: sql`upper(${users.name})`.as('name'), + * }) + * .from(users)); + * + * const result = await db.with(sq).select({ name: sq.name }).from(sq); + * ``` + */ + $with(alias: TAlias) { + const self = this; + return { + as( + qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), + ): WithSubqueryWithSelection { + if (typeof qb === 'function') { + qb = qb(new QueryBuilder(self.dialect)); + } + + return new Proxy( + new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true), + new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }), + ) as WithSubqueryWithSelection; + }, + }; + } + + $count( + source: SingleStoreTable | SQL | SQLWrapper, // SingleStoreViewBase | + filters?: SQL, + ) { + return new SingleStoreCountBuilder({ source, filters, session: this.session }); + } + + /** + * Incorporates a previously defined CTE (using `$with`) into the main query. + * + * This method allows the main query to reference a temporary named result set. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param queries The CTEs to incorporate into the main query. + * + * @example + * + * ```ts + * // Define a subquery 'sq' as a CTE using $with + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * // Incorporate the CTE 'sq' into the main query and select from it + * const result = await db.with(sq).select().from(sq); + * ``` + */ + with(...queries: WithSubquery[]) { + const self = this; + + /** + * Creates a select query. + * + * Calling this method with no arguments will select all columns from the table. Pass a selection object to specify the columns you want to select. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select} + * + * @param fields The selection object. + * + * @example + * + * ```ts + * // Select all columns and all rows from the 'cars' table + * const allCars: Car[] = await db.select().from(cars); + * + * // Select specific columns and all rows from the 'cars' table + * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ + * id: cars.id, + * brand: cars.brand + * }) + * .from(cars); + * ``` + * + * Like in SQL, you can use arbitrary expressions as selection fields, not just table columns: + * + * ```ts + * // Select specific columns along with expression and all rows from the 'cars' table + * const carsIdsAndLowerNames: { id: number; lowerBrand: string }[] = await db.select({ + * id: cars.id, + * lowerBrand: sql`lower(${cars.brand})`, + * }) + * .from(cars); + * ``` + */ + function select(): SingleStoreSelectBuilder; + function select( + fields: TSelection, + ): SingleStoreSelectBuilder; + function select(fields?: SelectedFields): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: self.session, + dialect: self.dialect, + withList: queries, + }); + } + + /** + * Adds `distinct` expression to the select query. + * + * Calling this method will return only unique values. When multiple columns are selected, it returns rows with unique combinations of values in these columns. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select#distinct} + * + * @param fields The selection object. + * + * @example + * ```ts + * // Select all unique rows from the 'cars' table + * await db.selectDistinct() + * .from(cars) + * .orderBy(cars.id, cars.brand, cars.color); + * + * // Select all unique brands from the 'cars' table + * await db.selectDistinct({ brand: cars.brand }) + * .from(cars) + * .orderBy(cars.brand); + * ``` + */ + function selectDistinct(): SingleStoreSelectBuilder; + function selectDistinct( + fields: TSelection, + ): SingleStoreSelectBuilder; + function selectDistinct( + fields?: SelectedFields, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: self.session, + dialect: self.dialect, + withList: queries, + distinct: true, + }); + } + + /** + * Creates an update query. + * + * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated. + * + * Use `.set()` method to specify which values to update. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param table The table to update. + * + * @example + * + * ```ts + * // Update all rows in the 'cars' table + * await db.update(cars).set({ color: 'red' }); + * + * // Update rows with filters and conditions + * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW')); + * ``` + */ + function update( + table: TTable, + ): SingleStoreUpdateBuilder { + return new SingleStoreUpdateBuilder(table, self.session, self.dialect, queries); + } + + /** + * Creates a delete query. + * + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param table The table to delete from. + * + * @example + * + * ```ts + * // Delete all rows in the 'cars' table + * await db.delete(cars); + * + * // Delete rows with filters and conditions + * await db.delete(cars).where(eq(cars.color, 'green')); + * ``` + */ + function delete_( + table: TTable, + ): SingleStoreDeleteBase { + return new SingleStoreDeleteBase(table, self.session, self.dialect, queries); + } + + return { select, selectDistinct, update, delete: delete_ }; + } + + /** + * Creates a select query. + * + * Calling this method with no arguments will select all columns from the table. Pass a selection object to specify the columns you want to select. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select} + * + * @param fields The selection object. + * + * @example + * + * ```ts + * // Select all columns and all rows from the 'cars' table + * const allCars: Car[] = await db.select().from(cars); + * + * // Select specific columns and all rows from the 'cars' table + * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ + * id: cars.id, + * brand: cars.brand + * }) + * .from(cars); + * ``` + * + * Like in SQL, you can use arbitrary expressions as selection fields, not just table columns: + * + * ```ts + * // Select specific columns along with expression and all rows from the 'cars' table + * const carsIdsAndLowerNames: { id: number; lowerBrand: string }[] = await db.select({ + * id: cars.id, + * lowerBrand: sql`lower(${cars.brand})`, + * }) + * .from(cars); + * ``` + */ + select(): SingleStoreSelectBuilder; + select( + fields: TSelection, + ): SingleStoreSelectBuilder; + select(fields?: SelectedFields): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ fields: fields ?? undefined, session: this.session, dialect: this.dialect }); + } + + /** + * Adds `distinct` expression to the select query. + * + * Calling this method will return only unique values. When multiple columns are selected, it returns rows with unique combinations of values in these columns. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select#distinct} + * + * @param fields The selection object. + * + * @example + * ```ts + * // Select all unique rows from the 'cars' table + * await db.selectDistinct() + * .from(cars) + * .orderBy(cars.id, cars.brand, cars.color); + * + * // Select all unique brands from the 'cars' table + * await db.selectDistinct({ brand: cars.brand }) + * .from(cars) + * .orderBy(cars.brand); + * ``` + */ + selectDistinct(): SingleStoreSelectBuilder; + selectDistinct( + fields: TSelection, + ): SingleStoreSelectBuilder; + selectDistinct(fields?: SelectedFields): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: this.session, + dialect: this.dialect, + distinct: true, + }); + } + + /** + * Creates an update query. + * + * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated. + * + * Use `.set()` method to specify which values to update. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param table The table to update. + * + * @example + * + * ```ts + * // Update all rows in the 'cars' table + * await db.update(cars).set({ color: 'red' }); + * + * // Update rows with filters and conditions + * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW')); + * ``` + */ + update( + table: TTable, + ): SingleStoreUpdateBuilder { + return new SingleStoreUpdateBuilder(table, this.session, this.dialect); + } + + /** + * Creates an insert query. + * + * Calling this method will create new rows in a table. Use `.values()` method to specify which values to insert. + * + * See docs: {@link https://orm.drizzle.team/docs/insert} + * + * @param table The table to insert into. + * + * @example + * + * ```ts + * // Insert one row + * await db.insert(cars).values({ brand: 'BMW' }); + * + * // Insert multiple rows + * await db.insert(cars).values([{ brand: 'BMW' }, { brand: 'Porsche' }]); + * ``` + */ + insert( + table: TTable, + ): SingleStoreInsertBuilder { + return new SingleStoreInsertBuilder(table, this.session, this.dialect); + } + + /** + * Creates a delete query. + * + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param table The table to delete from. + * + * @example + * + * ```ts + * // Delete all rows in the 'cars' table + * await db.delete(cars); + * + * // Delete rows with filters and conditions + * await db.delete(cars).where(eq(cars.color, 'green')); + * ``` + */ + delete( + table: TTable, + ): SingleStoreDeleteBase { + return new SingleStoreDeleteBase(table, this.session, this.dialect); + } + + execute( + query: SQLWrapper | string, + ): Promise> { + return this.session.execute(typeof query === 'string' ? sql.raw(query) : query.getSQL()); + } + + transaction( + transaction: ( + tx: SingleStoreTransaction, + config?: SingleStoreTransactionConfig, + ) => Promise, + config?: SingleStoreTransactionConfig, + ): Promise { + return this.session.transaction(transaction, config); + } +} + +export type SingleStoreWithReplicas = Q & { $primary: Q }; + +export const withReplicas = < + Q extends SingleStoreDriverDatabase, +>( + primary: Q, + replicas: [Q, ...Q[]], + getReplica: (replicas: Q[]) => Q = () => replicas[Math.floor(Math.random() * replicas.length)]!, +): SingleStoreWithReplicas => { + const select: Q['select'] = (...args: []) => getReplica(replicas).select(...args); + const selectDistinct: Q['selectDistinct'] = (...args: []) => getReplica(replicas).selectDistinct(...args); + const $with: Q['with'] = (...args: []) => getReplica(replicas).with(...args); + + const update: Q['update'] = (...args: [any]) => primary.update(...args); + const insert: Q['insert'] = (...args: [any]) => primary.insert(...args); + const $delete: Q['delete'] = (...args: [any]) => primary.delete(...args); + const execute: Q['execute'] = (...args: [any]) => primary.execute(...args); + const transaction: Q['transaction'] = (...args: [any, any]) => primary.transaction(...args); + + return { + ...primary, + update, + insert, + delete: $delete, + execute, + transaction, + $primary: primary, + select, + selectDistinct, + with: $with, + get query() { + return getReplica(replicas).query; + }, + }; +}; diff --git a/drizzle-orm/src/singlestore-core/dialect.ts b/drizzle-orm/src/singlestore-core/dialect.ts new file mode 100644 index 000000000..99a485ac6 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/dialect.ts @@ -0,0 +1,813 @@ +import { aliasedTable, aliasedTableColumn, mapColumnsInAliasedSQLToAlias, mapColumnsInSQLToAlias } from '~/alias.ts'; +import { CasingCache } from '~/casing.ts'; +import { Column } from '~/column.ts'; +import { entityKind, is } from '~/entity.ts'; +import { DrizzleError } from '~/errors.ts'; +import { and, eq } from '~/expressions.ts'; +import type { MigrationConfig, MigrationMeta } from '~/migrator.ts'; +import { + type BuildRelationalQueryResult, + type DBQueryConfig, + getOperators, + getOrderByOperators, + Many, + normalizeRelation, + One, + type Relation, + type TableRelationalConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import type { Name, Placeholder, QueryWithTypings, SQLChunk } from '~/sql/sql.ts'; +import { Param, SQL, sql, View } from '~/sql/sql.ts'; +import { Subquery } from '~/subquery.ts'; +import { getTableName, getTableUniqueName, Table } from '~/table.ts'; +import { type Casing, orderSelectedFields, type UpdateSet } from '~/utils.ts'; +import { ViewBaseConfig } from '~/view-common.ts'; +import { SingleStoreColumn } from './columns/common.ts'; +import type { SingleStoreDeleteConfig } from './query-builders/delete.ts'; +import type { SingleStoreInsertConfig } from './query-builders/insert.ts'; +import type { + SelectedFieldsOrdered, + SingleStoreSelectConfig, + SingleStoreSelectJoinConfig, +} from './query-builders/select.types.ts'; +import type { SingleStoreUpdateConfig } from './query-builders/update.ts'; +import type { SingleStoreSession } from './session.ts'; +import { SingleStoreTable } from './table.ts'; +/* import { SingleStoreViewBase } from './view-base.ts'; */ + +export interface SingleStoreDialectConfig { + casing?: Casing; +} + +export class SingleStoreDialect { + static readonly [entityKind]: string = 'SingleStoreDialect'; + + /** @internal */ + readonly casing: CasingCache; + + constructor(config?: SingleStoreDialectConfig) { + this.casing = new CasingCache(config?.casing); + } + + async migrate( + migrations: MigrationMeta[], + session: SingleStoreSession, + config: Omit, + ): Promise { + const migrationsTable = config.migrationsTable ?? '__drizzle_migrations'; + const migrationTableCreate = sql` + create table if not exists ${sql.identifier(migrationsTable)} ( + id serial primary key, + hash text not null, + created_at bigint + ) + `; + await session.execute(migrationTableCreate); + + const dbMigrations = await session.all<{ id: number; hash: string; created_at: string }>( + sql`select id, hash, created_at from ${sql.identifier(migrationsTable)} order by created_at desc limit 1`, + ); + + const lastDbMigration = dbMigrations[0]; + + await session.transaction(async (tx) => { + for (const migration of migrations) { + if ( + !lastDbMigration + || Number(lastDbMigration.created_at) < migration.folderMillis + ) { + for (const stmt of migration.sql) { + await tx.execute(sql.raw(stmt)); + } + await tx.execute( + sql`insert into ${ + sql.identifier(migrationsTable) + } (\`hash\`, \`created_at\`) values(${migration.hash}, ${migration.folderMillis})`, + ); + } + } + }); + } + + escapeName(name: string): string { + return `\`${name}\``; + } + + escapeParam(_num: number): string { + return `?`; + } + + escapeString(str: string): string { + return `'${str.replace(/'/g, "''")}'`; + } + + private buildWithCTE(queries: Subquery[] | undefined): SQL | undefined { + if (!queries?.length) return undefined; + + const withSqlChunks = [sql`with `]; + for (const [i, w] of queries.entries()) { + withSqlChunks.push(sql`${sql.identifier(w._.alias)} as (${w._.sql})`); + if (i < queries.length - 1) { + withSqlChunks.push(sql`, `); + } + } + withSqlChunks.push(sql` `); + return sql.join(withSqlChunks); + } + + buildDeleteQuery({ table, where, returning, withList, limit, orderBy }: SingleStoreDeleteConfig): SQL { + const withSql = this.buildWithCTE(withList); + + const returningSql = returning + ? sql` returning ${this.buildSelection(returning, { isSingleTable: true })}` + : undefined; + + const whereSql = where ? sql` where ${where}` : undefined; + + const orderBySql = this.buildOrderBy(orderBy); + + const limitSql = this.buildLimit(limit); + + return sql`${withSql}delete from ${table}${whereSql}${orderBySql}${limitSql}${returningSql}`; + } + + buildUpdateSet(table: SingleStoreTable, set: UpdateSet): SQL { + const tableColumns = table[Table.Symbol.Columns]; + + const columnNames = Object.keys(tableColumns).filter((colName) => + set[colName] !== undefined || tableColumns[colName]?.onUpdateFn !== undefined + ); + + const setSize = columnNames.length; + return sql.join(columnNames.flatMap((colName, i) => { + const col = tableColumns[colName]!; + + const value = set[colName] ?? sql.param(col.onUpdateFn!(), col); + const res = sql`${sql.identifier(this.casing.getColumnCasing(col))} = ${value}`; + + if (i < setSize - 1) { + return [res, sql.raw(', ')]; + } + return [res]; + })); + } + + buildUpdateQuery({ table, set, where, returning, withList, limit, orderBy }: SingleStoreUpdateConfig): SQL { + const withSql = this.buildWithCTE(withList); + + const setSql = this.buildUpdateSet(table, set); + + const returningSql = returning + ? sql` returning ${this.buildSelection(returning, { isSingleTable: true })}` + : undefined; + + const whereSql = where ? sql` where ${where}` : undefined; + + const orderBySql = this.buildOrderBy(orderBy); + + const limitSql = this.buildLimit(limit); + + return sql`${withSql}update ${table} set ${setSql}${whereSql}${orderBySql}${limitSql}${returningSql}`; + } + + /** + * Builds selection SQL with provided fields/expressions + * + * Examples: + * + * `select from` + * + * `insert ... returning ` + * + * If `isSingleTable` is true, then columns won't be prefixed with table name + */ + private buildSelection( + fields: SelectedFieldsOrdered, + { isSingleTable = false }: { isSingleTable?: boolean } = {}, + ): SQL { + const columnsLen = fields.length; + + const chunks = fields + .flatMap(({ field }, i) => { + const chunk: SQLChunk[] = []; + + if (is(field, SQL.Aliased) && field.isSelectionField) { + chunk.push(sql.identifier(field.fieldAlias)); + } else if (is(field, SQL.Aliased) || is(field, SQL)) { + const query = is(field, SQL.Aliased) ? field.sql : field; + + if (isSingleTable) { + chunk.push( + new SQL( + query.queryChunks.map((c) => { + if (is(c, SingleStoreColumn)) { + return sql.identifier(this.casing.getColumnCasing(c)); + } + return c; + }), + ), + ); + } else { + chunk.push(query); + } + + if (is(field, SQL.Aliased)) { + chunk.push(sql` as ${sql.identifier(field.fieldAlias)}`); + } + } else if (is(field, Column)) { + if (isSingleTable) { + chunk.push(sql.identifier(this.casing.getColumnCasing(field))); + } else { + chunk.push(field); + } + } + + if (i < columnsLen - 1) { + chunk.push(sql`, `); + } + + return chunk; + }); + + return sql.join(chunks); + } + + private buildLimit(limit: number | Placeholder | undefined): SQL | undefined { + return typeof limit === 'object' || (typeof limit === 'number' && limit >= 0) + ? sql` limit ${limit}` + : undefined; + } + + private buildOrderBy(orderBy: (SingleStoreColumn | SQL | SQL.Aliased)[] | undefined): SQL | undefined { + return orderBy && orderBy.length > 0 ? sql` order by ${sql.join(orderBy, sql`, `)}` : undefined; + } + + buildSelectQuery( + { + withList, + fields, + fieldsFlat, + where, + having, + table, + joins, + orderBy, + groupBy, + limit, + offset, + lockingClause, + distinct, + setOperators, + }: SingleStoreSelectConfig, + ): SQL { + const fieldsList = fieldsFlat ?? orderSelectedFields(fields); + for (const f of fieldsList) { + if ( + is(f.field, Column) + && getTableName(f.field.table) + !== (is(table, Subquery) + ? table._.alias + /* : is(table, SingleStoreViewBase) + ? table[ViewBaseConfig].name */ + : is(table, SQL) + ? undefined + : getTableName(table)) + && !((table) => + joins?.some(({ alias }) => + alias === (table[Table.Symbol.IsAlias] ? getTableName(table) : table[Table.Symbol.BaseName]) + ))(f.field.table) + ) { + const tableName = getTableName(f.field.table); + throw new Error( + `Your "${ + f.path.join('->') + }" field references a column "${tableName}"."${f.field.name}", but the table "${tableName}" is not part of the query! Did you forget to join it?`, + ); + } + } + + const isSingleTable = !joins || joins.length === 0; + + const withSql = this.buildWithCTE(withList); + + const distinctSql = distinct ? sql` distinct` : undefined; + + const selection = this.buildSelection(fieldsList, { isSingleTable }); + + const tableSql = (() => { + if (is(table, Table) && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) { + return sql`${sql.identifier(table[Table.Symbol.OriginalName])} ${sql.identifier(table[Table.Symbol.Name])}`; + } + + return table; + })(); + + const joinsArray: SQL[] = []; + + if (joins) { + for (const [index, joinMeta] of joins.entries()) { + if (index === 0) { + joinsArray.push(sql` `); + } + const table = joinMeta.table; + const lateralSql = joinMeta.lateral ? sql` lateral` : undefined; + + if (is(table, SingleStoreTable)) { + const tableName = table[SingleStoreTable.Symbol.Name]; + const tableSchema = table[SingleStoreTable.Symbol.Schema]; + const origTableName = table[SingleStoreTable.Symbol.OriginalName]; + const alias = tableName === origTableName ? undefined : joinMeta.alias; + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ + tableSchema ? sql`${sql.identifier(tableSchema)}.` : undefined + }${sql.identifier(origTableName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, + ); + } else if (is(table, View)) { + const viewName = table[ViewBaseConfig].name; + const viewSchema = table[ViewBaseConfig].schema; + const origViewName = table[ViewBaseConfig].originalName; + const alias = viewName === origViewName ? undefined : joinMeta.alias; + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ + viewSchema ? sql`${sql.identifier(viewSchema)}.` : undefined + }${sql.identifier(origViewName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, + ); + } else { + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${table} on ${joinMeta.on}`, + ); + } + if (index < joins.length - 1) { + joinsArray.push(sql` `); + } + } + } + + const joinsSql = sql.join(joinsArray); + + const whereSql = where ? sql` where ${where}` : undefined; + + const havingSql = having ? sql` having ${having}` : undefined; + + const orderBySql = this.buildOrderBy(orderBy); + + const groupBySql = groupBy && groupBy.length > 0 ? sql` group by ${sql.join(groupBy, sql`, `)}` : undefined; + + const limitSql = this.buildLimit(limit); + + const offsetSql = offset ? sql` offset ${offset}` : undefined; + + let lockingClausesSql; + if (lockingClause) { + const { config, strength } = lockingClause; + lockingClausesSql = sql` for ${sql.raw(strength)}`; + if (config.noWait) { + lockingClausesSql.append(sql` no wait`); + } else if (config.skipLocked) { + lockingClausesSql.append(sql` skip locked`); + } + } + + const finalQuery = + sql`${withSql}select${distinctSql} ${selection} from ${tableSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`; + + if (setOperators.length > 0) { + return this.buildSetOperations(finalQuery, setOperators); + } + + return finalQuery; + } + + buildSetOperations(leftSelect: SQL, setOperators: SingleStoreSelectConfig['setOperators']): SQL { + const [setOperator, ...rest] = setOperators; + + if (!setOperator) { + throw new Error('Cannot pass undefined values to any set operator'); + } + + if (rest.length === 0) { + return this.buildSetOperationQuery({ leftSelect, setOperator }); + } + + // Some recursive magic here + return this.buildSetOperations( + this.buildSetOperationQuery({ leftSelect, setOperator }), + rest, + ); + } + + buildSetOperationQuery({ + leftSelect, + setOperator: { type, isAll, rightSelect, limit, orderBy, offset }, + }: { leftSelect: SQL; setOperator: SingleStoreSelectConfig['setOperators'][number] }): SQL { + const leftChunk = sql`(${leftSelect.getSQL()}) `; + const rightChunk = sql`(${rightSelect.getSQL()})`; + + let orderBySql; + if (orderBy && orderBy.length > 0) { + const orderByValues: (SQL | Name)[] = []; + + // The next bit is necessary because the sql operator replaces ${table.column} with `table`.`column` + // which is invalid SingleStore syntax, Table from one of the SELECTs cannot be used in global ORDER clause + for (const orderByUnit of orderBy) { + if (is(orderByUnit, SingleStoreColumn)) { + orderByValues.push(sql.identifier(this.casing.getColumnCasing(orderByUnit))); + } else if (is(orderByUnit, SQL)) { + for (let i = 0; i < orderByUnit.queryChunks.length; i++) { + const chunk = orderByUnit.queryChunks[i]; + + if (is(chunk, SingleStoreColumn)) { + orderByUnit.queryChunks[i] = sql.identifier(this.casing.getColumnCasing(chunk)); + } + } + + orderByValues.push(sql`${orderByUnit}`); + } else { + orderByValues.push(sql`${orderByUnit}`); + } + } + + orderBySql = sql` order by ${sql.join(orderByValues, sql`, `)} `; + } + + const limitSql = typeof limit === 'object' || (typeof limit === 'number' && limit >= 0) + ? sql` limit ${limit}` + : undefined; + + const operatorChunk = sql.raw(`${type} ${isAll ? 'all ' : ''}`); + + const offsetSql = offset ? sql` offset ${offset}` : undefined; + + return sql`${leftChunk}${operatorChunk}${rightChunk}${orderBySql}${limitSql}${offsetSql}`; + } + + buildInsertQuery( + { table, values, ignore, onConflict }: SingleStoreInsertConfig, + ): { sql: SQL; generatedIds: Record[] } { + // const isSingleValue = values.length === 1; + const valuesSqlList: ((SQLChunk | SQL)[] | SQL)[] = []; + const columns: Record = table[Table.Symbol.Columns]; + const colEntries: [string, SingleStoreColumn][] = Object.entries(columns).filter(([_, col]) => + !col.shouldDisableInsert() + ); + + const insertOrder = colEntries.map(([, column]) => sql.identifier(this.casing.getColumnCasing(column))); + const generatedIdsResponse: Record[] = []; + + for (const [valueIndex, value] of values.entries()) { + const generatedIds: Record = {}; + + const valueList: (SQLChunk | SQL)[] = []; + for (const [fieldName, col] of colEntries) { + const colValue = value[fieldName]; + if (colValue === undefined || (is(colValue, Param) && colValue.value === undefined)) { + // eslint-disable-next-line unicorn/no-negated-condition + if (col.defaultFn !== undefined) { + const defaultFnResult = col.defaultFn(); + generatedIds[fieldName] = defaultFnResult; + const defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); + valueList.push(defaultValue); + // eslint-disable-next-line unicorn/no-negated-condition + } else if (!col.default && col.onUpdateFn !== undefined) { + const onUpdateFnResult = col.onUpdateFn(); + const newValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col); + valueList.push(newValue); + } else { + valueList.push(sql`default`); + } + } else { + if (col.defaultFn && is(colValue, Param)) { + generatedIds[fieldName] = colValue.value; + } + valueList.push(colValue); + } + } + + generatedIdsResponse.push(generatedIds); + valuesSqlList.push(valueList); + if (valueIndex < values.length - 1) { + valuesSqlList.push(sql`, `); + } + } + + const valuesSql = sql.join(valuesSqlList); + + const ignoreSql = ignore ? sql` ignore` : undefined; + + const onConflictSql = onConflict ? sql` on duplicate key ${onConflict}` : undefined; + + return { + sql: sql`insert${ignoreSql} into ${table} ${insertOrder} values ${valuesSql}${onConflictSql}`, + generatedIds: generatedIdsResponse, + }; + } + + sqlToQuery(sql: SQL, invokeSource?: 'indexes' | undefined): QueryWithTypings { + return sql.toQuery({ + casing: this.casing, + escapeName: this.escapeName, + escapeParam: this.escapeParam, + escapeString: this.escapeString, + invokeSource, + }); + } + + buildRelationalQuery({ + fullSchema, + schema, + tableNamesMap, + table, + tableConfig, + queryConfig: config, + tableAlias, + nestedQueryRelation, + joinOn, + }: { + fullSchema: Record; + schema: TablesRelationalConfig; + tableNamesMap: Record; + table: SingleStoreTable; + tableConfig: TableRelationalConfig; + queryConfig: true | DBQueryConfig<'many', true>; + tableAlias: string; + nestedQueryRelation?: Relation; + joinOn?: SQL; + }): BuildRelationalQueryResult { + let selection: BuildRelationalQueryResult['selection'] = []; + let limit, offset, orderBy: SingleStoreSelectConfig['orderBy'], where; + const joins: SingleStoreSelectJoinConfig[] = []; + + if (config === true) { + const selectionEntries = Object.entries(tableConfig.columns); + selection = selectionEntries.map(( + [key, value], + ) => ({ + dbKey: value.name, + tsKey: key, + field: aliasedTableColumn(value as SingleStoreColumn, tableAlias), + relationTableTsKey: undefined, + isJson: false, + selection: [], + })); + } else { + const aliasedColumns = Object.fromEntries( + Object.entries(tableConfig.columns).map(([key, value]) => [key, aliasedTableColumn(value, tableAlias)]), + ); + + if (config.where) { + const whereSql = typeof config.where === 'function' + ? config.where(aliasedColumns, getOperators()) + : config.where; + where = whereSql && mapColumnsInSQLToAlias(whereSql, tableAlias); + } + + const fieldsSelection: { tsKey: string; value: SingleStoreColumn | SQL.Aliased }[] = []; + let selectedColumns: string[] = []; + + // Figure out which columns to select + if (config.columns) { + let isIncludeMode = false; + + for (const [field, value] of Object.entries(config.columns)) { + if (value === undefined) { + continue; + } + + if (field in tableConfig.columns) { + if (!isIncludeMode && value === true) { + isIncludeMode = true; + } + selectedColumns.push(field); + } + } + + if (selectedColumns.length > 0) { + selectedColumns = isIncludeMode + ? selectedColumns.filter((c) => config.columns?.[c] === true) + : Object.keys(tableConfig.columns).filter((key) => !selectedColumns.includes(key)); + } + } else { + // Select all columns if selection is not specified + selectedColumns = Object.keys(tableConfig.columns); + } + + for (const field of selectedColumns) { + const column = tableConfig.columns[field]! as SingleStoreColumn; + fieldsSelection.push({ tsKey: field, value: column }); + } + + let selectedRelations: { + tsKey: string; + queryConfig: true | DBQueryConfig<'many', false>; + relation: Relation; + }[] = []; + + // Figure out which relations to select + if (config.with) { + selectedRelations = Object.entries(config.with) + .filter((entry): entry is [typeof entry[0], NonNullable] => !!entry[1]) + .map(([tsKey, queryConfig]) => ({ tsKey, queryConfig, relation: tableConfig.relations[tsKey]! })); + } + + let extras; + + // Figure out which extras to select + if (config.extras) { + extras = typeof config.extras === 'function' + ? config.extras(aliasedColumns, { sql }) + : config.extras; + for (const [tsKey, value] of Object.entries(extras)) { + fieldsSelection.push({ + tsKey, + value: mapColumnsInAliasedSQLToAlias(value, tableAlias), + }); + } + } + + // Transform `fieldsSelection` into `selection` + // `fieldsSelection` shouldn't be used after this point + for (const { tsKey, value } of fieldsSelection) { + selection.push({ + dbKey: is(value, SQL.Aliased) ? value.fieldAlias : tableConfig.columns[tsKey]!.name, + tsKey, + field: is(value, Column) ? aliasedTableColumn(value, tableAlias) : value, + relationTableTsKey: undefined, + isJson: false, + selection: [], + }); + } + + let orderByOrig = typeof config.orderBy === 'function' + ? config.orderBy(aliasedColumns, getOrderByOperators()) + : config.orderBy ?? []; + if (!Array.isArray(orderByOrig)) { + orderByOrig = [orderByOrig]; + } + orderBy = orderByOrig.map((orderByValue) => { + if (is(orderByValue, Column)) { + return aliasedTableColumn(orderByValue, tableAlias) as SingleStoreColumn; + } + return mapColumnsInSQLToAlias(orderByValue, tableAlias); + }); + + limit = config.limit; + offset = config.offset; + + // Process all relations + for ( + const { + tsKey: selectedRelationTsKey, + queryConfig: selectedRelationConfigValue, + relation, + } of selectedRelations + ) { + const normalizedRelation = normalizeRelation(schema, tableNamesMap, relation); + const relationTableName = getTableUniqueName(relation.referencedTable); + const relationTableTsName = tableNamesMap[relationTableName]!; + const relationTableAlias = `${tableAlias}_${selectedRelationTsKey}`; + const joinOn = and( + ...normalizedRelation.fields.map((field, i) => + eq( + aliasedTableColumn(normalizedRelation.references[i]!, relationTableAlias), + aliasedTableColumn(field, tableAlias), + ) + ), + ); + const builtRelation = this.buildRelationalQuery({ + fullSchema, + schema, + tableNamesMap, + table: fullSchema[relationTableTsName] as SingleStoreTable, + tableConfig: schema[relationTableTsName]!, + queryConfig: is(relation, One) + ? (selectedRelationConfigValue === true + ? { limit: 1 } + : { ...selectedRelationConfigValue, limit: 1 }) + : selectedRelationConfigValue, + tableAlias: relationTableAlias, + joinOn, + nestedQueryRelation: relation, + }); + const field = sql`${sql.identifier(relationTableAlias)}.${sql.identifier('data')}`.as(selectedRelationTsKey); + joins.push({ + on: sql`true`, + table: new Subquery(builtRelation.sql as SQL, {}, relationTableAlias), + alias: relationTableAlias, + joinType: 'left', + lateral: true, + }); + selection.push({ + dbKey: selectedRelationTsKey, + tsKey: selectedRelationTsKey, + field, + relationTableTsKey: relationTableTsName, + isJson: true, + selection: builtRelation.selection, + }); + } + } + + if (selection.length === 0) { + throw new DrizzleError({ message: `No fields selected for table "${tableConfig.tsName}" ("${tableAlias}")` }); + } + + let result; + + where = and(joinOn, where); + + if (nestedQueryRelation) { + let field = sql`JSON_TO_ARRAY(${ + sql.join( + selection.map(({ field, tsKey, isJson }) => + isJson + ? sql`${sql.identifier(`${tableAlias}_${tsKey}`)}.${sql.identifier('data')}` + : is(field, SQL.Aliased) + ? field.sql + : field + ), + sql`, `, + ) + })`; + if (is(nestedQueryRelation, Many)) { + field = sql`json_agg(${field})`; + } + const nestedSelection = [{ + dbKey: 'data', + tsKey: 'data', + field: field.as('data'), + isJson: true, + relationTableTsKey: tableConfig.tsName, + selection, + }]; + + const needsSubquery = limit !== undefined || offset !== undefined || (orderBy?.length ?? 0) > 0; + + if (needsSubquery) { + result = this.buildSelectQuery({ + table: aliasedTable(table, tableAlias), + fields: {}, + fieldsFlat: [ + { + path: [], + field: sql.raw('*'), + }, + ...(((orderBy?.length ?? 0) > 0) + ? [{ + path: [], + field: sql`row_number() over (order by ${sql.join(orderBy!, sql`, `)})`, + }] + : []), + ], + where, + limit, + offset, + setOperators: [], + }); + + where = undefined; + limit = undefined; + offset = undefined; + orderBy = undefined; + } else { + result = aliasedTable(table, tableAlias); + } + + result = this.buildSelectQuery({ + table: is(result, SingleStoreTable) ? result : new Subquery(result, {}, tableAlias), + fields: {}, + fieldsFlat: nestedSelection.map(({ field }) => ({ + path: [], + field: is(field, Column) ? aliasedTableColumn(field, tableAlias) : field, + })), + joins, + where, + limit, + offset, + orderBy, + setOperators: [], + }); + } else { + result = this.buildSelectQuery({ + table: aliasedTable(table, tableAlias), + fields: {}, + fieldsFlat: selection.map(({ field }) => ({ + path: [], + field: is(field, Column) ? aliasedTableColumn(field, tableAlias) : field, + })), + joins, + where, + limit, + offset, + orderBy, + setOperators: [], + }); + } + + return { + tableTsKey: tableConfig.tsName, + sql: result, + selection, + }; + } +} diff --git a/drizzle-orm/src/singlestore-core/expressions.ts b/drizzle-orm/src/singlestore-core/expressions.ts new file mode 100644 index 000000000..4e382e238 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/expressions.ts @@ -0,0 +1,34 @@ +import { bindIfParam } from '~/expressions.ts'; +import type { Placeholder, SQL, SQLChunk, SQLWrapper } from '~/sql/sql.ts'; +import { sql } from '~/sql/sql.ts'; +import type { SingleStoreColumn } from './columns/index.ts'; + +export * from '~/expressions.ts'; + +export function concat(column: SingleStoreColumn | SQL.Aliased, value: string | Placeholder | SQLWrapper): SQL { + return sql`${column} || ${bindIfParam(value, column)}`; +} + +export function substring( + column: SingleStoreColumn | SQL.Aliased, + { from, for: _for }: { from?: number | Placeholder | SQLWrapper; for?: number | Placeholder | SQLWrapper }, +): SQL { + const chunks: SQLChunk[] = [sql`substring(`, column]; + if (from !== undefined) { + chunks.push(sql` from `, bindIfParam(from, column)); + } + if (_for !== undefined) { + chunks.push(sql` for `, bindIfParam(_for, column)); + } + chunks.push(sql`)`); + return sql.join(chunks); +} + +// Vectors +export function dotProduct(column: SingleStoreColumn | SQL.Aliased, value: Array): SQL { + return sql`${column} <*> ${JSON.stringify(value)}`; +} + +export function euclideanDistance(column: SingleStoreColumn | SQL.Aliased, value: Array): SQL { + return sql`${column} <-> ${JSON.stringify(value)}`; +} diff --git a/drizzle-orm/src/singlestore-core/index.ts b/drizzle-orm/src/singlestore-core/index.ts new file mode 100644 index 000000000..4e7d4268e --- /dev/null +++ b/drizzle-orm/src/singlestore-core/index.ts @@ -0,0 +1,15 @@ +export * from './alias.ts'; +export * from './columns/index.ts'; +export * from './db.ts'; +export * from './dialect.ts'; +export * from './indexes.ts'; +export * from './primary-keys.ts'; +export * from './query-builders/index.ts'; +export * from './schema.ts'; +export * from './session.ts'; +export * from './subquery.ts'; +export * from './table.ts'; +export * from './unique-constraint.ts'; +export * from './utils.ts'; +/* export * from './view-common.ts'; +export * from './view.ts'; */ diff --git a/drizzle-orm/src/singlestore-core/indexes.ts b/drizzle-orm/src/singlestore-core/indexes.ts new file mode 100644 index 000000000..3120cab1b --- /dev/null +++ b/drizzle-orm/src/singlestore-core/indexes.ts @@ -0,0 +1,193 @@ +import { entityKind } from '~/entity.ts'; +import type { SQL } from '~/sql/sql.ts'; +import type { AnySingleStoreColumn, SingleStoreColumn } from './columns/index.ts'; +import type { SingleStoreTable } from './table.ts'; + +interface IndexConfig { + name: string; + + columns: IndexColumn[]; + + /** + * If true, the index will be created as `create unique index` instead of `create index`. + */ + unique?: boolean; + + /** + * If set, the index will be created as `create index ... using { 'btree' | 'hash' }`. + */ + using?: 'btree' | 'hash'; + + /** + * If set, the index will be created as `create index ... algorythm { 'default' | 'inplace' | 'copy' }`. + */ + algorythm?: 'default' | 'inplace' | 'copy'; + + /** + * If set, adds locks to the index creation. + */ + lock?: 'default' | 'none' | 'shared' | 'exclusive'; +} + +export type IndexColumn = SingleStoreColumn | SQL; + +export class IndexBuilderOn { + static readonly [entityKind]: string = 'SingleStoreIndexBuilderOn'; + + constructor(private name: string, private unique: boolean) {} + + on(...columns: [IndexColumn, ...IndexColumn[]]): IndexBuilder { + return new IndexBuilder(this.name, columns, this.unique); + } +} + +export interface AnyIndexBuilder { + build(table: SingleStoreTable): Index; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IndexBuilder extends AnyIndexBuilder {} + +export class IndexBuilder implements AnyIndexBuilder { + static readonly [entityKind]: string = 'SingleStoreIndexBuilder'; + + /** @internal */ + config: IndexConfig; + + constructor(name: string, columns: IndexColumn[], unique: boolean) { + this.config = { + name, + columns, + unique, + }; + } + + using(using: IndexConfig['using']): this { + this.config.using = using; + return this; + } + + algorythm(algorythm: IndexConfig['algorythm']): this { + this.config.algorythm = algorythm; + return this; + } + + lock(lock: IndexConfig['lock']): this { + this.config.lock = lock; + return this; + } + + /** @internal */ + build(table: SingleStoreTable): Index { + return new Index(this.config, table); + } +} + +export class Index { + static readonly [entityKind]: string = 'SingleStoreIndex'; + + readonly config: IndexConfig & { table: SingleStoreTable }; + + constructor(config: IndexConfig, table: SingleStoreTable) { + this.config = { ...config, table }; + } +} + +export type GetColumnsTableName = TColumns extends + AnySingleStoreColumn<{ tableName: infer TTableName extends string }> | AnySingleStoreColumn< + { tableName: infer TTableName extends string } + >[] ? TTableName + : never; + +export function index(name: string): IndexBuilderOn { + return new IndexBuilderOn(name, false); +} + +export function uniqueIndex(name: string): IndexBuilderOn { + return new IndexBuilderOn(name, true); +} + +/* export interface AnyFullTextIndexBuilder { + build(table: SingleStoreTable): FullTextIndex; +} */ +/* +interface FullTextIndexConfig { + version?: number; +} + +interface FullTextIndexFullConfig extends FullTextIndexConfig { + columns: IndexColumn[]; + + name: string; +} + +export class FullTextIndexBuilderOn { + static readonly [entityKind]: string = 'SingleStoreFullTextIndexBuilderOn'; + + constructor(private name: string, private config: FullTextIndexConfig) {} + + on(...columns: [IndexColumn, ...IndexColumn[]]): FullTextIndexBuilder { + return new FullTextIndexBuilder({ + name: this.name, + columns: columns, + ...this.config, + }); + } +} */ + +/* +export interface FullTextIndexBuilder extends AnyFullTextIndexBuilder {} + +export class FullTextIndexBuilder implements AnyFullTextIndexBuilder { + static readonly [entityKind]: string = 'SingleStoreFullTextIndexBuilder'; */ + +/** @internal */ +/* config: FullTextIndexFullConfig; + + constructor(config: FullTextIndexFullConfig) { + this.config = config; + } */ + +/** @internal */ +/* build(table: SingleStoreTable): FullTextIndex { + return new FullTextIndex(this.config, table); + } +} + +export class FullTextIndex { + static readonly [entityKind]: string = 'SingleStoreFullTextIndex'; + + readonly config: FullTextIndexConfig & { table: SingleStoreTable }; + + constructor(config: FullTextIndexConfig, table: SingleStoreTable) { + this.config = { ...config, table }; + } +} + +export function fulltext(name: string, config: FullTextIndexConfig): FullTextIndexBuilderOn { + return new FullTextIndexBuilderOn(name, config); +} + +export type SortKeyColumn = SingleStoreColumn | SQL; + +export class SortKeyBuilder { + static readonly [entityKind]: string = 'SingleStoreSortKeyBuilder'; + + constructor(private columns: SortKeyColumn[]) {} */ + +/** @internal */ +/* build(table: SingleStoreTable): SortKey { + return new SortKey(this.columns, table); + } +} + +export class SortKey { + static readonly [entityKind]: string = 'SingleStoreSortKey'; + + constructor(public columns: SortKeyColumn[], public table: SingleStoreTable) {} +} + +export function sortKey(...columns: SortKeyColumn[]): SortKeyBuilder { + return new SortKeyBuilder(columns); +} + */ diff --git a/drizzle-orm/src/singlestore-core/primary-keys.ts b/drizzle-orm/src/singlestore-core/primary-keys.ts new file mode 100644 index 000000000..47dc0a19c --- /dev/null +++ b/drizzle-orm/src/singlestore-core/primary-keys.ts @@ -0,0 +1,63 @@ +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreColumn, SingleStoreColumn } from './columns/index.ts'; +import { SingleStoreTable } from './table.ts'; + +export function primaryKey< + TTableName extends string, + TColumn extends AnySingleStoreColumn<{ tableName: TTableName }>, + TColumns extends AnySingleStoreColumn<{ tableName: TTableName }>[], +>(config: { name?: string; columns: [TColumn, ...TColumns] }): PrimaryKeyBuilder; +/** + * @deprecated: Please use primaryKey({ columns: [] }) instead of this function + * @param columns + */ +export function primaryKey< + TTableName extends string, + TColumns extends AnySingleStoreColumn<{ tableName: TTableName }>[], +>(...columns: TColumns): PrimaryKeyBuilder; +export function primaryKey(...config: any) { + if (config[0].columns) { + return new PrimaryKeyBuilder(config[0].columns, config[0].name); + } + return new PrimaryKeyBuilder(config); +} + +export class PrimaryKeyBuilder { + static readonly [entityKind]: string = 'SingleStorePrimaryKeyBuilder'; + + /** @internal */ + columns: SingleStoreColumn[]; + + /** @internal */ + name?: string; + + constructor( + columns: SingleStoreColumn[], + name?: string, + ) { + this.columns = columns; + this.name = name; + } + + /** @internal */ + build(table: SingleStoreTable): PrimaryKey { + return new PrimaryKey(table, this.columns, this.name); + } +} + +export class PrimaryKey { + static readonly [entityKind]: string = 'SingleStorePrimaryKey'; + + readonly columns: SingleStoreColumn[]; + readonly name?: string; + + constructor(readonly table: SingleStoreTable, columns: SingleStoreColumn[], name?: string) { + this.columns = columns; + this.name = name; + } + + getName(): string { + return this.name + ?? `${this.table[SingleStoreTable.Symbol.Name]}_${this.columns.map((column) => column.name).join('_')}_pk`; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/count.ts b/drizzle-orm/src/singlestore-core/query-builders/count.ts new file mode 100644 index 000000000..aba5b2f3f --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/count.ts @@ -0,0 +1,79 @@ +import { entityKind } from '~/entity.ts'; +import { SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; +import type { SingleStoreSession } from '../session.ts'; +import type { SingleStoreTable } from '../table.ts'; +/* import type { SingleStoreViewBase } from '../view-base.ts'; */ + +export class SingleStoreCountBuilder< + TSession extends SingleStoreSession, +> extends SQL implements Promise, SQLWrapper { + private sql: SQL; + + static override readonly [entityKind] = 'SingleStoreCountBuilder'; + [Symbol.toStringTag] = 'SingleStoreCountBuilder'; + + private session: TSession; + + private static buildEmbeddedCount( + source: SingleStoreTable | /* SingleStoreViewBase | */ SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`; + } + + private static buildCount( + source: SingleStoreTable | /* SingleStoreViewBase | */ SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`select count(*) as count from ${source}${sql.raw(' where ').if(filters)}${filters}`; + } + + constructor( + readonly params: { + source: SingleStoreTable | /* SingleStoreViewBase | */ SQL | SQLWrapper; + filters?: SQL; + session: TSession; + }, + ) { + super(SingleStoreCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks); + + this.mapWith(Number); + + this.session = params.session; + + this.sql = SingleStoreCountBuilder.buildCount( + params.source, + params.filters, + ); + } + + then( + onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined, + ): Promise { + return Promise.resolve(this.session.count(this.sql)) + .then( + onfulfilled, + onrejected, + ); + } + + catch( + onRejected?: ((reason: any) => never | PromiseLike) | null | undefined, + ): Promise { + return this.then(undefined, onRejected); + } + + finally(onFinally?: (() => void) | null | undefined): Promise { + return this.then( + (value) => { + onFinally?.(); + return value; + }, + (reason) => { + onFinally?.(); + throw reason; + }, + ); + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/delete.ts b/drizzle-orm/src/singlestore-core/query-builders/delete.ts new file mode 100644 index 000000000..1f41d29ba --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/delete.ts @@ -0,0 +1,207 @@ +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import type { Subquery } from '~/subquery.ts'; +import { Table } from '~/table.ts'; +import type { ValueOrArray } from '~/utils.ts'; +import type { SingleStoreColumn } from '../columns/common.ts'; +import type { SelectedFieldsOrdered } from './select.types.ts'; + +export type SingleStoreDeleteWithout< + T extends AnySingleStoreDeleteBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreDeleteBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreDelete< + TTable extends SingleStoreTable = SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreDeleteBase; + +export interface SingleStoreDeleteConfig { + where?: SQL | undefined; + limit?: number | Placeholder; + orderBy?: (SingleStoreColumn | SQL | SQL.Aliased)[]; + table: SingleStoreTable; + returning?: SelectedFieldsOrdered; + withList?: Subquery[]; +} + +export type SingleStoreDeletePrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +type SingleStoreDeleteDynamic = SingleStoreDelete< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +type AnySingleStoreDeleteBase = SingleStoreDeleteBase; + +export interface SingleStoreDeleteBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise> { + readonly _: { + readonly table: TTable; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreDeleteBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static override readonly [entityKind]: string = 'SingleStoreDelete'; + + private config: SingleStoreDeleteConfig; + + constructor( + private table: TTable, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + withList?: Subquery[], + ) { + super(); + this.config = { table, withList }; + } + + /** + * Adds a `where` clause to the query. + * + * Calling this method will delete only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be deleted. + * + * ```ts + * // Delete all cars with green color + * db.delete(cars).where(eq(cars.color, 'green')); + * // or + * db.delete(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Delete all BMW cars with a green color + * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Delete all cars with the green or blue color + * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + where(where: SQL | undefined): SingleStoreDeleteWithout { + this.config.where = where; + return this as any; + } + + orderBy( + builder: (deleteTable: TTable) => ValueOrArray, + ): SingleStoreDeleteWithout; + orderBy(...columns: (SingleStoreColumn | SQL | SQL.Aliased)[]): SingleStoreDeleteWithout; + orderBy( + ...columns: + | [(deleteTable: TTable) => ValueOrArray] + | (SingleStoreColumn | SQL | SQL.Aliased)[] + ): SingleStoreDeleteWithout { + if (typeof columns[0] === 'function') { + const orderBy = columns[0]( + new Proxy( + this.config.table[Table.Symbol.Columns], + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as any, + ); + + const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; + this.config.orderBy = orderByArray; + } else { + const orderByArray = columns as (SingleStoreColumn | SQL | SQL.Aliased)[]; + this.config.orderBy = orderByArray; + } + return this as any; + } + + limit(limit: number | Placeholder): SingleStoreDeleteWithout { + this.config.limit = limit; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildDeleteQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreDeletePrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + this.config.returning, + ) as SingleStoreDeletePrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreDeleteDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/index.ts b/drizzle-orm/src/singlestore-core/query-builders/index.ts new file mode 100644 index 000000000..704cb4afa --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/index.ts @@ -0,0 +1,11 @@ +/* export * from './attach.ts'; +export * from './branch.ts'; +export * from './createMilestone.ts'; */ +export * from './delete.ts'; +/* export * from './detach.ts'; */ +export * from './insert.ts'; +/* export * from './optimizeTable.ts'; */ +export * from './query-builder.ts'; +export * from './select.ts'; +export * from './select.types.ts'; +export * from './update.ts'; diff --git a/drizzle-orm/src/singlestore-core/query-builders/insert.ts b/drizzle-orm/src/singlestore-core/query-builders/insert.ts new file mode 100644 index 000000000..84a72fdab --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/insert.ts @@ -0,0 +1,305 @@ +import { entityKind, is } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { RunnableQuery } from '~/runnable-query.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Placeholder, Query, SQLWrapper } from '~/sql/sql.ts'; +import { Param, SQL, sql } from '~/sql/sql.ts'; +import type { InferModelFromColumns } from '~/table.ts'; +import { Table } from '~/table.ts'; +import { mapUpdateSet, orderSelectedFields } from '~/utils.ts'; +import type { AnySingleStoreColumn, SingleStoreColumn } from '../columns/common.ts'; +import type { SelectedFieldsOrdered } from './select.types.ts'; +import type { SingleStoreUpdateSetSource } from './update.ts'; + +export interface SingleStoreInsertConfig { + table: TTable; + values: Record[]; + ignore: boolean; + onConflict?: SQL; + returning?: SelectedFieldsOrdered; +} + +export type AnySingleStoreInsertConfig = SingleStoreInsertConfig; + +export type SingleStoreInsertValue = + & { + [Key in keyof TTable['$inferInsert']]: TTable['$inferInsert'][Key] | SQL | Placeholder; + } + & {}; + +export class SingleStoreInsertBuilder< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, +> { + static readonly [entityKind]: string = 'SingleStoreInsertBuilder'; + + private shouldIgnore = false; + + constructor( + private table: TTable, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) {} + + ignore(): this { + this.shouldIgnore = true; + return this; + } + + values(value: SingleStoreInsertValue): SingleStoreInsertBase; + values(values: SingleStoreInsertValue[]): SingleStoreInsertBase; + values( + values: SingleStoreInsertValue | SingleStoreInsertValue[], + ): SingleStoreInsertBase { + values = Array.isArray(values) ? values : [values]; + if (values.length === 0) { + throw new Error('values() must be called with at least one value'); + } + const mappedValues = values.map((entry) => { + const result: Record = {}; + const cols = this.table[Table.Symbol.Columns]; + for (const colKey of Object.keys(entry)) { + const colValue = entry[colKey as keyof typeof entry]; + result[colKey] = is(colValue, SQL) ? colValue : new Param(colValue, cols[colKey]); + } + return result; + }); + + return new SingleStoreInsertBase(this.table, mappedValues, this.shouldIgnore, this.session, this.dialect); + } +} + +export type SingleStoreInsertWithout< + T extends AnySingleStoreInsert, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreInsertBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + T['_']['returning'], + TDynamic, + T['_']['excludedMethods'] | '$returning' + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreInsertDynamic = SingleStoreInsert< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + T['_']['returning'] +>; + +export type SingleStoreInsertPrepare< + T extends AnySingleStoreInsert, + TReturning extends Record | undefined = undefined, +> = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: TReturning extends undefined ? SingleStoreQueryResultKind : TReturning[]; + iterator: never; + }, + true +>; + +export type SingleStoreInsertOnDuplicateKeyUpdateConfig = { + set: SingleStoreUpdateSetSource; +}; + +export type SingleStoreInsert< + TTable extends SingleStoreTable = SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TReturning extends Record | undefined = Record | undefined, +> = SingleStoreInsertBase; + +export type SingleStoreInsertReturning< + T extends AnySingleStoreInsert, + TDynamic extends boolean, +> = SingleStoreInsertBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + InferModelFromColumns>, + TDynamic, + T['_']['excludedMethods'] | '$returning' +>; + +export type AnySingleStoreInsert = SingleStoreInsertBase; + +export interface SingleStoreInsertBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TReturning extends Record | undefined = undefined, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends + QueryPromise : TReturning[]>, + RunnableQuery< + TReturning extends undefined ? SingleStoreQueryResultKind : TReturning[], + 'singlestore' + >, + SQLWrapper +{ + readonly _: { + readonly dialect: 'singlestore'; + readonly table: TTable; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + readonly returning: TReturning; + readonly result: TReturning extends undefined ? SingleStoreQueryResultKind : TReturning[]; + }; +} + +export type PrimaryKeyKeys> = { + [K in keyof T]: T[K]['_']['isPrimaryKey'] extends true ? T[K]['_']['isAutoincrement'] extends true ? K + : T[K]['_']['hasRuntimeDefault'] extends true ? T[K]['_']['isPrimaryKey'] extends true ? K : never + : never + : T[K]['_']['hasRuntimeDefault'] extends true ? T[K]['_']['isPrimaryKey'] extends true ? K : never + : never; +}[keyof T]; + +export type GetPrimarySerialOrDefaultKeys> = { + [K in PrimaryKeyKeys]: T[K]; +}; + +export class SingleStoreInsertBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TReturning extends Record | undefined = undefined, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise : TReturning[]> + implements + RunnableQuery< + TReturning extends undefined ? SingleStoreQueryResultKind : TReturning[], + 'singlestore' + >, + SQLWrapper +{ + static override readonly [entityKind]: string = 'SingleStoreInsert'; + + declare protected $table: TTable; + + private config: SingleStoreInsertConfig; + + constructor( + table: TTable, + values: SingleStoreInsertConfig['values'], + ignore: boolean, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) { + super(); + this.config = { table, values, ignore }; + } + + /** + * Adds an `on duplicate key update` clause to the query. + * + * Calling this method will update update the row if any unique index conflicts. MySQL will automatically determine the conflict target based on the primary key and unique indexes. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#on-duplicate-key-update} + * + * @param config The `set` clause + * + * @example + * ```ts + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW'}) + * .onDuplicateKeyUpdate({ set: { brand: 'Porsche' }}); + * ``` + * + * While MySQL does not directly support doing nothing on conflict, you can perform a no-op by setting any column's value to itself and achieve the same effect: + * + * ```ts + * import { sql } from 'drizzle-orm'; + * + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onDuplicateKeyUpdate({ set: { id: sql`id` } }); + * ``` + */ + onDuplicateKeyUpdate( + config: SingleStoreInsertOnDuplicateKeyUpdateConfig, + ): SingleStoreInsertWithout { + const setSql = this.dialect.buildUpdateSet(this.config.table, mapUpdateSet(this.config.table, config.set)); + this.config.onConflict = sql`update ${setSql}`; + return this as any; + } + + $returningId(): SingleStoreInsertWithout< + SingleStoreInsertReturning, + TDynamic, + '$returningId' + > { + const returning: SelectedFieldsOrdered = []; + for (const [key, value] of Object.entries(this.config.table[Table.Symbol.Columns])) { + if (value.primary) { + returning.push({ field: value, path: [key] }); + } + } + this.config.returning = orderSelectedFields(this.config.table[Table.Symbol.Columns]); + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildInsertQuery(this.config).sql; + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreInsertPrepare { + const { sql, generatedIds } = this.dialect.buildInsertQuery(this.config); + return this.session.prepareQuery( + this.dialect.sqlToQuery(sql), + undefined, + undefined, + generatedIds, + this.config.returning, + ) as SingleStoreInsertPrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreInsertDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/query-builder.ts b/drizzle-orm/src/singlestore-core/query-builders/query-builder.ts new file mode 100644 index 000000000..29d6c2290 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/query-builder.ts @@ -0,0 +1,114 @@ +import { entityKind, is } from '~/entity.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { SingleStoreDialectConfig } from '~/singlestore-core/dialect.ts'; +import { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { WithSubqueryWithSelection } from '~/singlestore-core/subquery.ts'; +import type { ColumnsSelection } from '~/sql/sql.ts'; +import { WithSubquery } from '~/subquery.ts'; +import { SingleStoreSelectBuilder } from './select.ts'; +import type { SelectedFields } from './select.types.ts'; + +export class QueryBuilder { + static readonly [entityKind]: string = 'SingleStoreQueryBuilder'; + + private dialect: SingleStoreDialect | undefined; + private dialectConfig: SingleStoreDialectConfig | undefined; + + constructor(dialect?: SingleStoreDialect | SingleStoreDialectConfig) { + this.dialect = is(dialect, SingleStoreDialect) ? dialect : undefined; + this.dialectConfig = is(dialect, SingleStoreDialect) ? undefined : dialect; + } + + $with(alias: TAlias) { + const queryBuilder = this; + + return { + as( + qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), + ): WithSubqueryWithSelection { + if (typeof qb === 'function') { + qb = qb(queryBuilder); + } + + return new Proxy( + new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true), + new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }), + ) as WithSubqueryWithSelection; + }, + }; + } + + with(...queries: WithSubquery[]) { + const self = this; + + function select(): SingleStoreSelectBuilder; + function select( + fields: TSelection, + ): SingleStoreSelectBuilder; + function select( + fields?: TSelection, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: undefined, + dialect: self.getDialect(), + withList: queries, + }); + } + + function selectDistinct(): SingleStoreSelectBuilder; + function selectDistinct( + fields: TSelection, + ): SingleStoreSelectBuilder; + function selectDistinct( + fields?: TSelection, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: undefined, + dialect: self.getDialect(), + withList: queries, + distinct: true, + }); + } + + return { select, selectDistinct }; + } + + select(): SingleStoreSelectBuilder; + select(fields: TSelection): SingleStoreSelectBuilder; + select( + fields?: TSelection, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: undefined, + dialect: this.getDialect(), + }); + } + + selectDistinct(): SingleStoreSelectBuilder; + selectDistinct( + fields: TSelection, + ): SingleStoreSelectBuilder; + selectDistinct( + fields?: TSelection, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: undefined, + dialect: this.getDialect(), + distinct: true, + }); + } + + // Lazy load dialect to avoid circular dependency + private getDialect() { + if (!this.dialect) { + this.dialect = new SingleStoreDialect(this.dialectConfig); + } + + return this.dialect; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/query.ts b/drizzle-orm/src/singlestore-core/query-builders/query.ts new file mode 100644 index 000000000..c15f7ad59 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/query.ts @@ -0,0 +1,141 @@ +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import { + type BuildQueryResult, + type BuildRelationalQueryResult, + type DBQueryConfig, + mapRelationalRow, + type TableRelationalConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import type { Query, QueryWithTypings, SQL } from '~/sql/sql.ts'; +import type { KnownKeysOnly } from '~/utils.ts'; +import type { SingleStoreDialect } from '../dialect.ts'; +import type { + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreSession, +} from '../session.ts'; +import type { SingleStoreTable } from '../table.ts'; + +export class RelationalQueryBuilder< + TPreparedQueryHKT extends PreparedQueryHKTBase, + TSchema extends TablesRelationalConfig, + TFields extends TableRelationalConfig, +> { + static readonly [entityKind]: string = 'SingleStoreRelationalQueryBuilder'; + + constructor( + private fullSchema: Record, + private schema: TSchema, + private tableNamesMap: Record, + private table: SingleStoreTable, + private tableConfig: TableRelationalConfig, + private dialect: SingleStoreDialect, + private session: SingleStoreSession, + ) {} + + findMany>( + config?: KnownKeysOnly>, + ): SingleStoreRelationalQuery[]> { + return new SingleStoreRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.dialect, + this.session, + config ? (config as DBQueryConfig<'many', true>) : {}, + 'many', + ); + } + + findFirst, 'limit'>>( + config?: KnownKeysOnly, 'limit'>>, + ): SingleStoreRelationalQuery | undefined> { + return new SingleStoreRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.dialect, + this.session, + config ? { ...(config as DBQueryConfig<'many', true> | undefined), limit: 1 } : { limit: 1 }, + 'first', + ); + } +} + +export class SingleStoreRelationalQuery< + TPreparedQueryHKT extends PreparedQueryHKTBase, + TResult, +> extends QueryPromise { + static override readonly [entityKind]: string = 'SingleStoreRelationalQuery'; + + declare protected $brand: 'SingleStoreRelationalQuery'; + + constructor( + private fullSchema: Record, + private schema: TablesRelationalConfig, + private tableNamesMap: Record, + private table: SingleStoreTable, + private tableConfig: TableRelationalConfig, + private dialect: SingleStoreDialect, + private session: SingleStoreSession, + private config: DBQueryConfig<'many', true> | true, + private queryMode: 'many' | 'first', + ) { + super(); + } + + prepare() { + const { query, builtQuery } = this._toSQL(); + return this.session.prepareQuery( + builtQuery, + undefined, + (rawRows) => { + const rows = rawRows.map((row) => mapRelationalRow(this.schema, this.tableConfig, row, query.selection)); + if (this.queryMode === 'first') { + return rows[0] as TResult; + } + return rows as TResult; + }, + ) as PreparedQueryKind; + } + + private _getQuery() { + return this.dialect.buildRelationalQuery({ + fullSchema: this.fullSchema, + schema: this.schema, + tableNamesMap: this.tableNamesMap, + table: this.table, + tableConfig: this.tableConfig, + queryConfig: this.config, + tableAlias: this.tableConfig.tsName, + }); + } + + private _toSQL(): { query: BuildRelationalQueryResult; builtQuery: QueryWithTypings } { + const query = this._getQuery(); + + const builtQuery = this.dialect.sqlToQuery(query.sql as SQL); + + return { builtQuery, query }; + } + + /** @internal */ + getSQL(): SQL { + return this._getQuery().sql as SQL; + } + + toSQL(): Query { + return this._toSQL().builtQuery; + } + + override execute(): Promise { + return this.prepare().execute(); + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/select.ts b/drizzle-orm/src/singlestore-core/query-builders/select.ts new file mode 100644 index 000000000..36a3893a2 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/select.ts @@ -0,0 +1,1082 @@ +import { entityKind, is } from '~/entity.ts'; +import { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import type { + BuildSubquerySelection, + GetSelectTableName, + GetSelectTableSelection, + JoinNullability, + JoinType, + SelectMode, + SelectResult, + SetOperator, +} from '~/query-builders/select.types.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { SingleStoreColumn } from '~/singlestore-core/columns/index.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + PreparedQueryHKTBase, + SingleStorePreparedQueryConfig, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { SubqueryWithSelection } from '~/singlestore-core/subquery.ts'; +import type { SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { ColumnsSelection, Query } from '~/sql/sql.ts'; +import { SQL } from '~/sql/sql.ts'; +import { Subquery } from '~/subquery.ts'; +import { Table } from '~/table.ts'; +import { + applyMixins, + getTableColumns, + getTableLikeName, + haveSameKeys, + orderSelectedFields, + type ValueOrArray, +} from '~/utils.ts'; +import type { + AnySingleStoreSelect, + CreateSingleStoreSelectFromBuilderMode, + GetSingleStoreSetOperators, + LockConfig, + LockStrength, + SelectedFields, + SetOperatorRightSelect, + SingleStoreCreateSetOperatorFn, + SingleStoreJoinFn, + SingleStoreSelectConfig, + SingleStoreSelectDynamic, + SingleStoreSelectHKT, + SingleStoreSelectHKTBase, + SingleStoreSelectPrepare, + SingleStoreSelectWithout, + SingleStoreSetOperatorExcludedMethods, + SingleStoreSetOperatorWithResult, +} from './select.types.ts'; + +export class SingleStoreSelectBuilder< + TSelection extends SelectedFields | undefined, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TBuilderMode extends 'db' | 'qb' = 'db', +> { + static readonly [entityKind]: string = 'SingleStoreSelectBuilder'; + + private fields: TSelection; + private session: SingleStoreSession | undefined; + private dialect: SingleStoreDialect; + private withList: Subquery[] = []; + private distinct: boolean | undefined; + + constructor( + config: { + fields: TSelection; + session: SingleStoreSession | undefined; + dialect: SingleStoreDialect; + withList?: Subquery[]; + distinct?: boolean; + }, + ) { + this.fields = config.fields; + this.session = config.session; + this.dialect = config.dialect; + if (config.withList) { + this.withList = config.withList; + } + this.distinct = config.distinct; + } + + from( // | SingleStoreViewBase + source: TFrom, + ): CreateSingleStoreSelectFromBuilderMode< + TBuilderMode, + GetSelectTableName, + TSelection extends undefined ? GetSelectTableSelection : TSelection, + TSelection extends undefined ? 'single' : 'partial', + TPreparedQueryHKT + > { + const isPartialSelect = !!this.fields; + + let fields: SelectedFields; + if (this.fields) { + fields = this.fields; + } else if (is(source, Subquery)) { + // This is required to use the proxy handler to get the correct field values from the subquery + fields = Object.fromEntries( + Object.keys(source._.selectedFields).map(( + key, + ) => [key, source[key as unknown as keyof typeof source] as unknown as SelectedFields[string]]), + ); + /* } else if (is(source, SingleStoreViewBase)) { + fields = source[ViewBaseConfig].selectedFields as SelectedFields; */ + } else if (is(source, SQL)) { + fields = {}; + } else { + fields = getTableColumns(source); + } + + return new SingleStoreSelectBase( + { + table: source, + fields, + isPartialSelect, + session: this.session, + dialect: this.dialect, + withList: this.withList, + distinct: this.distinct, + }, + ) as any; + } +} + +export abstract class SingleStoreSelectQueryBuilderBase< + THKT extends SingleStoreSelectHKTBase, + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, +> extends TypedQueryBuilder { + static override readonly [entityKind]: string = 'SingleStoreSelectQueryBuilder'; + + override readonly _: { + readonly hkt: THKT; + readonly tableName: TTableName; + readonly selection: TSelection; + readonly selectMode: TSelectMode; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly nullabilityMap: TNullabilityMap; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + readonly result: TResult; + readonly selectedFields: TSelectedFields; + }; + + protected config: SingleStoreSelectConfig; + protected joinsNotNullableMap: Record; + private tableName: string | undefined; + private isPartialSelect: boolean; + /** @internal */ + readonly session: SingleStoreSession | undefined; + protected dialect: SingleStoreDialect; + + constructor( + { table, fields, isPartialSelect, session, dialect, withList, distinct }: { + table: SingleStoreSelectConfig['table']; + fields: SingleStoreSelectConfig['fields']; + isPartialSelect: boolean; + session: SingleStoreSession | undefined; + dialect: SingleStoreDialect; + withList: Subquery[]; + distinct: boolean | undefined; + }, + ) { + super(); + this.config = { + withList, + table, + fields: { ...fields }, + distinct, + setOperators: [], + }; + this.isPartialSelect = isPartialSelect; + this.session = session; + this.dialect = dialect; + this._ = { + selectedFields: fields as TSelectedFields, + } as this['_']; + this.tableName = getTableLikeName(table); + this.joinsNotNullableMap = typeof this.tableName === 'string' ? { [this.tableName]: true } : {}; + } + + private createJoin( + joinType: TJoinType, + ): SingleStoreJoinFn { + return ( + table: SingleStoreTable | Subquery | SQL, // | SingleStoreViewBase + on: ((aliases: TSelection) => SQL | undefined) | SQL | undefined, + ) => { + const baseTableName = this.tableName; + const tableName = getTableLikeName(table); + + if (typeof tableName === 'string' && this.config.joins?.some((join) => join.alias === tableName)) { + throw new Error(`Alias "${tableName}" is already used in this query`); + } + + if (!this.isPartialSelect) { + // If this is the first join and this is not a partial select and we're not selecting from raw SQL, "move" the fields from the main table to the nested object + if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === 'string') { + this.config.fields = { + [baseTableName]: this.config.fields, + }; + } + if (typeof tableName === 'string' && !is(table, SQL)) { + const selection = is(table, Subquery) + ? table._.selectedFields + /* : is(table, View) + ? table[ViewBaseConfig].selectedFields */ + : table[Table.Symbol.Columns]; + this.config.fields[tableName] = selection; + } + } + + if (typeof on === 'function') { + on = on( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as TSelection, + ); + } + + if (!this.config.joins) { + this.config.joins = []; + } + + this.config.joins.push({ on, table, joinType, alias: tableName }); + + if (typeof tableName === 'string') { + switch (joinType) { + case 'left': { + this.joinsNotNullableMap[tableName] = false; + break; + } + case 'right': { + this.joinsNotNullableMap = Object.fromEntries( + Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false]), + ); + this.joinsNotNullableMap[tableName] = true; + break; + } + case 'inner': { + this.joinsNotNullableMap[tableName] = true; + break; + } + case 'full': { + this.joinsNotNullableMap = Object.fromEntries( + Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false]), + ); + this.joinsNotNullableMap[tableName] = false; + break; + } + } + } + + return this as any; + }; + } + + /** + * Executes a `left join` operation by adding another table to the current query. + * + * Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#left-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet | null }[] = await db.select() + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ + leftJoin = this.createJoin('left'); + + /** + * Executes a `right join` operation by adding another table to the current query. + * + * Calling this method associates each row of the joined table with the corresponding row from the main table, if a match is found. If no matching row exists, it sets all columns of the main table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#right-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet }[] = await db.select() + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ + rightJoin = this.createJoin('right'); + + /** + * Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values. + * + * Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#inner-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet }[] = await db.select() + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ + innerJoin = this.createJoin('inner'); + + /** + * Executes a `full join` operation by combining rows from two tables into a new table. + * + * Calling this method retrieves all rows from both main and joined tables, merging rows with matching values and filling in `null` for non-matching columns. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#full-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet | null }[] = await db.select() + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ + fullJoin = this.createJoin('full'); + + private createSetOperator( + type: SetOperator, + isAll: boolean, + ): >( + rightSelection: + | ((setOperators: GetSingleStoreSetOperators) => SetOperatorRightSelect) + | SetOperatorRightSelect, + ) => SingleStoreSelectWithout< + this, + TDynamic, + SingleStoreSetOperatorExcludedMethods, + true + > { + return (rightSelection) => { + const rightSelect = (typeof rightSelection === 'function' + ? rightSelection(getSingleStoreSetOperators()) + : rightSelection) as TypedQueryBuilder< + any, + TResult + >; + + if (!haveSameKeys(this.getSelectedFields(), rightSelect.getSelectedFields())) { + throw new Error( + 'Set operator error (union / intersect / except): selected fields are not the same or are in a different order', + ); + } + + this.config.setOperators.push({ type, isAll, rightSelect }); + return this as any; + }; + } + + /** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * import { union } from 'drizzle-orm/singlestore-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ + union = this.createSetOperator('union', false); + + /** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * import { unionAll } from 'drizzle-orm/singlestore-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ + unionAll = this.createSetOperator('union', true); + + /** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { intersect } from 'drizzle-orm/singlestore-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ + intersect = this.createSetOperator('intersect', false); + + /** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { except } from 'drizzle-orm/singlestore-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ + except = this.createSetOperator('except', false); + + /** + * Adds `minus` set operator to the query. + * + * This is an alias of `except` supported by SingleStore. + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .minus( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { minus } from 'drizzle-orm/singlestore-core' + * + * await minus( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ + minus = this.createSetOperator('except', false); + + /** @internal */ + addSetOperators(setOperators: SingleStoreSelectConfig['setOperators']): SingleStoreSelectWithout< + this, + TDynamic, + SingleStoreSetOperatorExcludedMethods, + true + > { + this.config.setOperators.push(...setOperators); + return this as any; + } + + /** + * Adds a `where` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#filtering} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be selected. + * + * ```ts + * // Select all cars with green color + * await db.select().from(cars).where(eq(cars.color, 'green')); + * // or + * await db.select().from(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Select all BMW cars with a green color + * await db.select().from(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Select all cars with the green or blue color + * await db.select().from(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + where( + where: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, + ): SingleStoreSelectWithout { + if (typeof where === 'function') { + where = where( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as TSelection, + ); + } + this.config.where = where; + return this as any; + } + + /** + * Adds a `having` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. It is typically used with aggregate functions to filter the aggregated data based on a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @param having the `having` clause. + * + * @example + * + * ```ts + * // Select all brands with more than one car + * await db.select({ + * brand: cars.brand, + * count: sql`cast(count(${cars.id}) as int)`, + * }) + * .from(cars) + * .groupBy(cars.brand) + * .having(({ count }) => gt(count, 1)); + * ``` + */ + having( + having: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, + ): SingleStoreSelectWithout { + if (typeof having === 'function') { + having = having( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as TSelection, + ); + } + this.config.having = having; + return this as any; + } + + /** + * Adds a `group by` clause to the query. + * + * Calling this method will group rows that have the same values into summary rows, often used for aggregation purposes. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @example + * + * ```ts + * // Group and count people by their last names + * await db.select({ + * lastName: people.lastName, + * count: sql`cast(count(*) as int)` + * }) + * .from(people) + * .groupBy(people.lastName); + * ``` + */ + groupBy( + builder: (aliases: this['_']['selection']) => ValueOrArray, + ): SingleStoreSelectWithout; + groupBy(...columns: (SingleStoreColumn | SQL | SQL.Aliased)[]): SingleStoreSelectWithout; + groupBy( + ...columns: + | [(aliases: this['_']['selection']) => ValueOrArray] + | (SingleStoreColumn | SQL | SQL.Aliased)[] + ): SingleStoreSelectWithout { + if (typeof columns[0] === 'function') { + const groupBy = columns[0]( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as TSelection, + ); + this.config.groupBy = Array.isArray(groupBy) ? groupBy : [groupBy]; + } else { + this.config.groupBy = columns as (SingleStoreColumn | SQL | SQL.Aliased)[]; + } + return this as any; + } + + /** + * Adds an `order by` clause to the query. + * + * Calling this method will sort the result-set in ascending or descending order. By default, the sort order is ascending. + * + * See docs: {@link https://orm.drizzle.team/docs/select#order-by} + * + * @example + * + * ``` + * // Select cars ordered by year + * await db.select().from(cars).orderBy(cars.year); + * ``` + * + * You can specify whether results are in ascending or descending order with the `asc()` and `desc()` operators. + * + * ```ts + * // Select cars ordered by year in descending order + * await db.select().from(cars).orderBy(desc(cars.year)); + * + * // Select cars ordered by year and price + * await db.select().from(cars).orderBy(asc(cars.year), desc(cars.price)); + * ``` + */ + orderBy( + builder: (aliases: this['_']['selection']) => ValueOrArray, + ): SingleStoreSelectWithout; + orderBy(...columns: (SingleStoreColumn | SQL | SQL.Aliased)[]): SingleStoreSelectWithout; + orderBy( + ...columns: + | [(aliases: this['_']['selection']) => ValueOrArray] + | (SingleStoreColumn | SQL | SQL.Aliased)[] + ): SingleStoreSelectWithout { + if (typeof columns[0] === 'function') { + const orderBy = columns[0]( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as TSelection, + ); + + const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; + + if (this.config.setOperators.length > 0) { + this.config.setOperators.at(-1)!.orderBy = orderByArray; + } else { + this.config.orderBy = orderByArray; + } + } else { + const orderByArray = columns as (SingleStoreColumn | SQL | SQL.Aliased)[]; + + if (this.config.setOperators.length > 0) { + this.config.setOperators.at(-1)!.orderBy = orderByArray; + } else { + this.config.orderBy = orderByArray; + } + } + return this as any; + } + + /** + * Adds a `limit` clause to the query. + * + * Calling this method will set the maximum number of rows that will be returned by this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param limit the `limit` clause. + * + * @example + * + * ```ts + * // Get the first 10 people from this query. + * await db.select().from(people).limit(10); + * ``` + */ + limit(limit: number): SingleStoreSelectWithout { + if (this.config.setOperators.length > 0) { + this.config.setOperators.at(-1)!.limit = limit; + } else { + this.config.limit = limit; + } + return this as any; + } + + /** + * Adds an `offset` clause to the query. + * + * Calling this method will skip a number of rows when returning results from this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param offset the `offset` clause. + * + * @example + * + * ```ts + * // Get the 10th-20th people from this query. + * await db.select().from(people).offset(10).limit(10); + * ``` + */ + offset(offset: number): SingleStoreSelectWithout { + if (this.config.setOperators.length > 0) { + this.config.setOperators.at(-1)!.offset = offset; + } else { + this.config.offset = offset; + } + return this as any; + } + + /** + * Adds a `for` clause to the query. + * + * Calling this method will specify a lock strength for this query that controls how strictly it acquires exclusive access to the rows being queried. + * + * @param strength the lock strength. + * @param config the lock configuration. + */ + for(strength: LockStrength, config: LockConfig = {}): SingleStoreSelectWithout { + this.config.lockingClause = { strength, config }; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildSelectQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + as( + alias: TAlias, + ): SubqueryWithSelection { + return new Proxy( + new Subquery(this.getSQL(), this.config.fields, alias), + new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }), + ) as SubqueryWithSelection; + } + + /** @internal */ + override getSelectedFields(): this['_']['selectedFields'] { + return new Proxy( + this.config.fields, + new SelectionProxyHandler({ alias: this.tableName, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }), + ) as this['_']['selectedFields']; + } + + $dynamic(): SingleStoreSelectDynamic { + return this as any; + } +} + +export interface SingleStoreSelectBase< + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, +> extends + SingleStoreSelectQueryBuilderBase< + SingleStoreSelectHKT, + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields + >, + QueryPromise +{} + +export class SingleStoreSelectBase< + TTableName extends string | undefined, + TSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult = SelectResult[], + TSelectedFields = BuildSubquerySelection, +> extends SingleStoreSelectQueryBuilderBase< + SingleStoreSelectHKT, + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields +> { + static override readonly [entityKind]: string = 'SingleStoreSelect'; + + prepare(): SingleStoreSelectPrepare { + if (!this.session) { + throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); + } + const fieldsList = orderSelectedFields(this.config.fields); + const query = this.session.prepareQuery< + SingleStorePreparedQueryConfig & { execute: SelectResult[] }, + TPreparedQueryHKT + >(this.dialect.sqlToQuery(this.getSQL()), fieldsList); + query.joinsNotNullableMap = this.joinsNotNullableMap; + return query as SingleStoreSelectPrepare; + } + + execute = ((placeholderValues) => { + return this.prepare().execute(placeholderValues); + }) as ReturnType['execute']; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); +} + +applyMixins(SingleStoreSelectBase, [QueryPromise]); + +function createSetOperator(type: SetOperator, isAll: boolean): SingleStoreCreateSetOperatorFn { + return (leftSelect, rightSelect, ...restSelects) => { + const setOperators = [rightSelect, ...restSelects].map((select) => ({ + type, + isAll, + rightSelect: select as AnySingleStoreSelect, + })); + + for (const setOperator of setOperators) { + if (!haveSameKeys((leftSelect as any).getSelectedFields(), setOperator.rightSelect.getSelectedFields())) { + throw new Error( + 'Set operator error (union / intersect / except): selected fields are not the same or are in a different order', + ); + } + } + + return (leftSelect as AnySingleStoreSelect).addSetOperators(setOperators) as any; + }; +} + +const getSingleStoreSetOperators = () => ({ + union, + unionAll, + intersect, + except, + minus, +}); + +/** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * import { union } from 'drizzle-orm/singlestore-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ +export const union = createSetOperator('union', false); + +/** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * import { unionAll } from 'drizzle-orm/singlestore-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ +export const unionAll = createSetOperator('union', true); + +/** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * import { intersect } from 'drizzle-orm/singlestore-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ +export const intersect = createSetOperator('intersect', false); + +/** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * import { except } from 'drizzle-orm/singlestore-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ +export const except = createSetOperator('except', false); + +/** + * Adds `minus` set operator to the query. + * + * This is an alias of `except` supported by SingleStore. + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * import { minus } from 'drizzle-orm/singlestore-core' + * + * await minus( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .minus( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ +export const minus = createSetOperator('except', true); diff --git a/drizzle-orm/src/singlestore-core/query-builders/select.types.ts b/drizzle-orm/src/singlestore-core/query-builders/select.types.ts new file mode 100644 index 000000000..75bc8d783 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/select.types.ts @@ -0,0 +1,457 @@ +import type { + SelectedFields as SelectedFieldsBase, + SelectedFieldsFlat as SelectedFieldsFlatBase, + SelectedFieldsOrdered as SelectedFieldsOrderedBase, +} from '~/operations.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import type { + AppendToNullabilityMap, + AppendToResult, + BuildSubquerySelection, + GetSelectTableName, + JoinNullability, + JoinType, + MapColumnsToTableAlias, + SelectMode, + SelectResult, + SetOperator, +} from '~/query-builders/select.types.ts'; +import type { SingleStoreColumn } from '~/singlestore-core/columns/index.ts'; +import type { SingleStoreTable, SingleStoreTableWithColumns } from '~/singlestore-core/table.ts'; +import type { ColumnsSelection, Placeholder, SQL, View } from '~/sql/sql.ts'; +import type { Subquery } from '~/subquery.ts'; +import type { Table, UpdateTableConfig } from '~/table.ts'; +import type { Assume, ValidateShape } from '~/utils.ts'; +import type { PreparedQueryHKTBase, PreparedQueryKind, SingleStorePreparedQueryConfig } from '../session.ts'; +/* import type { SingleStoreViewBase } from '../view-base.ts'; */ +/* import type { SingleStoreViewWithSelection } from '../view.ts'; */ +import type { SingleStoreSelectBase, SingleStoreSelectQueryBuilderBase } from './select.ts'; + +export interface SingleStoreSelectJoinConfig { + on: SQL | undefined; + table: SingleStoreTable | Subquery | SQL; // SingleStoreViewBase | + alias: string | undefined; + joinType: JoinType; + lateral?: boolean; +} + +export type BuildAliasTable = TTable extends Table + ? SingleStoreTableWithColumns< + UpdateTableConfig; + }> + > + /* : TTable extends View ? SingleStoreViewWithSelection< + TAlias, + TTable['_']['existing'], + MapColumnsToTableAlias + > */ + : never; + +export interface SingleStoreSelectConfig { + withList?: Subquery[]; + fields: Record; + fieldsFlat?: SelectedFieldsOrdered; + where?: SQL; + having?: SQL; + table: SingleStoreTable | Subquery | SQL; // | SingleStoreViewBase + limit?: number | Placeholder; + offset?: number | Placeholder; + joins?: SingleStoreSelectJoinConfig[]; + orderBy?: (SingleStoreColumn | SQL | SQL.Aliased)[]; + groupBy?: (SingleStoreColumn | SQL | SQL.Aliased)[]; + lockingClause?: { + strength: LockStrength; + config: LockConfig; + }; + distinct?: boolean; + setOperators: { + rightSelect: TypedQueryBuilder; + type: SetOperator; + isAll: boolean; + orderBy?: (SingleStoreColumn | SQL | SQL.Aliased)[]; + limit?: number | Placeholder; + offset?: number | Placeholder; + }[]; +} + +export type SingleStoreJoin< + T extends AnySingleStoreSelectQueryBuilder, + TDynamic extends boolean, + TJoinType extends JoinType, + TJoinedTable extends SingleStoreTable | Subquery | SQL, // | SingleStoreViewBase + TJoinedName extends GetSelectTableName = GetSelectTableName, +> = T extends any ? SingleStoreSelectWithout< + SingleStoreSelectKind< + T['_']['hkt'], + T['_']['tableName'], + AppendToResult< + T['_']['tableName'], + T['_']['selection'], + TJoinedName, + TJoinedTable extends SingleStoreTable ? TJoinedTable['_']['columns'] + : TJoinedTable extends Subquery ? Assume + : never, + T['_']['selectMode'] + >, + T['_']['selectMode'] extends 'partial' ? T['_']['selectMode'] : 'multiple', + T['_']['preparedQueryHKT'], + AppendToNullabilityMap, + TDynamic, + T['_']['excludedMethods'] + >, + TDynamic, + T['_']['excludedMethods'] + > + : never; + +export type SingleStoreJoinFn< + T extends AnySingleStoreSelectQueryBuilder, + TDynamic extends boolean, + TJoinType extends JoinType, +> = < + TJoinedTable extends SingleStoreTable | Subquery | SQL, // | SingleStoreViewBase + TJoinedName extends GetSelectTableName = GetSelectTableName, +>( + table: TJoinedTable, + on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, +) => SingleStoreJoin; + +export type SelectedFieldsFlat = SelectedFieldsFlatBase; + +export type SelectedFields = SelectedFieldsBase; + +export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; + +export type LockStrength = 'update' | 'share'; + +export type LockConfig = { + noWait: true; + skipLocked?: undefined; +} | { + noWait?: undefined; + skipLocked: true; +} | { + noWait?: undefined; + skipLocked?: undefined; +}; + +export interface SingleStoreSelectHKTBase { + tableName: string | undefined; + selection: unknown; + selectMode: SelectMode; + preparedQueryHKT: unknown; + nullabilityMap: unknown; + dynamic: boolean; + excludedMethods: string; + result: unknown; + selectedFields: unknown; + _type: unknown; +} + +export type SingleStoreSelectKind< + T extends SingleStoreSelectHKTBase, + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TNullabilityMap extends Record, + TDynamic extends boolean, + TExcludedMethods extends string, + TResult = SelectResult[], + TSelectedFields = BuildSubquerySelection, +> = (T & { + tableName: TTableName; + selection: TSelection; + selectMode: TSelectMode; + preparedQueryHKT: TPreparedQueryHKT; + nullabilityMap: TNullabilityMap; + dynamic: TDynamic; + excludedMethods: TExcludedMethods; + result: TResult; + selectedFields: TSelectedFields; +})['_type']; + +export interface SingleStoreSelectQueryBuilderHKT extends SingleStoreSelectHKTBase { + _type: SingleStoreSelectQueryBuilderBase< + SingleStoreSelectQueryBuilderHKT, + this['tableName'], + Assume, + this['selectMode'], + Assume, + Assume>, + this['dynamic'], + this['excludedMethods'], + Assume, + Assume + >; +} + +export interface SingleStoreSelectHKT extends SingleStoreSelectHKTBase { + _type: SingleStoreSelectBase< + this['tableName'], + Assume, + this['selectMode'], + Assume, + Assume>, + this['dynamic'], + this['excludedMethods'], + Assume, + Assume + >; +} + +export type SingleStoreSetOperatorExcludedMethods = + | 'where' + | 'having' + | 'groupBy' + | 'session' + | 'leftJoin' + | 'rightJoin' + | 'innerJoin' + | 'fullJoin' + | 'for'; + +export type SingleStoreSelectWithout< + T extends AnySingleStoreSelectQueryBuilder, + TDynamic extends boolean, + K extends keyof T & string, + TResetExcluded extends boolean = false, +> = TDynamic extends true ? T : Omit< + SingleStoreSelectKind< + T['_']['hkt'], + T['_']['tableName'], + T['_']['selection'], + T['_']['selectMode'], + T['_']['preparedQueryHKT'], + T['_']['nullabilityMap'], + TDynamic, + TResetExcluded extends true ? K : T['_']['excludedMethods'] | K, + T['_']['result'], + T['_']['selectedFields'] + >, + TResetExcluded extends true ? K : T['_']['excludedMethods'] | K +>; + +export type SingleStoreSelectPrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: T['_']['result']; + iterator: T['_']['result'][number]; + }, + true +>; + +export type SingleStoreSelectDynamic = SingleStoreSelectKind< + T['_']['hkt'], + T['_']['tableName'], + T['_']['selection'], + T['_']['selectMode'], + T['_']['preparedQueryHKT'], + T['_']['nullabilityMap'], + true, + never, + T['_']['result'], + T['_']['selectedFields'] +>; + +export type CreateSingleStoreSelectFromBuilderMode< + TBuilderMode extends 'db' | 'qb', + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, +> = TBuilderMode extends 'db' ? SingleStoreSelectBase + : SingleStoreSelectQueryBuilderBase< + SingleStoreSelectQueryBuilderHKT, + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT + >; + +export type SingleStoreSelectQueryBuilder< + THKT extends SingleStoreSelectHKTBase = SingleStoreSelectQueryBuilderHKT, + TTableName extends string | undefined = string | undefined, + TSelection extends ColumnsSelection = ColumnsSelection, + TSelectMode extends SelectMode = SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TNullabilityMap extends Record = Record, + TResult extends any[] = unknown[], + TSelectedFields extends ColumnsSelection = ColumnsSelection, +> = SingleStoreSelectQueryBuilderBase< + THKT, + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + true, + never, + TResult, + TSelectedFields +>; + +export type AnySingleStoreSelectQueryBuilder = SingleStoreSelectQueryBuilderBase< + any, + any, + any, + any, + any, + any, + any, + any, + any +>; + +export type AnySingleStoreSetOperatorInterface = SingleStoreSetOperatorInterface< + any, + any, + any, + any, + any, + any, + any, + any, + any +>; + +export interface SingleStoreSetOperatorInterface< + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, +> { + _: { + readonly hkt: SingleStoreSelectHKT; + readonly tableName: TTableName; + readonly selection: TSelection; + readonly selectMode: TSelectMode; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly nullabilityMap: TNullabilityMap; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + readonly result: TResult; + readonly selectedFields: TSelectedFields; + }; +} + +export type SingleStoreSetOperatorWithResult = SingleStoreSetOperatorInterface< + any, + any, + any, + any, + any, + any, + any, + TResult, + any +>; + +export type SingleStoreSelect< + TTableName extends string | undefined = string | undefined, + TSelection extends ColumnsSelection = Record, + TSelectMode extends SelectMode = SelectMode, + TNullabilityMap extends Record = Record, +> = SingleStoreSelectBase; + +export type AnySingleStoreSelect = SingleStoreSelectBase; + +export type SingleStoreSetOperator< + TTableName extends string | undefined = string | undefined, + TSelection extends ColumnsSelection = Record, + TSelectMode extends SelectMode = SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TNullabilityMap extends Record = Record, +> = SingleStoreSelectBase< + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + true, + SingleStoreSetOperatorExcludedMethods +>; + +export type SetOperatorRightSelect< + TValue extends SingleStoreSetOperatorWithResult, + TResult extends any[], +> = TValue extends SingleStoreSetOperatorInterface + ? ValidateShape< + TValueResult[number], + TResult[number], + TypedQueryBuilder + > + : TValue; + +export type SetOperatorRestSelect< + TValue extends readonly SingleStoreSetOperatorWithResult[], + TResult extends any[], +> = TValue extends [infer First, ...infer Rest] + ? First extends SingleStoreSetOperatorInterface + ? Rest extends AnySingleStoreSetOperatorInterface[] ? [ + ValidateShape>, + ...SetOperatorRestSelect, + ] + : ValidateShape[]> + : never + : TValue; + +export type SingleStoreCreateSetOperatorFn = < + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TValue extends SingleStoreSetOperatorWithResult, + TRest extends SingleStoreSetOperatorWithResult[], + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, +>( + leftSelect: SingleStoreSetOperatorInterface< + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields + >, + rightSelect: SetOperatorRightSelect, + ...restSelects: SetOperatorRestSelect +) => SingleStoreSelectWithout< + SingleStoreSelectBase< + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields + >, + false, + SingleStoreSetOperatorExcludedMethods, + true +>; + +export type GetSingleStoreSetOperators = { + union: SingleStoreCreateSetOperatorFn; + intersect: SingleStoreCreateSetOperatorFn; + except: SingleStoreCreateSetOperatorFn; + unionAll: SingleStoreCreateSetOperatorFn; + minus: SingleStoreCreateSetOperatorFn; +}; diff --git a/drizzle-orm/src/singlestore-core/query-builders/update.ts b/drizzle-orm/src/singlestore-core/query-builders/update.ts new file mode 100644 index 000000000..6a843373c --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/update.ts @@ -0,0 +1,252 @@ +import type { GetColumnData } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import type { Subquery } from '~/subquery.ts'; +import { Table } from '~/table.ts'; +import { mapUpdateSet, type UpdateSet, type ValueOrArray } from '~/utils.ts'; +import type { SingleStoreColumn } from '../columns/common.ts'; +import type { SelectedFieldsOrdered } from './select.types.ts'; + +export interface SingleStoreUpdateConfig { + where?: SQL | undefined; + limit?: number | Placeholder; + orderBy?: (SingleStoreColumn | SQL | SQL.Aliased)[]; + set: UpdateSet; + table: SingleStoreTable; + returning?: SelectedFieldsOrdered; + withList?: Subquery[]; +} + +export type SingleStoreUpdateSetSource = + & { + [Key in keyof TTable['$inferInsert']]?: + | GetColumnData + | SQL + | undefined; + } + & {}; + +export class SingleStoreUpdateBuilder< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, +> { + static readonly [entityKind]: string = 'SingleStoreUpdateBuilder'; + + declare readonly _: { + readonly table: TTable; + }; + + constructor( + private table: TTable, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + private withList?: Subquery[], + ) {} + + set(values: SingleStoreUpdateSetSource): SingleStoreUpdateBase { + return new SingleStoreUpdateBase( + this.table, + mapUpdateSet(this.table, values), + this.session, + this.dialect, + this.withList, + ); + } +} + +export type SingleStoreUpdateWithout< + T extends AnySingleStoreUpdateBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T : Omit< + SingleStoreUpdateBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K +>; + +export type SingleStoreUpdatePrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +export type SingleStoreUpdateDynamic = SingleStoreUpdate< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +export type SingleStoreUpdate< + TTable extends SingleStoreTable = SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreUpdateBase; + +export type AnySingleStoreUpdateBase = SingleStoreUpdateBase; + +export interface SingleStoreUpdateBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise>, SQLWrapper { + readonly _: { + readonly table: TTable; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreUpdateBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static override readonly [entityKind]: string = 'SingleStoreUpdate'; + + private config: SingleStoreUpdateConfig; + + constructor( + table: TTable, + set: UpdateSet, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + withList?: Subquery[], + ) { + super(); + this.config = { set, table, withList }; + } + + /** + * Adds a 'where' clause to the query. + * + * Calling this method will update only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param where the 'where' clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be updated. + * + * ```ts + * // Update all cars with green color + * db.update(cars).set({ color: 'red' }) + * .where(eq(cars.color, 'green')); + * // or + * db.update(cars).set({ color: 'red' }) + * .where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Update all BMW cars with a green color + * db.update(cars).set({ color: 'red' }) + * .where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Update all cars with the green or blue color + * db.update(cars).set({ color: 'red' }) + * .where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + where(where: SQL | undefined): SingleStoreUpdateWithout { + this.config.where = where; + return this as any; + } + + orderBy( + builder: (updateTable: TTable) => ValueOrArray, + ): SingleStoreUpdateWithout; + orderBy(...columns: (SingleStoreColumn | SQL | SQL.Aliased)[]): SingleStoreUpdateWithout; + orderBy( + ...columns: + | [(updateTable: TTable) => ValueOrArray] + | (SingleStoreColumn | SQL | SQL.Aliased)[] + ): SingleStoreUpdateWithout { + if (typeof columns[0] === 'function') { + const orderBy = columns[0]( + new Proxy( + this.config.table[Table.Symbol.Columns], + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as any, + ); + + const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; + this.config.orderBy = orderByArray; + } else { + const orderByArray = columns as (SingleStoreColumn | SQL | SQL.Aliased)[]; + this.config.orderBy = orderByArray; + } + return this as any; + } + + limit(limit: number | Placeholder): SingleStoreUpdateWithout { + this.config.limit = limit; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildUpdateQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreUpdatePrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + this.config.returning, + ) as SingleStoreUpdatePrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreUpdateDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/schema.ts b/drizzle-orm/src/singlestore-core/schema.ts new file mode 100644 index 000000000..ea7a53924 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/schema.ts @@ -0,0 +1,40 @@ +import { entityKind, is } from '~/entity.ts'; +import { type SingleStoreTableFn, singlestoreTableWithSchema } from './table.ts'; +/* import { type singlestoreView, singlestoreViewWithSchema } from './view.ts'; */ + +export class SingleStoreSchema { + static readonly [entityKind]: string = 'SingleStoreSchema'; + + constructor( + public readonly schemaName: TName, + ) {} + + table: SingleStoreTableFn = (name, columns, extraConfig) => { + return singlestoreTableWithSchema(name, columns, extraConfig, this.schemaName); + }; + /* + view = ((name, columns) => { + return singlestoreViewWithSchema(name, columns, this.schemaName); + }) as typeof singlestoreView; */ +} + +/** @deprecated - use `instanceof SingleStoreSchema` */ +export function isSingleStoreSchema(obj: unknown): obj is SingleStoreSchema { + return is(obj, SingleStoreSchema); +} + +/** + * Create a SingleStore schema. + * https://docs.singlestore.com/cloud/create-a-database/ + * + * @param name singlestore use schema name + * @returns SingleStore schema + */ +export function singlestoreDatabase(name: TName) { + return new SingleStoreSchema(name); +} + +/** + * @see singlestoreDatabase + */ +export const singlestoreSchema = singlestoreDatabase; diff --git a/drizzle-orm/src/singlestore-core/session.ts b/drizzle-orm/src/singlestore-core/session.ts new file mode 100644 index 000000000..bc31f3d97 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/session.ts @@ -0,0 +1,157 @@ +import { entityKind } from '~/entity.ts'; +import { TransactionRollbackError } from '~/errors.ts'; +import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import { type Query, type SQL, sql } from '~/sql/sql.ts'; +import type { Assume, Equal } from '~/utils.ts'; +import { SingleStoreDatabase } from './db.ts'; +import type { SingleStoreDialect } from './dialect.ts'; +import type { SelectedFieldsOrdered } from './query-builders/select.types.ts'; + +export interface SingleStoreQueryResultHKT { + readonly $brand: 'SingleStoreQueryResultHKT'; + readonly row: unknown; + readonly type: unknown; +} + +export interface AnySingleStoreQueryResultHKT extends SingleStoreQueryResultHKT { + readonly type: any; +} + +export type SingleStoreQueryResultKind = (TKind & { + readonly row: TRow; +})['type']; + +export interface SingleStorePreparedQueryConfig { + execute: unknown; + iterator: unknown; +} + +export interface SingleStorePreparedQueryHKT { + readonly $brand: 'SingleStorePreparedQueryHKT'; + readonly config: unknown; + readonly type: unknown; +} + +export type PreparedQueryKind< + TKind extends SingleStorePreparedQueryHKT, + TConfig extends SingleStorePreparedQueryConfig, + TAssume extends boolean = false, +> = Equal extends true + ? Assume<(TKind & { readonly config: TConfig })['type'], SingleStorePreparedQuery> + : (TKind & { readonly config: TConfig })['type']; + +export abstract class SingleStorePreparedQuery { + static readonly [entityKind]: string = 'SingleStorePreparedQuery'; + + /** @internal */ + joinsNotNullableMap?: Record; + + abstract execute(placeholderValues?: Record): Promise; + + abstract iterator(placeholderValues?: Record): AsyncGenerator; +} + +export interface SingleStoreTransactionConfig { + withConsistentSnapshot?: boolean; + accessMode?: 'read only' | 'read write'; + isolationLevel: 'read committed'; // SingleStore only supports read committed isolation level (https://docs.singlestore.com/db/v8.7/introduction/faqs/durability/) +} + +export abstract class SingleStoreSession< + TQueryResult extends SingleStoreQueryResultHKT = SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TFullSchema extends Record = Record, + TSchema extends TablesRelationalConfig = Record, +> { + static readonly [entityKind]: string = 'SingleStoreSession'; + + constructor(protected dialect: SingleStoreDialect) {} + + abstract prepareQuery< + T extends SingleStorePreparedQueryConfig, + TPreparedQueryHKT extends SingleStorePreparedQueryHKT, + >( + query: Query, + fields: SelectedFieldsOrdered | undefined, + customResultMapper?: (rows: unknown[][]) => T['execute'], + generatedIds?: Record[], + returningIds?: SelectedFieldsOrdered, + ): PreparedQueryKind; + + execute(query: SQL): Promise { + return this.prepareQuery( + this.dialect.sqlToQuery(query), + undefined, + ).execute(); + } + + abstract all(query: SQL): Promise; + + async count(sql: SQL): Promise { + const res = await this.execute<[[{ count: string }]]>(sql); + + return Number( + res[0][0]['count'], + ); + } + + abstract transaction( + transaction: (tx: SingleStoreTransaction) => Promise, + config?: SingleStoreTransactionConfig, + ): Promise; + + protected getSetTransactionSQL(config: SingleStoreTransactionConfig): SQL | undefined { + const parts: string[] = []; + + if (config.isolationLevel) { + parts.push(`isolation level ${config.isolationLevel}`); + } + + return parts.length ? sql`set transaction ${sql.raw(parts.join(' '))}` : undefined; + } + + protected getStartTransactionSQL(config: SingleStoreTransactionConfig): SQL | undefined { + const parts: string[] = []; + + if (config.withConsistentSnapshot) { + parts.push('with consistent snapshot'); + } + + if (config.accessMode) { + parts.push(config.accessMode); + } + + return parts.length ? sql`start transaction ${sql.raw(parts.join(' '))}` : undefined; + } +} + +export abstract class SingleStoreTransaction< + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TFullSchema extends Record = Record, + TSchema extends TablesRelationalConfig = Record, +> extends SingleStoreDatabase { + static override readonly [entityKind]: string = 'SingleStoreTransaction'; + + constructor( + dialect: SingleStoreDialect, + session: SingleStoreSession, + protected schema: RelationalSchemaConfig | undefined, + protected readonly nestedIndex: number, + ) { + super(dialect, session, schema); + } + + rollback(): never { + throw new TransactionRollbackError(); + } + + /** Nested transactions (aka savepoints) only work with InnoDB engine. */ + abstract override transaction( + transaction: (tx: SingleStoreTransaction) => Promise, + ): Promise; +} + +export interface PreparedQueryHKTBase extends SingleStorePreparedQueryHKT { + type: SingleStorePreparedQuery>; +} diff --git a/drizzle-orm/src/singlestore-core/subquery.ts b/drizzle-orm/src/singlestore-core/subquery.ts new file mode 100644 index 000000000..a4605c56d --- /dev/null +++ b/drizzle-orm/src/singlestore-core/subquery.ts @@ -0,0 +1,17 @@ +import type { AddAliasToSelection } from '~/query-builders/select.types.ts'; +import type { ColumnsSelection } from '~/sql/sql.ts'; +import type { Subquery, WithSubquery } from '~/subquery.ts'; + +export type SubqueryWithSelection< + TSelection extends ColumnsSelection, + TAlias extends string, +> = + & Subquery> + & AddAliasToSelection; + +export type WithSubqueryWithSelection< + TSelection extends ColumnsSelection, + TAlias extends string, +> = + & WithSubquery> + & AddAliasToSelection; diff --git a/drizzle-orm/src/singlestore-core/table.ts b/drizzle-orm/src/singlestore-core/table.ts new file mode 100644 index 000000000..ffad22d74 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/table.ts @@ -0,0 +1,217 @@ +import type { BuildColumns, BuildExtraConfigColumns } from '~/column-builder.ts'; +import { entityKind } from '~/entity.ts'; +import { Table, type TableConfig as TableConfigBase, type UpdateTableConfig } from '~/table.ts'; +import { getSingleStoreColumnBuilders, type SingleStoreColumnBuilders } from './columns/all.ts'; +import type { SingleStoreColumn, SingleStoreColumnBuilder, SingleStoreColumnBuilderBase } from './columns/common.ts'; +import type { AnyIndexBuilder } from './indexes.ts'; +import type { PrimaryKeyBuilder } from './primary-keys.ts'; +import type { UniqueConstraintBuilder } from './unique-constraint.ts'; + +export type SingleStoreTableExtraConfigValue = + | AnyIndexBuilder + | PrimaryKeyBuilder + | UniqueConstraintBuilder; + +export type SingleStoreTableExtraConfig = Record< + string, + SingleStoreTableExtraConfigValue +>; + +export type TableConfig = TableConfigBase; + +export class SingleStoreTable extends Table { + static override readonly [entityKind]: string = 'SingleStoreTable'; + + declare protected $columns: T['columns']; + + /** @internal */ + static override readonly Symbol = Object.assign({}, Table.Symbol, {}); + + /** @internal */ + override [Table.Symbol.Columns]!: NonNullable; + + /** @internal */ + override [Table.Symbol.ExtraConfigBuilder]: + | ((self: Record) => SingleStoreTableExtraConfig) + | undefined = undefined; +} + +export type AnySingleStoreTable = {}> = SingleStoreTable< + UpdateTableConfig +>; + +export type SingleStoreTableWithColumns = + & SingleStoreTable + & { + [Key in keyof T['columns']]: T['columns'][Key]; + }; + +export function singlestoreTableWithSchema< + TTableName extends string, + TSchemaName extends string | undefined, + TColumnsMap extends Record, +>( + name: TTableName, + columns: TColumnsMap | ((columnTypes: SingleStoreColumnBuilders) => TColumnsMap), + extraConfig: + | (( + self: BuildColumns, + ) => SingleStoreTableExtraConfig | SingleStoreTableExtraConfigValue[]) + | undefined, + schema: TSchemaName, + baseName = name, +): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; +}> { + const rawTable = new SingleStoreTable<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>(name, schema, baseName); + + const parsedColumns: TColumnsMap = typeof columns === 'function' ? columns(getSingleStoreColumnBuilders()) : columns; + + const builtColumns = Object.fromEntries( + Object.entries(parsedColumns).map(([name, colBuilderBase]) => { + const colBuilder = colBuilderBase as SingleStoreColumnBuilder; + colBuilder.setName(name); + const column = colBuilder.build(rawTable); + return [name, column]; + }), + ) as unknown as BuildColumns; + + const table = Object.assign(rawTable, builtColumns); + + table[Table.Symbol.Columns] = builtColumns; + table[Table.Symbol.ExtraConfigColumns] = builtColumns as unknown as BuildExtraConfigColumns< + TTableName, + TColumnsMap, + 'singlestore' + >; + + if (extraConfig) { + table[SingleStoreTable.Symbol.ExtraConfigBuilder] = extraConfig as unknown as ( + self: Record, + ) => SingleStoreTableExtraConfig; + } + + return table; +} + +export interface SingleStoreTableFn { + /** + * @deprecated The third parameter of singlestoreTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = singlestoreTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = singlestoreTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig?: (self: BuildColumns) => SingleStoreTableExtraConfig, + ): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>; + + /** + * @deprecated The third parameter of singlestoreTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = singlestoreTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = singlestoreTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: SingleStoreColumnBuilders) => TColumnsMap, + extraConfig?: (self: BuildColumns) => SingleStoreTableExtraConfig, + ): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig?: ( + self: BuildColumns, + ) => SingleStoreTableExtraConfigValue[], + ): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: SingleStoreColumnBuilders) => TColumnsMap, + extraConfig?: (self: BuildColumns) => SingleStoreTableExtraConfigValue[], + ): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>; +} + +export const singlestoreTable: SingleStoreTableFn = (name, columns, extraConfig) => { + return singlestoreTableWithSchema(name, columns, extraConfig, undefined, name); +}; + +export function singlestoreTableCreator(customizeTableName: (name: string) => string): SingleStoreTableFn { + return (name, columns, extraConfig) => { + return singlestoreTableWithSchema(customizeTableName(name) as typeof name, columns, extraConfig, undefined, name); + }; +} diff --git a/drizzle-orm/src/singlestore-core/unique-constraint.ts b/drizzle-orm/src/singlestore-core/unique-constraint.ts new file mode 100644 index 000000000..511e466dc --- /dev/null +++ b/drizzle-orm/src/singlestore-core/unique-constraint.ts @@ -0,0 +1,65 @@ +import { entityKind } from '~/entity.ts'; +import { TableName } from '~/table.utils.ts'; +import type { SingleStoreColumn } from './columns/index.ts'; +import type { SingleStoreTable } from './table.ts'; + +export function unique(name?: string): UniqueOnConstraintBuilder { + return new UniqueOnConstraintBuilder(name); +} + +export function uniqueKeyName(table: SingleStoreTable, columns: string[]) { + return `${table[TableName]}_${columns.join('_')}_unique`; +} + +export class UniqueConstraintBuilder { + static readonly [entityKind]: string = 'SingleStoreUniqueConstraintBuilder'; + + /** @internal */ + columns: SingleStoreColumn[]; + + constructor( + columns: SingleStoreColumn[], + private name?: string, + ) { + this.columns = columns; + } + + /** @internal */ + build(table: SingleStoreTable): UniqueConstraint { + return new UniqueConstraint(table, this.columns, this.name); + } +} + +export class UniqueOnConstraintBuilder { + static readonly [entityKind]: string = 'SingleStoreUniqueOnConstraintBuilder'; + + /** @internal */ + name?: string; + + constructor( + name?: string, + ) { + this.name = name; + } + + on(...columns: [SingleStoreColumn, ...SingleStoreColumn[]]) { + return new UniqueConstraintBuilder(columns, this.name); + } +} + +export class UniqueConstraint { + static readonly [entityKind]: string = 'SingleStoreUniqueConstraint'; + + readonly columns: SingleStoreColumn[]; + readonly name?: string; + readonly nullsNotDistinct: boolean = false; + + constructor(readonly table: SingleStoreTable, columns: SingleStoreColumn[], name?: string) { + this.columns = columns; + this.name = name ?? uniqueKeyName(this.table, this.columns.map((column) => column.name)); + } + + getName() { + return this.name; + } +} diff --git a/drizzle-orm/src/singlestore-core/utils.ts b/drizzle-orm/src/singlestore-core/utils.ts new file mode 100644 index 000000000..9ec8b7b0d --- /dev/null +++ b/drizzle-orm/src/singlestore-core/utils.ts @@ -0,0 +1,56 @@ +import { is } from '~/entity.ts'; +import { Table } from '~/table.ts'; +import type { Index } from './indexes.ts'; +import { IndexBuilder } from './indexes.ts'; +import type { PrimaryKey } from './primary-keys.ts'; +import { PrimaryKeyBuilder } from './primary-keys.ts'; +import { SingleStoreTable } from './table.ts'; +import { type UniqueConstraint, UniqueConstraintBuilder } from './unique-constraint.ts'; +/* import { SingleStoreViewConfig } from './view-common.ts'; +import type { SingleStoreView } from './view.ts'; */ + +export function getTableConfig(table: SingleStoreTable) { + const columns = Object.values(table[SingleStoreTable.Symbol.Columns]); + const indexes: Index[] = []; + const primaryKeys: PrimaryKey[] = []; + const uniqueConstraints: UniqueConstraint[] = []; + const name = table[Table.Symbol.Name]; + const schema = table[Table.Symbol.Schema]; + const baseName = table[Table.Symbol.BaseName]; + + const extraConfigBuilder = table[SingleStoreTable.Symbol.ExtraConfigBuilder]; + + if (extraConfigBuilder !== undefined) { + const extraConfig = extraConfigBuilder(table[SingleStoreTable.Symbol.Columns]); + const extraValues = Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); + for (const builder of Object.values(extraValues)) { + if (is(builder, IndexBuilder)) { + indexes.push(builder.build(table)); + } else if (is(builder, UniqueConstraintBuilder)) { + uniqueConstraints.push(builder.build(table)); + } else if (is(builder, PrimaryKeyBuilder)) { + primaryKeys.push(builder.build(table)); + } + } + } + + return { + columns, + indexes, + primaryKeys, + uniqueConstraints, + name, + schema, + baseName, + }; +} + +/* export function getViewConfig< + TName extends string = string, + TExisting extends boolean = boolean, +>(view: SingleStoreView) { + return { + ...view[ViewBaseConfig], + ...view[SingleStoreViewConfig], + }; +} */ diff --git a/drizzle-orm/src/singlestore-core/view-base.ts b/drizzle-orm/src/singlestore-core/view-base.ts new file mode 100644 index 000000000..1ad8d62d5 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/view-base.ts @@ -0,0 +1,15 @@ +import { entityKind } from '~/entity.ts'; +import type { ColumnsSelection } from '~/sql/sql.ts'; +import { View } from '~/sql/sql.ts'; + +export abstract class SingleStoreViewBase< + TName extends string = string, + TExisting extends boolean = boolean, + TSelectedFields extends ColumnsSelection = ColumnsSelection, +> extends View { + static override readonly [entityKind]: string = 'SingleStoreViewBase'; + + declare readonly _: View['_'] & { + readonly viewBrand: 'SingleStoreViewBase'; + }; +} diff --git a/drizzle-orm/src/singlestore-core/view-common.ts b/drizzle-orm/src/singlestore-core/view-common.ts new file mode 100644 index 000000000..d29c3d5ad --- /dev/null +++ b/drizzle-orm/src/singlestore-core/view-common.ts @@ -0,0 +1 @@ +export const SingleStoreViewConfig = Symbol.for('drizzle:SingleStoreViewConfig'); diff --git a/drizzle-orm/src/singlestore-core/view.ts b/drizzle-orm/src/singlestore-core/view.ts new file mode 100644 index 000000000..58f111428 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/view.ts @@ -0,0 +1,209 @@ +import type { BuildColumns } from '~/column-builder.ts'; +import { entityKind } from '~/entity.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import type { AddAliasToSelection } from '~/query-builders/select.types.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { ColumnsSelection, SQL } from '~/sql/sql.ts'; +import { getTableColumns } from '~/utils.ts'; +import type { SingleStoreColumn, SingleStoreColumnBuilderBase } from './columns/index.ts'; +import { QueryBuilder } from './query-builders/query-builder.ts'; +import type { SelectedFields } from './query-builders/select.types.ts'; +import { singlestoreTable } from './table.ts'; +import { SingleStoreViewBase } from './view-base.ts'; +import { SingleStoreViewConfig } from './view-common.ts'; + +export interface ViewBuilderConfig { + algorithm?: 'undefined' | 'merge' | 'temptable'; + definer?: string; + sqlSecurity?: 'definer' | 'invoker'; + withCheckOption?: 'cascaded' | 'local'; +} + +export class ViewBuilderCore { + static readonly [entityKind]: string = 'SingleStoreViewBuilder'; + + declare readonly _: { + readonly name: TConfig['name']; + readonly columns: TConfig['columns']; + }; + + constructor( + protected name: TConfig['name'], + protected schema: string | undefined, + ) {} + + protected config: ViewBuilderConfig = {}; + + algorithm( + algorithm: Exclude, + ): this { + this.config.algorithm = algorithm; + return this; + } + + definer( + definer: Exclude, + ): this { + this.config.definer = definer; + return this; + } + + sqlSecurity( + sqlSecurity: Exclude, + ): this { + this.config.sqlSecurity = sqlSecurity; + return this; + } + + withCheckOption( + withCheckOption?: Exclude, + ): this { + this.config.withCheckOption = withCheckOption ?? 'cascaded'; + return this; + } +} + +export class ViewBuilder extends ViewBuilderCore<{ name: TName }> { + static override readonly [entityKind]: string = 'SingleStoreViewBuilder'; + + as( + qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), + ): SingleStoreViewWithSelection> { + if (typeof qb === 'function') { + qb = qb(new QueryBuilder()); + } + const selectionProxy = new SelectionProxyHandler({ + alias: this.name, + sqlBehavior: 'error', + sqlAliasedBehavior: 'alias', + replaceOriginalName: true, + }); + const aliasedSelection = new Proxy(qb.getSelectedFields(), selectionProxy); + return new Proxy( + new SingleStoreView({ + singlestoreConfig: this.config, + config: { + name: this.name, + schema: this.schema, + selectedFields: aliasedSelection, + query: qb.getSQL().inlineParams(), + }, + }), + selectionProxy as any, + ) as SingleStoreViewWithSelection>; + } +} + +export class ManualViewBuilder< + TName extends string = string, + TColumns extends Record = Record, +> extends ViewBuilderCore<{ name: TName; columns: TColumns }> { + static override readonly [entityKind]: string = 'SingleStoreManualViewBuilder'; + + private columns: Record; + + constructor( + name: TName, + columns: TColumns, + schema: string | undefined, + ) { + super(name, schema); + this.columns = getTableColumns(singlestoreTable(name, columns)) as BuildColumns; + } + + existing(): SingleStoreViewWithSelection> { + return new Proxy( + new SingleStoreView({ + singlestoreConfig: undefined, + config: { + name: this.name, + schema: this.schema, + selectedFields: this.columns, + query: undefined, + }, + }), + new SelectionProxyHandler({ + alias: this.name, + sqlBehavior: 'error', + sqlAliasedBehavior: 'alias', + replaceOriginalName: true, + }), + ) as SingleStoreViewWithSelection>; + } + + as(query: SQL): SingleStoreViewWithSelection> { + return new Proxy( + new SingleStoreView({ + singlestoreConfig: this.config, + config: { + name: this.name, + schema: this.schema, + selectedFields: this.columns, + query: query.inlineParams(), + }, + }), + new SelectionProxyHandler({ + alias: this.name, + sqlBehavior: 'error', + sqlAliasedBehavior: 'alias', + replaceOriginalName: true, + }), + ) as SingleStoreViewWithSelection>; + } +} + +export class SingleStoreView< + TName extends string = string, + TExisting extends boolean = boolean, + TSelectedFields extends ColumnsSelection = ColumnsSelection, +> extends SingleStoreViewBase { + static override readonly [entityKind]: string = 'SingleStoreView'; + + declare protected $SingleStoreViewBrand: 'SingleStoreView'; + + [SingleStoreViewConfig]: ViewBuilderConfig | undefined; + + constructor({ singlestoreConfig, config }: { + singlestoreConfig: ViewBuilderConfig | undefined; + config: { + name: TName; + schema: string | undefined; + selectedFields: SelectedFields; + query: SQL | undefined; + }; + }) { + super(config); + this[SingleStoreViewConfig] = singlestoreConfig; + } +} + +export type SingleStoreViewWithSelection< + TName extends string, + TExisting extends boolean, + TSelectedFields extends ColumnsSelection, +> = SingleStoreView & TSelectedFields; + +// TODO: needs to be implemented differently compared to MySQL. +// /** @internal */ +// export function singlestoreViewWithSchema( +// name: string, +// selection: Record | undefined, +// schema: string | undefined, +// ): ViewBuilder | ManualViewBuilder { +// if (selection) { +// return new ManualViewBuilder(name, selection, schema); +// } +// return new ViewBuilder(name, schema); +// } + +// export function singlestoreView(name: TName): ViewBuilder; +// export function singlestoreView>( +// name: TName, +// columns: TColumns, +// ): ManualViewBuilder; +// export function singlestoreView( +// name: string, +// selection?: Record, +// ): ViewBuilder | ManualViewBuilder { +// return singlestoreViewWithSchema(name, selection, undefined); +// } diff --git a/drizzle-orm/src/singlestore-proxy/driver.ts b/drizzle-orm/src/singlestore-proxy/driver.ts new file mode 100644 index 000000000..ea24ae2d8 --- /dev/null +++ b/drizzle-orm/src/singlestore-proxy/driver.ts @@ -0,0 +1,59 @@ +import { entityKind } from '~/entity.ts'; +import { DefaultLogger } from '~/logger.ts'; +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + type RelationalSchemaConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import { SingleStoreDatabase } from '~/singlestore-core/db.ts'; +import { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { DrizzleConfig } from '~/utils.ts'; +import { + type SingleStoreRemotePreparedQueryHKT, + type SingleStoreRemoteQueryResultHKT, + SingleStoreRemoteSession, +} from './session.ts'; + +export class SingleStoreRemoteDatabase< + TSchema extends Record = Record, +> extends SingleStoreDatabase { + static override readonly [entityKind]: string = 'SingleStoreRemoteDatabase'; +} + +export type RemoteCallback = ( + sql: string, + params: any[], + method: 'all' | 'execute', +) => Promise<{ rows: any[]; insertId?: number; affectedRows?: number }>; + +export function drizzle = Record>( + callback: RemoteCallback, + config: DrizzleConfig = {}, +): SingleStoreRemoteDatabase { + const dialect = new SingleStoreDialect({ casing: config.casing }); + let logger; + if (config.logger === true) { + logger = new DefaultLogger(); + } else if (config.logger !== false) { + logger = config.logger; + } + + let schema: RelationalSchemaConfig | undefined; + if (config.schema) { + const tablesConfig = extractTablesRelationalConfig( + config.schema, + createTableRelationsHelpers, + ); + schema = { + fullSchema: config.schema, + schema: tablesConfig.tables, + tableNamesMap: tablesConfig.tableNamesMap, + }; + } + + const session = new SingleStoreRemoteSession(callback, dialect, schema, { logger }); + return new SingleStoreRemoteDatabase(dialect, session, schema as any) as SingleStoreRemoteDatabase< + TSchema + >; +} diff --git a/drizzle-orm/src/singlestore-proxy/index.ts b/drizzle-orm/src/singlestore-proxy/index.ts new file mode 100644 index 000000000..b1b6a52e7 --- /dev/null +++ b/drizzle-orm/src/singlestore-proxy/index.ts @@ -0,0 +1,2 @@ +export * from './driver.ts'; +export * from './session.ts'; diff --git a/drizzle-orm/src/singlestore-proxy/migrator.ts b/drizzle-orm/src/singlestore-proxy/migrator.ts new file mode 100644 index 000000000..2ed0172fb --- /dev/null +++ b/drizzle-orm/src/singlestore-proxy/migrator.ts @@ -0,0 +1,52 @@ +import type { MigrationConfig } from '~/migrator.ts'; +import { readMigrationFiles } from '~/migrator.ts'; +import { sql } from '~/sql/sql.ts'; +import type { SingleStoreRemoteDatabase } from './driver.ts'; + +export type ProxyMigrator = (migrationQueries: string[]) => Promise; + +export async function migrate>( + db: SingleStoreRemoteDatabase, + callback: ProxyMigrator, + config: MigrationConfig, +) { + const migrations = readMigrationFiles(config); + + const migrationsTable = config.migrationsTable ?? '__drizzle_migrations'; + const migrationTableCreate = sql` + create table if not exists ${sql.identifier(migrationsTable)} ( + id serial primary key, + hash text not null, + created_at bigint + ) + `; + await db.execute(migrationTableCreate); + + const dbMigrations = await db.select({ + id: sql.raw('id'), + hash: sql.raw('hash'), + created_at: sql.raw('created_at'), + }).from(sql.identifier(migrationsTable).getSQL()).orderBy( + sql.raw('created_at desc'), + ).limit(1); + + const lastDbMigration = dbMigrations[0]; + + const queriesToRun: string[] = []; + + for (const migration of migrations) { + if ( + !lastDbMigration + || Number(lastDbMigration.created_at) < migration.folderMillis + ) { + queriesToRun.push( + ...migration.sql, + `insert into ${ + sql.identifier(migrationsTable).value + } (\`hash\`, \`created_at\`) values('${migration.hash}', '${migration.folderMillis}')`, + ); + } + } + + await callback(queriesToRun); +} diff --git a/drizzle-orm/src/singlestore-proxy/session.ts b/drizzle-orm/src/singlestore-proxy/session.ts new file mode 100644 index 000000000..42cc8ecde --- /dev/null +++ b/drizzle-orm/src/singlestore-proxy/session.ts @@ -0,0 +1,178 @@ +import type { FieldPacket, ResultSetHeader } from 'mysql2/promise'; +import { Column } from '~/column.ts'; +import { entityKind, is } from '~/entity.ts'; +import type { Logger } from '~/logger.ts'; +import { NoopLogger } from '~/logger.ts'; +import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import { SingleStoreTransaction } from '~/singlestore-core/index.ts'; +import type { SelectedFieldsOrdered } from '~/singlestore-core/query-builders/select.types.ts'; +import type { + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStorePreparedQueryHKT, + SingleStoreQueryResultHKT, + SingleStoreTransactionConfig, +} from '~/singlestore-core/session.ts'; +import { SingleStorePreparedQuery as PreparedQueryBase, SingleStoreSession } from '~/singlestore-core/session.ts'; +import type { Query, SQL } from '~/sql/sql.ts'; +import { fillPlaceholders } from '~/sql/sql.ts'; +import { type Assume, mapResultRow } from '~/utils.ts'; +import type { RemoteCallback } from './driver.ts'; + +export type SingleStoreRawQueryResult = [ResultSetHeader, FieldPacket[]]; + +export interface SingleStoreRemoteSessionOptions { + logger?: Logger; +} + +export class SingleStoreRemoteSession< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SingleStoreSession { + static override readonly [entityKind]: string = 'SingleStoreRemoteSession'; + + private logger: Logger; + + constructor( + private client: RemoteCallback, + dialect: SingleStoreDialect, + private schema: RelationalSchemaConfig | undefined, + options: SingleStoreRemoteSessionOptions, + ) { + super(dialect); + this.logger = options.logger ?? new NoopLogger(); + } + + prepareQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + customResultMapper?: (rows: unknown[][]) => T['execute'], + generatedIds?: Record[], + returningIds?: SelectedFieldsOrdered, + ): PreparedQueryKind { + return new PreparedQuery( + this.client, + query.sql, + query.params, + this.logger, + fields, + customResultMapper, + generatedIds, + returningIds, + ) as PreparedQueryKind; + } + + override all(query: SQL): Promise { + const querySql = this.dialect.sqlToQuery(query); + this.logger.logQuery(querySql.sql, querySql.params); + return this.client(querySql.sql, querySql.params, 'all').then(({ rows }) => rows) as Promise; + } + + override async transaction( + _transaction: (tx: SingleStoreProxyTransaction) => Promise, + _config?: SingleStoreTransactionConfig, + ): Promise { + throw new Error('Transactions are not supported by the SingleStore Proxy driver'); + } +} + +export class SingleStoreProxyTransaction< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SingleStoreTransaction< + SingleStoreRemoteQueryResultHKT, + SingleStoreRemotePreparedQueryHKT, + TFullSchema, + TSchema +> { + static override readonly [entityKind]: string = 'SingleStoreProxyTransaction'; + + override async transaction( + _transaction: (tx: SingleStoreProxyTransaction) => Promise, + ): Promise { + throw new Error('Transactions are not supported by the SingleStore Proxy driver'); + } +} + +export class PreparedQuery extends PreparedQueryBase { + static override readonly [entityKind]: string = 'SingleStoreProxyPreparedQuery'; + + constructor( + private client: RemoteCallback, + private queryString: string, + private params: unknown[], + private logger: Logger, + private fields: SelectedFieldsOrdered | undefined, + private customResultMapper?: (rows: unknown[][]) => T['execute'], + // Keys that were used in $default and the value that was generated for them + private generatedIds?: Record[], + // Keys that should be returned, it has the column with all properries + key from object + private returningIds?: SelectedFieldsOrdered, + ) { + super(); + } + + async execute(placeholderValues: Record | undefined = {}): Promise { + const params = fillPlaceholders(this.params, placeholderValues); + + const { fields, client, queryString, logger, joinsNotNullableMap, customResultMapper, returningIds, generatedIds } = + this; + + logger.logQuery(queryString, params); + + if (!fields && !customResultMapper) { + const { rows: data } = await client(queryString, params, 'execute'); + + const insertId = data[0].insertId as number; + const affectedRows = data[0].affectedRows; + + if (returningIds) { + const returningResponse = []; + let j = 0; + for (let i = insertId; i < insertId + affectedRows; i++) { + for (const column of returningIds) { + const key = returningIds[0]!.path[0]!; + if (is(column.field, Column)) { + // @ts-ignore + if (column.field.primary && column.field.autoIncrement) { + returningResponse.push({ [key]: i }); + } + if (column.field.defaultFn && generatedIds) { + // generatedIds[rowIdx][key] + returningResponse.push({ [key]: generatedIds[j]![key] }); + } + } + } + j++; + } + + return returningResponse; + } + + return data; + } + + const { rows } = await client(queryString, params, 'all'); + + if (customResultMapper) { + return customResultMapper(rows); + } + + return rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + } + + override iterator( + _placeholderValues: Record = {}, + ): AsyncGenerator { + throw new Error('Streaming is not supported by the SingleStore Proxy driver'); + } +} + +export interface SingleStoreRemoteQueryResultHKT extends SingleStoreQueryResultHKT { + type: SingleStoreRawQueryResult; +} + +export interface SingleStoreRemotePreparedQueryHKT extends SingleStorePreparedQueryHKT { + type: PreparedQuery>; +} diff --git a/drizzle-orm/src/singlestore/driver.ts b/drizzle-orm/src/singlestore/driver.ts new file mode 100644 index 000000000..ba294f6dc --- /dev/null +++ b/drizzle-orm/src/singlestore/driver.ts @@ -0,0 +1,168 @@ +import { type Connection as CallbackConnection, createPool, type Pool as CallbackPool, type PoolOptions } from 'mysql2'; +import type { Connection, Pool } from 'mysql2/promise'; +import { entityKind } from '~/entity.ts'; +import type { Logger } from '~/logger.ts'; +import { DefaultLogger } from '~/logger.ts'; +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + type RelationalSchemaConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import { SingleStoreDatabase } from '~/singlestore-core/db.ts'; +import { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import { type DrizzleConfig, type IfNotImported, type ImportTypeError, isConfig } from '~/utils.ts'; +import type { + SingleStoreDriverClient, + SingleStoreDriverPreparedQueryHKT, + SingleStoreDriverQueryResultHKT, +} from './session.ts'; +import { SingleStoreDriverSession } from './session.ts'; + +export interface SingleStoreDriverOptions { + logger?: Logger; +} + +export class SingleStoreDriverDriver { + static readonly [entityKind]: string = 'SingleStoreDriverDriver'; + + constructor( + private client: SingleStoreDriverClient, + private dialect: SingleStoreDialect, + private options: SingleStoreDriverOptions = {}, + ) { + } + + createSession( + schema: RelationalSchemaConfig | undefined, + ): SingleStoreDriverSession, TablesRelationalConfig> { + return new SingleStoreDriverSession(this.client, this.dialect, schema, { logger: this.options.logger }); + } +} + +export { SingleStoreDatabase } from '~/singlestore-core/db.ts'; + +export class SingleStoreDriverDatabase< + TSchema extends Record = Record, +> extends SingleStoreDatabase { + static override readonly [entityKind]: string = 'SingleStoreDriverDatabase'; +} + +export type SingleStoreDriverDrizzleConfig = Record> = + & Omit, 'schema'> + & ({ schema: TSchema } | { schema?: undefined }); + +function construct< + TSchema extends Record = Record, + TClient extends Pool | Connection | CallbackPool | CallbackConnection = CallbackPool, +>( + client: TClient, + config: SingleStoreDriverDrizzleConfig = {}, +): SingleStoreDriverDatabase & { + $client: TClient; +} { + const dialect = new SingleStoreDialect({ casing: config.casing }); + let logger; + if (config.logger === true) { + logger = new DefaultLogger(); + } else if (config.logger !== false) { + logger = config.logger; + } + + const clientForInstance = isCallbackClient(client) ? client.promise() : client; + + let schema: RelationalSchemaConfig | undefined; + if (config.schema) { + const tablesConfig = extractTablesRelationalConfig( + config.schema, + createTableRelationsHelpers, + ); + schema = { + fullSchema: config.schema, + schema: tablesConfig.tables, + tableNamesMap: tablesConfig.tableNamesMap, + }; + } + + const driver = new SingleStoreDriverDriver(clientForInstance as SingleStoreDriverClient, dialect, { logger }); + const session = driver.createSession(schema); + const db = new SingleStoreDriverDatabase(dialect, session, schema as any) as SingleStoreDriverDatabase; + ( db).$client = client; + + return db as any; +} + +interface CallbackClient { + promise(): SingleStoreDriverClient; +} + +function isCallbackClient(client: any): client is CallbackClient { + return typeof client.promise === 'function'; +} + +export type AnySingleStoreDriverConnection = Pool | Connection | CallbackPool | CallbackConnection; + +export function drizzle< + TSchema extends Record = Record, + TClient extends AnySingleStoreDriverConnection = CallbackPool, +>( + ...params: IfNotImported< + CallbackPool, + [ImportTypeError<'singlestore'>], + [ + TClient | string, + ] | [ + TClient | string, + SingleStoreDriverDrizzleConfig, + ] | [ + ( + & SingleStoreDriverDrizzleConfig + & ({ + connection: string | PoolOptions; + } | { + client: TClient; + }) + ), + ] + > +): SingleStoreDriverDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const connectionString = params[0]!; + const instance = createPool({ + uri: connectionString, + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: PoolOptions | string; client?: TClient } + & SingleStoreDriverDrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' + ? createPool({ + uri: connection, + }) + : createPool(connection!); + const db = construct(instance, drizzleConfig); + + return db as any; + } + + return construct(params[0] as TClient, params[1] as SingleStoreDriverDrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: SingleStoreDriverDrizzleConfig, + ): SingleStoreDriverDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/singlestore/index.ts b/drizzle-orm/src/singlestore/index.ts new file mode 100644 index 000000000..b1b6a52e7 --- /dev/null +++ b/drizzle-orm/src/singlestore/index.ts @@ -0,0 +1,2 @@ +export * from './driver.ts'; +export * from './session.ts'; diff --git a/drizzle-orm/src/singlestore/migrator.ts b/drizzle-orm/src/singlestore/migrator.ts new file mode 100644 index 000000000..6f342c0c5 --- /dev/null +++ b/drizzle-orm/src/singlestore/migrator.ts @@ -0,0 +1,11 @@ +import type { MigrationConfig } from '~/migrator.ts'; +import { readMigrationFiles } from '~/migrator.ts'; +import type { SingleStoreDriverDatabase } from './driver.ts'; + +export async function migrate>( + db: SingleStoreDriverDatabase, + config: MigrationConfig, +) { + const migrations = readMigrationFiles(config); + await db.dialect.migrate(migrations, db.session, config); +} diff --git a/drizzle-orm/src/singlestore/session.ts b/drizzle-orm/src/singlestore/session.ts new file mode 100644 index 000000000..fd70a1d52 --- /dev/null +++ b/drizzle-orm/src/singlestore/session.ts @@ -0,0 +1,340 @@ +import type { Connection as CallbackConnection } from 'mysql2'; +import type { + Connection, + FieldPacket, + OkPacket, + Pool, + PoolConnection, + QueryOptions, + ResultSetHeader, + RowDataPacket, +} from 'mysql2/promise'; +import { once } from 'node:events'; +import { Column } from '~/column.ts'; +import { entityKind, is } from '~/entity.ts'; +import type { Logger } from '~/logger.ts'; +import { NoopLogger } from '~/logger.ts'; +import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { SelectedFieldsOrdered } from '~/singlestore-core/query-builders/select.types.ts'; +import { + type PreparedQueryKind, + SingleStorePreparedQuery, + type SingleStorePreparedQueryConfig, + type SingleStorePreparedQueryHKT, + type SingleStoreQueryResultHKT, + SingleStoreSession, + SingleStoreTransaction, + type SingleStoreTransactionConfig, +} from '~/singlestore-core/session.ts'; +import type { Query, SQL } from '~/sql/sql.ts'; +import { fillPlaceholders, sql } from '~/sql/sql.ts'; +import { type Assume, mapResultRow } from '~/utils.ts'; + +export type SingleStoreDriverClient = Pool | Connection; + +export type SingleStoreRawQueryResult = [ResultSetHeader, FieldPacket[]]; +export type SingleStoreQueryResultType = RowDataPacket[][] | RowDataPacket[] | OkPacket | OkPacket[] | ResultSetHeader; +export type SingleStoreQueryResult< + T = any, +> = [T extends ResultSetHeader ? T : T[], FieldPacket[]]; + +export class SingleStoreDriverPreparedQuery + extends SingleStorePreparedQuery +{ + static override readonly [entityKind]: string = 'SingleStoreDriverPreparedQuery'; + + private rawQuery: QueryOptions; + private query: QueryOptions; + + constructor( + private client: SingleStoreDriverClient, + queryString: string, + private params: unknown[], + private logger: Logger, + private fields: SelectedFieldsOrdered | undefined, + private customResultMapper?: (rows: unknown[][]) => T['execute'], + // Keys that were used in $default and the value that was generated for them + private generatedIds?: Record[], + // Keys that should be returned, it has the column with all properries + key from object + private returningIds?: SelectedFieldsOrdered, + ) { + super(); + this.rawQuery = { + sql: queryString, + // rowsAsArray: true, + typeCast: function(field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }; + this.query = { + sql: queryString, + rowsAsArray: true, + typeCast: function(field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }; + } + + async execute(placeholderValues: Record = {}): Promise { + const params = fillPlaceholders(this.params, placeholderValues); + + this.logger.logQuery(this.rawQuery.sql, params); + + const { fields, client, rawQuery, query, joinsNotNullableMap, customResultMapper, returningIds, generatedIds } = + this; + if (!fields && !customResultMapper) { + const res = await client.query(rawQuery, params); + const insertId = res[0].insertId; + const affectedRows = res[0].affectedRows; + // for each row, I need to check keys from + if (returningIds) { + const returningResponse = []; + let j = 0; + for (let i = insertId; i < insertId + affectedRows; i++) { + for (const column of returningIds) { + const key = returningIds[0]!.path[0]!; + if (is(column.field, Column)) { + // @ts-ignore + if (column.field.primary && column.field.autoIncrement) { + returningResponse.push({ [key]: i }); + } + if (column.field.defaultFn && generatedIds) { + // generatedIds[rowIdx][key] + returningResponse.push({ [key]: generatedIds[j]![key] }); + } + } + } + j++; + } + + return returningResponse; + } + return res; + } + + const result = await client.query(query, params); + const rows = result[0]; + + if (customResultMapper) { + return customResultMapper(rows); + } + + return rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + } + + async *iterator( + placeholderValues: Record = {}, + ): AsyncGenerator { + const params = fillPlaceholders(this.params, placeholderValues); + const conn = ((isPool(this.client) ? await this.client.getConnection() : this.client) as {} as { + connection: CallbackConnection; + }).connection; + + const { fields, query, rawQuery, joinsNotNullableMap, client, customResultMapper } = this; + const hasRowsMapper = Boolean(fields || customResultMapper); + const driverQuery = hasRowsMapper ? conn.query(query, params) : conn.query(rawQuery, params); + + const stream = driverQuery.stream(); + + function dataListener() { + stream.pause(); + } + + stream.on('data', dataListener); + + try { + const onEnd = once(stream, 'end'); + const onError = once(stream, 'error'); + + while (true) { + stream.resume(); + const row = await Promise.race([onEnd, onError, new Promise((resolve) => stream.once('data', resolve))]); + if (row === undefined || (Array.isArray(row) && row.length === 0)) { + break; + } else if (row instanceof Error) { // eslint-disable-line no-instanceof/no-instanceof + throw row; + } else { + if (hasRowsMapper) { + if (customResultMapper) { + const mappedRow = customResultMapper([row as unknown[]]); + yield (Array.isArray(mappedRow) ? mappedRow[0] : mappedRow); + } else { + yield mapResultRow(fields!, row as unknown[], joinsNotNullableMap); + } + } else { + yield row as T['execute']; + } + } + } + } finally { + stream.off('data', dataListener); + if (isPool(client)) { + conn.end(); + } + } + } +} + +export interface SingleStoreDriverSessionOptions { + logger?: Logger; +} + +export class SingleStoreDriverSession< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SingleStoreSession { + static override readonly [entityKind]: string = 'SingleStoreDriverSession'; + + private logger: Logger; + + constructor( + private client: SingleStoreDriverClient, + dialect: SingleStoreDialect, + private schema: RelationalSchemaConfig | undefined, + private options: SingleStoreDriverSessionOptions, + ) { + super(dialect); + this.logger = options.logger ?? new NoopLogger(); + } + + prepareQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + customResultMapper?: (rows: unknown[][]) => T['execute'], + generatedIds?: Record[], + returningIds?: SelectedFieldsOrdered, + ): PreparedQueryKind { + // Add returningId fields + // Each driver gets them from response from database + return new SingleStoreDriverPreparedQuery( + this.client, + query.sql, + query.params, + this.logger, + fields, + customResultMapper, + generatedIds, + returningIds, + ) as PreparedQueryKind; + } + + /** + * @internal + * What is its purpose? + */ + async query(query: string, params: unknown[]): Promise { + this.logger.logQuery(query, params); + const result = await this.client.query({ + sql: query, + values: params, + rowsAsArray: true, + typeCast: function(field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }); + return result; + } + + override all(query: SQL): Promise { + const querySql = this.dialect.sqlToQuery(query); + this.logger.logQuery(querySql.sql, querySql.params); + return this.client.execute(querySql.sql, querySql.params).then((result) => result[0]) as Promise; + } + + override async transaction( + transaction: (tx: SingleStoreDriverTransaction) => Promise, + config?: SingleStoreTransactionConfig, + ): Promise { + const session = isPool(this.client) + ? new SingleStoreDriverSession( + await this.client.getConnection(), + this.dialect, + this.schema, + this.options, + ) + : this; + const tx = new SingleStoreDriverTransaction( + this.dialect, + session as SingleStoreSession, + this.schema, + 0, + ); + if (config) { + const setTransactionConfigSql = this.getSetTransactionSQL(config); + if (setTransactionConfigSql) { + await tx.execute(setTransactionConfigSql); + } + const startTransactionSql = this.getStartTransactionSQL(config); + await (startTransactionSql ? tx.execute(startTransactionSql) : tx.execute(sql`begin`)); + } else { + await tx.execute(sql`begin`); + } + try { + const result = await transaction(tx); + await tx.execute(sql`commit`); + return result; + } catch (err) { + await tx.execute(sql`rollback`); + throw err; + } finally { + if (isPool(this.client)) { + (session.client as PoolConnection).release(); + } + } + } +} + +export class SingleStoreDriverTransaction< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SingleStoreTransaction< + SingleStoreDriverQueryResultHKT, + SingleStoreDriverPreparedQueryHKT, + TFullSchema, + TSchema +> { + static override readonly [entityKind]: string = 'SingleStoreDriverTransaction'; + + override async transaction( + transaction: (tx: SingleStoreDriverTransaction) => Promise, + ): Promise { + const savepointName = `sp${this.nestedIndex + 1}`; + const tx = new SingleStoreDriverTransaction( + this.dialect, + this.session, + this.schema, + this.nestedIndex + 1, + ); + await tx.execute(sql.raw(`savepoint ${savepointName}`)); + try { + const result = await transaction(tx); + await tx.execute(sql.raw(`release savepoint ${savepointName}`)); + return result; + } catch (err) { + await tx.execute(sql.raw(`rollback to savepoint ${savepointName}`)); + throw err; + } + } +} + +function isPool(client: SingleStoreDriverClient): client is Pool { + return 'getConnection' in client; +} + +export interface SingleStoreDriverQueryResultHKT extends SingleStoreQueryResultHKT { + type: SingleStoreRawQueryResult; +} + +export interface SingleStoreDriverPreparedQueryHKT extends SingleStorePreparedQueryHKT { + type: SingleStoreDriverPreparedQuery>; +} diff --git a/drizzle-orm/src/sql-js/driver.ts b/drizzle-orm/src/sql-js/driver.ts index 2e7080721..994d80bc8 100644 --- a/drizzle-orm/src/sql-js/driver.ts +++ b/drizzle-orm/src/sql-js/driver.ts @@ -19,7 +19,7 @@ export function drizzle = Record = {}, ): SQLJsDatabase { - const dialect = new SQLiteSyncDialect(); + const dialect = new SQLiteSyncDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); diff --git a/drizzle-orm/src/sql-js/migrator.ts b/drizzle-orm/src/sql-js/migrator.ts index 2b6f128f8..1b3d5faa3 100644 --- a/drizzle-orm/src/sql-js/migrator.ts +++ b/drizzle-orm/src/sql-js/migrator.ts @@ -4,7 +4,7 @@ import type { SQLJsDatabase } from './driver.ts'; export function migrate>( db: SQLJsDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/sql-js/session.ts b/drizzle-orm/src/sql-js/session.ts index 4325cd13f..0a09babbd 100644 --- a/drizzle-orm/src/sql-js/session.ts +++ b/drizzle-orm/src/sql-js/session.ts @@ -25,7 +25,7 @@ export class SQLJsSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteSession<'sync', void, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'SQLJsSession'; + static override readonly [entityKind]: string = 'SQLJsSession'; private logger: Logger; @@ -90,7 +90,7 @@ export class SQLJsTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteTransaction<'sync', void, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'SQLJsTransaction'; + static override readonly [entityKind]: string = 'SQLJsTransaction'; override transaction(transaction: (tx: SQLJsTransaction) => T): T { const savepointName = `sp${this.nestedIndex + 1}`; @@ -110,7 +110,7 @@ export class SQLJsTransaction< export class PreparedQuery extends PreparedQueryBase< { type: 'sync'; run: void; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { - static readonly [entityKind]: string = 'SQLJsPreparedQuery'; + static override readonly [entityKind]: string = 'SQLJsPreparedQuery'; constructor( private stmt: Statement, diff --git a/drizzle-orm/src/sql/expressions/conditions.ts b/drizzle-orm/src/sql/expressions/conditions.ts index 0a911e1ff..7ad1355dd 100644 --- a/drizzle-orm/src/sql/expressions/conditions.ts +++ b/drizzle-orm/src/sql/expressions/conditions.ts @@ -256,11 +256,6 @@ export const lte: BinaryOperator = (left: SQLWrapper, right: unknown): SQL => { * Test whether the first parameter, a column or expression, * has a value from a list passed as the second argument. * - * ## Throws - * - * The argument passed in the second array can't be empty: - * if an empty is provided, this method will throw. - * * ## Examples * * ```ts @@ -302,11 +297,6 @@ export function inArray( * has a value that is not present in a list passed as the * second argument. * - * ## Throws - * - * The argument passed in the second array can't be empty: - * if an empty is provided, this method will throw. - * * ## Examples * * ```ts @@ -536,7 +526,7 @@ export function notBetween( * * @see ilike for a case-insensitive version of this condition */ -export function like(column: Column, value: string | SQLWrapper): SQL { +export function like(column: Column | SQL.Aliased | SQL, value: string | SQLWrapper): SQL { return sql`${column} like ${value}`; } @@ -558,7 +548,7 @@ export function like(column: Column, value: string | SQLWrapper): SQL { * @see like for the inverse condition * @see notIlike for a case-insensitive version of this condition */ -export function notLike(column: Column, value: string | SQLWrapper): SQL { +export function notLike(column: Column | SQL.Aliased | SQL, value: string | SQLWrapper): SQL { return sql`${column} not like ${value}`; } @@ -581,7 +571,7 @@ export function notLike(column: Column, value: string | SQLWrapper): SQL { * * @see like for a case-sensitive version of this condition */ -export function ilike(column: Column, value: string | SQLWrapper): SQL { +export function ilike(column: Column | SQL.Aliased | SQL, value: string | SQLWrapper): SQL { return sql`${column} ilike ${value}`; } @@ -603,7 +593,7 @@ export function ilike(column: Column, value: string | SQLWrapper): SQL { * @see ilike for the inverse condition * @see notLike for a case-sensitive version of this condition */ -export function notIlike(column: Column, value: string | SQLWrapper): SQL { +export function notIlike(column: Column | SQL.Aliased | SQL, value: string | SQLWrapper): SQL { return sql`${column} not ilike ${value}`; } diff --git a/drizzle-orm/src/sql/functions/vector.ts b/drizzle-orm/src/sql/functions/vector.ts index 1b8f62451..fb0eadfc7 100644 --- a/drizzle-orm/src/sql/functions/vector.ts +++ b/drizzle-orm/src/sql/functions/vector.ts @@ -1,4 +1,4 @@ -import type { AnyColumn } from '~/index.ts'; +import type { AnyColumn } from '~/column.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import { type SQL, sql, type SQLWrapper } from '../sql.ts'; diff --git a/drizzle-orm/src/sql/sql.ts b/drizzle-orm/src/sql/sql.ts index f5b3f30b4..ba7586fe8 100644 --- a/drizzle-orm/src/sql/sql.ts +++ b/drizzle-orm/src/sql/sql.ts @@ -1,12 +1,14 @@ +import type { CasingCache } from '~/casing.ts'; import { entityKind, is } from '~/entity.ts'; -import type { SelectedFields } from '~/operations.ts'; import { isPgEnum } from '~/pg-core/columns/enum.ts'; +import type { SelectResult } from '~/query-builders/select.types.ts'; import { Subquery } from '~/subquery.ts'; import { tracer } from '~/tracing.ts'; +import type { Assume, Equal } from '~/utils.ts'; import { ViewBaseConfig } from '~/view-common.ts'; import type { AnyColumn } from '../column.ts'; import { Column } from '../column.ts'; -import { Table } from '../table.ts'; +import { IsAlias, Table } from '../table.ts'; /** * This class is used to indicate a primitive param value that is used in `sql` tag. @@ -28,6 +30,7 @@ export type Chunk = | SQL; export interface BuildQueryConfig { + casing: CasingCache; escapeName(name: string): string; escapeParam(num: number, value: unknown): string; escapeString(str: string): string; @@ -134,6 +137,7 @@ export class SQL implements SQLWrapper { }); const { + casing, escapeName, escapeParam, prepareTyping, @@ -185,10 +189,19 @@ export class SQL implements SQLWrapper { } if (is(chunk, Column)) { + const columnName = casing.getColumnCasing(chunk); if (_config.invokeSource === 'indexes') { - return { sql: escapeName(chunk.name), params: [] }; + return { sql: escapeName(columnName), params: [] }; } - return { sql: escapeName(chunk.table[Table.Symbol.Name]) + '.' + escapeName(chunk.name), params: [] }; + + const schemaName = chunk.table[Table.Symbol.Schema]; + return { + sql: chunk.table[IsAlias] || schemaName === undefined + ? escapeName(chunk.table[Table.Symbol.Name]) + '.' + escapeName(columnName) + : escapeName(schemaName) + '.' + escapeName(chunk.table[Table.Symbol.Name]) + '.' + + escapeName(columnName), + params: [], + }; } if (is(chunk, View)) { @@ -203,7 +216,11 @@ export class SQL implements SQLWrapper { } if (is(chunk, Param)) { - const mappedValue = (chunk.value === null) ? null : chunk.encoder.mapToDriverValue(chunk.value); + if (is(chunk.value, Placeholder)) { + return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ['none'] }; + } + + const mappedValue = chunk.value === null ? null : chunk.encoder.mapToDriverValue(chunk.value); if (is(mappedValue, SQL)) { return this.buildQueryFromSourceParams([mappedValue], config); @@ -213,7 +230,7 @@ export class SQL implements SQLWrapper { return { sql: this.mapInlineParam(mappedValue, config), params: [] }; } - let typings: QueryTypingsValue[] | undefined; + let typings: QueryTypingsValue[] = ['none']; if (prepareTyping) { typings = [prepareTyping(chunk.encoder)]; } @@ -263,7 +280,7 @@ export class SQL implements SQLWrapper { return { sql: this.mapInlineParam(chunk, config), params: [] }; } - return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk] }; + return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ['none'] }; })); } @@ -583,15 +600,26 @@ export function fillPlaceholders(params: unknown[], values: Record; +const IsDrizzleView = Symbol.for('drizzle:IsDrizzleView'); + export abstract class View< TName extends string = string, TExisting extends boolean = boolean, @@ -612,17 +640,22 @@ export abstract class View< name: TName; originalName: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: ColumnsSelection; isExisting: TExisting; query: TExisting extends true ? undefined : SQL; isAlias: boolean; }; + /** @internal */ + [IsDrizzleView] = true; + + declare readonly $inferSelect: InferSelectViewModel, TExisting, TSelection>>; + constructor( { name, schema, selectedFields, query }: { name: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: ColumnsSelection; query: SQL | undefined; }, ) { @@ -642,6 +675,18 @@ export abstract class View< } } +export function isView(view: unknown): view is View { + return typeof view === 'object' && view !== null && IsDrizzleView in view; +} + +export type InferSelectViewModel = + Equal extends true ? { [x: string]: unknown } + : SelectResult< + TView['_']['selectedFields'], + 'single', + Record + >; + // Defined separately from the Column class to resolve circular dependency Column.prototype.getSQL = function() { return new SQL([this]); diff --git a/drizzle-orm/src/sqlite-core/README.md b/drizzle-orm/src/sqlite-core/README.md index 6d4ebd8b6..ae5fbe660 100644 --- a/drizzle-orm/src/sqlite-core/README.md +++ b/drizzle-orm/src/sqlite-core/README.md @@ -8,7 +8,7 @@
-DrizzleORM is a [tiny](https://twitter.com/_alexblokh/status/1594735880417472512), [blazingly fast](#️-performance-and-prepared-statements) TypeScript ORM library with a [drizzle-kit](#-migrations) CLI companion for automatic SQL migrations generation. +Drizzle ORM is a [tiny](https://twitter.com/_alexblokh/status/1594735880417472512), [blazingly fast](#️-performance-and-prepared-statements) TypeScript ORM library with a [drizzle-kit](#-migrations) CLI companion for automatic SQL migrations generation. Here you can find extensive docs for SQLite module. | Driver | Support | | @@ -365,7 +365,8 @@ const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); const result = db.with(sq).select().from(sq).all(); ``` -> **Note**: Keep in mind that if you need to select raw `sql` in a WITH subquery and reference that field in other queries, you must add an alias to it: +> [!NOTE] +> Keep in mind that if you need to select raw `sql` in a WITH subquery and reference that field in other queries, you must add an alias to it: ```typescript const sq = db.$with('sq').as(db.select({ name: sql`upper(${users.name})`.as('name') }).from(users)); @@ -582,7 +583,8 @@ db ### Joins -> **Note**: for in-depth partial select joins documentation, refer to [this page](/docs/joins.md). +> [!NOTE] +> For in-depth partial select joins documentation, refer to [this page](/docs/joins.md). ### Many-to-one @@ -721,7 +723,8 @@ const { sql, params } = query.toSQL(); ## Views (WIP) -> **Warning**: views are currently only implemented on the ORM side. That means you can query the views that already exist in the database, but they won't be added to drizzle-kit migrations or `db push` yet. +> [!WARNING] +> views are currently only implemented on the ORM side. That means you can query the views that already exist in the database, but they won't be added to drizzle-kit migrations or `db push` yet. ### Creating a view @@ -731,7 +734,8 @@ import { sqliteView } from 'drizzle-orm/sqlite-core'; const newYorkers = sqliteView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); ``` -> **Warning**: All the parameters inside the query will be inlined, instead of replaced by `$1`, `$2`, etc. +> [!WARNING] +> All the parameters inside the query will be inlined, instead of replaced by `$1`, `$2`, etc. You can also use the [`queryBuilder` instance](#query-builder) directly instead of passing a callback, if you already have it imported. @@ -794,8 +798,8 @@ q.all({ name: '%an%' }) // SELECT * FROM customers WHERE name ilike '%an%' ### Automatic SQL migrations generation with drizzle-kit -[Drizzle Kit](https://www.npmjs.com/package/drizzle-kit) is a CLI migrator tool for Drizzle ORM. It is probably one and only tool that lets you completely automatically generate SQL migrations and covers ~95% of the common cases like deletions and renames by prompting user input. -Check out the [docs for Drizzle Kit](https://github.com/drizzle-team/drizzle-kit-mirror) +[Drizzle Kit](https://www.npmjs.com/package/drizzle-kit) is a CLI migrator tool for Drizzle ORM. It is probably the one and only tool that lets you completely automatically generate SQL migrations and covers ~95% of the common cases like deletions and renames by prompting user input. +Check out the [docs for Drizzle Kit](https://github.com/drizzle-team/drizzle-kit-mirror). For schema file: diff --git a/drizzle-orm/src/sqlite-core/columns/all.ts b/drizzle-orm/src/sqlite-core/columns/all.ts new file mode 100644 index 000000000..a30ba3eac --- /dev/null +++ b/drizzle-orm/src/sqlite-core/columns/all.ts @@ -0,0 +1,19 @@ +import { blob } from './blob.ts'; +import { customType } from './custom.ts'; +import { integer } from './integer.ts'; +import { numeric } from './numeric.ts'; +import { real } from './real.ts'; +import { text } from './text.ts'; + +export function getSQLiteColumnBuilders() { + return { + blob, + customType, + integer, + numeric, + real, + text, + }; +} + +export type SQLiteColumnBuilders = ReturnType; diff --git a/drizzle-orm/src/sqlite-core/columns/blob.ts b/drizzle-orm/src/sqlite-core/columns/blob.ts index 7371eb299..dfd2795a4 100644 --- a/drizzle-orm/src/sqlite-core/columns/blob.ts +++ b/drizzle-orm/src/sqlite-core/columns/blob.ts @@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnySQLiteTable } from '~/sqlite-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { SQLiteColumn, SQLiteColumnBuilder } from './common.ts'; type BlobMode = 'buffer' | 'json' | 'bigint'; @@ -14,13 +14,12 @@ export type SQLiteBigIntBuilderInitial = SQLiteBigIntBuild data: bigint; driverParam: Buffer; enumValues: undefined; - generated: undefined; }>; export class SQLiteBigIntBuilder> extends SQLiteColumnBuilder { - static readonly [entityKind]: string = 'SQLiteBigIntBuilder'; + static override readonly [entityKind]: string = 'SQLiteBigIntBuilder'; constructor(name: T['name']) { super(name, 'bigint', 'SQLiteBigInt'); @@ -35,14 +34,25 @@ export class SQLiteBigIntBuilder> extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteBigInt'; + static override readonly [entityKind]: string = 'SQLiteBigInt'; getSQLType(): string { return 'blob'; } - override mapFromDriverValue(value: Buffer): bigint { - return BigInt(value.toString()); + override mapFromDriverValue(value: Buffer | Uint8Array | ArrayBuffer): bigint { + if (Buffer.isBuffer(value)) { + return BigInt(value.toString()); + } + + // for sqlite durable objects + // eslint-disable-next-line no-instanceof/no-instanceof + if (value instanceof ArrayBuffer) { + const decoder = new TextDecoder(); + return BigInt(decoder.decode(value)); + } + + return BigInt(String.fromCodePoint(...value)); } override mapToDriverValue(value: bigint): Buffer { @@ -57,13 +67,12 @@ export type SQLiteBlobJsonBuilderInitial = SQLiteBlobJsonB data: unknown; driverParam: Buffer; enumValues: undefined; - generated: undefined; }>; export class SQLiteBlobJsonBuilder> extends SQLiteColumnBuilder { - static readonly [entityKind]: string = 'SQLiteBlobJsonBuilder'; + static override readonly [entityKind]: string = 'SQLiteBlobJsonBuilder'; constructor(name: T['name']) { super(name, 'json', 'SQLiteBlobJson'); @@ -81,14 +90,25 @@ export class SQLiteBlobJsonBuilder> extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteBlobJson'; + static override readonly [entityKind]: string = 'SQLiteBlobJson'; getSQLType(): string { return 'blob'; } - override mapFromDriverValue(value: Buffer): T['data'] { - return JSON.parse(value.toString()); + override mapFromDriverValue(value: Buffer | Uint8Array | ArrayBuffer): T['data'] { + if (Buffer.isBuffer(value)) { + return JSON.parse(value.toString()); + } + + // for sqlite durable objects + // eslint-disable-next-line no-instanceof/no-instanceof + if (value instanceof ArrayBuffer) { + const decoder = new TextDecoder(); + return JSON.parse(decoder.decode(value)); + } + + return JSON.parse(String.fromCodePoint(...value)); } override mapToDriverValue(value: T['data']): Buffer { @@ -103,13 +123,12 @@ export type SQLiteBlobBufferBuilderInitial = SQLiteBlobBuf data: Buffer; driverParam: Buffer; enumValues: undefined; - generated: undefined; }>; export class SQLiteBlobBufferBuilder> extends SQLiteColumnBuilder { - static readonly [entityKind]: string = 'SQLiteBlobBufferBuilder'; + static override readonly [entityKind]: string = 'SQLiteBlobBufferBuilder'; constructor(name: T['name']) { super(name, 'buffer', 'SQLiteBlobBuffer'); @@ -124,7 +143,7 @@ export class SQLiteBlobBufferBuilder> extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteBlobBuffer'; + static override readonly [entityKind]: string = 'SQLiteBlobBuffer'; getSQLType(): string { return 'blob'; @@ -141,13 +160,20 @@ export interface BlobConfig { * * https://www.sqlite.org/json1.html */ +export function blob(): SQLiteBlobJsonBuilderInitial<''>; +export function blob( + config?: BlobConfig, +): Equal extends true ? SQLiteBigIntBuilderInitial<''> + : Equal extends true ? SQLiteBlobBufferBuilderInitial<''> + : SQLiteBlobJsonBuilderInitial<''>; export function blob( name: TName, config?: BlobConfig, ): Equal extends true ? SQLiteBigIntBuilderInitial : Equal extends true ? SQLiteBlobBufferBuilderInitial : SQLiteBlobJsonBuilderInitial; -export function blob(name: string, config?: BlobConfig) { +export function blob(a?: string | BlobConfig, b?: BlobConfig) { + const { name, config } = getColumnNameAndConfig(a, b); if (config?.mode === 'json') { return new SQLiteBlobJsonBuilder(name); } diff --git a/drizzle-orm/src/sqlite-core/columns/common.ts b/drizzle-orm/src/sqlite-core/columns/common.ts index a0cdd755d..8eacb8c93 100644 --- a/drizzle-orm/src/sqlite-core/columns/common.ts +++ b/drizzle-orm/src/sqlite-core/columns/common.ts @@ -43,7 +43,7 @@ export abstract class SQLiteColumnBuilder< > extends ColumnBuilder implements SQLiteColumnBuilderBase { - static readonly [entityKind]: string = 'SQLiteColumnBuilder'; + static override readonly [entityKind]: string = 'SQLiteColumnBuilder'; private foreignKeyConfigs: ReferenceConfig[] = []; @@ -63,7 +63,9 @@ export abstract class SQLiteColumnBuilder< return this; } - generatedAlwaysAs(as: SQL | T['data'] | (() => SQL), config?: SQLiteGeneratedColumnConfig): HasGenerated { + generatedAlwaysAs(as: SQL | T['data'] | (() => SQL), config?: SQLiteGeneratedColumnConfig): HasGenerated { this.config.generated = { as, type: 'always', @@ -100,9 +102,10 @@ export abstract class SQLiteColumnBuilder< // To understand how to use `SQLiteColumn` and `AnySQLiteColumn`, see `Column` and `AnyColumn` documentation. export abstract class SQLiteColumn< T extends ColumnBaseConfig = ColumnBaseConfig, - TRuntimeConfig extends object = object, -> extends Column { - static readonly [entityKind]: string = 'SQLiteColumn'; + TRuntimeConfig extends object = {}, + TTypeConfig extends object = {}, +> extends Column { + static override readonly [entityKind]: string = 'SQLiteColumn'; constructor( override readonly table: SQLiteTable, diff --git a/drizzle-orm/src/sqlite-core/columns/custom.ts b/drizzle-orm/src/sqlite-core/columns/custom.ts index 513f380e0..b23d6442f 100644 --- a/drizzle-orm/src/sqlite-core/columns/custom.ts +++ b/drizzle-orm/src/sqlite-core/columns/custom.ts @@ -3,7 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { SQL } from '~/sql/sql.ts'; import type { AnySQLiteTable } from '~/sqlite-core/table.ts'; -import type { Equal } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig } from '~/utils.ts'; import { SQLiteColumn, SQLiteColumnBuilder } from './common.ts'; export type ConvertCustomConfig> = @@ -14,7 +14,6 @@ export type ConvertCustomConfig { - static readonly [entityKind]: string = 'SQLiteCustomColumnBuilder'; + static override readonly [entityKind]: string = 'SQLiteCustomColumnBuilder'; constructor( name: T['name'], @@ -59,7 +58,7 @@ export class SQLiteCustomColumnBuilder> extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteCustomColumn'; + static override readonly [entityKind]: string = 'SQLiteCustomColumn'; private sqlName: string; private mapTo?: (value: T['data']) => T['driverParam']; @@ -108,7 +107,7 @@ export type CustomTypeValues = { /** * What config type should be used for {@link CustomTypeParams} `dataType` generation */ - config?: unknown; + config?: Record; /** * Whether the config argument should be required or not @@ -203,22 +202,34 @@ export interface CustomTypeParams { */ export function customType( customTypeParams: CustomTypeParams, -): Equal extends true ? ( - dbName: TName, - fieldConfig: T['config'], - ) => SQLiteCustomColumnBuilder> - : ( - dbName: TName, - fieldConfig?: T['config'], - ) => SQLiteCustomColumnBuilder> +): Equal extends true ? { + & T['config']>( + fieldConfig: TConfig, + ): SQLiteCustomColumnBuilder>; + ( + dbName: TName, + fieldConfig: T['config'], + ): SQLiteCustomColumnBuilder>; + } + : { + (): SQLiteCustomColumnBuilder>; + & T['config']>( + fieldConfig?: TConfig, + ): SQLiteCustomColumnBuilder>; + ( + dbName: TName, + fieldConfig?: T['config'], + ): SQLiteCustomColumnBuilder>; + } { return ( - dbName: TName, - fieldConfig?: T['config'], + a?: TName | T['config'], + b?: T['config'], ): SQLiteCustomColumnBuilder> => { + const { name, config } = getColumnNameAndConfig(a, b); return new SQLiteCustomColumnBuilder( - dbName as ConvertCustomConfig['name'], - fieldConfig, + name as ConvertCustomConfig['name'], + config, customTypeParams, ); }; diff --git a/drizzle-orm/src/sqlite-core/columns/integer.ts b/drizzle-orm/src/sqlite-core/columns/integer.ts index 1c839837d..13d52c0ea 100644 --- a/drizzle-orm/src/sqlite-core/columns/integer.ts +++ b/drizzle-orm/src/sqlite-core/columns/integer.ts @@ -11,7 +11,7 @@ import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import { sql } from '~/sql/sql.ts'; import type { OnConflict } from '~/sqlite-core/utils.ts'; -import type { Equal, Or } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig, type Or } from '~/utils.ts'; import type { AnySQLiteTable } from '../table.ts'; import { SQLiteColumn, SQLiteColumnBuilder } from './common.ts'; @@ -29,7 +29,7 @@ export abstract class SQLiteBaseIntegerBuilder< {}, { primaryKeyHasDefault: true } > { - static readonly [entityKind]: string = 'SQLiteBaseIntegerBuilder'; + static override readonly [entityKind]: string = 'SQLiteBaseIntegerBuilder'; constructor(name: T['name'], dataType: T['dataType'], columnType: T['columnType']) { super(name, dataType, columnType); @@ -54,7 +54,7 @@ export abstract class SQLiteBaseInteger< T extends ColumnBaseConfig, TRuntimeConfig extends object = object, > extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteBaseInteger'; + static override readonly [entityKind]: string = 'SQLiteBaseInteger'; readonly autoIncrement: boolean = this.config.autoIncrement; @@ -70,13 +70,12 @@ export type SQLiteIntegerBuilderInitial = SQLiteIntegerBui data: number; driverParam: number; enumValues: undefined; - generated: undefined; }>; export class SQLiteIntegerBuilder> extends SQLiteBaseIntegerBuilder { - static readonly [entityKind]: string = 'SQLiteIntegerBuilder'; + static override readonly [entityKind]: string = 'SQLiteIntegerBuilder'; constructor(name: T['name']) { super(name, 'number', 'SQLiteInteger'); @@ -93,7 +92,7 @@ export class SQLiteIntegerBuilder> extends SQLiteBaseInteger { - static readonly [entityKind]: string = 'SQLiteInteger'; + static override readonly [entityKind]: string = 'SQLiteInteger'; } export type SQLiteTimestampBuilderInitial = SQLiteTimestampBuilder<{ @@ -103,13 +102,12 @@ export type SQLiteTimestampBuilderInitial = SQLiteTimestam data: Date; driverParam: number; enumValues: undefined; - generated: undefined; }>; export class SQLiteTimestampBuilder> extends SQLiteBaseIntegerBuilder { - static readonly [entityKind]: string = 'SQLiteTimestampBuilder'; + static override readonly [entityKind]: string = 'SQLiteTimestampBuilder'; constructor(name: T['name'], mode: 'timestamp' | 'timestamp_ms') { super(name, 'date', 'SQLiteTimestamp'); @@ -138,7 +136,7 @@ export class SQLiteTimestampBuilder> extends SQLiteBaseInteger { - static readonly [entityKind]: string = 'SQLiteTimestamp'; + static override readonly [entityKind]: string = 'SQLiteTimestamp'; readonly mode: 'timestamp' | 'timestamp_ms' = this.config.mode; @@ -165,13 +163,12 @@ export type SQLiteBooleanBuilderInitial = SQLiteBooleanBui data: boolean; driverParam: number; enumValues: undefined; - generated: undefined; }>; export class SQLiteBooleanBuilder> extends SQLiteBaseIntegerBuilder { - static readonly [entityKind]: string = 'SQLiteBooleanBuilder'; + static override readonly [entityKind]: string = 'SQLiteBooleanBuilder'; constructor(name: T['name'], mode: 'boolean') { super(name, 'boolean', 'SQLiteBoolean'); @@ -191,7 +188,7 @@ export class SQLiteBooleanBuilder> extends SQLiteBaseInteger { - static readonly [entityKind]: string = 'SQLiteBoolean'; + static override readonly [entityKind]: string = 'SQLiteBoolean'; readonly mode: 'boolean' = this.config.mode; @@ -214,13 +211,20 @@ export interface IntegerConfig< mode: TMode; } +export function integer(): SQLiteIntegerBuilderInitial<''>; +export function integer( + config?: IntegerConfig, +): Or, Equal> extends true ? SQLiteTimestampBuilderInitial<''> + : Equal extends true ? SQLiteBooleanBuilderInitial<''> + : SQLiteIntegerBuilderInitial<''>; export function integer( name: TName, config?: IntegerConfig, ): Or, Equal> extends true ? SQLiteTimestampBuilderInitial : Equal extends true ? SQLiteBooleanBuilderInitial : SQLiteIntegerBuilderInitial; -export function integer(name: string, config?: IntegerConfig) { +export function integer(a?: string | IntegerConfig, b?: IntegerConfig) { + const { name, config } = getColumnNameAndConfig(a, b); if (config?.mode === 'timestamp' || config?.mode === 'timestamp_ms') { return new SQLiteTimestampBuilder(name, config.mode); } diff --git a/drizzle-orm/src/sqlite-core/columns/numeric.ts b/drizzle-orm/src/sqlite-core/columns/numeric.ts index 5cd29f78f..9505367a2 100644 --- a/drizzle-orm/src/sqlite-core/columns/numeric.ts +++ b/drizzle-orm/src/sqlite-core/columns/numeric.ts @@ -11,13 +11,12 @@ export type SQLiteNumericBuilderInitial = SQLiteNumericBui data: string; driverParam: string; enumValues: undefined; - generated: undefined; }>; export class SQLiteNumericBuilder> extends SQLiteColumnBuilder { - static readonly [entityKind]: string = 'SQLiteNumericBuilder'; + static override readonly [entityKind]: string = 'SQLiteNumericBuilder'; constructor(name: T['name']) { super(name, 'string', 'SQLiteNumeric'); @@ -35,13 +34,15 @@ export class SQLiteNumericBuilder> extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteNumeric'; + static override readonly [entityKind]: string = 'SQLiteNumeric'; getSQLType(): string { return 'numeric'; } } -export function numeric(name: TName): SQLiteNumericBuilderInitial { - return new SQLiteNumericBuilder(name); +export function numeric(): SQLiteNumericBuilderInitial<''>; +export function numeric(name: TName): SQLiteNumericBuilderInitial; +export function numeric(name?: string) { + return new SQLiteNumericBuilder(name ?? ''); } diff --git a/drizzle-orm/src/sqlite-core/columns/real.ts b/drizzle-orm/src/sqlite-core/columns/real.ts index c04cb1be3..8821eb275 100644 --- a/drizzle-orm/src/sqlite-core/columns/real.ts +++ b/drizzle-orm/src/sqlite-core/columns/real.ts @@ -11,13 +11,12 @@ export type SQLiteRealBuilderInitial = SQLiteRealBuilder<{ data: number; driverParam: number; enumValues: undefined; - generated: undefined; }>; export class SQLiteRealBuilder> extends SQLiteColumnBuilder { - static readonly [entityKind]: string = 'SQLiteRealBuilder'; + static override readonly [entityKind]: string = 'SQLiteRealBuilder'; constructor(name: T['name']) { super(name, 'number', 'SQLiteReal'); @@ -32,13 +31,15 @@ export class SQLiteRealBuilder> extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteReal'; + static override readonly [entityKind]: string = 'SQLiteReal'; getSQLType(): string { return 'real'; } } -export function real(name: TName): SQLiteRealBuilderInitial { - return new SQLiteRealBuilder(name); +export function real(): SQLiteRealBuilderInitial<''>; +export function real(name: TName): SQLiteRealBuilderInitial; +export function real(name?: string) { + return new SQLiteRealBuilder(name ?? ''); } diff --git a/drizzle-orm/src/sqlite-core/columns/text.ts b/drizzle-orm/src/sqlite-core/columns/text.ts index 7eecf1d1f..0696b76f8 100644 --- a/drizzle-orm/src/sqlite-core/columns/text.ts +++ b/drizzle-orm/src/sqlite-core/columns/text.ts @@ -2,26 +2,33 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon import type { ColumnBaseConfig } from '~/column.ts'; import { entityKind } from '~/entity.ts'; import type { AnySQLiteTable } from '~/sqlite-core/table.ts'; -import type { Equal, Writable } from '~/utils.ts'; +import { type Equal, getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { SQLiteColumn, SQLiteColumnBuilder } from './common.ts'; -export type SQLiteTextBuilderInitial = SQLiteTextBuilder<{ +export type SQLiteTextBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = SQLiteTextBuilder<{ name: TName; dataType: 'string'; columnType: 'SQLiteText'; data: TEnum[number]; driverParam: string; enumValues: TEnum; - generated: undefined; + length: TLength; }>; -export class SQLiteTextBuilder> extends SQLiteColumnBuilder< +export class SQLiteTextBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SQLiteText'> & { length?: number | undefined }, +> extends SQLiteColumnBuilder< T, - { length: number | undefined; enumValues: T['enumValues'] } + { length: T['length']; enumValues: T['enumValues'] }, + { length: T['length'] } > { - static readonly [entityKind]: string = 'SQLiteTextBuilder'; + static override readonly [entityKind]: string = 'SQLiteTextBuilder'; - constructor(name: T['name'], config: SQLiteTextConfig<'text', T['enumValues']>) { + constructor(name: T['name'], config: SQLiteTextConfig<'text', T['enumValues'], T['length']>) { super(name, 'string', 'SQLiteText'); this.config.enumValues = config.enum; this.config.length = config.length; @@ -30,19 +37,22 @@ export class SQLiteTextBuilder( table: AnySQLiteTable<{ name: TTableName }>, - ): SQLiteText> { - return new SQLiteText>(table, this.config as ColumnBuilderRuntimeConfig); + ): SQLiteText & { length: T['length'] }> { + return new SQLiteText & { length: T['length'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); } } -export class SQLiteText> - extends SQLiteColumn +export class SQLiteText & { length?: number | undefined }> + extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteText'; + static override readonly [entityKind]: string = 'SQLiteText'; override readonly enumValues = this.config.enumValues; - readonly length: number | undefined = this.config.length; + readonly length: T['length'] = this.config.length; constructor( table: AnySQLiteTable<{ name: T['tableName'] }>, @@ -69,7 +79,7 @@ export type SQLiteTextJsonBuilderInitial = SQLiteTextJsonB export class SQLiteTextJsonBuilder> extends SQLiteColumnBuilder { - static readonly [entityKind]: string = 'SQLiteTextJsonBuilder'; + static override readonly [entityKind]: string = 'SQLiteTextJsonBuilder'; constructor(name: T['name']) { super(name, 'json', 'SQLiteTextJson'); @@ -89,7 +99,7 @@ export class SQLiteTextJsonBuilder> extends SQLiteColumn { - static readonly [entityKind]: string = 'SQLiteTextJson'; + static override readonly [entityKind]: string = 'SQLiteTextJson'; getSQLType(): string { return 'text'; @@ -105,31 +115,43 @@ export class SQLiteTextJson } export type SQLiteTextConfig< - TMode extends 'text' | 'json', - TEnum extends readonly string[] | string[] | undefined, + TMode extends 'text' | 'json' = 'text' | 'json', + TEnum extends readonly string[] | string[] | undefined = readonly string[] | string[] | undefined, + TLength extends number | undefined = number | undefined, > = TMode extends 'text' ? { mode?: TMode; - length?: number; + length?: TLength; enum?: TEnum; } : { mode?: TMode; }; +export function text(): SQLiteTextBuilderInitial<'', [string, ...string[]], undefined>; +export function text< + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, + TMode extends 'text' | 'json' = 'text' | 'json', +>( + config?: SQLiteTextConfig, L>, +): Equal extends true ? SQLiteTextJsonBuilderInitial<''> + : SQLiteTextBuilderInitial<'', Writable, L>; export function text< TName extends string, U extends string, T extends Readonly<[U, ...U[]]>, + L extends number | undefined, TMode extends 'text' | 'json' = 'text' | 'json', >( name: TName, - config: SQLiteTextConfig> = {} as SQLiteTextConfig>, + config?: SQLiteTextConfig, L>, ): Equal extends true ? SQLiteTextJsonBuilderInitial - : SQLiteTextBuilderInitial> -{ - return (config.mode === 'json' - ? new SQLiteTextJsonBuilder(name) - : new SQLiteTextBuilder(name, config as SQLiteTextConfig<'text', Writable>)) as Equal extends true - ? SQLiteTextJsonBuilderInitial - : SQLiteTextBuilderInitial>; + : SQLiteTextBuilderInitial, L>; +export function text(a?: string | SQLiteTextConfig, b: SQLiteTextConfig = {}): any { + const { name, config } = getColumnNameAndConfig(a, b); + if (config.mode === 'json') { + return new SQLiteTextJsonBuilder(name); + } + return new SQLiteTextBuilder(name, config as any); } diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts index 65f807d08..f63384f98 100644 --- a/drizzle-orm/src/sqlite-core/db.ts +++ b/drizzle-orm/src/sqlite-core/db.ts @@ -2,7 +2,7 @@ import { entityKind } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; -import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; +import { type ColumnsSelection, type SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; import type { SQLiteAsyncDialect, SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; import { QueryBuilder, @@ -21,10 +21,12 @@ import type { import type { SQLiteTable } from '~/sqlite-core/table.ts'; import { WithSubquery } from '~/subquery.ts'; import type { DrizzleTypeError } from '~/utils.ts'; +import { SQLiteCountBuilder } from './query-builders/count.ts'; import { RelationalQueryBuilder } from './query-builders/query.ts'; import { SQLiteRaw } from './query-builders/raw.ts'; import type { SelectedFields } from './query-builders/select.types.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; +import type { SQLiteViewBase } from './view-base.ts'; export class BaseSQLiteDatabase< TResultKind extends 'sync' | 'async', @@ -118,12 +120,13 @@ export class BaseSQLiteDatabase< * ``` */ $with(alias: TAlias) { + const self = this; return { as( qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), ): WithSubqueryWithSelection { if (typeof qb === 'function') { - qb = qb(new QueryBuilder()); + qb = qb(new QueryBuilder(self.dialect)); } return new Proxy( @@ -134,6 +137,13 @@ export class BaseSQLiteDatabase< }; } + $count( + source: SQLiteTable | SQLiteViewBase | SQL | SQLWrapper, + filters?: SQL, + ) { + return new SQLiteCountBuilder({ source, filters, session: this.session }); + } + /** * Incorporates a previously defined CTE (using `$with`) into the main query. * @@ -509,60 +519,60 @@ export class BaseSQLiteDatabase< return new SQLiteDeleteBase(from, this.session, this.dialect); } - run(query: SQLWrapper): DBResult { - const sql = query.getSQL(); + run(query: SQLWrapper | string): DBResult { + const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); if (this.resultKind === 'async') { return new SQLiteRaw( - async () => this.session.run(sql), - () => sql, + async () => this.session.run(sequel), + () => sequel, 'run', this.dialect as SQLiteAsyncDialect, this.session.extractRawRunValueFromBatchResult.bind(this.session), ) as DBResult; } - return this.session.run(sql) as DBResult; + return this.session.run(sequel) as DBResult; } - all(query: SQLWrapper): DBResult { - const sql = query.getSQL(); + all(query: SQLWrapper | string): DBResult { + const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); if (this.resultKind === 'async') { return new SQLiteRaw( - async () => this.session.all(sql), - () => sql, + async () => this.session.all(sequel), + () => sequel, 'all', this.dialect as SQLiteAsyncDialect, this.session.extractRawAllValueFromBatchResult.bind(this.session), ) as any; } - return this.session.all(sql) as DBResult; + return this.session.all(sequel) as DBResult; } - get(query: SQLWrapper): DBResult { - const sql = query.getSQL(); + get(query: SQLWrapper | string): DBResult { + const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); if (this.resultKind === 'async') { return new SQLiteRaw( - async () => this.session.get(sql), - () => sql, + async () => this.session.get(sequel), + () => sequel, 'get', this.dialect as SQLiteAsyncDialect, this.session.extractRawGetValueFromBatchResult.bind(this.session), ) as DBResult; } - return this.session.get(sql) as DBResult; + return this.session.get(sequel) as DBResult; } - values(query: SQLWrapper): DBResult { - const sql = query.getSQL(); + values(query: SQLWrapper | string): DBResult { + const sequel = typeof query === 'string' ? sql.raw(query) : query.getSQL(); if (this.resultKind === 'async') { return new SQLiteRaw( - async () => this.session.values(sql), - () => sql, + async () => this.session.values(sequel), + () => sequel, 'values', this.dialect as SQLiteAsyncDialect, this.session.extractRawValuesValueFromBatchResult.bind(this.session), ) as any; } - return this.session.values(sql) as DBResult; + return this.session.values(sequel) as DBResult; } transaction( diff --git a/drizzle-orm/src/sqlite-core/dialect.ts b/drizzle-orm/src/sqlite-core/dialect.ts index 645e15592..8db4ee56b 100644 --- a/drizzle-orm/src/sqlite-core/dialect.ts +++ b/drizzle-orm/src/sqlite-core/dialect.ts @@ -1,4 +1,5 @@ import { aliasedTable, aliasedTableColumn, mapColumnsInAliasedSQLToAlias, mapColumnsInSQLToAlias } from '~/alias.ts'; +import { CasingCache } from '~/casing.ts'; import type { AnyColumn } from '~/column.ts'; import { Column } from '~/column.ts'; import { entityKind, is } from '~/entity.ts'; @@ -16,15 +17,20 @@ import { type TableRelationalConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { Name } from '~/sql/index.ts'; +import type { Name, Placeholder } from '~/sql/index.ts'; import { and, eq } from '~/sql/index.ts'; import { Param, type QueryWithTypings, SQL, sql, type SQLChunk } from '~/sql/sql.ts'; import { SQLiteColumn } from '~/sqlite-core/columns/index.ts'; -import type { SQLiteDeleteConfig, SQLiteInsertConfig, SQLiteUpdateConfig } from '~/sqlite-core/query-builders/index.ts'; +import type { + AnySQLiteSelectQueryBuilder, + SQLiteDeleteConfig, + SQLiteInsertConfig, + SQLiteUpdateConfig, +} from '~/sqlite-core/query-builders/index.ts'; import { SQLiteTable } from '~/sqlite-core/table.ts'; import { Subquery } from '~/subquery.ts'; import { getTableName, getTableUniqueName, Table } from '~/table.ts'; -import { orderSelectedFields, type UpdateSet } from '~/utils.ts'; +import { type Casing, orderSelectedFields, type UpdateSet } from '~/utils.ts'; import { ViewBaseConfig } from '~/view-common.ts'; import type { SelectedFieldsOrdered, @@ -34,9 +40,20 @@ import type { import type { SQLiteSession } from './session.ts'; import { SQLiteViewBase } from './view-base.ts'; +export interface SQLiteDialectConfig { + casing?: Casing; +} + export abstract class SQLiteDialect { static readonly [entityKind]: string = 'SQLiteDialect'; + /** @internal */ + readonly casing: CasingCache; + + constructor(config?: SQLiteDialectConfig) { + this.casing = new CasingCache(config?.casing); + } + escapeName(name: string): string { return `"${name}"`; } @@ -63,7 +80,7 @@ export abstract class SQLiteDialect { return sql.join(withSqlChunks); } - buildDeleteQuery({ table, where, returning, withList }: SQLiteDeleteConfig): SQL { + buildDeleteQuery({ table, where, returning, withList, limit, orderBy }: SQLiteDeleteConfig): SQL { const withSql = this.buildWithCTE(withList); const returningSql = returning @@ -72,7 +89,11 @@ export abstract class SQLiteDialect { const whereSql = where ? sql` where ${where}` : undefined; - return sql`${withSql}delete from ${table}${whereSql}${returningSql}`; + const orderBySql = this.buildOrderBy(orderBy); + + const limitSql = this.buildLimit(limit); + + return sql`${withSql}delete from ${table}${whereSql}${returningSql}${orderBySql}${limitSql}`; } buildUpdateSet(table: SQLiteTable, set: UpdateSet): SQL { @@ -87,7 +108,7 @@ export abstract class SQLiteDialect { const col = tableColumns[colName]!; const value = set[colName] ?? sql.param(col.onUpdateFn!(), col); - const res = sql`${sql.identifier(col.name)} = ${value}`; + const res = sql`${sql.identifier(this.casing.getColumnCasing(col))} = ${value}`; if (i < setSize - 1) { return [res, sql.raw(', ')]; @@ -96,18 +117,26 @@ export abstract class SQLiteDialect { })); } - buildUpdateQuery({ table, set, where, returning, withList }: SQLiteUpdateConfig): SQL { + buildUpdateQuery({ table, set, where, returning, withList, joins, from, limit, orderBy }: SQLiteUpdateConfig): SQL { const withSql = this.buildWithCTE(withList); const setSql = this.buildUpdateSet(table, set); + const fromSql = from && sql.join([sql.raw(' from '), this.buildFromTable(from)]); + + const joinsSql = this.buildJoins(joins); + const returningSql = returning ? sql` returning ${this.buildSelection(returning, { isSingleTable: true })}` : undefined; const whereSql = where ? sql` where ${where}` : undefined; - return sql`${withSql}update ${table} set ${setSql}${whereSql}${returningSql}`; + const orderBySql = this.buildOrderBy(orderBy); + + const limitSql = this.buildLimit(limit); + + return sql`${withSql}update ${table} set ${setSql}${fromSql}${joinsSql}${whereSql}${returningSql}${orderBySql}${limitSql}`; } /** @@ -141,7 +170,7 @@ export abstract class SQLiteDialect { new SQL( query.queryChunks.map((c) => { if (is(c, Column)) { - return sql.identifier(c.name); + return sql.identifier(this.casing.getColumnCasing(c)); } return c; }), @@ -156,11 +185,10 @@ export abstract class SQLiteDialect { } } else if (is(field, Column)) { const tableName = field.table[Table.Symbol.Name]; - const columnName = field.name; if (isSingleTable) { - chunk.push(sql.identifier(columnName)); + chunk.push(sql.identifier(this.casing.getColumnCasing(field))); } else { - chunk.push(sql`${sql.identifier(tableName)}.${sql.identifier(columnName)}`); + chunk.push(sql`${sql.identifier(tableName)}.${sql.identifier(this.casing.getColumnCasing(field))}`); } } @@ -174,6 +202,76 @@ export abstract class SQLiteDialect { return sql.join(chunks); } + private buildJoins(joins: SQLiteSelectJoinConfig[] | undefined): SQL | undefined { + if (!joins || joins.length === 0) { + return undefined; + } + + const joinsArray: SQL[] = []; + + if (joins) { + for (const [index, joinMeta] of joins.entries()) { + if (index === 0) { + joinsArray.push(sql` `); + } + const table = joinMeta.table; + + if (is(table, SQLiteTable)) { + const tableName = table[SQLiteTable.Symbol.Name]; + const tableSchema = table[SQLiteTable.Symbol.Schema]; + const origTableName = table[SQLiteTable.Symbol.OriginalName]; + const alias = tableName === origTableName ? undefined : joinMeta.alias; + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join ${tableSchema ? sql`${sql.identifier(tableSchema)}.` : undefined}${ + sql.identifier(origTableName) + }${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, + ); + } else { + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join ${table} on ${joinMeta.on}`, + ); + } + if (index < joins.length - 1) { + joinsArray.push(sql` `); + } + } + } + + return sql.join(joinsArray); + } + + private buildLimit(limit: number | Placeholder | undefined): SQL | undefined { + return typeof limit === 'object' || (typeof limit === 'number' && limit >= 0) + ? sql` limit ${limit}` + : undefined; + } + + private buildOrderBy(orderBy: (SQLiteColumn | SQL | SQL.Aliased)[] | undefined): SQL | undefined { + const orderByList: (SQLiteColumn | SQL | SQL.Aliased)[] = []; + + if (orderBy) { + for (const [index, orderByValue] of orderBy.entries()) { + orderByList.push(orderByValue); + + if (index < orderBy.length - 1) { + orderByList.push(sql`, `); + } + } + } + + return orderByList.length > 0 ? sql` order by ${sql.join(orderByList)}` : undefined; + } + + private buildFromTable( + table: SQL | Subquery | SQLiteViewBase | SQLiteTable | undefined, + ): SQL | Subquery | SQLiteViewBase | SQLiteTable | undefined { + if (is(table, Table) && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) { + return sql`${sql.identifier(table[Table.Symbol.OriginalName])} ${sql.identifier(table[Table.Symbol.Name])}`; + } + + return table; + } + buildSelectQuery( { withList, @@ -225,61 +323,14 @@ export abstract class SQLiteDialect { const selection = this.buildSelection(fieldsList, { isSingleTable }); - const tableSql = (() => { - if (is(table, Table) && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) { - return sql`${sql.identifier(table[Table.Symbol.OriginalName])} ${sql.identifier(table[Table.Symbol.Name])}`; - } - - return table; - })(); - - const joinsArray: SQL[] = []; - - if (joins) { - for (const [index, joinMeta] of joins.entries()) { - if (index === 0) { - joinsArray.push(sql` `); - } - const table = joinMeta.table; - - if (is(table, SQLiteTable)) { - const tableName = table[SQLiteTable.Symbol.Name]; - const tableSchema = table[SQLiteTable.Symbol.Schema]; - const origTableName = table[SQLiteTable.Symbol.OriginalName]; - const alias = tableName === origTableName ? undefined : joinMeta.alias; - joinsArray.push( - sql`${sql.raw(joinMeta.joinType)} join ${tableSchema ? sql`${sql.identifier(tableSchema)}.` : undefined}${ - sql.identifier(origTableName) - }${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, - ); - } else { - joinsArray.push( - sql`${sql.raw(joinMeta.joinType)} join ${table} on ${joinMeta.on}`, - ); - } - if (index < joins.length - 1) { - joinsArray.push(sql` `); - } - } - } + const tableSql = this.buildFromTable(table); - const joinsSql = sql.join(joinsArray); + const joinsSql = this.buildJoins(joins); const whereSql = where ? sql` where ${where}` : undefined; const havingSql = having ? sql` having ${having}` : undefined; - const orderByList: (SQLiteColumn | SQL | SQL.Aliased)[] = []; - if (orderBy) { - for (const [index, orderByValue] of orderBy.entries()) { - orderByList.push(orderByValue); - - if (index < orderBy.length - 1) { - orderByList.push(sql`, `); - } - } - } - const groupByList: (SQL | AnyColumn | SQL.Aliased)[] = []; if (groupBy) { for (const [index, groupByValue] of groupBy.entries()) { @@ -293,11 +344,9 @@ export abstract class SQLiteDialect { const groupBySql = groupByList.length > 0 ? sql` group by ${sql.join(groupByList)}` : undefined; - const orderBySql = orderByList.length > 0 ? sql` order by ${sql.join(orderByList)}` : undefined; + const orderBySql = this.buildOrderBy(orderBy); - const limitSql = typeof limit === 'object' || (typeof limit === 'number' && limit >= 0) - ? sql` limit ${limit}` - : undefined; + const limitSql = this.buildLimit(limit); const offsetSql = offset ? sql` offset ${offset}` : undefined; @@ -351,7 +400,7 @@ export abstract class SQLiteDialect { const chunk = singleOrderBy.queryChunks[i]; if (is(chunk, SQLiteColumn)) { - singleOrderBy.queryChunks[i] = sql.identifier(chunk.name); + singleOrderBy.queryChunks[i] = sql.identifier(this.casing.getColumnCasing(chunk)); } } @@ -375,7 +424,9 @@ export abstract class SQLiteDialect { return sql`${leftChunk}${operatorChunk}${rightChunk}${orderBySql}${limitSql}${offsetSql}`; } - buildInsertQuery({ table, values, onConflict, returning, withList }: SQLiteInsertConfig): SQL { + buildInsertQuery( + { table, values: valuesOrSelect, onConflict, returning, withList, select }: SQLiteInsertConfig, + ): SQL { // const isSingleValue = values.length === 1; const valuesSqlList: ((SQLChunk | SQL)[] | SQL)[] = []; const columns: Record = table[Table.Symbol.Columns]; @@ -383,35 +434,48 @@ export abstract class SQLiteDialect { const colEntries: [string, SQLiteColumn][] = Object.entries(columns).filter(([_, col]) => !col.shouldDisableInsert() ); - const insertOrder = colEntries.map(([, column]) => sql.identifier(column.name)); - - for (const [valueIndex, value] of values.entries()) { - const valueList: (SQLChunk | SQL)[] = []; - for (const [fieldName, col] of colEntries) { - const colValue = value[fieldName]; - if (colValue === undefined || (is(colValue, Param) && colValue.value === undefined)) { - let defaultValue; - if (col.default !== null && col.default !== undefined) { - defaultValue = is(col.default, SQL) ? col.default : sql.param(col.default, col); - // eslint-disable-next-line unicorn/no-negated-condition - } else if (col.defaultFn !== undefined) { - const defaultFnResult = col.defaultFn(); - defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); - // eslint-disable-next-line unicorn/no-negated-condition - } else if (!col.default && col.onUpdateFn !== undefined) { - const onUpdateFnResult = col.onUpdateFn(); - defaultValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col); + const insertOrder = colEntries.map(([, column]) => sql.identifier(this.casing.getColumnCasing(column))); + + if (select) { + const select = valuesOrSelect as AnySQLiteSelectQueryBuilder | SQL; + + if (is(select, SQL)) { + valuesSqlList.push(select); + } else { + valuesSqlList.push(select.getSQL()); + } + } else { + const values = valuesOrSelect as Record[]; + valuesSqlList.push(sql.raw('values ')); + + for (const [valueIndex, value] of values.entries()) { + const valueList: (SQLChunk | SQL)[] = []; + for (const [fieldName, col] of colEntries) { + const colValue = value[fieldName]; + if (colValue === undefined || (is(colValue, Param) && colValue.value === undefined)) { + let defaultValue; + if (col.default !== null && col.default !== undefined) { + defaultValue = is(col.default, SQL) ? col.default : sql.param(col.default, col); + // eslint-disable-next-line unicorn/no-negated-condition + } else if (col.defaultFn !== undefined) { + const defaultFnResult = col.defaultFn(); + defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); + // eslint-disable-next-line unicorn/no-negated-condition + } else if (!col.default && col.onUpdateFn !== undefined) { + const onUpdateFnResult = col.onUpdateFn(); + defaultValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col); + } else { + defaultValue = sql`null`; + } + valueList.push(defaultValue); } else { - defaultValue = sql`null`; + valueList.push(colValue); } - valueList.push(defaultValue); - } else { - valueList.push(colValue); } - } - valuesSqlList.push(valueList); - if (valueIndex < values.length - 1) { - valuesSqlList.push(sql`, `); + valuesSqlList.push(valueList); + if (valueIndex < values.length - 1) { + valuesSqlList.push(sql`, `); + } } } @@ -429,11 +493,12 @@ export abstract class SQLiteDialect { // return sql`insert into ${table} default values ${onConflictSql}${returningSql}`; // } - return sql`${withSql}insert into ${table} ${insertOrder} values ${valuesSql}${onConflictSql}${returningSql}`; + return sql`${withSql}insert into ${table} ${insertOrder} ${valuesSql}${onConflictSql}${returningSql}`; } sqlToQuery(sql: SQL, invokeSource?: 'indexes' | undefined): QueryWithTypings { return sql.toQuery({ + casing: this.casing, escapeName: this.escapeName, escapeParam: this.escapeParam, escapeString: this.escapeString, @@ -645,7 +710,11 @@ export abstract class SQLiteDialect { let field = sql`json_array(${ sql.join( selection.map(({ field }) => - is(field, SQLiteColumn) ? sql.identifier(field.name) : is(field, SQL.Aliased) ? field.sql : field + is(field, SQLiteColumn) + ? sql.identifier(this.casing.getColumnCasing(field)) + : is(field, SQL.Aliased) + ? field.sql + : field ), sql`, `, ) @@ -729,7 +798,7 @@ export abstract class SQLiteDialect { } export class SQLiteSyncDialect extends SQLiteDialect { - static readonly [entityKind]: string = 'SQLiteSyncDialect'; + static override readonly [entityKind]: string = 'SQLiteSyncDialect'; migrate( migrations: MigrationMeta[], @@ -781,7 +850,7 @@ export class SQLiteSyncDialect extends SQLiteDialect { } export class SQLiteAsyncDialect extends SQLiteDialect { - static readonly [entityKind]: string = 'SQLiteAsyncDialect'; + static override readonly [entityKind]: string = 'SQLiteAsyncDialect'; async migrate( migrations: MigrationMeta[], diff --git a/drizzle-orm/src/sqlite-core/foreign-keys.ts b/drizzle-orm/src/sqlite-core/foreign-keys.ts index 9cc14072f..1c947f7f5 100644 --- a/drizzle-orm/src/sqlite-core/foreign-keys.ts +++ b/drizzle-orm/src/sqlite-core/foreign-keys.ts @@ -1,6 +1,7 @@ import { entityKind } from '~/entity.ts'; +import { TableName } from '~/table.utils.ts'; import type { AnySQLiteColumn, SQLiteColumn } from './columns/index.ts'; -import { SQLiteTable } from './table.ts'; +import type { SQLiteTable } from './table.ts'; export type UpdateDeleteAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default'; @@ -83,9 +84,9 @@ export class ForeignKey { const columnNames = columns.map((column) => column.name); const foreignColumnNames = foreignColumns.map((column) => column.name); const chunks = [ - this.table[SQLiteTable.Symbol.Name], + this.table[TableName], ...columnNames, - foreignColumns[0]!.table[SQLiteTable.Symbol.Name], + foreignColumns[0]!.table[TableName], ...foreignColumnNames, ]; return name ?? `${chunks.join('_')}_fk`; diff --git a/drizzle-orm/src/sqlite-core/query-builders/count.ts b/drizzle-orm/src/sqlite-core/query-builders/count.ts new file mode 100644 index 000000000..1c1234034 --- /dev/null +++ b/drizzle-orm/src/sqlite-core/query-builders/count.ts @@ -0,0 +1,76 @@ +import { entityKind } from '~/entity.ts'; +import { SQL, sql, type SQLWrapper } from '~/sql/sql.ts'; +import type { SQLiteSession } from '../session.ts'; +import type { SQLiteTable } from '../table.ts'; +import type { SQLiteView } from '../view.ts'; + +export class SQLiteCountBuilder< + TSession extends SQLiteSession, +> extends SQL implements Promise, SQLWrapper { + private sql: SQL; + + static override readonly [entityKind] = 'SQLiteCountBuilderAsync'; + [Symbol.toStringTag] = 'SQLiteCountBuilderAsync'; + + private session: TSession; + + private static buildEmbeddedCount( + source: SQLiteTable | SQLiteView | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`; + } + + private static buildCount( + source: SQLiteTable | SQLiteView | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters}`; + } + + constructor( + readonly params: { + source: SQLiteTable | SQLiteView | SQL | SQLWrapper; + filters?: SQL; + session: TSession; + }, + ) { + super(SQLiteCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks); + + this.session = params.session; + + this.sql = SQLiteCountBuilder.buildCount( + params.source, + params.filters, + ); + } + + then( + onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined, + ): Promise { + return Promise.resolve(this.session.count(this.sql)).then( + onfulfilled, + onrejected, + ); + } + + catch( + onRejected?: ((reason: any) => never | PromiseLike) | null | undefined, + ): Promise { + return this.then(undefined, onRejected); + } + + finally(onFinally?: (() => void) | null | undefined): Promise { + return this.then( + (value) => { + onFinally?.(); + return value; + }, + (reason) => { + onFinally?.(); + throw reason; + }, + ); + } +} diff --git a/drizzle-orm/src/sqlite-core/query-builders/delete.ts b/drizzle-orm/src/sqlite-core/query-builders/delete.ts index 1a028c09a..53e8d6227 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/delete.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/delete.ts @@ -2,12 +2,14 @@ import { entityKind } from '~/entity.ts'; import type { SelectResultFields } from '~/query-builders/select.types.ts'; import { QueryPromise } from '~/query-promise.ts'; import type { RunnableQuery } from '~/runnable-query.ts'; -import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts'; import type { SQLiteDialect } from '~/sqlite-core/dialect.ts'; import type { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.ts'; import { SQLiteTable } from '~/sqlite-core/table.ts'; import type { Subquery } from '~/subquery.ts'; -import { type DrizzleTypeError, orderSelectedFields } from '~/utils.ts'; +import { Table } from '~/table.ts'; +import { type DrizzleTypeError, orderSelectedFields, type ValueOrArray } from '~/utils.ts'; import type { SQLiteColumn } from '../columns/common.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; @@ -37,6 +39,8 @@ export type SQLiteDelete< export interface SQLiteDeleteConfig { where?: SQL | undefined; + limit?: number | Placeholder; + orderBy?: (SQLiteColumn | SQL | SQL.Aliased)[]; table: SQLiteTable; returning?: SelectedFieldsOrdered; withList?: Subquery[]; @@ -136,7 +140,7 @@ export class SQLiteDeleteBase< > extends QueryPromise implements RunnableQuery, SQLWrapper { - static readonly [entityKind]: string = 'SQLiteDelete'; + static override readonly [entityKind]: string = 'SQLiteDelete'; /** @internal */ config: SQLiteDeleteConfig; @@ -185,6 +189,37 @@ export class SQLiteDeleteBase< return this as any; } + orderBy( + builder: (deleteTable: TTable) => ValueOrArray, + ): SQLiteDeleteWithout; + orderBy(...columns: (SQLiteColumn | SQL | SQL.Aliased)[]): SQLiteDeleteWithout; + orderBy( + ...columns: + | [(deleteTable: TTable) => ValueOrArray] + | (SQLiteColumn | SQL | SQL.Aliased)[] + ): SQLiteDeleteWithout { + if (typeof columns[0] === 'function') { + const orderBy = columns[0]( + new Proxy( + this.config.table[Table.Symbol.Columns], + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as any, + ); + + const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; + this.config.orderBy = orderByArray; + } else { + const orderByArray = columns as (SQLiteColumn | SQL | SQL.Aliased)[]; + this.config.orderBy = orderByArray; + } + return this as any; + } + + limit(limit: number | Placeholder): SQLiteDeleteWithout { + this.config.limit = limit; + return this as any; + } + /** * Adds a `returning` clause to the query. * diff --git a/drizzle-orm/src/sqlite-core/query-builders/insert.ts b/drizzle-orm/src/sqlite-core/query-builders/insert.ts index b0861fade..2c26df8df 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/insert.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/insert.ts @@ -1,4 +1,5 @@ import { entityKind, is } from '~/entity.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { SelectResultFields } from '~/query-builders/select.types.ts'; import { QueryPromise } from '~/query-promise.ts'; import type { RunnableQuery } from '~/runnable-query.ts'; @@ -9,18 +10,20 @@ import type { IndexColumn } from '~/sqlite-core/indexes.ts'; import type { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.ts'; import { SQLiteTable } from '~/sqlite-core/table.ts'; import type { Subquery } from '~/subquery.ts'; -import { Table } from '~/table.ts'; -import { type DrizzleTypeError, mapUpdateSet, orderSelectedFields, type Simplify } from '~/utils.ts'; -import type { SQLiteColumn } from '../columns/common.ts'; +import { Columns, Table } from '~/table.ts'; +import { type DrizzleTypeError, haveSameKeys, mapUpdateSet, orderSelectedFields, type Simplify } from '~/utils.ts'; +import type { AnySQLiteColumn, SQLiteColumn } from '../columns/common.ts'; +import { QueryBuilder } from './query-builder.ts'; import type { SelectedFieldsFlat, SelectedFieldsOrdered } from './select.types.ts'; import type { SQLiteUpdateSetSource } from './update.ts'; export interface SQLiteInsertConfig { table: TTable; - values: Record[]; + values: Record[] | SQLiteInsertSelectQueryBuilder | SQL; withList?: Subquery[]; onConflict?: SQL; returning?: SelectedFieldsOrdered; + select?: boolean; } export type SQLiteInsertValue = Simplify< @@ -29,6 +32,10 @@ export type SQLiteInsertValue = Simplify< } >; +export type SQLiteInsertSelectQueryBuilder = TypedQueryBuilder< + { [K in keyof TTable['$inferInsert']]: AnySQLiteColumn | SQL | SQL.Aliased | TTable['$inferInsert'][K] } +>; + export class SQLiteInsertBuilder< TTable extends SQLiteTable, TResultType extends 'sync' | 'async', @@ -70,6 +77,32 @@ export class SQLiteInsertBuilder< return new SQLiteInsertBase(this.table, mappedValues, this.session, this.dialect, this.withList); } + + select( + selectQuery: (qb: QueryBuilder) => SQLiteInsertSelectQueryBuilder, + ): SQLiteInsertBase; + select(selectQuery: (qb: QueryBuilder) => SQL): SQLiteInsertBase; + select(selectQuery: SQL): SQLiteInsertBase; + select(selectQuery: SQLiteInsertSelectQueryBuilder): SQLiteInsertBase; + select( + selectQuery: + | SQL + | SQLiteInsertSelectQueryBuilder + | ((qb: QueryBuilder) => SQLiteInsertSelectQueryBuilder | SQL), + ): SQLiteInsertBase { + const select = typeof selectQuery === 'function' ? selectQuery(new QueryBuilder()) : selectQuery; + + if ( + !is(select, SQL) + && !haveSameKeys(this.table[Columns], select._.selectedFields) + ) { + throw new Error( + 'Insert select error: selected fields are not the same or are in a different order compared to the table definition', + ); + } + + return new SQLiteInsertBase(this.table, select, this.session, this.dialect, this.withList, true); + } } export type SQLiteInsertWithout = @@ -199,7 +232,7 @@ export class SQLiteInsertBase< > extends QueryPromise implements RunnableQuery, SQLWrapper { - static readonly [entityKind]: string = 'SQLiteInsert'; + static override readonly [entityKind]: string = 'SQLiteInsert'; /** @internal */ config: SQLiteInsertConfig; @@ -210,9 +243,10 @@ export class SQLiteInsertBase< private session: SQLiteSession, private dialect: SQLiteDialect, withList?: Subquery[], + select?: boolean, ) { super(); - this.config = { table, values, withList }; + this.config = { table, values: values as any, withList, select }; } /** diff --git a/drizzle-orm/src/sqlite-core/query-builders/query-builder.ts b/drizzle-orm/src/sqlite-core/query-builders/query-builder.ts index 43000fc10..d2df0cf99 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/query-builder.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/query-builder.ts @@ -1,8 +1,9 @@ -import { entityKind } from '~/entity.ts'; +import { entityKind, is } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; import type { ColumnsSelection } from '~/sql/sql.ts'; -import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; +import type { SQLiteDialectConfig } from '~/sqlite-core/dialect.ts'; +import { SQLiteDialect, SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; import type { WithSubqueryWithSelection } from '~/sqlite-core/subquery.ts'; import { WithSubquery } from '~/subquery.ts'; import { SQLiteSelectBuilder } from './select.ts'; @@ -11,7 +12,13 @@ import type { SelectedFields } from './select.types.ts'; export class QueryBuilder { static readonly [entityKind]: string = 'SQLiteQueryBuilder'; - private dialect: SQLiteSyncDialect | undefined; + private dialect: SQLiteDialect | undefined; + private dialectConfig: SQLiteDialectConfig | undefined; + + constructor(dialect?: SQLiteDialect | SQLiteDialectConfig) { + this.dialect = is(dialect, SQLiteDialect) ? dialect : undefined; + this.dialectConfig = is(dialect, SQLiteDialect) ? undefined : dialect; + } $with(alias: TAlias) { const queryBuilder = this; @@ -97,7 +104,7 @@ export class QueryBuilder { // Lazy load dialect to avoid circular dependency private getDialect() { if (!this.dialect) { - this.dialect = new SQLiteSyncDialect(); + this.dialect = new SQLiteSyncDialect(this.dialectConfig); } return this.dialect; diff --git a/drizzle-orm/src/sqlite-core/query-builders/query.ts b/drizzle-orm/src/sqlite-core/query-builders/query.ts index 9ae47f9ce..ef93e992a 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/query.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/query.ts @@ -98,7 +98,7 @@ export class RelationalQueryBuilder< export class SQLiteRelationalQuery extends QueryPromise implements RunnableQuery, SQLWrapper { - static readonly [entityKind]: string = 'SQLiteAsyncRelationalQuery'; + static override readonly [entityKind]: string = 'SQLiteAsyncRelationalQuery'; declare readonly _: { readonly dialect: 'sqlite'; @@ -199,7 +199,7 @@ export class SQLiteRelationalQuery exte } export class SQLiteSyncRelationalQuery extends SQLiteRelationalQuery<'sync', TResult> { - static readonly [entityKind]: string = 'SQLiteSyncRelationalQuery'; + static override readonly [entityKind]: string = 'SQLiteSyncRelationalQuery'; sync(): TResult { return this.executeRaw(); diff --git a/drizzle-orm/src/sqlite-core/query-builders/raw.ts b/drizzle-orm/src/sqlite-core/query-builders/raw.ts index 48fc5fbf9..488f45afe 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/raw.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/raw.ts @@ -1,8 +1,8 @@ import { entityKind } from '~/entity.ts'; -import type { SQL, SQLWrapper } from '~/index.ts'; import { QueryPromise } from '~/query-promise.ts'; import type { RunnableQuery } from '~/runnable-query.ts'; import type { PreparedQuery } from '~/session.ts'; +import type { SQL, SQLWrapper } from '~/sql/sql.ts'; import type { SQLiteAsyncDialect } from '../dialect.ts'; type SQLiteRawAction = 'all' | 'get' | 'values' | 'run'; @@ -15,7 +15,7 @@ export interface SQLiteRaw extends QueryPromise, RunnableQuery export class SQLiteRaw extends QueryPromise implements RunnableQuery, SQLWrapper, PreparedQuery { - static readonly [entityKind]: string = 'SQLiteRaw'; + static override readonly [entityKind]: string = 'SQLiteRaw'; declare readonly _: { readonly dialect: 'sqlite'; diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.ts b/drizzle-orm/src/sqlite-core/query-builders/select.ts index b7f4b0465..443bf8480 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.ts @@ -39,12 +39,12 @@ import type { SelectedFields, SetOperatorRightSelect, SQLiteCreateSetOperatorFn, - SQLiteJoinFn, SQLiteSelectConfig, SQLiteSelectDynamic, SQLiteSelectExecute, SQLiteSelectHKT, SQLiteSelectHKTBase, + SQLiteSelectJoinFn, SQLiteSelectPrepare, SQLiteSelectWithout, SQLiteSetOperatorExcludedMethods, @@ -137,7 +137,7 @@ export abstract class SQLiteSelectQueryBuilderBase< TResult extends any[] = SelectResult[], TSelectedFields extends ColumnsSelection = BuildSubquerySelection, > extends TypedQueryBuilder { - static readonly [entityKind]: string = 'SQLiteSelectQueryBuilder'; + static override readonly [entityKind]: string = 'SQLiteSelectQueryBuilder'; override readonly _: { readonly dialect: 'sqlite'; @@ -193,7 +193,7 @@ export abstract class SQLiteSelectQueryBuilderBase< private createJoin( joinType: TJoinType, - ): SQLiteJoinFn { + ): SQLiteSelectJoinFn { return ( table: SQLiteTable | Subquery | SQLiteViewBase | SQL, on: ((aliases: TSelection) => SQL | undefined) | SQL | undefined, @@ -854,7 +854,7 @@ export class SQLiteSelectBase< TResult, TSelectedFields > implements RunnableQuery, SQLWrapper { - static readonly [entityKind]: string = 'SQLiteSelect'; + static override readonly [entityKind]: string = 'SQLiteSelect'; /** @internal */ _prepare(isOneTimeQuery = true): SQLiteSelectPrepare { diff --git a/drizzle-orm/src/sqlite-core/query-builders/select.types.ts b/drizzle-orm/src/sqlite-core/query-builders/select.types.ts index 48c7054fa..e76a09f3e 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/select.types.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/select.types.ts @@ -72,7 +72,7 @@ export interface SQLiteSelectConfig { }[]; } -export type SQLiteJoin< +export type SQLiteSelectJoin< T extends AnySQLiteSelectQueryBuilder, TDynamic extends boolean, TJoinType extends JoinType, @@ -103,7 +103,7 @@ export type SQLiteJoin< > : never; -export type SQLiteJoinFn< +export type SQLiteSelectJoinFn< T extends AnySQLiteSelectQueryBuilder, TDynamic extends boolean, TJoinType extends JoinType, @@ -113,7 +113,7 @@ export type SQLiteJoinFn< >( table: TJoinedTable, on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, -) => SQLiteJoin; +) => SQLiteSelectJoin; export type SelectedFieldsFlat = SelectFieldsFlatBase; diff --git a/drizzle-orm/src/sqlite-core/query-builders/update.ts b/drizzle-orm/src/sqlite-core/query-builders/update.ts index 7b25c090a..6915d60a9 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/update.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/update.ts @@ -1,21 +1,36 @@ import type { GetColumnData } from '~/column.ts'; -import { entityKind } from '~/entity.ts'; -import type { SelectResultFields } from '~/query-builders/select.types.ts'; +import { entityKind, is } from '~/entity.ts'; +import type { JoinType, SelectResultFields } from '~/query-builders/select.types.ts'; import { QueryPromise } from '~/query-promise.ts'; import type { RunnableQuery } from '~/runnable-query.ts'; -import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { Placeholder, Query, SQL, SQLWrapper } from '~/sql/sql.ts'; import type { SQLiteDialect } from '~/sqlite-core/dialect.ts'; import type { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.ts'; import { SQLiteTable } from '~/sqlite-core/table.ts'; -import type { Subquery } from '~/subquery.ts'; -import { type DrizzleTypeError, mapUpdateSet, orderSelectedFields, type UpdateSet } from '~/utils.ts'; +import { Subquery } from '~/subquery.ts'; +import { Table } from '~/table.ts'; +import { + type DrizzleTypeError, + getTableLikeName, + mapUpdateSet, + orderSelectedFields, + type UpdateSet, + type ValueOrArray, +} from '~/utils.ts'; +import { ViewBaseConfig } from '~/view-common.ts'; import type { SQLiteColumn } from '../columns/common.ts'; -import type { SelectedFields, SelectedFieldsOrdered } from './select.types.ts'; +import { SQLiteViewBase } from '../view-base.ts'; +import type { SelectedFields, SelectedFieldsOrdered, SQLiteSelectJoinConfig } from './select.types.ts'; export interface SQLiteUpdateConfig { where?: SQL | undefined; + limit?: number | Placeholder; + orderBy?: (SQLiteColumn | SQL | SQL.Aliased)[]; set: UpdateSet; table: SQLiteTable; + from?: SQLiteTable | Subquery | SQLiteViewBase | SQL; + joins: SQLiteSelectJoinConfig[]; returning?: SelectedFieldsOrdered; withList?: Subquery[]; } @@ -24,7 +39,9 @@ export type SQLiteUpdateSetSource = & { [Key in keyof TTable['$inferInsert']]?: | GetColumnData - | SQL; + | SQL + | SQLiteColumn + | undefined; } & {}; @@ -46,14 +63,20 @@ export class SQLiteUpdateBuilder< private withList?: Subquery[], ) {} - set(values: SQLiteUpdateSetSource): SQLiteUpdateBase { + set( + values: SQLiteUpdateSetSource, + ): SQLiteUpdateWithout< + SQLiteUpdateBase, + false, + 'leftJoin' | 'rightJoin' | 'innerJoin' | 'fullJoin' + > { return new SQLiteUpdateBase( this.table, mapUpdateSet(this.table, values), this.session, this.dialect, this.withList, - ); + ) as any; } } @@ -66,6 +89,7 @@ export type SQLiteUpdateWithout< T['_']['table'], T['_']['resultType'], T['_']['runResult'], + T['_']['from'], T['_']['returning'], TDynamic, T['_']['excludedMethods'] | K @@ -73,11 +97,29 @@ export type SQLiteUpdateWithout< T['_']['excludedMethods'] | K >; +export type SQLiteUpdateWithJoins< + T extends AnySQLiteUpdate, + TDynamic extends boolean, + TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL, +> = TDynamic extends true ? T : Omit< + SQLiteUpdateBase< + T['_']['table'], + T['_']['resultType'], + T['_']['runResult'], + TFrom, + T['_']['returning'], + TDynamic, + Exclude + >, + Exclude +>; + export type SQLiteUpdateReturningAll = SQLiteUpdateWithout< SQLiteUpdateBase< T['_']['table'], T['_']['resultType'], T['_']['runResult'], + T['_']['from'], T['_']['table']['$inferSelect'], TDynamic, T['_']['excludedMethods'] @@ -95,6 +137,7 @@ export type SQLiteUpdateReturning< T['_']['table'], T['_']['resultType'], T['_']['runResult'], + T['_']['from'], SelectResultFields, TDynamic, T['_']['excludedMethods'] @@ -120,6 +163,25 @@ export type SQLiteUpdatePrepare = SQLitePreparedQuery } >; +export type SQLiteUpdateJoinFn< + T extends AnySQLiteUpdate, +> = < + TJoinedTable extends SQLiteTable | Subquery | SQLiteViewBase | SQL, +>( + table: TJoinedTable, + on: + | ( + ( + updateTable: T['_']['table']['_']['columns'], + from: T['_']['from'] extends SQLiteTable ? T['_']['from']['_']['columns'] + : T['_']['from'] extends Subquery | SQLiteViewBase ? T['_']['from']['_']['selectedFields'] + : never, + ) => SQL | undefined + ) + | SQL + | undefined, +) => T; + export type SQLiteUpdateDynamic = SQLiteUpdate< T['_']['table'], T['_']['resultType'], @@ -131,15 +193,17 @@ export type SQLiteUpdate< TTable extends SQLiteTable = SQLiteTable, TResultType extends 'sync' | 'async' = 'sync' | 'async', TRunResult = any, + TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined, TReturning extends Record | undefined = Record | undefined, -> = SQLiteUpdateBase; +> = SQLiteUpdateBase; -type AnySQLiteUpdate = SQLiteUpdateBase; +export type AnySQLiteUpdate = SQLiteUpdateBase; export interface SQLiteUpdateBase< TTable extends SQLiteTable = SQLiteTable, TResultType extends 'sync' | 'async' = 'sync' | 'async', TRunResult = unknown, + TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined, TReturning = undefined, TDynamic extends boolean = false, TExcludedMethods extends string = never, @@ -149,6 +213,7 @@ export interface SQLiteUpdateBase< readonly table: TTable; readonly resultType: TResultType; readonly runResult: TRunResult; + readonly from: TFrom; readonly returning: TReturning; readonly dynamic: TDynamic; readonly excludedMethods: TExcludedMethods; @@ -161,6 +226,7 @@ export class SQLiteUpdateBase< // eslint-disable-next-line @typescript-eslint/no-unused-vars TResultType extends 'sync' | 'async' = 'sync' | 'async', TRunResult = unknown, + TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined, TReturning = undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars TDynamic extends boolean = false, @@ -169,7 +235,7 @@ export class SQLiteUpdateBase< > extends QueryPromise implements RunnableQuery, SQLWrapper { - static readonly [entityKind]: string = 'SQLiteUpdate'; + static override readonly [entityKind]: string = 'SQLiteUpdate'; /** @internal */ config: SQLiteUpdateConfig; @@ -182,9 +248,65 @@ export class SQLiteUpdateBase< withList?: Subquery[], ) { super(); - this.config = { set, table, withList }; + this.config = { set, table, withList, joins: [] }; + } + + from( + source: TFrom, + ): SQLiteUpdateWithJoins { + this.config.from = source; + return this as any; } + private createJoin( + joinType: TJoinType, + ): SQLiteUpdateJoinFn { + return (( + table: SQLiteTable | Subquery | SQLiteViewBase | SQL, + on: ((updateTable: TTable, from: TFrom) => SQL | undefined) | SQL | undefined, + ) => { + const tableName = getTableLikeName(table); + + if (typeof tableName === 'string' && this.config.joins.some((join) => join.alias === tableName)) { + throw new Error(`Alias "${tableName}" is already used in this query`); + } + + if (typeof on === 'function') { + const from = this.config.from + ? is(table, SQLiteTable) + ? table[Table.Symbol.Columns] + : is(table, Subquery) + ? table._.selectedFields + : is(table, SQLiteViewBase) + ? table[ViewBaseConfig].selectedFields + : undefined + : undefined; + on = on( + new Proxy( + this.config.table[Table.Symbol.Columns], + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as any, + from && new Proxy( + from, + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as any, + ); + } + + this.config.joins.push({ on, table, joinType, alias: tableName }); + + return this as any; + }) as any; + } + + leftJoin = this.createJoin('left'); + + rightJoin = this.createJoin('right'); + + innerJoin = this.createJoin('inner'); + + fullJoin = this.createJoin('full'); + /** * Adds a 'where' clause to the query. * @@ -223,6 +345,37 @@ export class SQLiteUpdateBase< return this as any; } + orderBy( + builder: (updateTable: TTable) => ValueOrArray, + ): SQLiteUpdateWithout; + orderBy(...columns: (SQLiteColumn | SQL | SQL.Aliased)[]): SQLiteUpdateWithout; + orderBy( + ...columns: + | [(updateTable: TTable) => ValueOrArray] + | (SQLiteColumn | SQL | SQL.Aliased)[] + ): SQLiteUpdateWithout { + if (typeof columns[0] === 'function') { + const orderBy = columns[0]( + new Proxy( + this.config.table[Table.Symbol.Columns], + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as any, + ); + + const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; + this.config.orderBy = orderByArray; + } else { + const orderByArray = columns as (SQLiteColumn | SQL | SQL.Aliased)[]; + this.config.orderBy = orderByArray; + } + return this as any; + } + + limit(limit: number | Placeholder): SQLiteUpdateWithout { + this.config.limit = limit; + return this as any; + } + /** * Adds a `returning` clause to the query. * diff --git a/drizzle-orm/src/sqlite-core/session.ts b/drizzle-orm/src/sqlite-core/session.ts index 4ac987b4a..9e6924ca0 100644 --- a/drizzle-orm/src/sqlite-core/session.ts +++ b/drizzle-orm/src/sqlite-core/session.ts @@ -20,7 +20,7 @@ export interface PreparedQueryConfig { } export class ExecuteResultSync extends QueryPromise { - static readonly [entityKind]: string = 'ExecuteResultSync'; + static override readonly [entityKind]: string = 'ExecuteResultSync'; constructor(private resultCb: () => T) { super(); @@ -187,6 +187,12 @@ export abstract class SQLiteSession< >; } + async count(sql: SQL) { + const result = await this.values(sql) as [[number]]; + + return result[0][0]; + } + /** @internal */ extractRawValuesValueFromBatchResult(_result: unknown): unknown { throw new Error('Not implemented'); @@ -203,7 +209,7 @@ export abstract class SQLiteTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends BaseSQLiteDatabase { - static readonly [entityKind]: string = 'SQLiteTransaction'; + static override readonly [entityKind]: string = 'SQLiteTransaction'; constructor( resultType: TResultType, diff --git a/drizzle-orm/src/sqlite-core/table.ts b/drizzle-orm/src/sqlite-core/table.ts index 8f787d98c..290605b66 100644 --- a/drizzle-orm/src/sqlite-core/table.ts +++ b/drizzle-orm/src/sqlite-core/table.ts @@ -2,19 +2,23 @@ import type { BuildColumns, BuildExtraConfigColumns } from '~/column-builder.ts' import { entityKind } from '~/entity.ts'; import { Table, type TableConfig as TableConfigBase, type UpdateTableConfig } from '~/table.ts'; import type { CheckBuilder } from './checks.ts'; +import { getSQLiteColumnBuilders, type SQLiteColumnBuilders } from './columns/all.ts'; import type { SQLiteColumn, SQLiteColumnBuilder, SQLiteColumnBuilderBase } from './columns/common.ts'; import type { ForeignKey, ForeignKeyBuilder } from './foreign-keys.ts'; import type { IndexBuilder } from './indexes.ts'; import type { PrimaryKeyBuilder } from './primary-keys.ts'; import type { UniqueConstraintBuilder } from './unique-constraint.ts'; -export type SQLiteTableExtraConfig = Record< - string, +export type SQLiteTableExtraConfigValue = | IndexBuilder | CheckBuilder | ForeignKeyBuilder | PrimaryKeyBuilder - | UniqueConstraintBuilder + | UniqueConstraintBuilder; + +export type SQLiteTableExtraConfig = Record< + string, + SQLiteTableExtraConfigValue >; export type TableConfig = TableConfigBase>; @@ -23,7 +27,7 @@ export type TableConfig = TableConfigBase>; export const InlineForeignKeys = Symbol.for('drizzle:SQLiteInlineForeignKeys'); export class SQLiteTable extends Table { - static readonly [entityKind]: string = 'SQLiteTable'; + static override readonly [entityKind]: string = 'SQLiteTable'; /** @internal */ static override readonly Symbol = Object.assign({}, Table.Symbol, { @@ -53,6 +57,57 @@ export type SQLiteTableWithColumns = }; export interface SQLiteTableFn { + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig?: ( + self: BuildColumns, + ) => SQLiteTableExtraConfigValue[], + ): SQLiteTableWithColumns<{ + name: TTableName; + schema: TSchema; + columns: BuildColumns; + dialect: 'sqlite'; + }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: SQLiteColumnBuilders) => TColumnsMap, + extraConfig?: (self: BuildColumns) => SQLiteTableExtraConfigValue[], + ): SQLiteTableWithColumns<{ + name: TTableName; + schema: TSchema; + columns: BuildColumns; + dialect: 'sqlite'; + }>; + /** + * @deprecated The third parameter of sqliteTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = sqliteTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = sqliteTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ < TTableName extends string, TColumnsMap extends Record, @@ -66,6 +121,42 @@ export interface SQLiteTableFn { columns: BuildColumns; dialect: 'sqlite'; }>; + + /** + * @deprecated The third parameter of sqliteTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = sqliteTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = sqliteTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: SQLiteColumnBuilders) => TColumnsMap, + extraConfig?: (self: BuildColumns) => SQLiteTableExtraConfig, + ): SQLiteTableWithColumns<{ + name: TTableName; + schema: TSchema; + columns: BuildColumns; + dialect: 'sqlite'; + }>; } function sqliteTableBase< @@ -74,8 +165,12 @@ function sqliteTableBase< TSchema extends string | undefined, >( name: TTableName, - columns: TColumnsMap, - extraConfig?: (self: BuildColumns) => SQLiteTableExtraConfig, + columns: TColumnsMap | ((columnTypes: SQLiteColumnBuilders) => TColumnsMap), + extraConfig: + | (( + self: BuildColumns, + ) => SQLiteTableExtraConfig | SQLiteTableExtraConfigValue[]) + | undefined, schema?: TSchema, baseName = name, ): SQLiteTableWithColumns<{ @@ -91,9 +186,12 @@ function sqliteTableBase< dialect: 'sqlite'; }>(name, schema, baseName); + const parsedColumns: TColumnsMap = typeof columns === 'function' ? columns(getSQLiteColumnBuilders()) : columns; + const builtColumns = Object.fromEntries( - Object.entries(columns).map(([name, colBuilderBase]) => { + Object.entries(parsedColumns).map(([name, colBuilderBase]) => { const colBuilder = colBuilderBase as SQLiteColumnBuilder; + colBuilder.setName(name); const column = colBuilder.build(rawTable); rawTable[InlineForeignKeys].push(...colBuilder.buildForeignKeys(column, rawTable)); return [name, column]; diff --git a/drizzle-orm/src/sqlite-core/unique-constraint.ts b/drizzle-orm/src/sqlite-core/unique-constraint.ts index 83dfcebdd..e9c47e7d3 100644 --- a/drizzle-orm/src/sqlite-core/unique-constraint.ts +++ b/drizzle-orm/src/sqlite-core/unique-constraint.ts @@ -1,9 +1,10 @@ import { entityKind } from '~/entity.ts'; +import { TableName } from '~/table.utils.ts'; import type { SQLiteColumn } from './columns/common.ts'; -import { SQLiteTable } from './table.ts'; +import type { SQLiteTable } from './table.ts'; export function uniqueKeyName(table: SQLiteTable, columns: string[]) { - return `${table[SQLiteTable.Symbol.Name]}_${columns.join('_')}_unique`; + return `${table[TableName]}_${columns.join('_')}_unique`; } export function unique(name?: string): UniqueOnConstraintBuilder { diff --git a/drizzle-orm/src/sqlite-core/utils.ts b/drizzle-orm/src/sqlite-core/utils.ts index 2312466c4..7d21483b0 100644 --- a/drizzle-orm/src/sqlite-core/utils.ts +++ b/drizzle-orm/src/sqlite-core/utils.ts @@ -11,7 +11,6 @@ import type { PrimaryKey } from './primary-keys.ts'; import { PrimaryKeyBuilder } from './primary-keys.ts'; import { SQLiteTable } from './table.ts'; import { type UniqueConstraint, UniqueConstraintBuilder } from './unique-constraint.ts'; -import { SQLiteViewConfig } from './view-common.ts'; import type { SQLiteView } from './view.ts'; export function getTableConfig(table: TTable) { @@ -27,7 +26,8 @@ export function getTableConfig(table: TTable) { if (extraConfigBuilder !== undefined) { const extraConfig = extraConfigBuilder(table[SQLiteTable.Symbol.Columns]); - for (const builder of Object.values(extraConfig)) { + const extraValues = Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); + for (const builder of Object.values(extraValues)) { if (is(builder, IndexBuilder)) { indexes.push(builder.build(table)); } else if (is(builder, CheckBuilder)) { @@ -61,6 +61,6 @@ export function getViewConfig< >(view: SQLiteView) { return { ...view[ViewBaseConfig], - ...view[SQLiteViewConfig], + // ...view[SQLiteViewConfig], }; } diff --git a/drizzle-orm/src/sqlite-core/view-base.ts b/drizzle-orm/src/sqlite-core/view-base.ts index ac3328905..dd2f306a9 100644 --- a/drizzle-orm/src/sqlite-core/view-base.ts +++ b/drizzle-orm/src/sqlite-core/view-base.ts @@ -7,7 +7,7 @@ export abstract class SQLiteViewBase< TExisting extends boolean = boolean, TSelection extends ColumnsSelection = ColumnsSelection, > extends View { - static readonly [entityKind]: string = 'SQLiteViewBase'; + static override readonly [entityKind]: string = 'SQLiteViewBase'; declare _: View['_'] & { viewBrand: 'SQLiteView'; diff --git a/drizzle-orm/src/sqlite-core/view.ts b/drizzle-orm/src/sqlite-core/view.ts index d1f11969e..1caf211ce 100644 --- a/drizzle-orm/src/sqlite-core/view.ts +++ b/drizzle-orm/src/sqlite-core/view.ts @@ -7,10 +7,8 @@ import type { ColumnsSelection, SQL } from '~/sql/sql.ts'; import { getTableColumns } from '~/utils.ts'; import type { SQLiteColumn, SQLiteColumnBuilderBase } from './columns/common.ts'; import { QueryBuilder } from './query-builders/query-builder.ts'; -import type { SelectedFields } from './query-builders/select.types.ts'; import { sqliteTable } from './table.ts'; import { SQLiteViewBase } from './view-base.ts'; -import { SQLiteViewConfig } from './view-common.ts'; export interface ViewBuilderConfig { algorithm?: 'undefined' | 'merge' | 'temptable'; @@ -37,9 +35,9 @@ export class ViewBuilderCore< } export class ViewBuilder extends ViewBuilderCore<{ name: TName }> { - static readonly [entityKind]: string = 'SQLiteViewBuilder'; + static override readonly [entityKind]: string = 'SQLiteViewBuilder'; - as( + as( qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), ): SQLiteViewWithSelection> { if (typeof qb === 'function') { @@ -55,7 +53,7 @@ export class ViewBuilder extends ViewBuilderCore< const aliasedSelectedFields = qb.getSelectedFields(); return new Proxy( new SQLiteView({ - sqliteConfig: this.config, + // sqliteConfig: this.config, config: { name: this.name, schema: undefined, @@ -74,7 +72,7 @@ export class ManualViewBuilder< > extends ViewBuilderCore< { name: TName; columns: TColumns } > { - static readonly [entityKind]: string = 'SQLiteManualViewBuilder'; + static override readonly [entityKind]: string = 'SQLiteManualViewBuilder'; private columns: Record; @@ -89,7 +87,6 @@ export class ManualViewBuilder< existing(): SQLiteViewWithSelection> { return new Proxy( new SQLiteView({ - sqliteConfig: undefined, config: { name: this.name, schema: undefined, @@ -109,7 +106,6 @@ export class ManualViewBuilder< as(query: SQL): SQLiteViewWithSelection> { return new Proxy( new SQLiteView({ - sqliteConfig: this.config, config: { name: this.name, schema: undefined, @@ -132,22 +128,17 @@ export class SQLiteView< TExisting extends boolean = boolean, TSelection extends ColumnsSelection = ColumnsSelection, > extends SQLiteViewBase { - static readonly [entityKind]: string = 'SQLiteView'; + static override readonly [entityKind]: string = 'SQLiteView'; - /** @internal */ - [SQLiteViewConfig]: ViewBuilderConfig | undefined; - - constructor({ sqliteConfig, config }: { - sqliteConfig: ViewBuilderConfig | undefined; + constructor({ config }: { config: { name: TName; schema: string | undefined; - selectedFields: SelectedFields; + selectedFields: ColumnsSelection; query: SQL | undefined; }; }) { super(config); - this[SQLiteViewConfig] = sqliteConfig; } } diff --git a/drizzle-orm/src/sqlite-proxy/driver.ts b/drizzle-orm/src/sqlite-proxy/driver.ts index b7444efc0..e11e977c1 100644 --- a/drizzle-orm/src/sqlite-proxy/driver.ts +++ b/drizzle-orm/src/sqlite-proxy/driver.ts @@ -15,7 +15,7 @@ export interface SqliteRemoteResult { export class SqliteRemoteDatabase< TSchema extends Record = Record, > extends BaseSQLiteDatabase<'async', SqliteRemoteResult, TSchema> { - static readonly [entityKind]: string = 'SqliteRemoteDatabase'; + static override readonly [entityKind]: string = 'SqliteRemoteDatabase'; /** @internal */ declare readonly session: SQLiteRemoteSession>; @@ -55,7 +55,7 @@ export function drizzle = Record, config?: DrizzleConfig, ): SqliteRemoteDatabase { - const dialect = new SQLiteAsyncDialect(); + const dialect = new SQLiteAsyncDialect({ casing: config?.casing }); let logger; let _batchCallback: AsyncBatchRemoteCallback | undefined; let _config: DrizzleConfig = {}; diff --git a/drizzle-orm/src/sqlite-proxy/migrator.ts b/drizzle-orm/src/sqlite-proxy/migrator.ts index ccce912aa..cc4a7f71c 100644 --- a/drizzle-orm/src/sqlite-proxy/migrator.ts +++ b/drizzle-orm/src/sqlite-proxy/migrator.ts @@ -8,7 +8,7 @@ export type ProxyMigrator = (migrationQueries: string[]) => Promise; export async function migrate>( db: SqliteRemoteDatabase, callback: ProxyMigrator, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); diff --git a/drizzle-orm/src/sqlite-proxy/session.ts b/drizzle-orm/src/sqlite-proxy/session.ts index 398913568..93d277d69 100644 --- a/drizzle-orm/src/sqlite-proxy/session.ts +++ b/drizzle-orm/src/sqlite-proxy/session.ts @@ -27,7 +27,7 @@ export class SQLiteRemoteSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteSession<'async', SqliteRemoteResult, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'SQLiteRemoteSession'; + static override readonly [entityKind]: string = 'SQLiteRemoteSession'; private logger: Logger; @@ -108,7 +108,7 @@ export class SQLiteProxyTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends SQLiteTransaction<'async', SqliteRemoteResult, TFullSchema, TSchema> { - static readonly [entityKind]: string = 'SQLiteProxyTransaction'; + static override readonly [entityKind]: string = 'SQLiteProxyTransaction'; override async transaction( transaction: (tx: SQLiteProxyTransaction) => Promise, @@ -130,7 +130,7 @@ export class SQLiteProxyTransaction< export class RemotePreparedQuery extends SQLitePreparedQuery< { type: 'async'; run: SqliteRemoteResult; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] } > { - static readonly [entityKind]: string = 'SQLiteProxyPreparedQuery'; + static override readonly [entityKind]: string = 'SQLiteProxyPreparedQuery'; private method: SQLiteExecuteMethod; diff --git a/drizzle-orm/src/subquery.ts b/drizzle-orm/src/subquery.ts index 320ec46e6..37fe48d86 100644 --- a/drizzle-orm/src/subquery.ts +++ b/drizzle-orm/src/subquery.ts @@ -42,5 +42,5 @@ export class WithSubquery< TAlias extends string = string, TSelection extends Record = Record, > extends Subquery { - static readonly [entityKind]: string = 'WithSubquery'; + static override readonly [entityKind]: string = 'WithSubquery'; } diff --git a/drizzle-orm/src/supabase/index.ts b/drizzle-orm/src/supabase/index.ts new file mode 100644 index 000000000..ee201ff1c --- /dev/null +++ b/drizzle-orm/src/supabase/index.ts @@ -0,0 +1 @@ +export * from './rls.ts'; diff --git a/drizzle-orm/src/supabase/rls.ts b/drizzle-orm/src/supabase/rls.ts new file mode 100644 index 000000000..d5f813ac6 --- /dev/null +++ b/drizzle-orm/src/supabase/rls.ts @@ -0,0 +1,40 @@ +import { bigserial, pgSchema, text, timestamp, uuid, varchar } from '~/pg-core/index.ts'; +import { pgRole } from '~/pg-core/roles.ts'; +import { sql } from '~/sql/sql.ts'; + +export const anonRole = pgRole('anon').existing(); +export const authenticatedRole = pgRole('authenticated').existing(); +export const serviceRole = pgRole('service_role').existing(); +export const postgresRole = pgRole('postgres_role').existing(); +export const supabaseAuthAdminRole = pgRole('supabase_auth_admin').existing(); + +/* ------------------------------ auth schema; ------------------------------ */ +const auth = pgSchema('auth'); + +export const authUsers = auth.table('users', { + id: uuid().primaryKey().notNull(), + email: varchar({ length: 255 }), + phone: text().unique(), + emailConfirmedAt: timestamp('email_confirmed_at', { withTimezone: true }), + phoneConfirmedAt: timestamp('phone_confirmed_at', { withTimezone: true }), + lastSignInAt: timestamp('last_sign_in_at', { withTimezone: true }), + createdAt: timestamp('created_at', { withTimezone: true }), + updatedAt: timestamp('updated_at', { withTimezone: true }), +}); + +/* ------------------------------ realtime schema; ------------------------------- */ +const realtime = pgSchema('realtime'); + +export const realtimeMessages = realtime.table( + 'messages', + { + id: bigserial({ mode: 'bigint' }).primaryKey(), + topic: text().notNull(), + extension: text({ + enum: ['presence', 'broadcast', 'postgres_changes'], + }).notNull(), + }, +); + +export const authUid = sql`(select auth.uid())`; +export const realtimeTopic = sql`realtime.topic()`; diff --git a/drizzle-orm/src/table.ts b/drizzle-orm/src/table.ts index 3db9d5559..5f6b0d679 100644 --- a/drizzle-orm/src/table.ts +++ b/drizzle-orm/src/table.ts @@ -3,6 +3,7 @@ import { entityKind } from './entity.ts'; import type { OptionalKeyOnly, RequiredKeyOnly } from './operations.ts'; import type { ExtraConfigColumn } from './pg-core/index.ts'; import type { SQLWrapper } from './sql/sql.ts'; +import { TableName } from './table.utils.ts'; import type { Simplify, Update } from './utils.ts'; export interface TableConfig> { @@ -16,9 +17,6 @@ export type UpdateTableConfig >; -/** @internal */ -export const TableName = Symbol.for('drizzle:Name'); - /** @internal */ export const Schema = Symbol.for('drizzle:Schema'); @@ -108,7 +106,10 @@ export class Table implements SQLWrapper { [IsAlias] = false; /** @internal */ - [ExtraConfigBuilder]: ((self: any) => Record) | undefined = undefined; + [IsDrizzleTable] = true; + + /** @internal */ + [ExtraConfigBuilder]: ((self: any) => Record | unknown[]) | undefined = undefined; constructor(name: string, schema: string | undefined, baseName: string) { this[TableName] = this[OriginalName] = name; @@ -155,7 +156,7 @@ export type MapColumnName, TInferMode extends 'select' | 'insert' = 'select', - TConfig extends { dbColumnNames: boolean } = { dbColumnNames: false }, + TConfig extends { dbColumnNames: boolean; override?: boolean } = { dbColumnNames: false; override: false }, > = Simplify< TInferMode extends 'insert' ? & { @@ -170,9 +171,10 @@ export type InferModelFromColumns< [ Key in keyof TColumns & string as OptionalKeyOnly< MapColumnName, - TColumns[Key] + TColumns[Key], + TConfig['override'] > - ]?: GetColumnData; + ]?: GetColumnData | undefined; } : { [ @@ -200,5 +202,5 @@ export type InferSelectModel< export type InferInsertModel< TTable extends Table, - TConfig extends { dbColumnNames: boolean } = { dbColumnNames: false }, + TConfig extends { dbColumnNames: boolean; override?: boolean } = { dbColumnNames: false; override: false }, > = InferModelFromColumns; diff --git a/drizzle-orm/src/table.utils.ts b/drizzle-orm/src/table.utils.ts new file mode 100644 index 000000000..4e0278c05 --- /dev/null +++ b/drizzle-orm/src/table.utils.ts @@ -0,0 +1,2 @@ +/** @internal */ +export const TableName = Symbol.for('drizzle:Name'); diff --git a/drizzle-orm/src/tidb-serverless/driver.ts b/drizzle-orm/src/tidb-serverless/driver.ts index bdd5324db..69b9a0e44 100644 --- a/drizzle-orm/src/tidb-serverless/driver.ts +++ b/drizzle-orm/src/tidb-serverless/driver.ts @@ -1,4 +1,5 @@ -import type { Connection } from '@tidbcloud/serverless'; +import { type Config, connect, type Connection } from '@tidbcloud/serverless'; +import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; import { MySqlDatabase } from '~/mysql-core/db.ts'; @@ -9,7 +10,7 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import type { TiDBServerlessPreparedQueryHKT, TiDBServerlessQueryResultHKT } from './session.ts'; import { TiDBServerlessSession } from './session.ts'; @@ -17,15 +18,19 @@ export interface TiDBServerlessSDriverOptions { logger?: Logger; } -export type TiDBServerlessDatabase< +export class TiDBServerlessDatabase< TSchema extends Record = Record, -> = MySqlDatabase; +> extends MySqlDatabase { + static override readonly [entityKind]: string = 'TiDBServerlessDatabase'; +} -export function drizzle = Record>( +function construct = Record>( client: Connection, config: DrizzleConfig = {}, -): TiDBServerlessDatabase { - const dialect = new MySqlDialect(); +): TiDBServerlessDatabase & { + $client: Connection; +} { + const dialect = new MySqlDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -47,5 +52,65 @@ export function drizzle = Record; + const db = new TiDBServerlessDatabase(dialect, session, schema as any, 'default') as TiDBServerlessDatabase; + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends Connection = Connection, +>( + ...params: [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + & ({ + connection: string | Config; + } | { + client: TClient; + }) + & DrizzleConfig, + ] +): TiDBServerlessDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = connect({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config | string; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' + ? connect({ + url: connection, + }) + : connect(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): TiDBServerlessDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/tidb-serverless/session.ts b/drizzle-orm/src/tidb-serverless/session.ts index 64a8d61d7..279c60f3b 100644 --- a/drizzle-orm/src/tidb-serverless/session.ts +++ b/drizzle-orm/src/tidb-serverless/session.ts @@ -22,7 +22,7 @@ const executeRawConfig = { fullResult: true } satisfies ExecuteOptions; const queryConfig = { arrayMode: true } satisfies ExecuteOptions; export class TiDBServerlessPreparedQuery extends MySqlPreparedQuery { - static readonly [entityKind]: string = 'TiDBPreparedQuery'; + static override readonly [entityKind]: string = 'TiDBPreparedQuery'; constructor( private client: Tx | Connection, @@ -97,7 +97,7 @@ export class TiDBServerlessSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends MySqlSession { - static readonly [entityKind]: string = 'TiDBServerlessSession'; + static override readonly [entityKind]: string = 'TiDBServerlessSession'; private logger: Logger; private client: Tx | Connection; @@ -139,6 +139,14 @@ export class TiDBServerlessSession< return this.client.execute(querySql.sql, querySql.params) as Promise; } + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + + return Number( + res['rows'][0]['count'], + ); + } + override async transaction( transaction: (tx: TiDBServerlessTransaction) => Promise, ): Promise { @@ -164,7 +172,7 @@ export class TiDBServerlessTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends MySqlTransaction { - static readonly [entityKind]: string = 'TiDBServerlessTransaction'; + static override readonly [entityKind]: string = 'TiDBServerlessTransaction'; constructor( dialect: MySqlDialect, diff --git a/drizzle-orm/src/utils.ts b/drizzle-orm/src/utils.ts index 54bd44325..51d30e97c 100644 --- a/drizzle-orm/src/utils.ts +++ b/drizzle-orm/src/utils.ts @@ -114,7 +114,7 @@ export function mapUpdateSet(table: Table, values: Record): Upd .filter(([, value]) => value !== undefined) .map(([key, value]) => { // eslint-disable-next-line unicorn/prefer-ternary - if (is(value, SQL)) { + if (is(value, SQL) || is(value, Column)) { return [key, value]; } else { return [key, new Param(value, table[Table.Symbol.Columns][key])]; @@ -128,7 +128,7 @@ export function mapUpdateSet(table: Table, values: Record): Upd return Object.fromEntries(entries); } -export type UpdateSet = Record; +export type UpdateSet = Record; export type OneOrMany = T | T[]; @@ -188,6 +188,10 @@ export function getTableColumns(table: T): T['_']['columns'] { return table[Table.Symbol.Columns]; } +export function getViewSelectedFields(view: T): T['_']['selectedFields'] { + return view[ViewBaseConfig].selectedFields; +} + /** @internal */ export function getTableLikeName(table: TableLike): string | undefined { return is(table, Subquery) @@ -207,9 +211,12 @@ export type ColumnsWithTable< TColumns extends AnyColumn<{ tableName: TTableName }>[], > = { [Key in keyof TColumns]: AnyColumn<{ tableName: TForeignTableName }> }; +export type Casing = 'snake_case' | 'camelCase'; + export interface DrizzleConfig = Record> { logger?: boolean | Logger; schema?: TSchema; + casing?: Casing; } export type ValidateShape = T extends ValidShape ? Exclude extends never ? TResult @@ -223,3 +230,90 @@ export type KnownKeysOnly = { }; export type IsAny = 0 extends (1 & T) ? true : false; + +/** @internal */ +export function getColumnNameAndConfig< + TConfig extends Record | undefined, +>(a: string | TConfig | undefined, b: TConfig | undefined) { + return { + name: typeof a === 'string' && a.length > 0 ? a : '' as string, + config: typeof a === 'object' ? a : b as TConfig, + }; +} + +export type IfNotImported = unknown extends T ? Y : N; + +export type ImportTypeError = + `Please install \`${TPackageName}\` to allow Drizzle ORM to connect to the database`; + +export type RequireAtLeastOne = Keys extends any + ? Required> & Partial> + : never; + +type ExpectedConfigShape = { + logger?: boolean | { + logQuery(query: string, params: unknown[]): void; + }; + schema?: Record; + casing?: 'snake_case' | 'camelCase'; +}; + +// If this errors, you must update config shape checker function with new config specs +const _: DrizzleConfig = {} as ExpectedConfigShape; +const __: ExpectedConfigShape = {} as DrizzleConfig; + +export function isConfig(data: any): boolean { + if (typeof data !== 'object' || data === null) return false; + + if (data.constructor.name !== 'Object') return false; + + if ('logger' in data) { + const type = typeof data['logger']; + if ( + type !== 'boolean' && (type !== 'object' || typeof data['logger']['logQuery'] !== 'function') + && type !== 'undefined' + ) return false; + + return true; + } + + if ('schema' in data) { + const type = typeof data['logger']; + if (type !== 'object' && type !== 'undefined') return false; + + return true; + } + + if ('casing' in data) { + const type = typeof data['logger']; + if (type !== 'string' && type !== 'undefined') return false; + + return true; + } + + if ('mode' in data) { + if (data['mode'] !== 'default' || data['mode'] !== 'planetscale' || data['mode'] !== undefined) return false; + + return true; + } + + if ('connection' in data) { + const type = typeof data['connection']; + if (type !== 'string' && type !== 'object' && type !== 'undefined') return false; + + return true; + } + + if ('client' in data) { + const type = typeof data['client']; + if (type !== 'object' && type !== 'function' && type !== 'undefined') return false; + + return true; + } + + if (Object.keys(data).length === 0) return true; + + return false; +} + +export type NeonAuthToken = string | (() => string | Promise); diff --git a/drizzle-orm/src/vercel-postgres/driver.ts b/drizzle-orm/src/vercel-postgres/driver.ts index 07e73c732..3a7788783 100644 --- a/drizzle-orm/src/vercel-postgres/driver.ts +++ b/drizzle-orm/src/vercel-postgres/driver.ts @@ -1,4 +1,4 @@ -import { types } from '@vercel/postgres'; +import { sql } from '@vercel/postgres'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; @@ -10,7 +10,7 @@ import { type RelationalSchemaConfig, type TablesRelationalConfig, } from '~/relations.ts'; -import type { DrizzleConfig } from '~/utils.ts'; +import { type DrizzleConfig, isConfig } from '~/utils.ts'; import { type VercelPgClient, type VercelPgQueryResultHKT, VercelPgSession } from './session.ts'; export interface VercelPgDriverOptions { @@ -25,7 +25,6 @@ export class VercelPgDriver { private dialect: PgDialect, private options: VercelPgDriverOptions = {}, ) { - this.initMappers(); } createSession( @@ -33,24 +32,21 @@ export class VercelPgDriver { ): VercelPgSession, TablesRelationalConfig> { return new VercelPgSession(this.client, this.dialect, schema, { logger: this.options.logger }); } - - initMappers() { - types.setTypeParser(types.builtins.TIMESTAMPTZ, (val) => val); - types.setTypeParser(types.builtins.TIMESTAMP, (val) => val); - types.setTypeParser(types.builtins.DATE, (val) => val); - types.setTypeParser(types.builtins.INTERVAL, (val) => val); - } } -export type VercelPgDatabase< +export class VercelPgDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static override readonly [entityKind]: string = 'VercelPgDatabase'; +} -export function drizzle = Record>( +function construct = Record>( client: VercelPgClient, config: DrizzleConfig = {}, -): VercelPgDatabase { - const dialect = new PgDialect(); +): VercelPgDatabase & { + $client: VercelPgClient; +} { + const dialect = new PgDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -73,5 +69,46 @@ export function drizzle = Record; + const db = new VercelPgDatabase(dialect, session, schema as any) as VercelPgDatabase; + ( db).$client = client; + + return db as any; +} + +export function drizzle< + TSchema extends Record = Record, + TClient extends VercelPgClient = typeof sql, +>( + ...params: [] | [ + TClient, + ] | [ + TClient, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + client?: TClient; + }) + ), + ] +): VercelPgDatabase & { + $client: TClient; +} { + if (isConfig(params[0])) { + const { client, ...drizzleConfig } = params[0] as ({ client?: TClient } & DrizzleConfig); + return construct(client ?? sql, drizzleConfig) as any; + } + + return construct((params[0] ?? sql) as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): VercelPgDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } } diff --git a/drizzle-orm/src/vercel-postgres/migrator.ts b/drizzle-orm/src/vercel-postgres/migrator.ts index a2ae29a08..88c922271 100644 --- a/drizzle-orm/src/vercel-postgres/migrator.ts +++ b/drizzle-orm/src/vercel-postgres/migrator.ts @@ -4,7 +4,7 @@ import type { VercelPgDatabase } from './driver.ts'; export async function migrate>( db: VercelPgDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); await db.dialect.migrate(migrations, db.session, config); diff --git a/drizzle-orm/src/vercel-postgres/session.ts b/drizzle-orm/src/vercel-postgres/session.ts index 51a987905..a901f24c8 100644 --- a/drizzle-orm/src/vercel-postgres/session.ts +++ b/drizzle-orm/src/vercel-postgres/session.ts @@ -3,6 +3,7 @@ import { type QueryConfig, type QueryResult, type QueryResultRow, + types, type VercelClient, VercelPool, type VercelPoolClient, @@ -20,7 +21,7 @@ import { type Assume, mapResultRow } from '~/utils.ts'; export type VercelPgClient = VercelPool | VercelClient | VercelPoolClient; export class VercelPgPreparedQuery extends PgPreparedQuery { - static readonly [entityKind]: string = 'VercelPgPreparedQuery'; + static override readonly [entityKind]: string = 'VercelPgPreparedQuery'; private rawQuery: QueryConfig; private queryConfig: QueryArrayConfig; @@ -39,11 +40,49 @@ export class VercelPgPreparedQuery extends PgPrep this.rawQuery = { name, text: queryString, + types: { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === types.builtins.TIMESTAMPTZ) { + return (val: any) => val; + } + if (typeId === types.builtins.TIMESTAMP) { + return (val: any) => val; + } + if (typeId === types.builtins.DATE) { + return (val: any) => val; + } + if (typeId === types.builtins.INTERVAL) { + return (val: any) => val; + } + // @ts-ignore + return types.getTypeParser(typeId, format); + }, + }, }; this.queryConfig = { name, text: queryString, rowMode: 'array', + types: { + // @ts-ignore + getTypeParser: (typeId, format) => { + if (typeId === types.builtins.TIMESTAMPTZ) { + return (val: any) => val; + } + if (typeId === types.builtins.TIMESTAMP) { + return (val: any) => val; + } + if (typeId === types.builtins.DATE) { + return (val: any) => val; + } + if (typeId === types.builtins.INTERVAL) { + return (val: any) => val; + } + // @ts-ignore + return types.getTypeParser(typeId, format); + }, + }, }; } @@ -92,7 +131,7 @@ export class VercelPgSession< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgSession { - static readonly [entityKind]: string = 'VercelPgSession'; + static override readonly [entityKind]: string = 'VercelPgSession'; private logger: Logger; @@ -170,7 +209,7 @@ export class VercelPgTransaction< TFullSchema extends Record, TSchema extends TablesRelationalConfig, > extends PgTransaction { - static readonly [entityKind]: string = 'VercelPgTransaction'; + static override readonly [entityKind]: string = 'VercelPgTransaction'; override async transaction( transaction: (tx: VercelPgTransaction) => Promise, diff --git a/drizzle-orm/src/version.ts b/drizzle-orm/src/version.ts index 0c11937c8..6f22d27b3 100644 --- a/drizzle-orm/src/version.ts +++ b/drizzle-orm/src/version.ts @@ -1,4 +1,4 @@ // @ts-ignore - imported using Rollup json plugin export { version as npmVersion } from '../package.json'; // In version 7, we changed the PostgreSQL indexes API -export const compatibilityVersion = 7; +export const compatibilityVersion = 10; diff --git a/drizzle-orm/src/xata-http/driver.ts b/drizzle-orm/src/xata-http/driver.ts index 9838083aa..ce275a88d 100644 --- a/drizzle-orm/src/xata-http/driver.ts +++ b/drizzle-orm/src/xata-http/driver.ts @@ -40,7 +40,7 @@ export class XataHttpDriver { export class XataHttpDatabase = Record> extends PgDatabase { - static readonly [entityKind]: string = 'XataHttpDatabase'; + static override readonly [entityKind]: string = 'XataHttpDatabase'; /** @internal */ declare readonly session: XataHttpSession>; @@ -49,8 +49,10 @@ export class XataHttpDatabase = Record = Record>( client: XataHttpClient, config: DrizzleConfig = {}, -): XataHttpDatabase { - const dialect = new PgDialect(); +): XataHttpDatabase & { + $client: XataHttpClient; +} { + const dialect = new PgDialect({ casing: config.casing }); let logger; if (config.logger === true) { logger = new DefaultLogger(); @@ -71,9 +73,12 @@ export function drizzle = Record> | undefined, ); + ( db).$client = client; + + return db as any; } diff --git a/drizzle-orm/src/xata-http/migrator.ts b/drizzle-orm/src/xata-http/migrator.ts index 569b756d9..0eb261d88 100644 --- a/drizzle-orm/src/xata-http/migrator.ts +++ b/drizzle-orm/src/xata-http/migrator.ts @@ -16,12 +16,10 @@ export interface MigrationConfig { * @param config - path to migration folder generated by drizzle-kit */ export async function migrate>( db: XataHttpDatabase, - config: string | MigrationConfig, + config: MigrationConfig, ) { const migrations = readMigrationFiles(config); - const migrationsTable = typeof config === 'string' - ? '__drizzle_migrations' - : config.migrationsTable ?? '__drizzle_migrations'; + const migrationsTable = config.migrationsTable ?? '__drizzle_migrations'; const migrationTableCreate = sql` CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsTable)} ( id SERIAL PRIMARY KEY, diff --git a/drizzle-orm/src/xata-http/session.ts b/drizzle-orm/src/xata-http/session.ts index c666ba09d..df4cc1003 100644 --- a/drizzle-orm/src/xata-http/session.ts +++ b/drizzle-orm/src/xata-http/session.ts @@ -22,7 +22,7 @@ export interface QueryResults { } export class XataHttpPreparedQuery extends PgPreparedQuery { - static readonly [entityKind]: string = 'XataHttpPreparedQuery'; + static override readonly [entityKind]: string = 'XataHttpPreparedQuery'; constructor( private client: XataHttpClient, @@ -84,7 +84,7 @@ export class XataHttpSession, TSchem TSchema > { - static readonly [entityKind]: string = 'XataHttpSession'; + static override readonly [entityKind]: string = 'XataHttpSession'; private logger: Logger; @@ -152,7 +152,7 @@ export class XataTransaction, TSchem TSchema > { - static readonly [entityKind]: string = 'XataHttpTransaction'; + static override readonly [entityKind]: string = 'XataHttpTransaction'; override async transaction(_transaction: (tx: XataTransaction) => Promise): Promise { throw new Error('No transactions support in Xata Http driver'); diff --git a/drizzle-orm/tests/casing/casing.test.ts b/drizzle-orm/tests/casing/casing.test.ts new file mode 100644 index 000000000..f43f404a8 --- /dev/null +++ b/drizzle-orm/tests/casing/casing.test.ts @@ -0,0 +1,28 @@ +import { describe, it } from 'vitest'; +import { toCamelCase, toSnakeCase } from '~/casing'; + +describe.concurrent('casing', () => { + it('transforms to snake case', ({ expect }) => { + expect(toSnakeCase('drizzleKit')).toEqual('drizzle_kit'); + }); + + it('transforms an uppercase acronym/abbreviation to snake case', ({ expect }) => { + expect(toSnakeCase('drizzleORM')).toEqual('drizzle_orm'); + }); + + it('transforms a camel case acronym/abbreviation to snake case', ({ expect }) => { + expect(toSnakeCase('drizzleOrm')).toEqual('drizzle_orm'); + }); + + it('transforms an uppercase acronym/abbreviation followed by a word to snake case', ({ expect }) => { + expect(toSnakeCase('drizzleORMAndKit')).toEqual('drizzle_orm_and_kit'); + }); + + it('transforms a camel case acronym/abbreviation followed by a word to snake case', ({ expect }) => { + expect(toSnakeCase('drizzleOrmAndKit')).toEqual('drizzle_orm_and_kit'); + }); + + it('transforms to camel case 1', ({ expect }) => { + expect(toCamelCase('drizzle_kit')).toEqual('drizzleKit'); + }); +}); diff --git a/drizzle-orm/tests/casing/mysql-to-camel.test.ts b/drizzle-orm/tests/casing/mysql-to-camel.test.ts new file mode 100644 index 000000000..aa8aa04ee --- /dev/null +++ b/drizzle-orm/tests/casing/mysql-to-camel.test.ts @@ -0,0 +1,299 @@ +import { Client } from '@planetscale/database'; +import { connect } from '@tidbcloud/serverless'; +import { beforeEach, describe, it } from 'vitest'; +import { alias, boolean, int, mysqlSchema, mysqlTable, serial, text, union } from '~/mysql-core'; +import { drizzle as planetscale } from '~/planetscale-serverless'; +import { relations } from '~/relations'; +import { asc, eq, sql } from '~/sql'; +import { drizzle as mysql } from '~/tidb-serverless'; + +const testSchema = mysqlSchema('test'); +const users = mysqlTable('users', { + id: serial().primaryKey(), + first_name: text().notNull(), + last_name: text().notNull(), + // Test that custom aliases remain + age: int('AGE'), +}); +const usersRelations = relations(users, ({ one }) => ({ + developers: one(developers), +})); +const developers = testSchema.table('developers', { + user_id: serial().primaryKey().references(() => users.id), + uses_drizzle_orm: boolean().notNull(), +}); +const developersRelations = relations(developers, ({ one }) => ({ + user: one(users, { + fields: [developers.user_id], + references: [users.id], + }), +})); +const devs = alias(developers, 'devs'); +const schema = { users, usersRelations, developers, developersRelations }; + +const db = mysql(connect({}), { schema, casing: 'camelCase' }); +const ps = planetscale(new Client({}), { schema, casing: 'camelCase' }); + +const usersCache = { + 'public.users.id': 'id', + 'public.users.first_name': 'firstName', + 'public.users.last_name': 'lastName', + 'public.users.AGE': 'age', +}; +const developersCache = { + 'test.developers.user_id': 'userId', + 'test.developers.uses_drizzle_orm': 'usesDrizzleOrm', +}; +const cache = { + ...usersCache, + ...developersCache, +}; + +const fullName = sql`${users.first_name} || ' ' || ${users.last_name}`.as('name'); + +describe('mysql to snake case', () => { + beforeEach(() => { + ps.dialect.casing.clearCache(); + db.dialect.casing.clearCache(); + }); + + it('select', ({ expect }) => { + const query = db + .select({ name: fullName, age: users.age }) + .from(users) + .leftJoin(developers, eq(users.id, developers.user_id)) + .orderBy(asc(users.first_name)); + + expect(query.toSQL()).toEqual({ + sql: + "select `users`.`firstName` || ' ' || `users`.`lastName` as `name`, `users`.`AGE` from `users` left join `test`.`developers` on `users`.`id` = `test`.`developers`.`userId` order by `users`.`firstName` asc", + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('select (with alias)', ({ expect }) => { + const query = db + .select({ firstName: users.first_name }) + .from(users) + .leftJoin(devs, eq(users.id, devs.user_id)); + + expect(query.toSQL()).toEqual({ + sql: + 'select `users`.`firstName` from `users` left join `test`.`developers` `devs` on `users`.`id` = `devs`.`userId`', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('with CTE', ({ expect }) => { + const cte = db.$with('cte').as(db.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: "with `cte` as (select `firstName` || ' ' || `lastName` as `name` from `users`) select `name` from `cte`", + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('with CTE (with query builder)', ({ expect }) => { + const cte = db.$with('cte').as((qb) => qb.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: "with `cte` as (select `firstName` || ' ' || `lastName` as `name` from `users`) select `name` from `cte`", + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator', ({ expect }) => { + const query = db + .select({ firstName: users.first_name }) + .from(users) + .union(db.select({ firstName: users.first_name }).from(users)); + + expect(query.toSQL()).toEqual({ + sql: '(select `firstName` from `users`) union (select `firstName` from `users`)', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator (function)', ({ expect }) => { + const query = union( + db.select({ firstName: users.first_name }).from(users), + db.select({ firstName: users.first_name }).from(users), + ); + + expect(query.toSQL()).toEqual({ + sql: '(select `firstName` from `users`) union (select `firstName` from `users`)', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('query (find first)', ({ expect }) => { + const query = db.query.users.findFirst({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + uses_drizzle_orm: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + "select `users`.`id`, `users`.`AGE`, `users`.`firstName` || ' ' || `users`.`lastName` as `name`, `users_developers`.`data` as `developers` from `users` left join lateral (select json_array(`users_developers`.`usesDrizzleOrm`) as `data` from (select * from `developers` `users_developers` where `users_developers`.`userId` = `users`.`id` limit ?) `users_developers`) `users_developers` on true where `users`.`id` = ? limit ?", + params: [1, 1, 1], + typings: ['none', 'none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('query (find first, planetscale)', ({ expect }) => { + const query = ps.query.users.findFirst({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + uses_drizzle_orm: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + "select `id`, `AGE`, `firstName` || ' ' || `lastName` as `name`, (select json_array(`usesDrizzleOrm`) from (select * from `developers` `users_developers` where `users_developers`.`userId` = `users`.`id` limit ?) `users_developers`) as `developers` from `users` where `users`.`id` = ? limit ?", + params: [1, 1, 1], + typings: ['none', 'none', 'none'], + }); + expect(ps.dialect.casing.cache).toEqual(cache); + }); + + it('query (find many)', ({ expect }) => { + const query = db.query.users.findMany({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + uses_drizzle_orm: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + "select `users`.`id`, `users`.`AGE`, `users`.`firstName` || ' ' || `users`.`lastName` as `name`, `users_developers`.`data` as `developers` from `users` left join lateral (select json_array(`users_developers`.`usesDrizzleOrm`) as `data` from (select * from `developers` `users_developers` where `users_developers`.`userId` = `users`.`id` limit ?) `users_developers`) `users_developers` on true where `users`.`id` = ?", + params: [1, 1], + typings: ['none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('query (find many, planetscale)', ({ expect }) => { + const query = ps.query.users.findMany({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + uses_drizzle_orm: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + "select `id`, `AGE`, `firstName` || ' ' || `lastName` as `name`, (select json_array(`usesDrizzleOrm`) from (select * from `developers` `users_developers` where `users_developers`.`userId` = `users`.`id` limit ?) `users_developers`) as `developers` from `users` where `users`.`id` = ?", + params: [1, 1], + typings: ['none', 'none'], + }); + expect(ps.dialect.casing.cache).toEqual(cache); + }); + + it('insert', ({ expect }) => { + const query = db + .insert(users) + .values({ first_name: 'John', last_name: 'Doe', age: 30 }); + + expect(query.toSQL()).toEqual({ + sql: 'insert into `users` (`id`, `firstName`, `lastName`, `AGE`) values (default, ?, ?, ?)', + params: ['John', 'Doe', 30], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('insert (on duplicate key update)', ({ expect }) => { + const query = db + .insert(users) + .values({ first_name: 'John', last_name: 'Doe', age: 30 }) + .onDuplicateKeyUpdate({ set: { age: 31 } }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into `users` (`id`, `firstName`, `lastName`, `AGE`) values (default, ?, ?, ?) on duplicate key update `AGE` = ?', + params: ['John', 'Doe', 30, 31], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('update', ({ expect }) => { + const query = db + .update(users) + .set({ first_name: 'John', last_name: 'Doe', age: 30 }) + .where(eq(users.id, 1)); + + expect(query.toSQL()).toEqual({ + sql: 'update `users` set `firstName` = ?, `lastName` = ?, `AGE` = ? where `users`.`id` = ?', + params: ['John', 'Doe', 30, 1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('delete', ({ expect }) => { + const query = db + .delete(users) + .where(eq(users.id, 1)); + + expect(query.toSQL()).toEqual({ + sql: 'delete from `users` where `users`.`id` = ?', + params: [1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); +}); diff --git a/drizzle-orm/tests/casing/mysql-to-snake.test.ts b/drizzle-orm/tests/casing/mysql-to-snake.test.ts new file mode 100644 index 000000000..60496af22 --- /dev/null +++ b/drizzle-orm/tests/casing/mysql-to-snake.test.ts @@ -0,0 +1,299 @@ +import { Client } from '@planetscale/database'; +import { connect } from '@tidbcloud/serverless'; +import { beforeEach, describe, it } from 'vitest'; +import { alias, boolean, int, mysqlSchema, mysqlTable, serial, text, union } from '~/mysql-core'; +import { drizzle as planetscale } from '~/planetscale-serverless'; +import { relations } from '~/relations'; +import { asc, eq, sql } from '~/sql'; +import { drizzle as mysql } from '~/tidb-serverless'; + +const testSchema = mysqlSchema('test'); +const users = mysqlTable('users', { + id: serial().primaryKey(), + firstName: text().notNull(), + lastName: text().notNull(), + // Test that custom aliases remain + age: int('AGE'), +}); +const usersRelations = relations(users, ({ one }) => ({ + developers: one(developers), +})); +const developers = testSchema.table('developers', { + userId: serial().primaryKey().references(() => users.id), + usesDrizzleORM: boolean().notNull(), +}); +const developersRelations = relations(developers, ({ one }) => ({ + user: one(users, { + fields: [developers.userId], + references: [users.id], + }), +})); +const devs = alias(developers, 'devs'); +const schema = { users, usersRelations, developers, developersRelations }; + +const db = mysql(connect({}), { schema, casing: 'snake_case' }); +const ps = planetscale(new Client({}), { schema, casing: 'snake_case' }); + +const usersCache = { + 'public.users.id': 'id', + 'public.users.firstName': 'first_name', + 'public.users.lastName': 'last_name', + 'public.users.AGE': 'age', +}; +const developersCache = { + 'test.developers.userId': 'user_id', + 'test.developers.usesDrizzleORM': 'uses_drizzle_orm', +}; +const cache = { + ...usersCache, + ...developersCache, +}; + +const fullName = sql`${users.firstName} || ' ' || ${users.lastName}`.as('name'); + +describe('mysql to snake case', () => { + beforeEach(() => { + db.dialect.casing.clearCache(); + ps.dialect.casing.clearCache(); + }); + + it('select', ({ expect }) => { + const query = db + .select({ name: fullName, age: users.age }) + .from(users) + .leftJoin(developers, eq(users.id, developers.userId)) + .orderBy(asc(users.firstName)); + + expect(query.toSQL()).toEqual({ + sql: + "select `users`.`first_name` || ' ' || `users`.`last_name` as `name`, `users`.`AGE` from `users` left join `test`.`developers` on `users`.`id` = `test`.`developers`.`user_id` order by `users`.`first_name` asc", + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('select (with alias)', ({ expect }) => { + const query = db + .select({ firstName: users.firstName }) + .from(users) + .leftJoin(devs, eq(users.id, devs.userId)); + + expect(query.toSQL()).toEqual({ + sql: + 'select `users`.`first_name` from `users` left join `test`.`developers` `devs` on `users`.`id` = `devs`.`user_id`', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('with CTE', ({ expect }) => { + const cte = db.$with('cte').as(db.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: "with `cte` as (select `first_name` || ' ' || `last_name` as `name` from `users`) select `name` from `cte`", + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('with CTE (with query builder)', ({ expect }) => { + const cte = db.$with('cte').as((qb) => qb.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: "with `cte` as (select `first_name` || ' ' || `last_name` as `name` from `users`) select `name` from `cte`", + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator', ({ expect }) => { + const query = db + .select({ firstName: users.firstName }) + .from(users) + .union(db.select({ firstName: users.firstName }).from(users)); + + expect(query.toSQL()).toEqual({ + sql: '(select `first_name` from `users`) union (select `first_name` from `users`)', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator (function)', ({ expect }) => { + const query = union( + db.select({ firstName: users.firstName }).from(users), + db.select({ firstName: users.firstName }).from(users), + ); + + expect(query.toSQL()).toEqual({ + sql: '(select `first_name` from `users`) union (select `first_name` from `users`)', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('query (find first)', ({ expect }) => { + const query = db.query.users.findFirst({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + usesDrizzleORM: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + "select `users`.`id`, `users`.`AGE`, `users`.`first_name` || ' ' || `users`.`last_name` as `name`, `users_developers`.`data` as `developers` from `users` left join lateral (select json_array(`users_developers`.`uses_drizzle_orm`) as `data` from (select * from `developers` `users_developers` where `users_developers`.`user_id` = `users`.`id` limit ?) `users_developers`) `users_developers` on true where `users`.`id` = ? limit ?", + params: [1, 1, 1], + typings: ['none', 'none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('query (find first, planetscale)', ({ expect }) => { + const query = ps.query.users.findFirst({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + usesDrizzleORM: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + "select `id`, `AGE`, `first_name` || ' ' || `last_name` as `name`, (select json_array(`uses_drizzle_orm`) from (select * from `developers` `users_developers` where `users_developers`.`user_id` = `users`.`id` limit ?) `users_developers`) as `developers` from `users` where `users`.`id` = ? limit ?", + params: [1, 1, 1], + typings: ['none', 'none', 'none'], + }); + expect(ps.dialect.casing.cache).toEqual(cache); + }); + + it('query (find many)', ({ expect }) => { + const query = db.query.users.findMany({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + usesDrizzleORM: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + "select `users`.`id`, `users`.`AGE`, `users`.`first_name` || ' ' || `users`.`last_name` as `name`, `users_developers`.`data` as `developers` from `users` left join lateral (select json_array(`users_developers`.`uses_drizzle_orm`) as `data` from (select * from `developers` `users_developers` where `users_developers`.`user_id` = `users`.`id` limit ?) `users_developers`) `users_developers` on true where `users`.`id` = ?", + params: [1, 1], + typings: ['none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('query (find many, planetscale)', ({ expect }) => { + const query = ps.query.users.findMany({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + usesDrizzleORM: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + "select `id`, `AGE`, `first_name` || ' ' || `last_name` as `name`, (select json_array(`uses_drizzle_orm`) from (select * from `developers` `users_developers` where `users_developers`.`user_id` = `users`.`id` limit ?) `users_developers`) as `developers` from `users` where `users`.`id` = ?", + params: [1, 1], + typings: ['none', 'none'], + }); + expect(ps.dialect.casing.cache).toEqual(cache); + }); + + it('insert', ({ expect }) => { + const query = db + .insert(users) + .values({ firstName: 'John', lastName: 'Doe', age: 30 }); + + expect(query.toSQL()).toEqual({ + sql: 'insert into `users` (`id`, `first_name`, `last_name`, `AGE`) values (default, ?, ?, ?)', + params: ['John', 'Doe', 30], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('insert (on duplicate key update)', ({ expect }) => { + const query = db + .insert(users) + .values({ firstName: 'John', lastName: 'Doe', age: 30 }) + .onDuplicateKeyUpdate({ set: { age: 31 } }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into `users` (`id`, `first_name`, `last_name`, `AGE`) values (default, ?, ?, ?) on duplicate key update `AGE` = ?', + params: ['John', 'Doe', 30, 31], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('update', ({ expect }) => { + const query = db + .update(users) + .set({ firstName: 'John', lastName: 'Doe', age: 30 }) + .where(eq(users.id, 1)); + + expect(query.toSQL()).toEqual({ + sql: 'update `users` set `first_name` = ?, `last_name` = ?, `AGE` = ? where `users`.`id` = ?', + params: ['John', 'Doe', 30, 1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('delete', ({ expect }) => { + const query = db + .delete(users) + .where(eq(users.id, 1)); + + expect(query.toSQL()).toEqual({ + sql: 'delete from `users` where `users`.`id` = ?', + params: [1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); +}); diff --git a/drizzle-orm/tests/casing/pg-to-camel.test.ts b/drizzle-orm/tests/casing/pg-to-camel.test.ts new file mode 100644 index 000000000..e325745da --- /dev/null +++ b/drizzle-orm/tests/casing/pg-to-camel.test.ts @@ -0,0 +1,246 @@ +import postgres from 'postgres'; +import { beforeEach, describe, it } from 'vitest'; +import { alias, boolean, integer, pgSchema, pgTable, serial, text, union } from '~/pg-core'; +import { drizzle } from '~/postgres-js'; +import { relations } from '~/relations'; +import { asc, eq, sql } from '~/sql'; + +const testSchema = pgSchema('test'); +const users = pgTable('users', { + id: serial().primaryKey(), + first_name: text().notNull(), + last_name: text().notNull(), + // Test that custom aliases remain + age: integer('AGE'), +}); +const usersRelations = relations(users, ({ one }) => ({ + developers: one(developers), +})); +const developers = testSchema.table('developers', { + user_id: serial().primaryKey().references(() => users.id), + uses_drizzle_orm: boolean().notNull(), +}); +const developersRelations = relations(developers, ({ one }) => ({ + user: one(users, { + fields: [developers.user_id], + references: [users.id], + }), +})); +const devs = alias(developers, 'devs'); +const schema = { users, usersRelations, developers, developersRelations }; + +const db = drizzle(postgres(''), { schema, casing: 'camelCase' }); + +const usersCache = { + 'public.users.id': 'id', + 'public.users.first_name': 'firstName', + 'public.users.last_name': 'lastName', + 'public.users.AGE': 'age', +}; +const developersCache = { + 'test.developers.user_id': 'userId', + 'test.developers.uses_drizzle_orm': 'usesDrizzleOrm', +}; +const cache = { + ...usersCache, + ...developersCache, +}; + +const fullName = sql`${users.first_name} || ' ' || ${users.last_name}`.as('name'); + +describe('postgres to camel case', () => { + beforeEach(() => { + db.dialect.casing.clearCache(); + }); + + it('select', ({ expect }) => { + const query = db + .select({ name: fullName, age: users.age }) + .from(users) + .leftJoin(developers, eq(users.id, developers.user_id)) + .orderBy(asc(users.first_name)); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."firstName" || \' \' || "users"."lastName" as "name", "users"."AGE" from "users" left join "test"."developers" on "users"."id" = "test"."developers"."userId" order by "users"."firstName" asc', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('select (with alias)', ({ expect }) => { + const query = db + .select({ first_name: users.first_name }) + .from(users) + .leftJoin(devs, eq(users.id, devs.user_id)); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."firstName" from "users" left join "test"."developers" "devs" on "users"."id" = "devs"."userId"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('with CTE', ({ expect }) => { + const cte = db.$with('cte').as(db.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: 'with "cte" as (select "firstName" || \' \' || "lastName" as "name" from "users") select "name" from "cte"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('with CTE (with query builder)', ({ expect }) => { + const cte = db.$with('cte').as((qb) => qb.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: 'with "cte" as (select "firstName" || \' \' || "lastName" as "name" from "users") select "name" from "cte"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator', ({ expect }) => { + const query = db + .select({ first_name: users.first_name }) + .from(users) + .union(db.select({ first_name: users.first_name }).from(users)); + + expect(query.toSQL()).toEqual({ + sql: '(select "firstName" from "users") union (select "firstName" from "users")', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator (function)', ({ expect }) => { + const query = union( + db.select({ first_name: users.first_name }).from(users), + db.select({ first_name: users.first_name }).from(users), + ); + + expect(query.toSQL()).toEqual({ + sql: '(select "firstName" from "users") union (select "firstName" from "users")', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('query (find first)', ({ expect }) => { + const query = db.query.users.findFirst({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + uses_drizzle_orm: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."id", "users"."AGE", "users"."firstName" || \' \' || "users"."lastName" as "name", "users_developers"."data" as "developers" from "users" left join lateral (select json_build_array("users_developers"."usesDrizzleOrm") as "data" from (select * from "test"."developers" "users_developers" where "users_developers"."userId" = "users"."id" limit $1) "users_developers") "users_developers" on true where "users"."id" = $2 limit $3', + params: [1, 1, 1], + typings: ['none', 'none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('query (find many)', ({ expect }) => { + const query = db.query.users.findMany({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + uses_drizzle_orm: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."id", "users"."AGE", "users"."firstName" || \' \' || "users"."lastName" as "name", "users_developers"."data" as "developers" from "users" left join lateral (select json_build_array("users_developers"."usesDrizzleOrm") as "data" from (select * from "test"."developers" "users_developers" where "users_developers"."userId" = "users"."id" limit $1) "users_developers") "users_developers" on true where "users"."id" = $2', + params: [1, 1], + typings: ['none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('insert (on conflict do nothing)', ({ expect }) => { + const query = db + .insert(users) + .values({ first_name: 'John', last_name: 'Doe', age: 30 }) + .onConflictDoNothing({ target: users.first_name }) + .returning({ first_name: users.first_name, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into "users" ("id", "firstName", "lastName", "AGE") values (default, $1, $2, $3) on conflict ("firstName") do nothing returning "firstName", "AGE"', + params: ['John', 'Doe', 30], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('insert (on conflict do update)', ({ expect }) => { + const query = db + .insert(users) + .values({ first_name: 'John', last_name: 'Doe', age: 30 }) + .onConflictDoUpdate({ target: users.first_name, set: { age: 31 } }) + .returning({ first_name: users.first_name, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into "users" ("id", "firstName", "lastName", "AGE") values (default, $1, $2, $3) on conflict ("firstName") do update set "AGE" = $4 returning "firstName", "AGE"', + params: ['John', 'Doe', 30, 31], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('update', ({ expect }) => { + const query = db + .update(users) + .set({ first_name: 'John', last_name: 'Doe', age: 30 }) + .where(eq(users.id, 1)) + .returning({ first_name: users.first_name, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'update "users" set "firstName" = $1, "lastName" = $2, "AGE" = $3 where "users"."id" = $4 returning "firstName", "AGE"', + params: ['John', 'Doe', 30, 1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('delete', ({ expect }) => { + const query = db + .delete(users) + .where(eq(users.id, 1)) + .returning({ first_name: users.first_name, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: 'delete from "users" where "users"."id" = $1 returning "firstName", "AGE"', + params: [1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); +}); diff --git a/drizzle-orm/tests/casing/pg-to-snake.test.ts b/drizzle-orm/tests/casing/pg-to-snake.test.ts new file mode 100644 index 000000000..0c2aeaa27 --- /dev/null +++ b/drizzle-orm/tests/casing/pg-to-snake.test.ts @@ -0,0 +1,248 @@ +import postgres from 'postgres'; +import { beforeEach, describe, it } from 'vitest'; +import { alias, boolean, integer, pgSchema, pgTable, serial, text, union } from '~/pg-core'; +import { drizzle } from '~/postgres-js'; +import { relations } from '~/relations'; +import { asc, eq, sql } from '~/sql'; + +const testSchema = pgSchema('test'); +const users = pgTable('users', { + id: serial().primaryKey(), + firstName: text().notNull(), + lastName: text().notNull(), + // Test that custom aliases remain + age: integer('AGE'), +}); +const usersRelations = relations(users, ({ one }) => ({ + developers: one(developers), +})); +const developers = testSchema.table('developers', { + userId: serial().primaryKey().references(() => users.id), + usesDrizzleORM: boolean().notNull(), +}); +const developersRelations = relations(developers, ({ one }) => ({ + user: one(users, { + fields: [developers.userId], + references: [users.id], + }), +})); +const devs = alias(developers, 'devs'); +const schema = { users, usersRelations, developers, developersRelations }; + +const db = drizzle(postgres(''), { schema, casing: 'snake_case' }); + +const usersCache = { + 'public.users.id': 'id', + 'public.users.firstName': 'first_name', + 'public.users.lastName': 'last_name', + 'public.users.AGE': 'age', +}; +const developersCache = { + 'test.developers.userId': 'user_id', + 'test.developers.usesDrizzleORM': 'uses_drizzle_orm', +}; +const cache = { + ...usersCache, + ...developersCache, +}; + +const fullName = sql`${users.firstName} || ' ' || ${users.lastName}`.as('name'); + +describe('postgres to snake case', () => { + beforeEach(() => { + db.dialect.casing.clearCache(); + }); + + it('select', ({ expect }) => { + const query = db + .select({ name: fullName, age: users.age }) + .from(users) + .leftJoin(developers, eq(users.id, developers.userId)) + .orderBy(asc(users.firstName)); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."first_name" || \' \' || "users"."last_name" as "name", "users"."AGE" from "users" left join "test"."developers" on "users"."id" = "test"."developers"."user_id" order by "users"."first_name" asc', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('select (with alias)', ({ expect }) => { + const query = db + .select({ firstName: users.firstName }) + .from(users) + .leftJoin(devs, eq(users.id, devs.userId)); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."first_name" from "users" left join "test"."developers" "devs" on "users"."id" = "devs"."user_id"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('with CTE', ({ expect }) => { + const cte = db.$with('cte').as(db.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: + 'with "cte" as (select "first_name" || \' \' || "last_name" as "name" from "users") select "name" from "cte"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('with CTE (with query builder)', ({ expect }) => { + const cte = db.$with('cte').as((qb) => qb.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: + 'with "cte" as (select "first_name" || \' \' || "last_name" as "name" from "users") select "name" from "cte"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator', ({ expect }) => { + const query = db + .select({ firstName: users.firstName }) + .from(users) + .union(db.select({ firstName: users.firstName }).from(users)); + + expect(query.toSQL()).toEqual({ + sql: '(select "first_name" from "users") union (select "first_name" from "users")', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator (function)', ({ expect }) => { + const query = union( + db.select({ firstName: users.firstName }).from(users), + db.select({ firstName: users.firstName }).from(users), + ); + + expect(query.toSQL()).toEqual({ + sql: '(select "first_name" from "users") union (select "first_name" from "users")', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('query (find first)', ({ expect }) => { + const query = db.query.users.findFirst({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + usesDrizzleORM: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."id", "users"."AGE", "users"."first_name" || \' \' || "users"."last_name" as "name", "users_developers"."data" as "developers" from "users" left join lateral (select json_build_array("users_developers"."uses_drizzle_orm") as "data" from (select * from "test"."developers" "users_developers" where "users_developers"."user_id" = "users"."id" limit $1) "users_developers") "users_developers" on true where "users"."id" = $2 limit $3', + params: [1, 1, 1], + typings: ['none', 'none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('query (find many)', ({ expect }) => { + const query = db.query.users.findMany({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + usesDrizzleORM: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."id", "users"."AGE", "users"."first_name" || \' \' || "users"."last_name" as "name", "users_developers"."data" as "developers" from "users" left join lateral (select json_build_array("users_developers"."uses_drizzle_orm") as "data" from (select * from "test"."developers" "users_developers" where "users_developers"."user_id" = "users"."id" limit $1) "users_developers") "users_developers" on true where "users"."id" = $2', + params: [1, 1], + typings: ['none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('insert (on conflict do nothing)', ({ expect }) => { + const query = db + .insert(users) + .values({ firstName: 'John', lastName: 'Doe', age: 30 }) + .onConflictDoNothing({ target: users.firstName }) + .returning({ firstName: users.firstName, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into "users" ("id", "first_name", "last_name", "AGE") values (default, $1, $2, $3) on conflict ("first_name") do nothing returning "first_name", "AGE"', + params: ['John', 'Doe', 30], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('insert (on conflict do update)', ({ expect }) => { + const query = db + .insert(users) + .values({ firstName: 'John', lastName: 'Doe', age: 30 }) + .onConflictDoUpdate({ target: users.firstName, set: { age: 31 } }) + .returning({ firstName: users.firstName, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into "users" ("id", "first_name", "last_name", "AGE") values (default, $1, $2, $3) on conflict ("first_name") do update set "AGE" = $4 returning "first_name", "AGE"', + params: ['John', 'Doe', 30, 31], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('update', ({ expect }) => { + const query = db + .update(users) + .set({ firstName: 'John', lastName: 'Doe', age: 30 }) + .where(eq(users.id, 1)) + .returning({ firstName: users.firstName, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'update "users" set "first_name" = $1, "last_name" = $2, "AGE" = $3 where "users"."id" = $4 returning "first_name", "AGE"', + params: ['John', 'Doe', 30, 1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('delete', ({ expect }) => { + const query = db + .delete(users) + .where(eq(users.id, 1)) + .returning({ firstName: users.firstName, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: 'delete from "users" where "users"."id" = $1 returning "first_name", "AGE"', + params: [1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); +}); diff --git a/drizzle-orm/tests/casing/sqlite-to-camel.test.ts b/drizzle-orm/tests/casing/sqlite-to-camel.test.ts new file mode 100644 index 000000000..acfb55226 --- /dev/null +++ b/drizzle-orm/tests/casing/sqlite-to-camel.test.ts @@ -0,0 +1,244 @@ +import Database from 'better-sqlite3'; +import { beforeEach, describe, it } from 'vitest'; +import { drizzle } from '~/better-sqlite3'; +import { relations } from '~/relations'; +import { asc, eq, sql } from '~/sql'; +import { alias, integer, sqliteTable, text, union } from '~/sqlite-core'; + +const users = sqliteTable('users', { + id: integer().primaryKey({ autoIncrement: true }), + first_name: text().notNull(), + last_name: text().notNull(), + // Test that custom aliases remain + age: integer('AGE'), +}); +const usersRelations = relations(users, ({ one }) => ({ + developers: one(developers), +})); +const developers = sqliteTable('developers', { + user_id: integer().primaryKey().references(() => users.id), + uses_drizzle_orm: integer({ mode: 'boolean' }).notNull(), +}); +const developersRelations = relations(developers, ({ one }) => ({ + user: one(users, { + fields: [developers.user_id], + references: [users.id], + }), +})); +const devs = alias(developers, 'devs'); +const schema = { users, usersRelations, developers, developersRelations }; + +const db = drizzle(new Database(':memory:'), { schema, casing: 'camelCase' }); + +const usersCache = { + 'public.users.id': 'id', + 'public.users.first_name': 'firstName', + 'public.users.last_name': 'lastName', + 'public.users.AGE': 'age', +}; +const developersCache = { + 'public.developers.user_id': 'userId', + 'public.developers.uses_drizzle_orm': 'usesDrizzleOrm', +}; +const cache = { + ...usersCache, + ...developersCache, +}; + +const fullName = sql`${users.first_name} || ' ' || ${users.last_name}`.as('name'); + +describe('sqlite to camel case', () => { + beforeEach(() => { + db.dialect.casing.clearCache(); + }); + + it('select', ({ expect }) => { + const query = db + .select({ name: fullName, age: users.age }) + .from(users) + .leftJoin(developers, eq(users.id, developers.user_id)) + .orderBy(asc(users.first_name)); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."firstName" || \' \' || "users"."lastName" as "name", "users"."AGE" from "users" left join "developers" on "users"."id" = "developers"."userId" order by "users"."firstName" asc', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('select (with alias)', ({ expect }) => { + const query = db + .select({ first_name: users.first_name }) + .from(users) + .leftJoin(devs, eq(users.id, devs.user_id)); + + expect(query.toSQL()).toEqual({ + sql: 'select "users"."firstName" from "users" left join "developers" "devs" on "users"."id" = "devs"."userId"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('with CTE', ({ expect }) => { + const cte = db.$with('cte').as(db.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: 'with "cte" as (select "firstName" || \' \' || "lastName" as "name" from "users") select "name" from "cte"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('with CTE (with query builder)', ({ expect }) => { + const cte = db.$with('cte').as((qb) => qb.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: 'with "cte" as (select "firstName" || \' \' || "lastName" as "name" from "users") select "name" from "cte"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator', ({ expect }) => { + const query = db + .select({ first_name: users.first_name }) + .from(users) + .union(db.select({ first_name: users.first_name }).from(users)); + + expect(query.toSQL()).toEqual({ + sql: 'select "firstName" from "users" union select "firstName" from "users"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator (function)', ({ expect }) => { + const query = union( + db.select({ first_name: users.first_name }).from(users), + db.select({ first_name: users.first_name }).from(users), + ); + + expect(query.toSQL()).toEqual({ + sql: 'select "firstName" from "users" union select "firstName" from "users"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('query (find first)', ({ expect }) => { + const query = db.query.users.findFirst({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + uses_drizzle_orm: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + 'select "id", "AGE", "firstName" || \' \' || "lastName" as "name", (select json_array("usesDrizzleOrm") as "data" from (select * from "developers" "users_developers" where "users_developers"."userId" = "users"."id" limit ?) "users_developers") as "developers" from "users" where "users"."id" = ? limit ?', + params: [1, 1, 1], + typings: ['none', 'none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('query (find many)', ({ expect }) => { + const query = db.query.users.findMany({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + uses_drizzle_orm: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + 'select "id", "AGE", "firstName" || \' \' || "lastName" as "name", (select json_array("usesDrizzleOrm") as "data" from (select * from "developers" "users_developers" where "users_developers"."userId" = "users"."id" limit ?) "users_developers") as "developers" from "users" where "users"."id" = ?', + params: [1, 1], + typings: ['none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('insert (on conflict do nothing)', ({ expect }) => { + const query = db + .insert(users) + .values({ first_name: 'John', last_name: 'Doe', age: 30 }) + .onConflictDoNothing({ target: users.first_name }) + .returning({ first_name: users.first_name, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into "users" ("id", "firstName", "lastName", "AGE") values (null, ?, ?, ?) on conflict ("users"."firstName") do nothing returning "firstName", "AGE"', + params: ['John', 'Doe', 30], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('insert (on conflict do update)', ({ expect }) => { + const query = db + .insert(users) + .values({ first_name: 'John', last_name: 'Doe', age: 30 }) + .onConflictDoUpdate({ target: users.first_name, set: { age: 31 } }) + .returning({ first_name: users.first_name, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into "users" ("id", "firstName", "lastName", "AGE") values (null, ?, ?, ?) on conflict ("users"."firstName") do update set "AGE" = ? returning "firstName", "AGE"', + params: ['John', 'Doe', 30, 31], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('update', ({ expect }) => { + const query = db + .update(users) + .set({ first_name: 'John', last_name: 'Doe', age: 30 }) + .where(eq(users.id, 1)) + .returning({ first_name: users.first_name, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'update "users" set "firstName" = ?, "lastName" = ?, "AGE" = ? where "users"."id" = ? returning "firstName", "AGE"', + params: ['John', 'Doe', 30, 1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('delete', ({ expect }) => { + const query = db + .delete(users) + .where(eq(users.id, 1)) + .returning({ first_name: users.first_name, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: 'delete from "users" where "users"."id" = ? returning "firstName", "AGE"', + params: [1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); +}); diff --git a/drizzle-orm/tests/casing/sqlite-to-snake.test.ts b/drizzle-orm/tests/casing/sqlite-to-snake.test.ts new file mode 100644 index 000000000..d8f2a71a2 --- /dev/null +++ b/drizzle-orm/tests/casing/sqlite-to-snake.test.ts @@ -0,0 +1,246 @@ +import Database from 'better-sqlite3'; +import { beforeEach, describe, it } from 'vitest'; +import { drizzle } from '~/better-sqlite3'; +import { relations } from '~/relations'; +import { asc, eq, sql } from '~/sql'; +import { alias, integer, sqliteTable, text, union } from '~/sqlite-core'; + +const users = sqliteTable('users', { + id: integer().primaryKey({ autoIncrement: true }), + firstName: text().notNull(), + lastName: text().notNull(), + // Test that custom aliases remain + age: integer('AGE'), +}); +const usersRelations = relations(users, ({ one }) => ({ + developers: one(developers), +})); +const developers = sqliteTable('developers', { + userId: integer().primaryKey().references(() => users.id), + usesDrizzleORM: integer({ mode: 'boolean' }).notNull(), +}); +const developersRelations = relations(developers, ({ one }) => ({ + user: one(users, { + fields: [developers.userId], + references: [users.id], + }), +})); +const devs = alias(developers, 'devs'); +const schema = { users, usersRelations, developers, developersRelations }; + +const db = drizzle(new Database(':memory:'), { schema, casing: 'snake_case' }); + +const usersCache = { + 'public.users.id': 'id', + 'public.users.firstName': 'first_name', + 'public.users.lastName': 'last_name', + 'public.users.AGE': 'age', +}; +const developersCache = { + 'public.developers.userId': 'user_id', + 'public.developers.usesDrizzleORM': 'uses_drizzle_orm', +}; +const cache = { + ...usersCache, + ...developersCache, +}; + +const fullName = sql`${users.firstName} || ' ' || ${users.lastName}`.as('name'); + +describe('sqlite to camel case', () => { + beforeEach(() => { + db.dialect.casing.clearCache(); + }); + + it('select', ({ expect }) => { + const query = db + .select({ name: fullName, age: users.age }) + .from(users) + .leftJoin(developers, eq(users.id, developers.userId)) + .orderBy(asc(users.firstName)); + + expect(query.toSQL()).toEqual({ + sql: + 'select "users"."first_name" || \' \' || "users"."last_name" as "name", "users"."AGE" from "users" left join "developers" on "users"."id" = "developers"."user_id" order by "users"."first_name" asc', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('select (with alias)', ({ expect }) => { + const query = db + .select({ firstName: users.firstName }) + .from(users) + .leftJoin(devs, eq(users.id, devs.userId)); + + expect(query.toSQL()).toEqual({ + sql: 'select "users"."first_name" from "users" left join "developers" "devs" on "users"."id" = "devs"."user_id"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('with CTE', ({ expect }) => { + const cte = db.$with('cte').as(db.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: + 'with "cte" as (select "first_name" || \' \' || "last_name" as "name" from "users") select "name" from "cte"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('with CTE (with query builder)', ({ expect }) => { + const cte = db.$with('cte').as((qb) => qb.select({ name: fullName }).from(users)); + const query = db.with(cte).select().from(cte); + + expect(query.toSQL()).toEqual({ + sql: + 'with "cte" as (select "first_name" || \' \' || "last_name" as "name" from "users") select "name" from "cte"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator', ({ expect }) => { + const query = db + .select({ firstName: users.firstName }) + .from(users) + .union(db.select({ firstName: users.firstName }).from(users)); + + expect(query.toSQL()).toEqual({ + sql: 'select "first_name" from "users" union select "first_name" from "users"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('set operator (function)', ({ expect }) => { + const query = union( + db.select({ firstName: users.firstName }).from(users), + db.select({ firstName: users.firstName }).from(users), + ); + + expect(query.toSQL()).toEqual({ + sql: 'select "first_name" from "users" union select "first_name" from "users"', + params: [], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('query (find first)', ({ expect }) => { + const query = db.query.users.findFirst({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + usesDrizzleORM: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + 'select "id", "AGE", "first_name" || \' \' || "last_name" as "name", (select json_array("uses_drizzle_orm") as "data" from (select * from "developers" "users_developers" where "users_developers"."user_id" = "users"."id" limit ?) "users_developers") as "developers" from "users" where "users"."id" = ? limit ?', + params: [1, 1, 1], + typings: ['none', 'none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('query (find many)', ({ expect }) => { + const query = db.query.users.findMany({ + columns: { + id: true, + age: true, + }, + extras: { + fullName, + }, + where: eq(users.id, 1), + with: { + developers: { + columns: { + usesDrizzleORM: true, + }, + }, + }, + }); + + expect(query.toSQL()).toEqual({ + sql: + 'select "id", "AGE", "first_name" || \' \' || "last_name" as "name", (select json_array("uses_drizzle_orm") as "data" from (select * from "developers" "users_developers" where "users_developers"."user_id" = "users"."id" limit ?) "users_developers") as "developers" from "users" where "users"."id" = ?', + params: [1, 1], + typings: ['none', 'none'], + }); + expect(db.dialect.casing.cache).toEqual(cache); + }); + + it('insert (on conflict do nothing)', ({ expect }) => { + const query = db + .insert(users) + .values({ firstName: 'John', lastName: 'Doe', age: 30 }) + .onConflictDoNothing({ target: users.firstName }) + .returning({ firstName: users.firstName, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into "users" ("id", "first_name", "last_name", "AGE") values (null, ?, ?, ?) on conflict ("users"."first_name") do nothing returning "first_name", "AGE"', + params: ['John', 'Doe', 30], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('insert (on conflict do update)', ({ expect }) => { + const query = db + .insert(users) + .values({ firstName: 'John', lastName: 'Doe', age: 30 }) + .onConflictDoUpdate({ target: users.firstName, set: { age: 31 } }) + .returning({ firstName: users.firstName, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'insert into "users" ("id", "first_name", "last_name", "AGE") values (null, ?, ?, ?) on conflict ("users"."first_name") do update set "AGE" = ? returning "first_name", "AGE"', + params: ['John', 'Doe', 30, 31], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('update', ({ expect }) => { + const query = db + .update(users) + .set({ firstName: 'John', lastName: 'Doe', age: 30 }) + .where(eq(users.id, 1)) + .returning({ firstName: users.firstName, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: + 'update "users" set "first_name" = ?, "last_name" = ?, "AGE" = ? where "users"."id" = ? returning "first_name", "AGE"', + params: ['John', 'Doe', 30, 1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); + + it('delete', ({ expect }) => { + const query = db + .delete(users) + .where(eq(users.id, 1)) + .returning({ first_name: users.firstName, age: users.age }); + + expect(query.toSQL()).toEqual({ + sql: 'delete from "users" where "users"."id" = ? returning "first_name", "AGE"', + params: [1], + }); + expect(db.dialect.casing.cache).toEqual(usersCache); + }); +}); diff --git a/drizzle-orm/type-tests/mysql/count.ts b/drizzle-orm/type-tests/mysql/count.ts new file mode 100644 index 000000000..d9b9ba9ff --- /dev/null +++ b/drizzle-orm/type-tests/mysql/count.ts @@ -0,0 +1,61 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, gt, ne } from '~/expressions.ts'; +import { int, mysqlTable, serial, text } from '~/mysql-core/index.ts'; +import type { Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = mysqlTable('names', { + id: serial('id').primaryKey(), + name: text('name'), + authorId: int('author_id'), +}); + +const separate = await db.$count(names); + +const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))); + +const embedded = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names).as('count1'), + }) + .from(names); + +const embeddedFilters = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'), + }) + .from(names); + +Expect>; + +Expect>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embedded + > +>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embeddedFilters + > +>; diff --git a/drizzle-orm/type-tests/mysql/delete.ts b/drizzle-orm/type-tests/mysql/delete.ts index c3e5afbb2..84c827ba8 100644 --- a/drizzle-orm/type-tests/mysql/delete.ts +++ b/drizzle-orm/type-tests/mysql/delete.ts @@ -59,3 +59,7 @@ Expect>; .where(sql``) .where(sql``); } + +{ + db.delete(users).where(sql``).limit(1).orderBy(sql``); +} diff --git a/drizzle-orm/type-tests/mysql/generated-columns.ts b/drizzle-orm/type-tests/mysql/generated-columns.ts index d045fe1b3..56b9d4412 100644 --- a/drizzle-orm/type-tests/mysql/generated-columns.ts +++ b/drizzle-orm/type-tests/mysql/generated-columns.ts @@ -17,6 +17,7 @@ const users = mysqlTable( ).$type(), // There is no way for drizzle to detect nullability in these cases. This is how the user can work around it }, ); + { type User = typeof users.$inferSelect; type NewUser = typeof users.$inferInsert; diff --git a/drizzle-orm/type-tests/mysql/insert.ts b/drizzle-orm/type-tests/mysql/insert.ts index b354410bd..34fcbf9ae 100644 --- a/drizzle-orm/type-tests/mysql/insert.ts +++ b/drizzle-orm/type-tests/mysql/insert.ts @@ -1,6 +1,6 @@ import type { Equal } from 'type-tests/utils.ts'; import { Expect } from 'type-tests/utils.ts'; -import { int, mysqlTable, text } from '~/mysql-core/index.ts'; +import { boolean, int, mysqlTable, QueryBuilder, serial, text } from '~/mysql-core/index.ts'; import type { MySqlInsert } from '~/mysql-core/index.ts'; import type { MySqlRawQueryResult } from '~/mysql2/index.ts'; import { sql } from '~/sql/sql.ts'; @@ -133,3 +133,75 @@ Expect>; // @ts-expect-error method was already called .onDuplicateKeyUpdate({ set: {} }); } + +{ + const users1 = mysqlTable('users1', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + admin: boolean('admin').notNull().default(false), + }); + const users2 = mysqlTable('users2', { + id: serial('id').primaryKey(), + firstName: text('first_name').notNull(), + lastName: text('last_name').notNull(), + admin: boolean('admin').notNull().default(false), + phoneNumber: text('phone_number'), + }); + + const qb = new QueryBuilder(); + + db.insert(users1).select(sql`select * from users1`); + db.insert(users1).select(() => sql`select * from users1`); + + db + .insert(users1) + .select( + qb.select({ + name: users2.firstName, + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + qb.select({ + name: users2.firstName, + admin: users2.admin, + }).from(users2).where(sql``), + ); + + db + .insert(users2) + .select( + qb.select({ + firstName: users2.firstName, + lastName: users2.lastName, + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + qb.select({ + name: sql`${users2.firstName} || ' ' || ${users2.lastName}`.as('name'), + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + // @ts-expect-error name is undefined + qb.select({ admin: users1.admin }).from(users1), + ); + + db.insert(users1).select(db.select().from(users1)); + db.insert(users1).select(() => db.select().from(users1)); + db.insert(users1).select((qb) => qb.select().from(users1)); + // @ts-expect-error tables have different keys + db.insert(users1).select(db.select().from(users2)); + // @ts-expect-error tables have different keys + db.insert(users1).select(() => db.select().from(users2)); +} diff --git a/drizzle-orm/type-tests/mysql/select.ts b/drizzle-orm/type-tests/mysql/select.ts index 0a6af743b..c31021e26 100644 --- a/drizzle-orm/type-tests/mysql/select.ts +++ b/drizzle-orm/type-tests/mysql/select.ts @@ -22,11 +22,20 @@ import { or, } from '~/expressions.ts'; import { alias } from '~/mysql-core/alias.ts'; -import { param, sql } from '~/sql/sql.ts'; +import { type InferSelectViewModel, param, sql } from '~/sql/sql.ts'; import type { Equal } from 'type-tests/utils.ts'; import { Expect } from 'type-tests/utils.ts'; -import { type MySqlSelect, type MySqlSelectQueryBuilder, QueryBuilder } from '~/mysql-core/index.ts'; +import { + index, + int, + type MySqlSelect, + type MySqlSelectQueryBuilder, + mysqlTable, + mysqlView, + QueryBuilder, + text, +} from '~/mysql-core/index.ts'; import { db } from './db.ts'; import { cities, classes, newYorkers, users } from './tables.ts'; @@ -604,3 +613,203 @@ await db // @ts-expect-error method was already called .for('update'); } + +{ + const table1 = mysqlTable('table1', { + id: int().primaryKey(), + name: text().notNull(), + }); + const table2 = mysqlTable('table2', { + id: int().primaryKey(), + age: int().notNull(), + }); + const table3 = mysqlTable('table3', { + id: int().primaryKey(), + phone: text().notNull(), + }); + const view = mysqlView('view').as((qb) => + qb.select({ + table: table1, + column: table2.age, + nested: { + column: table3.phone, + }, + }).from(table1).innerJoin(table2, sql``).leftJoin(table3, sql``) + ); + const result = await db.select().from(view); + + Expect< + Equal + >; + Expect>; + Expect[]>>; +} + +{ + const table1 = mysqlTable('table1', { + id: int().primaryKey(), + name: text().notNull(), + }, () => [table1NameIndex]); + const table1NameIndex = index('table1_name_index').on(table1.name); + + const table2 = mysqlTable('table2', { + id: int().primaryKey(), + age: int().notNull(), + table1Id: int().references(() => table1.id).notNull(), + }, () => [table2AgeIndex, table2Table1Index]); + const table2AgeIndex = index('table2_name_index').on(table2.age); + const table2Table1Index = index('table2_table1_index').on(table2.table1Id); + + const view = mysqlView('view').as((qb) => qb.select().from(table2)); + const sq = db.select().from(table2, { useIndex: ['posts_text_index'] }).as('sq'); + + await db.select().from(table1, { + useIndex: table1NameIndex, + forceIndex: table1NameIndex, + ignoreIndex: table1NameIndex, + }); + await db.select().from(table1, { + useIndex: [table1NameIndex], + forceIndex: [table1NameIndex], + ignoreIndex: [table1NameIndex], + }); + await db.select().from(table1, { + useIndex: table1NameIndex, + // @ts-expect-error + table1NameIndex, + forceIndex: table1NameIndex, + ignoreIndex: table1NameIndex, + }); + + // @ts-expect-error + await db.select().from(view, { + useIndex: table1NameIndex, + forceIndex: table1NameIndex, + table1NameIndex, + ignoreIndex: [table1NameIndex], + }); + + // @ts-expect-error + await db.select().from(sq, { + useIndex: table1NameIndex, + forceIndex: table1NameIndex, + table1NameIndex, + ignoreIndex: [table1NameIndex], + }); + + const join1 = await db.select().from(table1) + .leftJoin(table2, eq(table1.id, table2.table1Id), { + useIndex: table2AgeIndex, + forceIndex: table2AgeIndex, + ignoreIndex: table2AgeIndex, + }); + + Expect< + Equal< + { + table1: { + id: number; + name: string; + }; + table2: { + id: number; + age: number; + table1Id: number; + } | null; + }[], + typeof join1 + > + >; + + const join2 = await db.select().from(table1) + .leftJoin(table2, eq(table1.id, table2.table1Id), { + useIndex: [table2AgeIndex, table2Table1Index], + forceIndex: [table2AgeIndex, table2Table1Index], + ignoreIndex: [table2AgeIndex, table2Table1Index], + }); + + Expect< + Equal< + { + table1: { + id: number; + name: string; + }; + table2: { + id: number; + age: number; + table1Id: number; + } | null; + }[], + typeof join2 + > + >; + + const sqJoin1 = await db.select().from(table1, { + useIndex: table1NameIndex, + }) + .leftJoin(sq, eq(table1.id, sq.table1Id)); + + Expect< + Equal< + { + table1: { + id: number; + name: string; + }; + sq: { + id: number; + age: number; + table1Id: number; + } | null; + }[], + typeof sqJoin1 + > + >; + + const sqJoin2 = await db.select().from(table1, { + useIndex: [table1NameIndex, table1NameIndex], + }) + .leftJoin(sq, eq(table1.id, sq.table1Id)); + + Expect< + Equal< + { + table1: { + id: number; + name: string; + }; + sq: { + id: number; + age: number; + table1Id: number; + } | null; + }[], + typeof sqJoin2 + > + >; + + await db.select().from(table1) + // @ts-expect-error + .leftJoin(view, eq(table1.id, view.table1Id), { + useIndex: table2AgeIndex, + forceIndex: table2AgeIndex, + table2Table1Index, + ignoreIndex: [table2AgeIndex, table2Table1Index], + }); + + await db.select().from(table1) + // @ts-expect-error + .leftJoin(sq, eq(table1.id, sq.table1Id), { + useIndex: table2AgeIndex, + forceIndex: table2AgeIndex, + table2Table1Index, + ignoreIndex: [table2AgeIndex, table2Table1Index], + }); +} diff --git a/drizzle-orm/type-tests/mysql/tables.ts b/drizzle-orm/type-tests/mysql/tables.ts index eac796e6f..24ce2582b 100644 --- a/drizzle-orm/type-tests/mysql/tables.ts +++ b/drizzle-orm/type-tests/mysql/tables.ts @@ -1,35 +1,48 @@ import { type Equal, Expect } from 'type-tests/utils.ts'; +import type { BuildColumn } from '~/column-builder.ts'; import { eq, gt } from '~/expressions.ts'; -import type { BuildColumn, InferSelectModel, Simplify } from '~/index.ts'; import { bigint, + binary, + boolean, char, check, customType, date, datetime, decimal, + double, + float, foreignKey, index, int, json, longtext, + mediumint, mediumtext, type MySqlColumn, mysqlEnum, mysqlTable, primaryKey, + real, serial, + smallint, text, + time, timestamp, + tinyint, tinytext, unique, uniqueIndex, + varbinary, varchar, + year, } from '~/mysql-core/index.ts'; import { mysqlSchema } from '~/mysql-core/schema.ts'; import { mysqlView, type MySqlViewWithSelection } from '~/mysql-core/view.ts'; import { sql } from '~/sql/sql.ts'; +import type { InferSelectModel } from '~/table.ts'; +import type { Simplify } from '~/utils.ts'; import { db } from './db.ts'; export const users = mysqlTable( @@ -78,54 +91,69 @@ export const cities = mysqlTable('cities_table', { Expect< Equal< { - id: MySqlColumn<{ - name: 'id'; - tableName: 'cities_table'; - dataType: 'number'; - columnType: 'MySqlSerial'; - data: number; - driverParam: number; - notNull: true; - hasDefault: true; - isPrimaryKey: true; - enumValues: undefined; - baseColumn: never; - generated: undefined; - isAutoincrement: true; - hasRuntimeDefault: false; - }, object>; - name: MySqlColumn<{ - name: 'name_db'; - tableName: 'cities_table'; - dataType: 'string'; - columnType: 'MySqlText'; - data: string; - driverParam: string; - notNull: true; - hasDefault: false; - isPrimaryKey: false; - enumValues: [string, ...string[]]; - baseColumn: never; - generated: undefined; - isAutoincrement: false; - hasRuntimeDefault: false; - }, object>; - population: MySqlColumn<{ - name: 'population'; - tableName: 'cities_table'; - dataType: 'number'; - columnType: 'MySqlInt'; - data: number; - driverParam: string | number; - notNull: false; - hasDefault: true; - isPrimaryKey: false; - enumValues: undefined; - baseColumn: never; - generated: undefined; - isAutoincrement: false; - hasRuntimeDefault: false; - }, object>; + id: MySqlColumn< + { + name: 'id'; + tableName: 'cities_table'; + dataType: 'number'; + columnType: 'MySqlSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + isPrimaryKey: true; + enumValues: undefined; + baseColumn: never; + generated: undefined; + identity: undefined; + isAutoincrement: true; + hasRuntimeDefault: false; + }, + {}, + {} + >; + name: MySqlColumn< + { + name: 'name_db'; + tableName: 'cities_table'; + dataType: 'string'; + columnType: 'MySqlText'; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + identity: undefined; + isAutoincrement: false; + hasRuntimeDefault: false; + }, + {}, + {} + >; + population: MySqlColumn< + { + name: 'population'; + tableName: 'cities_table'; + dataType: 'number'; + columnType: 'MySqlInt'; + data: number; + driverParam: string | number; + notNull: false; + hasDefault: true; + isPrimaryKey: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + identity: undefined; + isAutoincrement: false; + hasRuntimeDefault: false; + }, + {}, + {} + >; }, typeof cities._.columns > @@ -159,11 +187,11 @@ export const citiesCustom = customSchema.table('cities_table', { Expect>; -export const classes = mysqlTable('classes_table', { +export const classes = mysqlTable('classes_table', ({ serial, text }) => ({ id: serial('id').primaryKey(), class: text('class', { enum: ['A', 'C'] }), subClass: text('sub_class', { enum: ['B', 'D'] }).notNull(), -}); +})); /* export const classes2 = mysqlTable('classes_table', { id: serial().primaryKey(), @@ -173,7 +201,6 @@ export const classes = mysqlTable('classes_table', { export const newYorkers = mysqlView('new_yorkers') .algorithm('merge') - .definer('root@localhost') .sqlSecurity('definer') .as((qb) => { const sq = qb @@ -202,6 +229,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: true; hasRuntimeDefault: false; @@ -218,6 +246,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: true; hasRuntimeDefault: false; @@ -230,7 +259,6 @@ Expect< { const newYorkers = customSchema.view('new_yorkers') .algorithm('merge') - .definer('root@localhost') .sqlSecurity('definer') .as((qb) => { const sq = qb @@ -259,6 +287,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: true; hasRuntimeDefault: false; @@ -275,6 +304,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: true; hasRuntimeDefault: false; @@ -291,7 +321,6 @@ Expect< cityId: int('city_id'), }) .algorithm('merge') - .definer('root@localhost') .sqlSecurity('definer') .as( sql`select ${users.id} as user_id, ${cities.id} as city_id from ${users} left join ${cities} on ${ @@ -314,6 +343,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -330,6 +360,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -346,7 +377,6 @@ Expect< cityId: int('city_id'), }) .algorithm('merge') - .definer('root@localhost') .sqlSecurity('definer') .as( sql`select ${users.id} as user_id, ${cities.id} as city_id from ${users} left join ${cities} on ${ @@ -369,6 +399,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -385,6 +416,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -416,6 +448,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -432,6 +465,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -463,6 +497,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -479,6 +514,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -513,6 +549,7 @@ Expect< baseColumn: never; dialect: 'mysql'; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -760,3 +797,252 @@ Expect< }, typeof emailLog.$inferInsert> >; } + +{ + const customRequiredConfig = customType<{ + data: string; + driverData: string; + config: { length: number }; + configRequired: true; + }>({ + dataType(config) { + Expect>; + return `varchar(${config.length})`; + }, + + toDriver(value) { + Expect>(); + return value; + }, + + fromDriver(value) { + Expect>(); + return value; + }, + }); + + customRequiredConfig('t', { length: 10 }); + customRequiredConfig({ length: 10 }); + // @ts-expect-error - config is required + customRequiredConfig('t'); + // @ts-expect-error - config is required + customRequiredConfig(); +} + +{ + const customOptionalConfig = customType<{ + data: string; + driverData: string; + config: { length: number }; + }>({ + dataType(config) { + Expect>; + return config ? `varchar(${config.length})` : `text`; + }, + + toDriver(value) { + Expect>(); + return value; + }, + + fromDriver(value) { + Expect>(); + return value; + }, + }); + + customOptionalConfig('t', { length: 10 }); + customOptionalConfig('t'); + customOptionalConfig({ length: 10 }); + customOptionalConfig(); +} + +{ + mysqlTable('all_columns', { + bigint: bigint('bigint', { mode: 'number' }), + bigint2: bigint('bigint', { mode: 'number', unsigned: true }), + bigintdef: bigint('bigintdef', { mode: 'number' }).default(0), + binary: binary('binary'), + binary1: binary('binary1', { length: 1 }), + binarydef: binary('binarydef').default(''), + boolean: boolean('boolean'), + booleandef: boolean('booleandef').default(false), + char: char('char'), + char2: char('char2', { length: 1 }), + char3: char('char3', { enum: ['a', 'b', 'c'] }), + char4: char('char4', { length: 1, enum: ['a', 'b', 'c'] }), + chardef: char('chardef').default(''), + date: date('date'), + date2: date('date2', { mode: 'string' }), + datedef: date('datedef').default(new Date()), + datetime: datetime('datetime'), + datetime2: datetime('datetime2', { mode: 'string' }), + datetime3: datetime('datetime3', { mode: 'string', fsp: 3 }), + datetimedef: datetime('datetimedef').default(new Date()), + decimal: decimal('decimal'), + decimal2: decimal('decimal2', { precision: 10 }), + decimal3: decimal('decimal3', { scale: 2 }), + decimal4: decimal('decimal4', { precision: 10, scale: 2 }), + decimaldef: decimal('decimaldef').default('0'), + double: double('double'), + double2: double('double2', { precision: 10 }), + double3: double('double3', { scale: 2 }), + double4: double('double4', { precision: 10, scale: 2 }), + doubledef: double('doubledef').default(0), + enum: mysqlEnum('enum', ['a', 'b', 'c']), + enumdef: mysqlEnum('enumdef', ['a', 'b', 'c']).default('a'), + float: float('float'), + float2: float('float2', { precision: 10 }), + float3: float('float3', { scale: 2 }), + float4: float('float4', { precision: 10, scale: 2 }), + floatdef: float('floatdef').default(0), + int: int('int'), + int2: int('int2', { unsigned: true }), + intdef: int('intdef').default(0), + json: json('json'), + jsondef: json('jsondef').default({}), + mediumint: mediumint('mediumint'), + mediumint2: mediumint('mediumint2', { unsigned: true }), + mediumintdef: mediumint('mediumintdef').default(0), + real: real('real'), + real2: real('real2', { precision: 10 }), + real3: real('real3', { scale: 2 }), + real4: real('real4', { precision: 10, scale: 2 }), + realdef: real('realdef').default(0), + serial: serial('serial'), + serialdef: serial('serialdef').default(0), + smallint: smallint('smallint'), + smallint2: smallint('smallint2', { unsigned: true }), + smallintdef: smallint('smallintdef').default(0), + text: text('text'), + text2: text('text2', { enum: ['a', 'b', 'c'] }), + textdef: text('textdef').default(''), + tinytext: tinytext('tinytext'), + tinytext2: tinytext('tinytext2', { enum: ['a', 'b', 'c'] }), + tinytextdef: tinytext('tinytextdef').default(''), + mediumtext: mediumtext('mediumtext'), + mediumtext2: mediumtext('mediumtext2', { enum: ['a', 'b', 'c'] }), + mediumtextdef: mediumtext('mediumtextdef').default(''), + longtext: longtext('longtext'), + longtext2: longtext('longtext2', { enum: ['a', 'b', 'c'] }), + longtextdef: longtext('longtextdef').default(''), + time: time('time'), + time2: time('time2', { fsp: 1 }), + timedef: time('timedef').default('00:00:00'), + timestamp: timestamp('timestamp'), + timestamp2: timestamp('timestamp2', { mode: 'string' }), + timestamp3: timestamp('timestamp3', { mode: 'string', fsp: 1 }), + timestamp4: timestamp('timestamp4', { fsp: 1 }), + timestampdef: timestamp('timestampdef').default(new Date()), + tinyint: tinyint('tinyint'), + tinyint2: tinyint('tinyint2', { unsigned: true }), + tinyintdef: tinyint('tinyintdef').default(0), + varbinary: varbinary('varbinary', { length: 1 }), + varbinarydef: varbinary('varbinarydef', { length: 1 }).default(''), + varchar: varchar('varchar', { length: 1 }), + varchar2: varchar('varchar2', { length: 1, enum: ['a', 'b', 'c'] }), + varchardef: varchar('varchardef', { length: 1 }).default(''), + year: year('year'), + yeardef: year('yeardef').default(0), + }); +} + +{ + const keysAsColumnNames = mysqlTable('test', { + id: int(), + name: text(), + }); + + Expect>; + Expect>; +} + +{ + mysqlTable('all_columns_without_name', { + bigint: bigint({ mode: 'number' }), + bigint2: bigint({ mode: 'number', unsigned: true }), + bigintdef: bigint({ mode: 'number' }).default(0), + binary: binary(), + binrary1: binary({ length: 1 }), + binarydef: binary().default(''), + boolean: boolean(), + booleandef: boolean().default(false), + char: char(), + char2: char({ length: 1 }), + char3: char({ enum: ['a', 'b', 'c'] }), + char4: char({ length: 1, enum: ['a', 'b', 'c'] }), + chardef: char().default(''), + date: date(), + date2: date({ mode: 'string' }), + datedef: date('datedef').default(new Date()), + datetime: datetime(), + datetime2: datetime({ mode: 'string' }), + datetime3: datetime({ mode: 'string', fsp: 3 }), + datetimedef: datetime('datetimedef').default(new Date()), + decimal: decimal(), + decimal2: decimal({ precision: 10 }), + decimal3: decimal({ scale: 2 }), + decimal4: decimal({ precision: 10, scale: 2 }), + decimaldef: decimal('decimaldef').default('0'), + double: double(), + double2: double({ precision: 10 }), + double3: double({ scale: 2 }), + double4: double({ precision: 10, scale: 2 }), + doubledef: double().default(0), + enum: mysqlEnum(['a', 'b', 'c']), + enumdef: mysqlEnum(['a', 'b', 'c']).default('a'), + float: float(), + float2: float({ precision: 10 }), + float3: float({ scale: 2 }), + float4: float({ precision: 10, scale: 2 }), + floatdef: float().default(0), + int: int(), + int2: int({ unsigned: true }), + intdef: int().default(0), + json: json(), + jsondef: json().default({}), + mediumint: mediumint(), + mediumint2: mediumint({ unsigned: true }), + mediumintdef: mediumint().default(0), + real: real(), + real2: real({ precision: 10 }), + real3: real({ scale: 2 }), + real4: real({ precision: 10, scale: 2 }), + realdef: real().default(0), + serial: serial(), + serialdef: serial().default(0), + smallint: smallint(), + smallint2: smallint({ unsigned: true }), + smallintdef: smallint().default(0), + text: text(), + text2: text({ enum: ['a', 'b', 'c'] }), + textdef: text().default(''), + tinytext: tinytext(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }), + tinytextdef: tinytext().default(''), + mediumtext: mediumtext(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }), + mediumtextdef: mediumtext().default(''), + longtext: longtext(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }), + longtextdef: longtext().default(''), + time: time(), + time2: time({ fsp: 1 }), + timedef: time().default('00:00:00'), + timestamp: timestamp(), + timestamp2: timestamp({ mode: 'string' }), + timestamp3: timestamp({ mode: 'string', fsp: 1 }), + timestamp4: timestamp({ fsp: 1 }), + timestampdef: timestamp().default(new Date()), + tinyint: tinyint(), + tinyint2: tinyint({ unsigned: true }), + tinyintdef: tinyint().default(0), + varbinary: varbinary({ length: 1 }), + varbinarydef: varbinary({ length: 1 }).default(''), + varchar: varchar({ length: 1 }), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }), + varchardef: varchar({ length: 1 }).default(''), + year: year(), + yeardef: year().default(0), + }); +} diff --git a/drizzle-orm/type-tests/mysql/update.ts b/drizzle-orm/type-tests/mysql/update.ts index dc6967f44..abb127b5d 100644 --- a/drizzle-orm/type-tests/mysql/update.ts +++ b/drizzle-orm/type-tests/mysql/update.ts @@ -24,3 +24,7 @@ import { users } from './tables.ts'; // @ts-expect-error method was already called .where(sql``); } + +{ + db.update(users).set({}).where(sql``).limit(1).orderBy(sql``); +} diff --git a/drizzle-orm/type-tests/mysql/with.ts b/drizzle-orm/type-tests/mysql/with.ts index b4e528191..e6f240489 100644 --- a/drizzle-orm/type-tests/mysql/with.ts +++ b/drizzle-orm/type-tests/mysql/with.ts @@ -1,6 +1,6 @@ import type { Equal } from 'type-tests/utils.ts'; import { Expect } from 'type-tests/utils.ts'; -import { gt, inArray } from '~/expressions.ts'; +import { gt, inArray, like } from '~/expressions.ts'; import { int, mysqlTable, serial, text } from '~/mysql-core/index.ts'; import { sql } from '~/sql/sql.ts'; import { db } from './db.ts'; @@ -77,4 +77,7 @@ const orders = mysqlTable('orders', { generated: string | null; }[], typeof allFromWith> >; + + const regionalSalesWith = db.$with('regional_sales_with').as(db.select().from(regionalSales)); + db.with(regionalSalesWith).select().from(regionalSalesWith).where(like(regionalSalesWith.totalSales, 'abc')); } diff --git a/drizzle-orm/type-tests/pg/array.ts b/drizzle-orm/type-tests/pg/array.ts index 3961e92d0..d7a34cb2b 100644 --- a/drizzle-orm/type-tests/pg/array.ts +++ b/drizzle-orm/type-tests/pg/array.ts @@ -21,10 +21,13 @@ import { integer, pgTable } from '~/pg-core/index.ts'; enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; - } + }, + {}, + {} >, typeof table['a']['_']['baseColumn'] > diff --git a/drizzle-orm/type-tests/pg/count.ts b/drizzle-orm/type-tests/pg/count.ts new file mode 100644 index 000000000..9ed5eeaf9 --- /dev/null +++ b/drizzle-orm/type-tests/pg/count.ts @@ -0,0 +1,61 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, gt, ne } from '~/expressions.ts'; +import { integer, pgTable, serial, text } from '~/pg-core/index.ts'; +import type { Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = pgTable('names', { + id: serial('id').primaryKey(), + name: text('name'), + authorId: integer('author_id'), +}); + +const separate = await db.$count(names); + +const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))); + +const embedded = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names).as('count1'), + }) + .from(names); + +const embeddedFilters = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'), + }) + .from(names); + +Expect>; + +Expect>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embedded + > +>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embeddedFilters + > +>; diff --git a/drizzle-orm/type-tests/pg/insert.ts b/drizzle-orm/type-tests/pg/insert.ts index 6a5179804..a1722fa78 100644 --- a/drizzle-orm/type-tests/pg/insert.ts +++ b/drizzle-orm/type-tests/pg/insert.ts @@ -1,10 +1,11 @@ import type { QueryResult } from 'pg'; import type { Equal } from 'type-tests/utils.ts'; import { Expect } from 'type-tests/utils.ts'; +import { boolean, pgTable, QueryBuilder, serial, text } from '~/pg-core/index.ts'; import type { PgInsert } from '~/pg-core/query-builders/insert.ts'; import { sql } from '~/sql/sql.ts'; import { db } from './db.ts'; -import { users } from './tables.ts'; +import { identityColumnsTable, users } from './tables.ts'; const insert = await db .insert(users) @@ -204,3 +205,95 @@ Expect< // @ts-expect-error method was already called .returning(); } + +{ + const users1 = pgTable('users1', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + admin: boolean('admin').notNull().default(false), + }); + const users2 = pgTable('users2', { + id: serial('id').primaryKey(), + firstName: text('first_name').notNull(), + lastName: text('last_name').notNull(), + admin: boolean('admin').notNull().default(false), + phoneNumber: text('phone_number'), + }); + + const qb = new QueryBuilder(); + + db.insert(users1).select(sql`select * from users1`); + db.insert(users1).select(() => sql`select * from users1`); + + db + .insert(users1) + .select( + qb.select({ + name: users2.firstName, + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + qb.select({ + name: users2.firstName, + admin: users2.admin, + }).from(users2).where(sql``), + ); + + db + .insert(users2) + .select( + qb.select({ + firstName: users2.firstName, + lastName: users2.lastName, + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + qb.select({ + name: sql`${users2.firstName} || ' ' || ${users2.lastName}`.as('name'), + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + // @ts-expect-error name is undefined + qb.select({ admin: users1.admin }).from(users1), + ); + + db.insert(users1).select(db.select().from(users1)); + db.insert(users1).select(() => db.select().from(users1)); + db.insert(users1).select((qb) => qb.select().from(users1)); + // @ts-expect-error tables have different keys + db.insert(users1).select(db.select().from(users2)); + // @ts-expect-error tables have different keys + db.insert(users1).select(() => db.select().from(users2)); +} + +{ + db.insert(identityColumnsTable).values([ + { byDefaultAsIdentity: 4, name: 'fdf' }, + ]); + + // @ts-expect-error + db.insert(identityColumnsTable).values([ + { alwaysAsIdentity: 2 }, + ]); + + db.insert(identityColumnsTable).overridingSystemValue().values([ + { alwaysAsIdentity: 2 }, + ]); + + // @ts-expect-error + db.insert(identityColumnsTable).values([ + { generatedCol: 2 }, + ]); +} diff --git a/drizzle-orm/type-tests/pg/select.ts b/drizzle-orm/type-tests/pg/select.ts index 0fde90a71..4ab3a86b3 100644 --- a/drizzle-orm/type-tests/pg/select.ts +++ b/drizzle-orm/type-tests/pg/select.ts @@ -31,13 +31,15 @@ import { alias } from '~/pg-core/alias.ts'; import { boolean, integer, + pgMaterializedView, type PgSelect, type PgSelectQueryBuilder, pgTable, + pgView, QueryBuilder, text, } from '~/pg-core/index.ts'; -import { type SQL, sql } from '~/sql/sql.ts'; +import { type InferSelectViewModel, type SQL, sql } from '~/sql/sql.ts'; import { db } from './db.ts'; import { cities, classes, newYorkers, newYorkers2, users } from './tables.ts'; @@ -1156,3 +1158,77 @@ await db ), ); } + +{ + const table1 = pgTable('table1', { + id: integer().primaryKey(), + name: text().notNull(), + }); + const table2 = pgTable('table2', { + id: integer().primaryKey(), + age: integer().notNull(), + }); + const table3 = pgTable('table3', { + id: integer().primaryKey(), + phone: text().notNull(), + }); + const view = pgView('view').as((qb) => + qb.select({ + table: table1, + column: table2.age, + nested: { + column: table3.phone, + }, + }).from(table1).innerJoin(table2, sql``).leftJoin(table3, sql``) + ); + const result = await db.select().from(view); + + Expect< + Equal + >; + Expect>; + Expect[]>>; +} + +{ + const table1 = pgTable('table1', { + id: integer().primaryKey(), + name: text().notNull(), + }); + const table2 = pgTable('table2', { + id: integer().primaryKey(), + age: integer().notNull(), + }); + const table3 = pgTable('table3', { + id: integer().primaryKey(), + phone: text().notNull(), + }); + const view = pgMaterializedView('view').as((qb) => + qb.select({ + table: table1, + column: table2.age, + nested: { + column: table3.phone, + }, + }).from(table1).innerJoin(table2, sql``).leftJoin(table3, sql``) + ); + const result = await db.select().from(view); + + Expect< + Equal + >; + Expect>; + Expect[]>>; +} diff --git a/drizzle-orm/type-tests/pg/tables.ts b/drizzle-orm/type-tests/pg/tables.ts index 5faca5d7b..2b07a9fcd 100644 --- a/drizzle-orm/type-tests/pg/tables.ts +++ b/drizzle-orm/type-tests/pg/tables.ts @@ -6,6 +6,7 @@ import { eq, gt } from '~/expressions.ts'; import { bigint, bigserial, + bit, boolean, char, check, @@ -15,11 +16,14 @@ import { decimal, doublePrecision, foreignKey, + geometry, + halfvec, index, inet, integer, json, jsonb, + line, macaddr, macaddr8, numeric, @@ -27,17 +31,20 @@ import { pgEnum, pgTable, type PgTableWithColumns, + point, primaryKey, real, serial, smallint, smallserial, + sparsevec, text, time, timestamp, uniqueIndex, uuid, varchar, + vector, } from '~/pg-core/index.ts'; import { pgSchema } from '~/pg-core/schema.ts'; import { @@ -48,10 +55,35 @@ import { } from '~/pg-core/view.ts'; import { sql } from '~/sql/sql.ts'; import type { InferInsertModel, InferSelectModel } from '~/table.ts'; +import type { Simplify } from '~/utils.ts'; import { db } from './db.ts'; export const myEnum = pgEnum('my_enum', ['a', 'b', 'c']); +export const identityColumnsTable = pgTable('identity_columns_table', { + generatedCol: integer('generated_col').generatedAlwaysAs(1), + alwaysAsIdentity: integer('always_as_identity').generatedAlwaysAsIdentity(), + byDefaultAsIdentity: integer('by_default_as_identity').generatedByDefaultAsIdentity(), + name: text('name'), +}); + +Expect, typeof identityColumnsTable['$inferSelect']>>; +Expect, typeof identityColumnsTable['_']['inferSelect']>>; +Expect, typeof identityColumnsTable['$inferInsert']>>; +Expect, typeof identityColumnsTable['_']['inferInsert']>>; +Expect< + Equal< + InferInsertModel, + Simplify + > +>; +Expect< + Equal< + InferInsertModel, + Simplify + > +>; + export const users = pgTable( 'users_table', { @@ -71,23 +103,23 @@ export const users = pgTable( enumCol: myEnum('enum_col').notNull(), arrayCol: text('array_col').array().notNull(), }, - (users) => ({ - usersAge1Idx: uniqueIndex('usersAge1Idx').on(users.class.asc().nullsFirst(), sql``), - usersAge2Idx: index('usersAge2Idx').on(sql``), - uniqueClass: uniqueIndex('uniqueClass') + (users) => [ + uniqueIndex('usersAge1Idx').on(users.class.asc().nullsFirst(), sql``), + index('usersAge2Idx').on(sql``), + uniqueIndex('uniqueClass') .using('btree', users.class.desc().op('text_ops'), users.subClass.nullsLast()) .where(sql`${users.class} is not null`) .concurrently(), - legalAge: check('legalAge', sql`${users.age1} > 18`), - usersClassFK: foreignKey({ columns: [users.subClass], foreignColumns: [classes.subClass] }) + check('legalAge', sql`${users.age1} > 18`), + foreignKey({ columns: [users.subClass], foreignColumns: [classes.subClass] }) .onUpdate('cascade') .onDelete('cascade'), - usersClassComplexFK: foreignKey({ + foreignKey({ columns: [users.class, users.subClass], foreignColumns: [classes.class, classes.subClass], }), - pk: primaryKey(users.age1, users.class), - }), + primaryKey(users.age1, users.class), + ], ); Expect, typeof users['$inferSelect']>>; @@ -107,9 +139,7 @@ export const smallSerialTest = pgTable('cities_table', { id: smallserial('id').primaryKey(), name: text('name').notNull(), population: integer('population').default(0), -}, (cities) => ({ - citiesNameIdx: index().on(cities.id), -})); +}); Expect< Equal<{ @@ -165,9 +195,7 @@ export const citiesCustom = customSchema.table('cities_table', { id: serial('id').primaryKey(), name: text('name').notNull(), population: integer('population').default(0), -}, (cities) => ({ - citiesNameIdx: index().on(cities.id), -})); +}, (cities) => [index().on(cities.id)]); export const newYorkers = pgView('new_yorkers') .with({ @@ -202,6 +230,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; @@ -218,6 +247,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; @@ -261,6 +291,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; @@ -277,6 +308,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; @@ -318,6 +350,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -334,6 +367,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -375,6 +409,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -391,6 +426,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -422,6 +458,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -438,6 +475,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -469,6 +507,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -485,6 +524,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -499,8 +539,8 @@ export const newYorkers2 = pgMaterializedView('new_yorkers') .using('btree') .with({ fillfactor: 90, - toast_tuple_target: 0.5, - autovacuum_enabled: true, + toastTupleTarget: 0.5, + autovacuumEnabled: true, }) .tablespace('custom_tablespace') .withNoData() @@ -531,6 +571,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; @@ -547,6 +588,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; @@ -561,8 +603,8 @@ Expect< .using('btree') .with({ fillfactor: 90, - toast_tuple_target: 0.5, - autovacuum_enabled: true, + toastTupleTarget: 0.5, + autovacuumEnabled: true, }) .tablespace('custom_tablespace') .withNoData() @@ -593,6 +635,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; @@ -609,6 +652,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; @@ -627,8 +671,8 @@ Expect< .using('btree') .with({ fillfactor: 90, - toast_tuple_target: 0.5, - autovacuum_enabled: true, + toastTupleTarget: 0.5, + autovacuumEnabled: true, }) .tablespace('custom_tablespace') .withNoData() @@ -653,6 +697,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -669,6 +714,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -687,8 +733,8 @@ Expect< .using('btree') .with({ fillfactor: 90, - toast_tuple_target: 0.5, - autovacuum_enabled: true, + toastTupleTarget: 0.5, + autovacuumEnabled: true, }) .tablespace('custom_tablespace') .withNoData() @@ -713,6 +759,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -729,6 +776,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -760,6 +808,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -776,6 +825,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -807,6 +857,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -823,6 +874,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; @@ -872,8 +924,11 @@ await db.refreshMaterializedView(newYorkers2).withNoData().concurrently(); }); customTextRequired('t', { length: 10 }); + customTextRequired({ length: 10 }); // @ts-expect-error - config is required customTextRequired('t'); + // @ts-expect-error - config is required + customTextRequired(); } { @@ -900,92 +955,102 @@ await db.refreshMaterializedView(newYorkers2).withNoData().concurrently(); customTextOptional('t', { length: 10 }); customTextOptional('t'); + customTextOptional({ length: 10 }); + customTextOptional(); } { - const cities = pgTable('cities_table', { + const cities1 = pgTable('cities_table', { id: serial('id').primaryKey(), name: text('name').notNull().primaryKey(), role: text('role', { enum: ['admin', 'user'] }).default('user').notNull(), population: integer('population').default(0), }); + const cities2 = pgTable('cities_table', ({ serial, text, integer }) => ({ + id: serial('id').primaryKey(), + name: text('name').notNull().primaryKey(), + role: text('role', { enum: ['admin', 'user'] }).default('user').notNull(), + population: integer('population').default(0), + })); + + type Expected = PgTableWithColumns<{ + name: 'cities_table'; + schema: undefined; + dialect: 'pg'; + columns: { + id: PgColumn<{ + tableName: 'cities_table'; + name: 'id'; + dataType: 'number'; + columnType: 'PgSerial'; + data: number; + driverParam: number; + hasDefault: true; + notNull: true; + enumValues: undefined; + baseColumn: never; + generated: undefined; + identity: undefined; + isPrimaryKey: true; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + name: PgColumn<{ + tableName: 'cities_table'; + name: 'name'; + dataType: 'string'; + columnType: 'PgText'; + data: string; + driverParam: string; + hasDefault: false; + enumValues: [string, ...string[]]; + notNull: true; + baseColumn: never; + generated: undefined; + identity: undefined; + isPrimaryKey: true; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + role: PgColumn<{ + tableName: 'cities_table'; + name: 'role'; + dataType: 'string'; + columnType: 'PgText'; + data: 'admin' | 'user'; + driverParam: string; + hasDefault: true; + enumValues: ['admin', 'user']; + notNull: true; + baseColumn: never; + generated: undefined; + identity: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + population: PgColumn<{ + tableName: 'cities_table'; + name: 'population'; + dataType: 'number'; + columnType: 'PgInteger'; + data: number; + driverParam: string | number; + notNull: false; + hasDefault: true; + enumValues: undefined; + baseColumn: never; + generated: undefined; + identity: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }; + }>; - Expect< - Equal< - PgTableWithColumns<{ - name: 'cities_table'; - schema: undefined; - dialect: 'pg'; - columns: { - id: PgColumn<{ - tableName: 'cities_table'; - name: 'id'; - dataType: 'number'; - columnType: 'PgSerial'; - data: number; - driverParam: number; - hasDefault: true; - notNull: true; - enumValues: undefined; - baseColumn: never; - generated: undefined; - isPrimaryKey: true; - isAutoincrement: false; - hasRuntimeDefault: false; - }>; - name: PgColumn<{ - tableName: 'cities_table'; - name: 'name'; - dataType: 'string'; - columnType: 'PgText'; - data: string; - driverParam: string; - hasDefault: false; - enumValues: [string, ...string[]]; - notNull: true; - baseColumn: never; - generated: undefined; - isPrimaryKey: true; - isAutoincrement: false; - hasRuntimeDefault: false; - }>; - role: PgColumn<{ - tableName: 'cities_table'; - name: 'role'; - dataType: 'string'; - columnType: 'PgText'; - data: 'admin' | 'user'; - driverParam: string; - hasDefault: true; - enumValues: ['admin', 'user']; - notNull: true; - baseColumn: never; - generated: undefined; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - }>; - population: PgColumn<{ - tableName: 'cities_table'; - name: 'population'; - dataType: 'number'; - columnType: 'PgInteger'; - data: number; - driverParam: string | number; - notNull: false; - hasDefault: true; - enumValues: undefined; - baseColumn: never; - generated: undefined; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - }>; - }; - }>, - typeof cities - > - >; + Expect>; + Expect>; } { @@ -1180,7 +1245,11 @@ await db.refreshMaterializedView(newYorkers2).withNoData().concurrently(); } { + const enum_ = pgEnum('enum', ['a', 'b', 'c']); + pgTable('all_columns', { + enum: enum_('enum'), + enumdef: enum_('enumdef').default('a'), sm: smallint('smallint'), smdef: smallint('smallint_def').default(10), int: integer('integer'), @@ -1212,13 +1281,125 @@ await db.refreshMaterializedView(newYorkers2).withNoData().concurrently(); jsonbdef: jsonb('jsonbdef').$type<{ attr: string }>().default({ attr: 'value' }), time: time('time'), time2: time('time2', { precision: 6, withTimezone: true }), + timedef: time('timedef').default('00:00:00'), timedefnow: time('timedefnow').defaultNow(), timestamp: timestamp('timestamp'), timestamp2: timestamp('timestamp2', { precision: 6, withTimezone: true }), timestamp3: timestamp('timestamp3', { withTimezone: true }), timestamp4: timestamp('timestamp4', { precision: 4 }), - timestampdef: timestamp('timestampdef').defaultNow(), + timestampdef: timestamp('timestampdef').default(new Date()), date: date('date', { mode: 'date' }), - datedef: date('datedef').defaultNow(), + datedef: date('datedef').default('2024-01-01'), + datedefnow: date('datedefnow').defaultNow(), + }); + + pgTable('all_postgis_columns', { + geometry: geometry('geometry'), + geometry2: geometry('geometry2', { srid: 2, mode: 'xy' }), + geometry3: geometry('geometry3', { srid: 3, mode: 'tuple' }), + geometry4: geometry('geometry4', { mode: 'tuple' }), + geometrydef: geometry('geometrydef').default([1, 2]), + point: point('point'), + point2: point('point2', { mode: 'xy' }), + pointdef: point('pointdef').default([1, 2]), + line: line('line'), + line2: line('line2', { mode: 'abc' }), + linedef: line('linedef').default([1, 2, 3]), + }); + + pgTable('all_vector_columns', { + bit: bit('bit', { dimensions: 1 }), + bitdef: bit('bitdef', { dimensions: 1 }).default('1'), + halfvec: halfvec('halfvec', { dimensions: 1 }), + halfvecdef: halfvec('halfvecdef', { dimensions: 1 }).default([1]), + sparsevec: sparsevec('sparsevec', { dimensions: 1 }), + sparsevecdef: sparsevec('sparsevecdef', { dimensions: 1 }).default('{1:1}/1'), + vector: vector('vector', { dimensions: 1 }), + vectordef: vector('vectordef', { dimensions: 1 }).default([1]), + }); +} + +{ + const keysAsColumnNames = pgTable('test', { + id: serial(), + name: text(), + }); + + Expect>; + Expect>; +} + +{ + const enum_ = pgEnum('enum', ['a', 'b', 'c']); + + pgTable('all_columns_without_name', { + enum: enum_(), + enumdef: enum_().default('a'), + sm: smallint(), + smdef: smallint().default(10), + int: integer(), + intdef: integer().default(10), + numeric: numeric(), + numeric2: numeric({ precision: 5 }), + numeric3: numeric({ scale: 2 }), + numeric4: numeric({ precision: 5, scale: 2 }), + numericdef: numeric().default('100'), + bigint: bigint({ mode: 'number' }), + bigintdef: bigint({ mode: 'number' }).default(100), + bool: boolean(), + booldef: boolean().default(true), + text: text(), + textdef: text().default('text'), + varchar: varchar(), + varchardef: varchar().default('text'), + serial: serial(), + bigserial: bigserial({ mode: 'number' }), + decimal: decimal({ precision: 100, scale: 2 }), + decimaldef: decimal({ precision: 100, scale: 2 }).default('100.0'), + doublePrecision: doublePrecision(), + doublePrecisiondef: doublePrecision().default(100), + real: real(), + realdef: real().default(100), + json: json().$type<{ attr: string }>(), + jsondef: json().$type<{ attr: string }>().default({ attr: 'value' }), + jsonb: jsonb().$type<{ attr: string }>(), + jsonbdef: jsonb().$type<{ attr: string }>().default({ attr: 'value' }), + time: time(), + time2: time({ precision: 6, withTimezone: true }), + timedef: time().default('00:00:00'), + timedefnow: time().defaultNow(), + timestamp: timestamp(), + timestamp2: timestamp({ precision: 6, withTimezone: true }), + timestamp3: timestamp({ withTimezone: true }), + timestamp4: timestamp({ precision: 4 }), + timestampdef: timestamp().default(new Date()), + date: date({ mode: 'date' }), + datedef: date().default('2024-01-01'), + datedefnow: date().defaultNow(), + }); + + pgTable('all_postgis_columns', { + geometry: geometry(), + geometry2: geometry({ srid: 2, mode: 'xy' }), + geometry3: geometry({ srid: 3, mode: 'tuple' }), + geometry4: geometry({ mode: 'tuple' }), + geometrydef: geometry().default([1, 2]), + point: point(), + point2: point({ mode: 'xy' }), + pointdef: point().default([1, 2]), + line: line(), + line2: line({ mode: 'abc' }), + linedef: line().default([1, 2, 3]), + }); + + pgTable('all_vector_columns', { + bit: bit({ dimensions: 1 }), + bitdef: bit({ dimensions: 1 }).default('1'), + halfvec: halfvec({ dimensions: 1 }), + halfvecdef: halfvec({ dimensions: 1 }).default([1]), + sparsevec: sparsevec({ dimensions: 1 }), + sparsevecdef: sparsevec({ dimensions: 1 }).default('{1:1}/1'), + vector: vector({ dimensions: 1 }), + vectordef: vector({ dimensions: 1 }).default([1]), }); } diff --git a/drizzle-orm/type-tests/pg/update.ts b/drizzle-orm/type-tests/pg/update.ts index a667825e0..a53f70b03 100644 --- a/drizzle-orm/type-tests/pg/update.ts +++ b/drizzle-orm/type-tests/pg/update.ts @@ -4,8 +4,9 @@ import { Expect } from 'type-tests/utils.ts'; import { eq } from '~/expressions.ts'; import type { PgUpdate } from '~/pg-core/index.ts'; import { sql } from '~/sql/sql.ts'; +import type { Simplify } from '~/utils.ts'; import { db } from './db.ts'; -import { users } from './tables.ts'; +import { cities, salEmp, users } from './tables.ts'; const update = await db.update(users) .set({ @@ -86,3 +87,192 @@ Expect>; // @ts-expect-error method was already called .where(sql``); } + +{ + db + .update(users) + .set({}) + .from(sql``) + .leftJoin(sql``, (table, from) => { + Expect>; + Expect>; + return sql``; + }); + + db + .update(users) + .set({}) + .from(cities) + .leftJoin(sql``, (table, from) => { + Expect>; + Expect>; + return sql``; + }); + + const citiesSq = db.$with('cities_sq').as(db.select({ id: cities.id }).from(cities)); + + db + .with(citiesSq) + .update(users) + .set({}) + .from(citiesSq) + .leftJoin(sql``, (table, from) => { + Expect>; + Expect>; + return sql``; + }); + + db + .with(citiesSq) + .update(users) + .set({ + homeCity: citiesSq.id, + }) + .from(citiesSq); +} + +{ + const result = await db.update(users).set({}).from(cities).returning(); + Expect< + Equal[], typeof result> + >; +} + +{ + const result1 = await db.update(users).set({}).from(cities).leftJoin(salEmp, sql``).returning(); + Expect< + Equal[], typeof result1> + >; + + const result2 = await db.update(users).set({}).from(cities).rightJoin(salEmp, sql``).returning(); + Expect< + Equal[], typeof result2> + >; + + const result3 = await db.update(users).set({}).from(cities).innerJoin(salEmp, sql``).returning(); + Expect< + Equal[], typeof result3> + >; + + const result4 = await db.update(users).set({}).from(cities).fullJoin(salEmp, sql``).returning(); + Expect< + Equal[], typeof result4> + >; +} + +{ + const result = await db.update(users).set({}).from(cities).returning({ + id: users.id, + cities: cities, + cityName: cities.name, + }); + Expect< + Equal[], typeof result> + >; +} + +{ + const result1 = await db.update(users).set({}).from(cities).leftJoin(salEmp, sql``).returning({ + id: users.id, + cities: cities, + cityName: cities.name, + salEmp: salEmp, + salEmpName: salEmp.name, + }); + Expect< + Equal[], typeof result1> + >; + + const result2 = await db.update(users).set({}).from(cities).rightJoin(salEmp, sql``).returning({ + id: users.id, + cities: cities, + cityName: cities.name, + salEmp: salEmp, + salEmpName: salEmp.name, + }); + Expect< + Equal[], typeof result2> + >; + + const result3 = await db.update(users).set({}).from(cities).innerJoin(salEmp, sql``).returning({ + id: users.id, + cities: cities, + cityName: cities.name, + salEmp: salEmp, + salEmpName: salEmp.name, + }); + Expect< + Equal[], typeof result3> + >; + + const result4 = await db.update(users).set({}).from(cities).fullJoin(salEmp, sql``).returning({ + id: users.id, + cities: cities, + cityName: cities.name, + salEmp: salEmp, + salEmpName: salEmp.name, + }); + Expect< + Equal[], typeof result4> + >; +} + +{ + await db + .update(users) + .set({}) + // @ts-expect-error can't use joins before from + .fullJoin(salEmp, sql``); +} diff --git a/drizzle-orm/type-tests/pg/with.ts b/drizzle-orm/type-tests/pg/with.ts index d5fcc96ed..288e3b6d0 100644 --- a/drizzle-orm/type-tests/pg/with.ts +++ b/drizzle-orm/type-tests/pg/with.ts @@ -1,6 +1,6 @@ import type { Equal } from 'type-tests/utils.ts'; import { Expect } from 'type-tests/utils.ts'; -import { gt, inArray } from '~/expressions.ts'; +import { gt, inArray, like } from '~/expressions.ts'; import { integer, pgTable, serial, text } from '~/pg-core/index.ts'; import { sql } from '~/sql/sql.ts'; import { db } from './db.ts'; @@ -77,4 +77,7 @@ const orders = pgTable('orders', { generated: string | null; }[], typeof allFromWith> >; + + const regionalSalesWith = db.$with('regional_sales_with').as(db.select().from(regionalSales)); + db.with(regionalSalesWith).select().from(regionalSalesWith).where(like(regionalSalesWith.totalSales, 'abc')); } diff --git a/drizzle-orm/type-tests/singlestore/1000columns.ts b/drizzle-orm/type-tests/singlestore/1000columns.ts new file mode 100644 index 000000000..f84640858 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/1000columns.ts @@ -0,0 +1,904 @@ +import { bigint, double, singlestoreTable, varchar } from '~/singlestore-core/index.ts'; + +singlestoreTable('test', { + col0: double('col1').primaryKey().autoincrement().default(0), + col1: double('col1').primaryKey().autoincrement().default(0), + col2: double('col1').primaryKey().autoincrement().default(0), + col3: double('col1').primaryKey().autoincrement().default(0), + col4: double('col1').primaryKey().autoincrement().default(0), + col5: double('col1').primaryKey().autoincrement().default(0), + col6: double('col1').primaryKey().autoincrement().default(0), + col8: double('col1').primaryKey().autoincrement().default(0), + col9: double('col1').primaryKey().autoincrement().default(0), + col10: double('col1').primaryKey().autoincrement().default(0), + col11: double('col1').primaryKey().autoincrement().default(0), + col12: double('col1').primaryKey().autoincrement().default(0), + col13: double('col1').primaryKey().autoincrement().default(0), + col14: double('col1').primaryKey().autoincrement().default(0), + col15: double('col1').primaryKey().autoincrement().default(0), + col16: double('col1').primaryKey().autoincrement().default(0), + col18: double('col1').primaryKey().autoincrement().default(0), + col19: double('col1').primaryKey().autoincrement().default(0), + col20: double('col1').primaryKey().autoincrement().default(0), + col21: double('col1').primaryKey().autoincrement().default(0), + col22: double('col1').primaryKey().autoincrement().default(0), + col23: double('col1').primaryKey().autoincrement().default(0), + col24: double('col1').primaryKey().autoincrement().default(0), + col25: double('col1').primaryKey().autoincrement().default(0), + col26: double('col1').primaryKey().autoincrement().default(0), + col28: double('col1').primaryKey().autoincrement().default(0), + col29: double('col1').primaryKey().autoincrement().default(0), + col30: double('col1').primaryKey().autoincrement().default(0), + col31: double('col1').primaryKey().autoincrement().default(0), + col32: double('col1').primaryKey().autoincrement().default(0), + col33: double('col1').primaryKey().autoincrement().default(0), + col34: double('col1').primaryKey().autoincrement().default(0), + col35: double('col1').primaryKey().autoincrement().default(0), + col36: double('col1').primaryKey().autoincrement().default(0), + col38: double('col1').primaryKey().autoincrement().default(0), + col39: double('col1').primaryKey().autoincrement().default(0), + col40: double('col1').primaryKey().autoincrement().default(0), + col41: double('col1').primaryKey().autoincrement().default(0), + col42: double('col1').primaryKey().autoincrement().default(0), + col43: double('col1').primaryKey().autoincrement().default(0), + col44: double('col1').primaryKey().autoincrement().default(0), + col45: double('col1').primaryKey().autoincrement().default(0), + col46: double('col1').primaryKey().autoincrement().default(0), + col48: double('col1').primaryKey().autoincrement().default(0), + col49: double('col1').primaryKey().autoincrement().default(0), + col50: double('col1').primaryKey().autoincrement().default(0), + col51: double('col1').primaryKey().autoincrement().default(0), + col52: double('col1').primaryKey().autoincrement().default(0), + col53: double('col1').primaryKey().autoincrement().default(0), + col54: double('col1').primaryKey().autoincrement().default(0), + col55: double('col1').primaryKey().autoincrement().default(0), + col56: double('col1').primaryKey().autoincrement().default(0), + col58: double('col1').primaryKey().autoincrement().default(0), + col59: double('col1').primaryKey().autoincrement().default(0), + col60: double('col1').primaryKey().autoincrement().default(0), + col61: double('col1').primaryKey().autoincrement().default(0), + col62: double('col1').primaryKey().autoincrement().default(0), + col63: double('col1').primaryKey().autoincrement().default(0), + col64: double('col1').primaryKey().autoincrement().default(0), + col65: double('col1').primaryKey().autoincrement().default(0), + col66: double('col1').primaryKey().autoincrement().default(0), + col68: double('col1').primaryKey().autoincrement().default(0), + col69: double('col1').primaryKey().autoincrement().default(0), + col70: double('col1').primaryKey().autoincrement().default(0), + col71: double('col1').primaryKey().autoincrement().default(0), + col72: double('col1').primaryKey().autoincrement().default(0), + col73: double('col1').primaryKey().autoincrement().default(0), + col74: double('col1').primaryKey().autoincrement().default(0), + col75: double('col1').primaryKey().autoincrement().default(0), + col76: double('col1').primaryKey().autoincrement().default(0), + col78: double('col1').primaryKey().autoincrement().default(0), + col79: double('col1').primaryKey().autoincrement().default(0), + col80: double('col1').primaryKey().autoincrement().default(0), + col81: double('col1').primaryKey().autoincrement().default(0), + col82: double('col1').primaryKey().autoincrement().default(0), + col83: double('col1').primaryKey().autoincrement().default(0), + col84: double('col1').primaryKey().autoincrement().default(0), + col85: double('col1').primaryKey().autoincrement().default(0), + col86: double('col1').primaryKey().autoincrement().default(0), + col88: double('col1').primaryKey().autoincrement().default(0), + col89: double('col1').primaryKey().autoincrement().default(0), + col90: double('col1').primaryKey().autoincrement().default(0), + col91: double('col1').primaryKey().autoincrement().default(0), + col92: double('col1').primaryKey().autoincrement().default(0), + col93: double('col1').primaryKey().autoincrement().default(0), + col94: double('col1').primaryKey().autoincrement().default(0), + col95: double('col1').primaryKey().autoincrement().default(0), + col96: double('col1').primaryKey().autoincrement().default(0), + col98: double('col1').primaryKey().autoincrement().default(0), + col99: double('col1').primaryKey().autoincrement().default(0), + col100: double('col1').primaryKey().autoincrement().default(0), + col101: double('col1').primaryKey().autoincrement().default(0), + col102: double('col1').primaryKey().autoincrement().default(0), + col103: double('col1').primaryKey().autoincrement().default(0), + col104: double('col1').primaryKey().autoincrement().default(0), + col105: double('col1').primaryKey().autoincrement().default(0), + col106: double('col1').primaryKey().autoincrement().default(0), + col108: double('col1').primaryKey().autoincrement().default(0), + col109: double('col1').primaryKey().autoincrement().default(0), + col110: double('col11').primaryKey().autoincrement().default(0), + col111: double('col11').primaryKey().autoincrement().default(0), + col112: double('col11').primaryKey().autoincrement().default(0), + col113: double('col11').primaryKey().autoincrement().default(0), + col114: double('col11').primaryKey().autoincrement().default(0), + col115: double('col11').primaryKey().autoincrement().default(0), + col116: double('col11').primaryKey().autoincrement().default(0), + col118: double('col11').primaryKey().autoincrement().default(0), + col119: double('col11').primaryKey().autoincrement().default(0), + col120: double('col11').primaryKey().autoincrement().default(0), + col121: double('col11').primaryKey().autoincrement().default(0), + col122: double('col11').primaryKey().autoincrement().default(0), + col123: double('col11').primaryKey().autoincrement().default(0), + col124: double('col11').primaryKey().autoincrement().default(0), + col125: double('col11').primaryKey().autoincrement().default(0), + col126: double('col11').primaryKey().autoincrement().default(0), + col128: double('col11').primaryKey().autoincrement().default(0), + col129: double('col11').primaryKey().autoincrement().default(0), + col130: double('col11').primaryKey().autoincrement().default(0), + col131: double('col11').primaryKey().autoincrement().default(0), + col132: double('col11').primaryKey().autoincrement().default(0), + col133: double('col11').primaryKey().autoincrement().default(0), + col134: double('col11').primaryKey().autoincrement().default(0), + col135: double('col11').primaryKey().autoincrement().default(0), + col136: double('col11').primaryKey().autoincrement().default(0), + col138: double('col11').primaryKey().autoincrement().default(0), + col139: double('col11').primaryKey().autoincrement().default(0), + col140: double('col11').primaryKey().autoincrement().default(0), + col141: double('col11').primaryKey().autoincrement().default(0), + col142: double('col11').primaryKey().autoincrement().default(0), + col143: double('col11').primaryKey().autoincrement().default(0), + col144: double('col11').primaryKey().autoincrement().default(0), + col145: double('col11').primaryKey().autoincrement().default(0), + col146: double('col11').primaryKey().autoincrement().default(0), + col148: double('col11').primaryKey().autoincrement().default(0), + col149: double('col11').primaryKey().autoincrement().default(0), + col150: double('col11').primaryKey().autoincrement().default(0), + col151: double('col11').primaryKey().autoincrement().default(0), + col152: double('col11').primaryKey().autoincrement().default(0), + col153: double('col11').primaryKey().autoincrement().default(0), + col154: double('col11').primaryKey().autoincrement().default(0), + col155: double('col11').primaryKey().autoincrement().default(0), + col156: double('col11').primaryKey().autoincrement().default(0), + col158: double('col11').primaryKey().autoincrement().default(0), + col159: double('col11').primaryKey().autoincrement().default(0), + col160: double('col11').primaryKey().autoincrement().default(0), + col161: double('col11').primaryKey().autoincrement().default(0), + col162: double('col11').primaryKey().autoincrement().default(0), + col163: double('col11').primaryKey().autoincrement().default(0), + col164: double('col11').primaryKey().autoincrement().default(0), + col165: double('col11').primaryKey().autoincrement().default(0), + col166: double('col11').primaryKey().autoincrement().default(0), + col168: double('col11').primaryKey().autoincrement().default(0), + col169: double('col11').primaryKey().autoincrement().default(0), + col170: double('col11').primaryKey().autoincrement().default(0), + col171: double('col11').primaryKey().autoincrement().default(0), + col172: double('col11').primaryKey().autoincrement().default(0), + col173: double('col11').primaryKey().autoincrement().default(0), + col174: double('col11').primaryKey().autoincrement().default(0), + col175: double('col11').primaryKey().autoincrement().default(0), + col176: double('col11').primaryKey().autoincrement().default(0), + col178: double('col11').primaryKey().autoincrement().default(0), + col179: double('col11').primaryKey().autoincrement().default(0), + col180: double('col11').primaryKey().autoincrement().default(0), + col181: double('col11').primaryKey().autoincrement().default(0), + col182: double('col11').primaryKey().autoincrement().default(0), + col183: double('col11').primaryKey().autoincrement().default(0), + col184: double('col11').primaryKey().autoincrement().default(0), + col185: double('col11').primaryKey().autoincrement().default(0), + col186: double('col11').primaryKey().autoincrement().default(0), + col188: double('col11').primaryKey().autoincrement().default(0), + col189: double('col11').primaryKey().autoincrement().default(0), + col190: double('col11').primaryKey().autoincrement().default(0), + col191: double('col11').primaryKey().autoincrement().default(0), + col192: double('col11').primaryKey().autoincrement().default(0), + col193: double('col11').primaryKey().autoincrement().default(0), + col194: double('col11').primaryKey().autoincrement().default(0), + col195: double('col11').primaryKey().autoincrement().default(0), + col196: double('col11').primaryKey().autoincrement().default(0), + col198: double('col11').primaryKey().autoincrement().default(0), + col199: double('col11').primaryKey().autoincrement().default(0), + col200: double('col2').primaryKey().autoincrement().default(0), + col201: double('col2').primaryKey().autoincrement().default(0), + col202: double('col2').primaryKey().autoincrement().default(0), + col203: double('col2').primaryKey().autoincrement().default(0), + col204: double('col2').primaryKey().autoincrement().default(0), + col205: double('col2').primaryKey().autoincrement().default(0), + col206: double('col2').primaryKey().autoincrement().default(0), + col208: double('col2').primaryKey().autoincrement().default(0), + col209: double('col2').primaryKey().autoincrement().default(0), + col210: double('col21').primaryKey().autoincrement().default(0), + col211: double('col21').primaryKey().autoincrement().default(0), + col212: double('col21').primaryKey().autoincrement().default(0), + col213: double('col21').primaryKey().autoincrement().default(0), + col214: double('col21').primaryKey().autoincrement().default(0), + col215: double('col21').primaryKey().autoincrement().default(0), + col216: double('col21').primaryKey().autoincrement().default(0), + col218: double('col21').primaryKey().autoincrement().default(0), + col219: double('col21').primaryKey().autoincrement().default(0), + col220: double('col21').primaryKey().autoincrement().default(0), + col221: double('col21').primaryKey().autoincrement().default(0), + col222: double('col21').primaryKey().autoincrement().default(0), + col223: double('col21').primaryKey().autoincrement().default(0), + col224: double('col21').primaryKey().autoincrement().default(0), + col225: double('col21').primaryKey().autoincrement().default(0), + col226: double('col21').primaryKey().autoincrement().default(0), + col228: double('col21').primaryKey().autoincrement().default(0), + col229: double('col21').primaryKey().autoincrement().default(0), + col230: double('col21').primaryKey().autoincrement().default(0), + col231: double('col21').primaryKey().autoincrement().default(0), + col232: double('col21').primaryKey().autoincrement().default(0), + col233: double('col21').primaryKey().autoincrement().default(0), + col234: double('col21').primaryKey().autoincrement().default(0), + col235: double('col21').primaryKey().autoincrement().default(0), + col236: double('col21').primaryKey().autoincrement().default(0), + col238: double('col21').primaryKey().autoincrement().default(0), + col239: double('col21').primaryKey().autoincrement().default(0), + col240: double('col21').primaryKey().autoincrement().default(0), + col241: double('col21').primaryKey().autoincrement().default(0), + col242: double('col21').primaryKey().autoincrement().default(0), + col243: double('col21').primaryKey().autoincrement().default(0), + col244: double('col21').primaryKey().autoincrement().default(0), + col245: double('col21').primaryKey().autoincrement().default(0), + col246: double('col21').primaryKey().autoincrement().default(0), + col248: double('col21').primaryKey().autoincrement().default(0), + col249: double('col21').primaryKey().autoincrement().default(0), + col250: double('col21').primaryKey().autoincrement().default(0), + col251: double('col21').primaryKey().autoincrement().default(0), + col252: double('col21').primaryKey().autoincrement().default(0), + col253: double('col21').primaryKey().autoincrement().default(0), + col254: double('col21').primaryKey().autoincrement().default(0), + col255: double('col21').primaryKey().autoincrement().default(0), + col256: double('col21').primaryKey().autoincrement().default(0), + col258: double('col21').primaryKey().autoincrement().default(0), + col259: double('col21').primaryKey().autoincrement().default(0), + col260: double('col21').primaryKey().autoincrement().default(0), + col261: double('col21').primaryKey().autoincrement().default(0), + col262: double('col21').primaryKey().autoincrement().default(0), + col263: double('col21').primaryKey().autoincrement().default(0), + col264: double('col21').primaryKey().autoincrement().default(0), + col265: double('col21').primaryKey().autoincrement().default(0), + col266: double('col21').primaryKey().autoincrement().default(0), + col268: double('col21').primaryKey().autoincrement().default(0), + col269: double('col21').primaryKey().autoincrement().default(0), + col270: double('col21').primaryKey().autoincrement().default(0), + col271: double('col21').primaryKey().autoincrement().default(0), + col272: double('col21').primaryKey().autoincrement().default(0), + col273: double('col21').primaryKey().autoincrement().default(0), + col274: double('col21').primaryKey().autoincrement().default(0), + col275: double('col21').primaryKey().autoincrement().default(0), + col276: double('col21').primaryKey().autoincrement().default(0), + col278: double('col21').primaryKey().autoincrement().default(0), + col279: double('col21').primaryKey().autoincrement().default(0), + col280: double('col21').primaryKey().autoincrement().default(0), + col281: double('col21').primaryKey().autoincrement().default(0), + col282: double('col21').primaryKey().autoincrement().default(0), + col283: double('col21').primaryKey().autoincrement().default(0), + col284: double('col21').primaryKey().autoincrement().default(0), + col285: double('col21').primaryKey().autoincrement().default(0), + col286: double('col21').primaryKey().autoincrement().default(0), + col288: double('col21').primaryKey().autoincrement().default(0), + col289: double('col21').primaryKey().autoincrement().default(0), + col290: double('col21').primaryKey().autoincrement().default(0), + col291: double('col21').primaryKey().autoincrement().default(0), + col292: double('col21').primaryKey().autoincrement().default(0), + col293: double('col21').primaryKey().autoincrement().default(0), + col294: double('col21').primaryKey().autoincrement().default(0), + col295: double('col21').primaryKey().autoincrement().default(0), + col296: double('col21').primaryKey().autoincrement().default(0), + col298: double('col21').primaryKey().autoincrement().default(0), + col299: double('col21').primaryKey().autoincrement().default(0), + col300: double('col3').primaryKey().autoincrement().default(0), + col301: double('col3').primaryKey().autoincrement().default(0), + col302: double('col3').primaryKey().autoincrement().default(0), + col303: double('col3').primaryKey().autoincrement().default(0), + col304: double('col3').primaryKey().autoincrement().default(0), + col305: double('col3').primaryKey().autoincrement().default(0), + col306: double('col3').primaryKey().autoincrement().default(0), + col308: double('col3').primaryKey().autoincrement().default(0), + col309: double('col3').primaryKey().autoincrement().default(0), + col310: double('col31').primaryKey().autoincrement().default(0), + col311: double('col31').primaryKey().autoincrement().default(0), + col312: double('col31').primaryKey().autoincrement().default(0), + col313: double('col31').primaryKey().autoincrement().default(0), + col314: double('col31').primaryKey().autoincrement().default(0), + col315: double('col31').primaryKey().autoincrement().default(0), + col316: double('col31').primaryKey().autoincrement().default(0), + col318: double('col31').primaryKey().autoincrement().default(0), + col319: double('col31').primaryKey().autoincrement().default(0), + col320: double('col31').primaryKey().autoincrement().default(0), + col321: double('col31').primaryKey().autoincrement().default(0), + col322: double('col31').primaryKey().autoincrement().default(0), + col323: double('col31').primaryKey().autoincrement().default(0), + col324: double('col31').primaryKey().autoincrement().default(0), + col325: double('col31').primaryKey().autoincrement().default(0), + col326: double('col31').primaryKey().autoincrement().default(0), + col328: double('col31').primaryKey().autoincrement().default(0), + col329: double('col31').primaryKey().autoincrement().default(0), + col330: double('col31').primaryKey().autoincrement().default(0), + col331: double('col31').primaryKey().autoincrement().default(0), + col332: double('col31').primaryKey().autoincrement().default(0), + col333: double('col31').primaryKey().autoincrement().default(0), + col334: double('col31').primaryKey().autoincrement().default(0), + col335: double('col31').primaryKey().autoincrement().default(0), + col336: double('col31').primaryKey().autoincrement().default(0), + col338: double('col31').primaryKey().autoincrement().default(0), + col339: double('col31').primaryKey().autoincrement().default(0), + col340: double('col31').primaryKey().autoincrement().default(0), + col341: double('col31').primaryKey().autoincrement().default(0), + col342: double('col31').primaryKey().autoincrement().default(0), + col343: double('col31').primaryKey().autoincrement().default(0), + col344: double('col31').primaryKey().autoincrement().default(0), + col345: double('col31').primaryKey().autoincrement().default(0), + col346: double('col31').primaryKey().autoincrement().default(0), + col348: double('col31').primaryKey().autoincrement().default(0), + col349: double('col31').primaryKey().autoincrement().default(0), + col350: double('col31').primaryKey().autoincrement().default(0), + col351: double('col31').primaryKey().autoincrement().default(0), + col352: double('col31').primaryKey().autoincrement().default(0), + col353: double('col31').primaryKey().autoincrement().default(0), + col354: double('col31').primaryKey().autoincrement().default(0), + col355: double('col31').primaryKey().autoincrement().default(0), + col356: double('col31').primaryKey().autoincrement().default(0), + col358: double('col31').primaryKey().autoincrement().default(0), + col359: double('col31').primaryKey().autoincrement().default(0), + col360: double('col31').primaryKey().autoincrement().default(0), + col361: double('col31').primaryKey().autoincrement().default(0), + col362: double('col31').primaryKey().autoincrement().default(0), + col363: double('col31').primaryKey().autoincrement().default(0), + col364: double('col31').primaryKey().autoincrement().default(0), + col365: double('col31').primaryKey().autoincrement().default(0), + col366: double('col31').primaryKey().autoincrement().default(0), + col368: double('col31').primaryKey().autoincrement().default(0), + col369: double('col31').primaryKey().autoincrement().default(0), + col370: double('col31').primaryKey().autoincrement().default(0), + col371: double('col31').primaryKey().autoincrement().default(0), + col372: double('col31').primaryKey().autoincrement().default(0), + col373: double('col31').primaryKey().autoincrement().default(0), + col374: double('col31').primaryKey().autoincrement().default(0), + col375: double('col31').primaryKey().autoincrement().default(0), + col376: double('col31').primaryKey().autoincrement().default(0), + col378: double('col31').primaryKey().autoincrement().default(0), + col379: double('col31').primaryKey().autoincrement().default(0), + col380: double('col31').primaryKey().autoincrement().default(0), + col381: double('col31').primaryKey().autoincrement().default(0), + col382: double('col31').primaryKey().autoincrement().default(0), + col383: double('col31').primaryKey().autoincrement().default(0), + col384: double('col31').primaryKey().autoincrement().default(0), + col385: double('col31').primaryKey().autoincrement().default(0), + col386: double('col31').primaryKey().autoincrement().default(0), + col388: double('col31').primaryKey().autoincrement().default(0), + col389: double('col31').primaryKey().autoincrement().default(0), + col390: double('col31').primaryKey().autoincrement().default(0), + col391: double('col31').primaryKey().autoincrement().default(0), + col392: double('col31').primaryKey().autoincrement().default(0), + col393: double('col31').primaryKey().autoincrement().default(0), + col394: double('col31').primaryKey().autoincrement().default(0), + col395: double('col31').primaryKey().autoincrement().default(0), + col396: double('col31').primaryKey().autoincrement().default(0), + col398: double('col31').primaryKey().autoincrement().default(0), + col399: double('col31').primaryKey().autoincrement().default(0), + col400: double('col4').primaryKey().autoincrement().default(0), + col401: double('col4').primaryKey().autoincrement().default(0), + col402: double('col4').primaryKey().autoincrement().default(0), + col403: double('col4').primaryKey().autoincrement().default(0), + col404: double('col4').primaryKey().autoincrement().default(0), + col405: double('col4').primaryKey().autoincrement().default(0), + col406: double('col4').primaryKey().autoincrement().default(0), + col408: double('col4').primaryKey().autoincrement().default(0), + col409: double('col4').primaryKey().autoincrement().default(0), + col410: double('col41').primaryKey().autoincrement().default(0), + col411: double('col41').primaryKey().autoincrement().default(0), + col412: double('col41').primaryKey().autoincrement().default(0), + col413: double('col41').primaryKey().autoincrement().default(0), + col414: double('col41').primaryKey().autoincrement().default(0), + col415: double('col41').primaryKey().autoincrement().default(0), + col416: double('col41').primaryKey().autoincrement().default(0), + col418: double('col41').primaryKey().autoincrement().default(0), + col419: double('col41').primaryKey().autoincrement().default(0), + col420: double('col41').primaryKey().autoincrement().default(0), + col421: double('col41').primaryKey().autoincrement().default(0), + col422: double('col41').primaryKey().autoincrement().default(0), + col423: double('col41').primaryKey().autoincrement().default(0), + col424: double('col41').primaryKey().autoincrement().default(0), + col425: double('col41').primaryKey().autoincrement().default(0), + col426: double('col41').primaryKey().autoincrement().default(0), + col428: double('col41').primaryKey().autoincrement().default(0), + col429: double('col41').primaryKey().autoincrement().default(0), + col430: double('col41').primaryKey().autoincrement().default(0), + col431: double('col41').primaryKey().autoincrement().default(0), + col432: double('col41').primaryKey().autoincrement().default(0), + col433: double('col41').primaryKey().autoincrement().default(0), + col434: double('col41').primaryKey().autoincrement().default(0), + col435: double('col41').primaryKey().autoincrement().default(0), + col436: double('col41').primaryKey().autoincrement().default(0), + col438: double('col41').primaryKey().autoincrement().default(0), + col439: double('col41').primaryKey().autoincrement().default(0), + col440: double('col41').primaryKey().autoincrement().default(0), + col441: double('col41').primaryKey().autoincrement().default(0), + col442: double('col41').primaryKey().autoincrement().default(0), + col443: double('col41').primaryKey().autoincrement().default(0), + col444: double('col41').primaryKey().autoincrement().default(0), + col445: double('col41').primaryKey().autoincrement().default(0), + col446: double('col41').primaryKey().autoincrement().default(0), + col448: double('col41').primaryKey().autoincrement().default(0), + col449: double('col41').primaryKey().autoincrement().default(0), + col450: double('col41').primaryKey().autoincrement().default(0), + col451: double('col41').primaryKey().autoincrement().default(0), + col452: double('col41').primaryKey().autoincrement().default(0), + col453: double('col41').primaryKey().autoincrement().default(0), + col454: double('col41').primaryKey().autoincrement().default(0), + col455: double('col41').primaryKey().autoincrement().default(0), + col456: double('col41').primaryKey().autoincrement().default(0), + col458: double('col41').primaryKey().autoincrement().default(0), + col459: double('col41').primaryKey().autoincrement().default(0), + col460: double('col41').primaryKey().autoincrement().default(0), + col461: double('col41').primaryKey().autoincrement().default(0), + col462: double('col41').primaryKey().autoincrement().default(0), + col463: double('col41').primaryKey().autoincrement().default(0), + col464: double('col41').primaryKey().autoincrement().default(0), + col465: double('col41').primaryKey().autoincrement().default(0), + col466: double('col41').primaryKey().autoincrement().default(0), + col468: double('col41').primaryKey().autoincrement().default(0), + col469: double('col41').primaryKey().autoincrement().default(0), + col470: double('col41').primaryKey().autoincrement().default(0), + col471: double('col41').primaryKey().autoincrement().default(0), + col472: double('col41').primaryKey().autoincrement().default(0), + col473: double('col41').primaryKey().autoincrement().default(0), + col474: double('col41').primaryKey().autoincrement().default(0), + col475: double('col41').primaryKey().autoincrement().default(0), + col476: double('col41').primaryKey().autoincrement().default(0), + col478: double('col41').primaryKey().autoincrement().default(0), + col479: double('col41').primaryKey().autoincrement().default(0), + col480: double('col41').primaryKey().autoincrement().default(0), + col481: double('col41').primaryKey().autoincrement().default(0), + col482: double('col41').primaryKey().autoincrement().default(0), + col483: double('col41').primaryKey().autoincrement().default(0), + col484: double('col41').primaryKey().autoincrement().default(0), + col485: double('col41').primaryKey().autoincrement().default(0), + col486: double('col41').primaryKey().autoincrement().default(0), + col488: double('col41').primaryKey().autoincrement().default(0), + col489: double('col41').primaryKey().autoincrement().default(0), + col490: double('col41').primaryKey().autoincrement().default(0), + col491: double('col41').primaryKey().autoincrement().default(0), + col492: double('col41').primaryKey().autoincrement().default(0), + col493: double('col41').primaryKey().autoincrement().default(0), + col494: double('col41').primaryKey().autoincrement().default(0), + col495: double('col41').primaryKey().autoincrement().default(0), + col496: double('col41').primaryKey().autoincrement().default(0), + col498: double('col41').primaryKey().autoincrement().default(0), + col499: double('col41').primaryKey().autoincrement().default(0), + col500: double('col5').primaryKey().autoincrement().default(0), + col501: double('col5').primaryKey().autoincrement().default(0), + col502: double('col5').primaryKey().autoincrement().default(0), + col503: double('col5').primaryKey().autoincrement().default(0), + col504: double('col5').primaryKey().autoincrement().default(0), + col505: double('col5').primaryKey().autoincrement().default(0), + col506: double('col5').primaryKey().autoincrement().default(0), + col508: double('col5').primaryKey().autoincrement().default(0), + col509: double('col5').primaryKey().autoincrement().default(0), + col510: double('col51').primaryKey().autoincrement().default(0), + col511: double('col51').primaryKey().autoincrement().default(0), + col512: double('col51').primaryKey().autoincrement().default(0), + col513: double('col51').primaryKey().autoincrement().default(0), + col514: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col515: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col516: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col518: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col519: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col520: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col521: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col522: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col523: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col524: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col525: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col526: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col528: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col529: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col530: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col531: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col532: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col533: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col534: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col535: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col536: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col538: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col539: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col540: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col541: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col542: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col543: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col544: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col545: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col546: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col548: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col549: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col550: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col551: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col552: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col553: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col554: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col555: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col556: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col558: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col559: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col560: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col561: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col562: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col563: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col564: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col565: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col566: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col568: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col569: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col570: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col571: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col572: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col573: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col574: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col575: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col576: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col578: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col579: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col580: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col581: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col582: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col583: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col584: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col585: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col586: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col588: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col589: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col590: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col591: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col592: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col593: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col594: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col595: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col596: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col598: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col599: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col600: bigint('col6', { mode: 'number' }).primaryKey().autoincrement().default(0), + col601: double('col6').primaryKey().autoincrement().default(0), + col602: double('col6').primaryKey().autoincrement().default(0), + col603: double('col6').primaryKey().autoincrement().default(0), + col604: double('col6').primaryKey().autoincrement().default(0), + col605: double('col6').primaryKey().autoincrement().default(0), + col606: double('col6').primaryKey().autoincrement().default(0), + col608: double('col6').primaryKey().autoincrement().default(0), + col609: double('col6').primaryKey().autoincrement().default(0), + col610: double('col61').primaryKey().autoincrement().default(0), + col611: double('col61').primaryKey().autoincrement().default(0), + col612: double('col61').primaryKey().autoincrement().default(0), + col613: double('col61').primaryKey().autoincrement().default(0), + col614: double('col61').primaryKey().autoincrement().default(0), + col615: double('col61').primaryKey().autoincrement().default(0), + col616: double('col61').primaryKey().autoincrement().default(0), + col618: double('col61').primaryKey().autoincrement().default(0), + col619: double('col61').primaryKey().autoincrement().default(0), + col620: double('col61').primaryKey().autoincrement().default(0), + col621: double('col61').primaryKey().autoincrement().default(0), + col622: double('col61').primaryKey().autoincrement().default(0), + col623: double('col61').primaryKey().autoincrement().default(0), + col624: double('col61').primaryKey().autoincrement().default(0), + col625: double('col61').primaryKey().autoincrement().default(0), + col626: double('col61').primaryKey().autoincrement().default(0), + col628: double('col61').primaryKey().autoincrement().default(0), + col629: double('col61').primaryKey().autoincrement().default(0), + col630: double('col61').primaryKey().autoincrement().default(0), + col631: double('col61').primaryKey().autoincrement().default(0), + col632: double('col61').primaryKey().autoincrement().default(0), + col633: double('col61').primaryKey().autoincrement().default(0), + col634: double('col61').primaryKey().autoincrement().default(0), + col635: double('col61').primaryKey().autoincrement().default(0), + col636: double('col61').primaryKey().autoincrement().default(0), + col638: double('col61').primaryKey().autoincrement().default(0), + col639: double('col61').primaryKey().autoincrement().default(0), + col640: double('col61').primaryKey().autoincrement().default(0), + col641: double('col61').primaryKey().autoincrement().default(0), + col642: double('col61').primaryKey().autoincrement().default(0), + col643: double('col61').primaryKey().autoincrement().default(0), + col644: double('col61').primaryKey().autoincrement().default(0), + col645: double('col61').primaryKey().autoincrement().default(0), + col646: double('col61').primaryKey().autoincrement().default(0), + col648: double('col61').primaryKey().autoincrement().default(0), + col649: double('col61').primaryKey().autoincrement().default(0), + col650: double('col61').primaryKey().autoincrement().default(0), + col651: double('col61').primaryKey().autoincrement().default(0), + col652: double('col61').primaryKey().autoincrement().default(0), + col653: double('col61').primaryKey().autoincrement().default(0), + col654: double('col61').primaryKey().autoincrement().default(0), + col655: double('col61').primaryKey().autoincrement().default(0), + col656: double('col61').primaryKey().autoincrement().default(0), + col658: double('col61').primaryKey().autoincrement().default(0), + col659: double('col61').primaryKey().autoincrement().default(0), + col660: double('col61').primaryKey().autoincrement().default(0), + col661: double('col61').primaryKey().autoincrement().default(0), + col662: double('col61').primaryKey().autoincrement().default(0), + col663: double('col61').primaryKey().autoincrement().default(0), + col664: double('col61').primaryKey().autoincrement().default(0), + col665: double('col61').primaryKey().autoincrement().default(0), + col666: double('col61').primaryKey().autoincrement().default(0), + col668: double('col61').primaryKey().autoincrement().default(0), + col669: double('col61').primaryKey().autoincrement().default(0), + col670: double('col61').primaryKey().autoincrement().default(0), + col671: double('col61').primaryKey().autoincrement().default(0), + col672: double('col61').primaryKey().autoincrement().default(0), + col673: double('col61').primaryKey().autoincrement().default(0), + col674: double('col61').primaryKey().autoincrement().default(0), + col675: double('col61').primaryKey().autoincrement().default(0), + col676: double('col61').primaryKey().autoincrement().default(0), + col678: double('col61').primaryKey().autoincrement().default(0), + col679: double('col61').primaryKey().autoincrement().default(0), + col680: double('col61').primaryKey().autoincrement().default(0), + col681: double('col61').primaryKey().autoincrement().default(0), + col682: double('col61').primaryKey().autoincrement().default(0), + col683: double('col61').primaryKey().autoincrement().default(0), + col684: double('col61').primaryKey().autoincrement().default(0), + col685: double('col61').primaryKey().autoincrement().default(0), + col686: double('col61').primaryKey().autoincrement().default(0), + col688: double('col61').primaryKey().autoincrement().default(0), + col689: double('col61').primaryKey().autoincrement().default(0), + col690: double('col61').primaryKey().autoincrement().default(0), + col691: double('col61').primaryKey().autoincrement().default(0), + col692: double('col61').primaryKey().autoincrement().default(0), + col693: double('col61').primaryKey().autoincrement().default(0), + col694: double('col61').primaryKey().autoincrement().default(0), + col695: double('col61').primaryKey().autoincrement().default(0), + col696: double('col61').primaryKey().autoincrement().default(0), + col698: double('col61').primaryKey().autoincrement().default(0), + col699: double('col61').primaryKey().autoincrement().default(0), + col700: double('col7').primaryKey().autoincrement().default(0), + col701: double('col7').primaryKey().autoincrement().default(0), + col702: double('col7').primaryKey().autoincrement().default(0), + col703: double('col7').primaryKey().autoincrement().default(0), + col704: double('col7').primaryKey().autoincrement().default(0), + col705: double('col7').primaryKey().autoincrement().default(0), + col706: double('col7').primaryKey().autoincrement().default(0), + col708: double('col7').primaryKey().autoincrement().default(0), + col709: double('col7').primaryKey().autoincrement().default(0), + col710: double('col71').primaryKey().autoincrement().default(0), + col711: double('col71').primaryKey().autoincrement().default(0), + col712: double('col71').primaryKey().autoincrement().default(0), + col713: double('col71').primaryKey().autoincrement().default(0), + col714: double('col71').primaryKey().autoincrement().default(0), + col715: double('col71').primaryKey().autoincrement().default(0), + col716: double('col71').primaryKey().autoincrement().default(0), + col718: double('col71').primaryKey().autoincrement().default(0), + col719: double('col71').primaryKey().autoincrement().default(0), + col720: double('col71').primaryKey().autoincrement().default(0), + col721: double('col71').primaryKey().autoincrement().default(0), + col722: double('col71').primaryKey().autoincrement().default(0), + col723: double('col71').primaryKey().autoincrement().default(0), + col724: double('col71').primaryKey().autoincrement().default(0), + col725: double('col71').primaryKey().autoincrement().default(0), + col726: double('col71').primaryKey().autoincrement().default(0), + col728: double('col71').primaryKey().autoincrement().default(0), + col729: double('col71').primaryKey().autoincrement().default(0), + col730: double('col71').primaryKey().autoincrement().default(0), + col731: double('col71').primaryKey().autoincrement().default(0), + col732: double('col71').primaryKey().autoincrement().default(0), + col733: double('col71').primaryKey().autoincrement().default(0), + col734: double('col71').primaryKey().autoincrement().default(0), + col735: double('col71').primaryKey().autoincrement().default(0), + col736: double('col71').primaryKey().autoincrement().default(0), + col738: double('col71').primaryKey().autoincrement().default(0), + col739: double('col71').primaryKey().autoincrement().default(0), + col740: double('col71').primaryKey().autoincrement().default(0), + col741: double('col71').primaryKey().autoincrement().default(0), + col742: double('col71').primaryKey().autoincrement().default(0), + col743: double('col71').primaryKey().autoincrement().default(0), + col744: double('col71').primaryKey().autoincrement().default(0), + col745: double('col71').primaryKey().autoincrement().default(0), + col746: double('col71').primaryKey().autoincrement().default(0), + col748: double('col71').primaryKey().autoincrement().default(0), + col749: double('col71').primaryKey().autoincrement().default(0), + col750: double('col71').primaryKey().autoincrement().default(0), + col751: double('col71').primaryKey().autoincrement().default(0), + col752: double('col71').primaryKey().autoincrement().default(0), + col753: double('col71').primaryKey().autoincrement().default(0), + col754: double('col71').primaryKey().autoincrement().default(0), + col755: double('col71').primaryKey().autoincrement().default(0), + col756: double('col71').primaryKey().autoincrement().default(0), + col758: double('col71').primaryKey().autoincrement().default(0), + col759: double('col71').primaryKey().autoincrement().default(0), + col760: double('col71').primaryKey().autoincrement().default(0), + col761: double('col71').primaryKey().autoincrement().default(0), + col762: double('col71').primaryKey().autoincrement().default(0), + col763: double('col71').primaryKey().autoincrement().default(0), + col764: double('col71').primaryKey().autoincrement().default(0), + col765: double('col71').primaryKey().autoincrement().default(0), + col766: double('col71').primaryKey().autoincrement().default(0), + col768: double('col71').primaryKey().autoincrement().default(0), + col769: double('col71').primaryKey().autoincrement().default(0), + col770: double('col71').primaryKey().autoincrement().default(0), + col771: double('col71').primaryKey().autoincrement().default(0), + col772: double('col71').primaryKey().autoincrement().default(0), + col773: double('col71').primaryKey().autoincrement().default(0), + col774: double('col71').primaryKey().autoincrement().default(0), + col775: double('col71').primaryKey().autoincrement().default(0), + col776: double('col71').primaryKey().autoincrement().default(0), + col778: double('col71').primaryKey().autoincrement().default(0), + col779: double('col71').primaryKey().autoincrement().default(0), + col780: double('col71').primaryKey().autoincrement().default(0), + col781: double('col71').primaryKey().autoincrement().default(0), + col782: double('col71').primaryKey().autoincrement().default(0), + col783: double('col71').primaryKey().autoincrement().default(0), + col784: double('col71').primaryKey().autoincrement().default(0), + col785: double('col71').primaryKey().autoincrement().default(0), + col786: double('col71').primaryKey().autoincrement().default(0), + col788: double('col71').primaryKey().autoincrement().default(0), + col789: double('col71').primaryKey().autoincrement().default(0), + col790: double('col71').primaryKey().autoincrement().default(0), + col791: double('col71').primaryKey().autoincrement().default(0), + col792: double('col71').primaryKey().autoincrement().default(0), + col793: double('col71').primaryKey().autoincrement().default(0), + col794: double('col71').primaryKey().autoincrement().default(0), + col795: double('col71').primaryKey().autoincrement().default(0), + col796: double('col71').primaryKey().autoincrement().default(0), + col798: double('col71').primaryKey().autoincrement().default(0), + col799: double('col71').primaryKey().autoincrement().default(0), + col800: double('col8').primaryKey().autoincrement().default(0), + col801: double('col8').primaryKey().autoincrement().default(0), + col802: double('col8').primaryKey().autoincrement().default(0), + col803: double('col8').primaryKey().autoincrement().default(0), + col804: double('col8').primaryKey().autoincrement().default(0), + col805: double('col8').primaryKey().autoincrement().default(0), + col806: double('col8').primaryKey().autoincrement().default(0), + col808: double('col8').primaryKey().autoincrement().default(0), + col809: double('col8').primaryKey().autoincrement().default(0), + col810: double('col81').primaryKey().autoincrement().default(0), + col811: double('col81').primaryKey().autoincrement().default(0), + col812: double('col81').primaryKey().autoincrement().default(0), + col813: double('col81').primaryKey().autoincrement().default(0), + col814: double('col81').primaryKey().autoincrement().default(0), + col815: double('col81').primaryKey().autoincrement().default(0), + col816: double('col81').primaryKey().autoincrement().default(0), + col818: double('col81').primaryKey().autoincrement().default(0), + col819: double('col81').primaryKey().autoincrement().default(0), + col820: double('col81').primaryKey().autoincrement().default(0), + col821: double('col81').primaryKey().autoincrement().default(0), + col822: double('col81').primaryKey().autoincrement().default(0), + col823: double('col81').primaryKey().autoincrement().default(0), + col824: double('col81').primaryKey().autoincrement().default(0), + col825: double('col81').primaryKey().autoincrement().default(0), + col826: double('col81').primaryKey().autoincrement().default(0), + col828: double('col81').primaryKey().autoincrement().default(0), + col829: double('col81').primaryKey().autoincrement().default(0), + col830: double('col81').primaryKey().autoincrement().default(0), + col831: double('col81').primaryKey().autoincrement().default(0), + col832: double('col81').primaryKey().autoincrement().default(0), + col833: double('col81').primaryKey().autoincrement().default(0), + col834: double('col81').primaryKey().autoincrement().default(0), + col835: double('col81').primaryKey().autoincrement().default(0), + col836: double('col81').primaryKey().autoincrement().default(0), + col838: double('col81').primaryKey().autoincrement().default(0), + col839: double('col81').primaryKey().autoincrement().default(0), + col840: double('col81').primaryKey().autoincrement().default(0), + col841: double('col81').primaryKey().autoincrement().default(0), + col842: double('col81').primaryKey().autoincrement().default(0), + col843: double('col81').primaryKey().autoincrement().default(0), + col844: double('col81').primaryKey().autoincrement().default(0), + col845: double('col81').primaryKey().autoincrement().default(0), + col846: double('col81').primaryKey().autoincrement().default(0), + col848: double('col81').primaryKey().autoincrement().default(0), + col849: double('col81').primaryKey().autoincrement().default(0), + col850: double('col81').primaryKey().autoincrement().default(0), + col851: double('col81').primaryKey().autoincrement().default(0), + col852: double('col81').primaryKey().autoincrement().default(0), + col853: double('col81').primaryKey().autoincrement().default(0), + col854: double('col81').primaryKey().autoincrement().default(0), + col855: double('col81').primaryKey().autoincrement().default(0), + col856: double('col81').primaryKey().autoincrement().default(0), + col858: double('col81').primaryKey().autoincrement().default(0), + col859: double('col81').primaryKey().autoincrement().default(0), + col860: double('col81').primaryKey().autoincrement().default(0), + col861: double('col81').primaryKey().autoincrement().default(0), + col862: double('col81').primaryKey().autoincrement().default(0), + col863: double('col81').primaryKey().autoincrement().default(0), + col864: double('col81').primaryKey().autoincrement().default(0), + col865: double('col81').primaryKey().autoincrement().default(0), + col866: double('col81').primaryKey().autoincrement().default(0), + col868: double('col81').primaryKey().autoincrement().default(0), + col869: double('col81').primaryKey().autoincrement().default(0), + col870: double('col81').primaryKey().autoincrement().default(0), + col871: double('col81').primaryKey().autoincrement().default(0), + col872: double('col81').primaryKey().autoincrement().default(0), + col873: double('col81').primaryKey().autoincrement().default(0), + col874: double('col81').primaryKey().autoincrement().default(0), + col875: double('col81').primaryKey().autoincrement().default(0), + col876: double('col81').primaryKey().autoincrement().default(0), + col878: double('col81').primaryKey().autoincrement().default(0), + col879: double('col81').primaryKey().autoincrement().default(0), + col880: double('col81').primaryKey().autoincrement().default(0), + col881: double('col81').primaryKey().autoincrement().default(0), + col882: double('col81').primaryKey().autoincrement().default(0), + col883: double('col81').primaryKey().autoincrement().default(0), + col884: double('col81').primaryKey().autoincrement().default(0), + col885: double('col81').primaryKey().autoincrement().default(0), + col886: double('col81').primaryKey().autoincrement().default(0), + col888: double('col81').primaryKey().autoincrement().default(0), + col889: double('col81').primaryKey().autoincrement().default(0), + col890: double('col81').primaryKey().autoincrement().default(0), + col891: double('col81').primaryKey().autoincrement().default(0), + col892: double('col81').primaryKey().autoincrement().default(0), + col893: double('col81').primaryKey().autoincrement().default(0), + col894: double('col81').primaryKey().autoincrement().default(0), + col895: double('col81').primaryKey().autoincrement().default(0), + col896: double('col81').primaryKey().autoincrement().default(0), + col898: double('col81').primaryKey().autoincrement().default(0), + col899: double('col81').primaryKey().autoincrement().default(0), + col900: double('col9').primaryKey().autoincrement().default(0), + col901: double('col9').primaryKey().autoincrement().default(0), + col902: double('col9').primaryKey().autoincrement().default(0), + col903: double('col9').primaryKey().autoincrement().default(0), + col904: double('col9').primaryKey().autoincrement().default(0), + col905: double('col9').primaryKey().autoincrement().default(0), + col906: double('col9').primaryKey().autoincrement().default(0), + col908: double('col9').primaryKey().autoincrement().default(0), + col909: double('col9').primaryKey().autoincrement().default(0), + col910: double('col91').primaryKey().autoincrement().default(0), + col911: double('col91').primaryKey().autoincrement().default(0), + col912: double('col91').primaryKey().autoincrement().default(0), + col913: double('col91').primaryKey().autoincrement().default(0), + col914: double('col91').primaryKey().autoincrement().default(0), + col915: double('col91').primaryKey().autoincrement().default(0), + col916: double('col91').primaryKey().autoincrement().default(0), + col918: double('col91').primaryKey().autoincrement().default(0), + col919: double('col91').primaryKey().autoincrement().default(0), + col920: double('col91').primaryKey().autoincrement().default(0), + col921: double('col91').primaryKey().autoincrement().default(0), + col922: double('col91').primaryKey().autoincrement().default(0), + col923: double('col91').primaryKey().autoincrement().default(0), + col924: double('col91').primaryKey().autoincrement().default(0), + col925: double('col91').primaryKey().autoincrement().default(0), + col926: double('col91').primaryKey().autoincrement().default(0), + col928: double('col91').primaryKey().autoincrement().default(0), + col929: double('col91').primaryKey().autoincrement().default(0), + col930: double('col91').primaryKey().autoincrement().default(0), + col931: double('col91').primaryKey().autoincrement().default(0), + col932: double('col91').primaryKey().autoincrement().default(0), + col933: double('col91').primaryKey().autoincrement().default(0), + col934: double('col91').primaryKey().autoincrement().default(0), + col935: double('col91').primaryKey().autoincrement().default(0), + col936: double('col91').primaryKey().autoincrement().default(0), + col938: double('col91').primaryKey().autoincrement().default(0), + col939: double('col91').primaryKey().autoincrement().default(0), + col940: double('col91').primaryKey().autoincrement().default(0), + col941: double('col91').primaryKey().autoincrement().default(0), + col942: double('col91').primaryKey().autoincrement().default(0), + col943: double('col91').primaryKey().autoincrement().default(0), + col944: varchar('col91', { length: 200 }).primaryKey().default('0'), + col945: varchar('col91', { length: 200 }).primaryKey().default('0'), + col946: varchar('col91', { length: 200 }).primaryKey().default('0'), + col948: varchar('col91', { length: 200 }).primaryKey().default('0'), + col949: varchar('col91', { length: 200 }).primaryKey().default('0'), + col950: varchar('col91', { length: 200 }).primaryKey().default('0'), + col951: varchar('col91', { length: 200 }).primaryKey().default('0'), + col952: varchar('col91', { length: 200 }).primaryKey().default('0'), + col953: varchar('col91', { length: 200 }).primaryKey().default('0'), + col954: varchar('col91', { length: 200 }).primaryKey().default('0'), + col955: varchar('col91', { length: 200 }).primaryKey().default('0'), + col956: varchar('col91', { length: 200 }).primaryKey().default('0'), + col958: varchar('col91', { length: 200 }).primaryKey().default('0'), + col959: varchar('col91', { length: 200 }).primaryKey().default('0'), + col960: varchar('col91', { length: 200 }).primaryKey().default('0'), + col961: varchar('col91', { length: 200 }).primaryKey().default('0'), + col962: varchar('col91', { length: 200 }).primaryKey().default('0'), + col963: varchar('col91', { length: 200 }).primaryKey().default('0'), + col964: varchar('col91', { length: 200 }).primaryKey().default('0'), + col965: varchar('col91', { length: 200 }).primaryKey().default('0'), + col966: varchar('col91', { length: 200 }).primaryKey().default('0'), + col968: varchar('col91', { length: 200 }).primaryKey().default('0'), + col969: varchar('col91', { length: 200 }).primaryKey().default('0'), + col970: varchar('col91', { length: 200 }).primaryKey().default('0'), + col971: varchar('col91', { length: 200 }).primaryKey().default('0'), + col972: varchar('col91', { length: 200 }).primaryKey().default('0'), + col973: varchar('col91', { length: 200 }).primaryKey().default('0'), + col974: varchar('col91', { length: 200 }).primaryKey().default('0'), + col975: varchar('col91', { length: 200 }).primaryKey().default('0'), + col976: varchar('col91', { length: 200 }).primaryKey().default('0'), + col978: varchar('col91', { length: 200 }).primaryKey().default('0'), + col979: varchar('col91', { length: 200 }).primaryKey().default('0'), + col980: varchar('col91', { length: 200 }).primaryKey().default('0'), + col981: varchar('col91', { length: 200 }).primaryKey().default('0'), + col982: varchar('col91', { length: 200 }).primaryKey().default('0'), + col983: varchar('col91', { length: 200 }).primaryKey().default('0'), + col984: varchar('col91', { length: 200 }).primaryKey().default('0'), + col985: varchar('col91', { length: 200 }).primaryKey().default('0'), + col986: varchar('col91', { length: 200 }).primaryKey().default('0'), + col988: varchar('col91', { length: 200 }).primaryKey().default('0'), + col989: varchar('col91', { length: 200 }).primaryKey().default('0'), + col990: varchar('col91', { length: 200 }).primaryKey().default('0'), + col991: varchar('col91', { length: 200 }).primaryKey().default('0'), + col992: varchar('col91', { length: 200 }).primaryKey().default('0'), + col993: varchar('col91', { length: 200 }).primaryKey().default('0'), + col994: varchar('col91', { length: 200 }).primaryKey().default('0'), + col995: varchar('col91', { length: 200 }).primaryKey().default('0'), + col996: varchar('col91', { length: 200 }).primaryKey().default('0'), + col998: varchar('col91', { length: 200 }).primaryKey().default('0'), + col999: varchar('col91', { length: 200 }).primaryKey().default('0'), +}); diff --git a/drizzle-orm/type-tests/singlestore/count.ts b/drizzle-orm/type-tests/singlestore/count.ts new file mode 100644 index 000000000..50abc8c3a --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/count.ts @@ -0,0 +1,61 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, gt, ne } from '~/expressions.ts'; +import { int, serial, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import type { Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = singlestoreTable('names', { + id: serial('id').primaryKey(), + name: text('name'), + authorId: int('author_id'), +}); + +const separate = await db.$count(names); + +const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))); + +const embedded = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names).as('count1'), + }) + .from(names); + +const embeddedFilters = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'), + }) + .from(names); + +Expect>; + +Expect>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embedded + > +>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embeddedFilters + > +>; diff --git a/drizzle-orm/type-tests/singlestore/db.ts b/drizzle-orm/type-tests/singlestore/db.ts new file mode 100644 index 000000000..b314e504d --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/db.ts @@ -0,0 +1,13 @@ +import { createPool } from 'mysql2/promise'; +import { drizzle } from '~/singlestore/index.ts'; + +const pool = createPool({}); + +export const db = drizzle(pool); + +{ + drizzle(pool); + drizzle(pool, { schema: {} }); + drizzle(pool, { schema: {} }); + drizzle(pool, {}); +} diff --git a/drizzle-orm/type-tests/singlestore/delete.ts b/drizzle-orm/type-tests/singlestore/delete.ts new file mode 100644 index 000000000..db58ac2ec --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/delete.ts @@ -0,0 +1,65 @@ +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { eq } from '~/expressions.ts'; +import type { SingleStoreDelete } from '~/singlestore-core/index.ts'; +import type { SingleStoreRawQueryResult } from '~/singlestore/index.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; +import { users } from './tables.ts'; + +const deleteAll = await db.delete(users); +Expect>; + +const deleteAllStmt = db.delete(users).prepare(); +const deleteAllPrepared = await deleteAllStmt.execute(); +Expect>; + +const deleteWhere = await db.delete(users).where(eq(users.id, 1)); +Expect>; + +const deleteWhereStmt = db.delete(users).where(eq(users.id, 1)).prepare(); +const deleteWherePrepared = await deleteWhereStmt.execute(); +Expect>; + +const deleteReturningAll = await db.delete(users); +Expect>; + +const deleteReturningAllStmt = db.delete(users).prepare(); +const deleteReturningAllPrepared = await deleteReturningAllStmt.execute(); +Expect>; + +const deleteReturningPartial = await db.delete(users); +Expect>; + +const deleteReturningPartialStmt = db.delete(users).prepare(); +const deleteReturningPartialPrepared = await deleteReturningPartialStmt.execute(); +Expect>; + +{ + function dynamic(qb: T) { + return qb.where(sql``); + } + + const qbBase = db.delete(users).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + Expect>; +} + +{ + db + .delete(users) + .where(sql``) + // @ts-expect-error method was already called + .where(sql``); + + db + .delete(users) + .$dynamic() + .where(sql``) + .where(sql``); +} + +{ + db.delete(users).where(sql``).limit(1).orderBy(sql``); +} diff --git a/drizzle-orm/type-tests/singlestore/insert.ts b/drizzle-orm/type-tests/singlestore/insert.ts new file mode 100644 index 000000000..738bf669d --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/insert.ts @@ -0,0 +1,135 @@ +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { int, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import type { SingleStoreInsert } from '~/singlestore-core/index.ts'; +import type { SingleStoreRawQueryResult } from '~/singlestore/index.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; +import { users } from './tables.ts'; + +const singlestoreInsertReturning = await db.insert(users).values({ + // ^? + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).$returningId(); + +Expect>; + +const insert = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertPrepared = await insertStmt.execute(); +Expect>; + +const insertSql = await db.insert(users).values({ + homeCity: sql`123`, + class: 'A', + age1: 1, + enumCol: sql`foobar`, +}); +Expect>; + +const insertSqlStmt = db.insert(users).values({ + homeCity: sql`123`, + class: 'A', + age1: 1, + enumCol: sql`foobar`, +}).prepare(); +const insertSqlPrepared = await insertSqlStmt.execute(); +Expect>; + +const insertReturning = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertReturningStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertReturningPrepared = await insertReturningStmt.execute(); +Expect>; + +const insertReturningPartial = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertReturningPartialStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertReturningPartialPrepared = await insertReturningPartialStmt.execute(); +Expect>; + +const insertReturningSql = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: sql`2 + 2`, + enumCol: 'a', +}); +Expect>; + +const insertReturningSqlStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: sql`2 + 2`, + enumCol: 'a', +}).prepare(); +const insertReturningSqlPrepared = await insertReturningSqlStmt.execute(); +Expect>; + +{ + const users = singlestoreTable('users', { + id: int('id').autoincrement().primaryKey(), + name: text('name').notNull(), + age: int('age'), + occupation: text('occupation'), + }); + + await db.insert(users).values({ name: 'John Wick', age: 58, occupation: 'housekeeper' }); +} + +{ + function dynamic(qb: T) { + return qb.onDuplicateKeyUpdate({ set: {} }); + } + + const qbBase = db.insert(users).values({ age1: 0, class: 'A', enumCol: 'a', homeCity: 0 }).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + + Expect>; +} + +{ + db + .insert(users) + .values({ age1: 0, class: 'A', enumCol: 'a', homeCity: 0 }) + .onDuplicateKeyUpdate({ set: {} }) + // @ts-expect-error method was already called + .onDuplicateKeyUpdate({ set: {} }); +} diff --git a/drizzle-orm/type-tests/singlestore/select.ts b/drizzle-orm/type-tests/singlestore/select.ts new file mode 100644 index 000000000..f7a5094ea --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/select.ts @@ -0,0 +1,607 @@ +import { + and, + between, + eq, + exists, + gt, + gte, + ilike, + inArray, + isNotNull, + isNull, + like, + lt, + lte, + ne, + not, + notBetween, + notExists, + notIlike, + notInArray, + notLike, + or, +} from '~/expressions.ts'; +import { alias } from '~/singlestore-core/alias.ts'; +import { param, sql } from '~/sql/sql.ts'; + +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { QueryBuilder, type SingleStoreSelect, type SingleStoreSelectQueryBuilder } from '~/singlestore-core/index.ts'; +import { db } from './db.ts'; +import { cities, classes, users } from './tables.ts'; + +const city = alias(cities, 'city'); +const city1 = alias(cities, 'city1'); + +const join = await db + .select({ + users, + cities, + city, + city1: { + id: city1.id, + }, + }) + .from(users) + .leftJoin(cities, eq(users.id, cities.id)) + .rightJoin(city, eq(city.id, users.id)) + .rightJoin(city1, eq(city1.id, users.id)); + +Expect< + Equal< + { + users: { + id: number; + text: string | null; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + } | null; + cities: { + id: number; + name: string; + population: number | null; + } | null; + city: { + id: number; + name: string; + population: number | null; + } | null; + city1: { + id: number; + }; + }[], + typeof join + > +>; + +const join2 = await db + .select({ + userId: users.id, + cityId: cities.id, + }) + .from(users) + .fullJoin(cities, eq(users.id, cities.id)); + +Expect< + Equal< + { + userId: number | null; + cityId: number | null; + }[], + typeof join2 + > +>; + +const join3 = await db + .select({ + userId: users.id, + cityId: cities.id, + classId: classes.id, + }) + .from(users) + .fullJoin(cities, eq(users.id, cities.id)) + .rightJoin(classes, eq(users.id, classes.id)); + +Expect< + Equal< + { + userId: number | null; + cityId: number | null; + classId: number; + }[], + typeof join3 + > +>; + +db + .select() + .from(users) + .where(exists(db.select().from(cities).where(eq(users.homeCity, cities.id)))); + +function mapFunkyFuncResult(valueFromDriver: unknown) { + return { + foo: (valueFromDriver as Record)['foo'], + }; +} + +const age = 1; + +const allOperators = await db + .select({ + col2: sql`5 - ${users.id} + 1`, // unknown + col3: sql`${users.id} + 1`, // number + col33: sql`${users.id} + 1`.mapWith(users.id), // number + col34: sql`${users.id} + 1`.mapWith(mapFunkyFuncResult), // number + col4: sql`one_or_another(${users.id}, ${users.class})`, // string | number + col5: sql`true`, // unknown + col6: sql`true`, // boolean + col7: sql`random()`, // number + col8: sql`some_funky_func(${users.id})`.mapWith(mapFunkyFuncResult), // { foo: string } + col9: sql`greatest(${users.createdAt}, ${param(new Date(), users.createdAt)})`, // unknown + col10: sql`date_or_false(${users.createdAt}, ${param(new Date(), users.createdAt)})`, // Date | boolean + col11: sql`${users.age1} + ${age}`, // unknown + col12: sql`${users.age1} + ${param(age, users.age1)}`, // unknown + col13: sql`lower(${users.class})`, // unknown + col14: sql`length(${users.class})`, // number + count: sql`count(*)::int`, // number + }) + .from(users) + .where(and( + eq(users.id, 1), + ne(users.id, 1), + or(eq(users.id, 1), ne(users.id, 1)), + not(eq(users.id, 1)), + gt(users.id, 1), + gte(users.id, 1), + lt(users.id, 1), + lte(users.id, 1), + inArray(users.id, [1, 2, 3]), + inArray(users.id, db.select({ id: users.id }).from(users)), + inArray(users.id, sql`select id from ${users}`), + notInArray(users.id, [1, 2, 3]), + notInArray(users.id, db.select({ id: users.id }).from(users)), + notInArray(users.id, sql`select id from ${users}`), + isNull(users.subClass), + isNotNull(users.id), + exists(db.select({ id: users.id }).from(users)), + exists(sql`select id from ${users}`), + notExists(db.select({ id: users.id }).from(users)), + notExists(sql`select id from ${users}`), + between(users.id, 1, 2), + notBetween(users.id, 1, 2), + like(users.id, '%1%'), + notLike(users.id, '%1%'), + ilike(users.id, '%1%'), + notIlike(users.id, '%1%'), + )); + +Expect< + Equal<{ + col2: unknown; + col3: number; + col33: number; + col34: { foo: any }; + col4: string | number; + col5: unknown; + col6: boolean; + col7: number; + col8: { + foo: any; + }; + col9: unknown; + col10: boolean | Date; + col11: unknown; + col12: unknown; + col13: unknown; + col14: number; + count: number; + }[], typeof allOperators> +>; + +const textSelect = await db + .select({ + t: users.text, + }) + .from(users); + +Expect>; + +const homeCity = alias(cities, 'homeCity'); +const c = alias(classes, 'c'); +const otherClass = alias(classes, 'otherClass'); +const anotherClass = alias(classes, 'anotherClass'); +const friend = alias(users, 'friend'); +const currentCity = alias(cities, 'currentCity'); +const subscriber = alias(users, 'subscriber'); +const closestCity = alias(cities, 'closestCity'); + +const megaJoin = await db + .select({ + user: { + id: users.id, + maxAge: sql`max(${users.age1})`, + }, + city: { + id: cities.id, + }, + homeCity, + c, + otherClass, + anotherClass, + friend, + currentCity, + subscriber, + closestCity, + }) + .from(users) + .innerJoin(cities, sql`${users.id} = ${cities.id}`) + .innerJoin(homeCity, sql`${users.homeCity} = ${homeCity.id}`) + .innerJoin(c, eq(c.id, users.class)) + .innerJoin(otherClass, sql`${c.id} = ${otherClass.id}`) + .innerJoin(anotherClass, sql`${users.class} = ${anotherClass.id}`) + .innerJoin(friend, sql`${users.id} = ${friend.id}`) + .innerJoin(currentCity, sql`${homeCity.id} = ${currentCity.id}`) + .innerJoin(subscriber, sql`${users.class} = ${subscriber.id}`) + .innerJoin(closestCity, sql`${users.currentCity} = ${closestCity.id}`) + .where(and(sql`${users.age1} > 0`, eq(cities.id, 1))) + .limit(1) + .offset(1); + +Expect< + Equal< + { + user: { + id: number; + maxAge: unknown; + }; + city: { + id: number; + }; + homeCity: { + id: number; + name: string; + population: number | null; + }; + c: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + otherClass: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + anotherClass: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + friend: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + currentCity: { + id: number; + name: string; + population: number | null; + }; + subscriber: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + closestCity: { + id: number; + name: string; + population: number | null; + }; + }[], + typeof megaJoin + > +>; + +const friends = alias(users, 'friends'); + +const join4 = await db + .select({ + user: { + id: users.id, + }, + city: { + id: cities.id, + }, + class: classes, + friend: friends, + }) + .from(users) + .innerJoin(cities, sql`${users.id} = ${cities.id}`) + .innerJoin(classes, sql`${cities.id} = ${classes.id}`) + .innerJoin(friends, sql`${friends.id} = ${users.id}`) + .where(sql`${users.age1} > 0`); + +Expect< + Equal<{ + user: { + id: number; + }; + city: { + id: number; + }; + class: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + friend: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + }[], typeof join4> +>; + +{ + const authenticated = false as boolean; + + const result = await db + .select({ + id: users.id, + ...(authenticated ? { city: users.homeCity } : {}), + }) + .from(users); + + Expect< + Equal< + { + id: number; + city?: number; + }[], + typeof result + > + >; +} + +await db.select().from(users).for('update'); +await db.select().from(users).for('share', { skipLocked: true }); +await db.select().from(users).for('update', { noWait: true }); +// TODO Implement views for SingleStore (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-view/) +/* await db + .select() + .from(users) + // @ts-expect-error - can't use both skipLocked and noWait + .for('share', { noWait: true, skipLocked: true }); + +{ + const result = await db.select().from(newYorkers); + Expect< + Equal< + { + userId: number; + cityId: number | null; + }[], + typeof result + > + >; +} + +{ + const result = await db.select({ userId: newYorkers.userId }).from(newYorkers); + Expect< + Equal< + { + userId: number; + }[], + typeof result + > + >; +} */ + +{ + const query = db.select().from(users).prepare().iterator(); + for await (const row of query) { + Expect>(); + } +} + +{ + db + .select() + .from(users) + .where(eq(users.id, 1)); + + db + .select() + .from(users) + .where(eq(users.id, 1)) + // @ts-expect-error - can't use where twice + .where(eq(users.id, 1)); + + db + .select() + .from(users) + .where(eq(users.id, 1)) + .limit(10) + // @ts-expect-error - can't use where twice + .where(eq(users.id, 1)); +} + +{ + function withFriends(qb: T) { + const friends = alias(users, 'friends'); + const friends2 = alias(users, 'friends2'); + const friends3 = alias(users, 'friends3'); + const friends4 = alias(users, 'friends4'); + const friends5 = alias(users, 'friends5'); + return qb + .leftJoin(friends, sql`true`) + .leftJoin(friends2, sql`true`) + .leftJoin(friends3, sql`true`) + .leftJoin(friends4, sql`true`) + .leftJoin(friends5, sql`true`); + } + + const qb = db.select().from(users).$dynamic(); + const result = await withFriends(qb); + Expect< + Equal + >; +} + +{ + function withFriends(qb: T) { + const friends = alias(users, 'friends'); + const friends2 = alias(users, 'friends2'); + const friends3 = alias(users, 'friends3'); + const friends4 = alias(users, 'friends4'); + const friends5 = alias(users, 'friends5'); + return qb + .leftJoin(friends, sql`true`) + .leftJoin(friends2, sql`true`) + .leftJoin(friends3, sql`true`) + .leftJoin(friends4, sql`true`) + .leftJoin(friends5, sql`true`); + } + + const qb = db.select().from(users).$dynamic(); + const result = await withFriends(qb); + Expect< + Equal + >; +} + +{ + function dynamic(qb: T) { + return qb.where(sql``).having(sql``).groupBy(sql``).orderBy(sql``).limit(1).offset(1).for('update'); + } + + const qb = db.select().from(users).$dynamic(); + const result = await dynamic(qb); + Expect>; +} + +{ + // TODO: add to docs + function dynamic(qb: T) { + return qb.where(sql``).having(sql``).groupBy(sql``).orderBy(sql``).limit(1).offset(1).for('update'); + } + + const query = new QueryBuilder().select().from(users).$dynamic(); + dynamic(query); +} + +{ + // TODO: add to docs + function paginated(qb: T, page: number) { + return qb.limit(10).offset((page - 1) * 10); + } + + const qb = db.select().from(users).$dynamic(); + const result = await paginated(qb, 1); + + Expect>; +} + +{ + db + .select() + .from(users) + .where(sql``) + .limit(10) + // @ts-expect-error method was already called + .where(sql``); + + db + .select() + .from(users) + .having(sql``) + .limit(10) + // @ts-expect-error method was already called + .having(sql``); + + db + .select() + .from(users) + .groupBy(sql``) + .limit(10) + // @ts-expect-error method was already called + .groupBy(sql``); + + db + .select() + .from(users) + .orderBy(sql``) + .limit(10) + // @ts-expect-error method was already called + .orderBy(sql``); + + db + .select() + .from(users) + .limit(10) + .where(sql``) + // @ts-expect-error method was already called + .limit(10); + + db + .select() + .from(users) + .offset(10) + .limit(10) + // @ts-expect-error method was already called + .offset(10); + + db + .select() + .from(users) + .for('update') + .limit(10) + // @ts-expect-error method was already called + .for('update'); +} diff --git a/drizzle-orm/type-tests/singlestore/set-operators.ts b/drizzle-orm/type-tests/singlestore/set-operators.ts new file mode 100644 index 000000000..1db4bb7f1 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/set-operators.ts @@ -0,0 +1,235 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import { eq } from '~/expressions.ts'; +import { intersect, type SingleStoreSetOperator, union, unionAll } from '~/singlestore-core/index.ts'; +import { sql } from '~/sql/index.ts'; +import { db } from './db.ts'; +import { cities, classes, users } from './tables.ts'; + +const unionTest = await db + .select({ id: users.id }) + .from(users) + .union( + db + .select({ id: users.id }) + .from(users), + ); + +Expect>; + +const unionAllTest = await db + .select({ id: users.id, age: users.age1 }) + .from(users) + .unionAll( + db.select({ id: users.id, age: users.age1 }) + .from(users) + .leftJoin(cities, eq(users.id, cities.id)), + ); + +Expect>; + +const intersectTest = await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + .intersect(({ intersect }) => + intersect( + db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users), + db + .select({ id: users.id, homeCity: sql`${users.homeCity}`.mapWith(Number) }) + .from(users), + ) + ); + +Expect>; + +const exceptTest = await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + .except( + db + .select({ id: users.id, homeCity: sql`${users.homeCity}`.mapWith(Number) }) + .from(users), + ); + +Expect>; + +const minusTest = await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + .minus( + db + .select({ id: users.id, homeCity: sql`${users.homeCity}`.mapWith(Number) }) + .from(users), + ); + +Expect>; + +const union2Test = await union(db.select().from(cities), db.select().from(cities), db.select().from(cities)); + +Expect>; + +const unionAll2Test = await unionAll( + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select().from(cities), +); + +Expect>; + +const intersect2Test = await intersect( + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), +); + +Expect>; + +// TODO Implement views for SingleStore (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-view/) +/* const except2Test = await except( + db.select({ + userId: newYorkers.userId, + }) + .from(newYorkers), + db.select({ + userId: newYorkers.userId, + }).from(newYorkers), +); + +Expect>; */ + +const unionfull = await union(db.select().from(users), db.select().from(users)).orderBy(sql``).limit(1).offset(2); + +Expect< + Equal<{ + id: number; + text: string | null; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }[], typeof unionfull> +>; + +union(db.select().from(users), db.select().from(users)) + .orderBy(sql``) + // @ts-expect-error - method was already called + .orderBy(sql``); + +union(db.select().from(users), db.select().from(users)) + .offset(1) + // @ts-expect-error - method was already called + .offset(2); + +union(db.select().from(users), db.select().from(users)) + .orderBy(sql``) + // @ts-expect-error - method was already called + .orderBy(sql``); + +{ + function dynamic(qb: T) { + return qb.orderBy(sql``).limit(1).offset(2); + } + + const qb = union(db.select().from(users), db.select().from(users)).$dynamic(); + const result = await dynamic(qb); + Expect>; +} + +await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + .intersect(({ intersect }) => intersect(db.select().from(users), db.select().from(users))); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select().from(classes).union(db.select({ id: classes.id }).from(classes)); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select({ id: classes.id }).from(classes).union(db.select().from(classes).where(sql``)); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select({ id: classes.id }).from(classes).union(db.select().from(classes)); + +union( + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``), + db.select({ id: cities.id, name: cities.name }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select().from(cities), +); + +union( + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name, population: cities.population }).from(cities), + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``).limit(3).$dynamic(), + db.select({ id: cities.id, name: cities.name }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name }).from(cities), + db.select({ id: cities.id }).from(cities), + /* db.select({ id: newYorkers.userId }).from(newYorkers), */ + db.select({ id: cities.id }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities).where(sql``), + db.select({ id: sql`${cities.id}` }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name, population: cities.population }).from(cities).where(sql``), +); diff --git a/drizzle-orm/type-tests/singlestore/subquery.ts b/drizzle-orm/type-tests/singlestore/subquery.ts new file mode 100644 index 000000000..e8ee4e80b --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/subquery.ts @@ -0,0 +1,97 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, eq } from '~/expressions.ts'; +import { alias, int, serial, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import { sql } from '~/sql/sql.ts'; +import type { DrizzleTypeError, Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = singlestoreTable('names', { + id: serial('id').primaryKey(), + name: text('name'), + authorId: int('author_id'), +}); + +const n1 = db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: sql`count(1)::int`.as('count1'), + }) + .from(names) + .groupBy(names.id, names.name, names.authorId) + .as('n1'); + +const n2 = db + .select({ + id: names.id, + authorId: names.authorId, + totalCount: sql`count(1)::int`.as('totalCount'), + }) + .from(names) + .groupBy(names.id, names.authorId) + .as('n2'); + +const result = await db + .select({ + name: n1.name, + authorId: n1.authorId, + count1: n1.count1, + totalCount: n2.totalCount, + }) + .from(n1) + .innerJoin(n2, and(eq(n2.id, n1.id), eq(n2.authorId, n1.authorId))); + +Expect< + Equal< + { + name: string | null; + authorId: number | null; + count1: number; + totalCount: number; + }[], + typeof result + > +>; + +const names2 = alias(names, 'names2'); + +const sq1 = db + .select({ + id: names.id, + name: names.name, + id2: names2.id, + }) + .from(names) + .leftJoin(names2, eq(names.name, names2.name)) + .as('sq1'); + +const res = await db.select().from(sq1); + +Expect< + Equal< + { + id: number; + name: string | null; + id2: number | null; + }[], + typeof res + > +>; + +{ + const sq = db.select({ count: sql`count(1)::int` }).from(names).as('sq'); + Expect ? true : false>; +} + +const sqUnion = db.select().from(names).union(db.select().from(names2)).as('sqUnion'); + +const resUnion = await db.select().from(sqUnion); + +Expect< + Equal<{ + id: number; + name: string | null; + authorId: number | null; + }[], typeof resUnion> +>; diff --git a/drizzle-orm/type-tests/singlestore/tables.ts b/drizzle-orm/type-tests/singlestore/tables.ts new file mode 100644 index 000000000..fb02eb774 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/tables.ts @@ -0,0 +1,1026 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import type { BuildColumn } from '~/column-builder.ts'; +import { eq } from '~/expressions.ts'; +import { + bigint, + binary, + boolean, + char, + customType, + date, + datetime, + decimal, + double, + float, + index, + int, + json, + longtext, + mediumint, + mediumtext, + primaryKey, + real, + serial, + type SingleStoreColumn, + singlestoreEnum, + singlestoreTable, + smallint, + text, + time, + timestamp, + tinyint, + tinytext, + unique, + uniqueIndex, + varbinary, + varchar, + vector, + year, +} from '~/singlestore-core/index.ts'; +import { singlestoreSchema } from '~/singlestore-core/schema.ts'; +/* import { singlestoreView, type SingleStoreViewWithSelection } from '~/singlestore-core/view.ts'; */ +import type { InferSelectModel } from '~/table.ts'; +import type { Simplify } from '~/utils.ts'; +import { db } from './db.ts'; + +export const users = singlestoreTable( + 'users_table', + { + id: serial('id').primaryKey(), + homeCity: int('home_city') + .notNull(), + currentCity: int('current_city'), + serialNullable: serial('serial1'), + serialNotNull: serial('serial2').notNull(), + class: text('class', { enum: ['A', 'C'] }).notNull(), + subClass: text('sub_class', { enum: ['B', 'D'] }), + text: text('text'), + age1: int('age1').notNull(), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + enumCol: singlestoreEnum('enum_col', ['a', 'b', 'c']).notNull(), + }, + (users) => ({ + usersAge1Idx: uniqueIndex('usersAge1Idx').on(users.class), + usersAge2Idx: index('usersAge2Idx').on(users.class), + uniqueClass: uniqueIndex('uniqueClass') + .on(users.class, users.subClass) + .lock('default') + .algorythm('copy') + .using(`btree`), + pk: primaryKey(users.age1, users.class), + }), +); + +export const cities = singlestoreTable('cities_table', { + id: serial('id').primaryKey(), + name: text('name_db').notNull(), + population: int('population').default(0), +}, (cities) => ({ + citiesNameIdx: index('citiesNameIdx').on(cities.id), +})); + +Expect< + Equal< + { + id: SingleStoreColumn< + { + name: 'id'; + tableName: 'cities_table'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + identity: undefined; + generated: undefined; + }, + {}, + {} + >; + name: SingleStoreColumn< + { + name: 'name_db'; + tableName: 'cities_table'; + dataType: 'string'; + columnType: 'SingleStoreText'; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + identity: undefined; + generated: undefined; + }, + {}, + {} + >; + population: SingleStoreColumn< + { + name: 'population'; + tableName: 'cities_table'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + notNull: false; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + identity: undefined; + generated: undefined; + }, + {}, + {} + >; + }, + typeof cities._.columns + > +>; + +Expect< + Equal<{ + id: number; + name_db: string; + population: number | null; + }, InferSelectModel> +>; + +Expect< + Equal<{ + id?: number; + name: string; + population?: number | null; + }, typeof cities.$inferInsert> +>; + +export const customSchema = singlestoreSchema('custom_schema'); + +export const citiesCustom = customSchema.table('cities_table', { + id: serial('id').primaryKey(), + name: text('name_db').notNull(), + population: int('population').default(0), +}, (cities) => ({ + citiesNameIdx: index('citiesNameIdx').on(cities.id), +})); + +Expect>; + +export const classes = singlestoreTable('classes_table', ({ serial, text }) => ({ + id: serial('id').primaryKey(), + class: text('class', { enum: ['A', 'C'] }), + subClass: text('sub_class', { enum: ['B', 'D'] }).notNull(), +})); + +// TODO Implement views for SingleStore (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-view/) +/* export const classes2 = singlestoreTable('classes_table', { + id: serial().primaryKey(), + class: text({ enum: ['A', 'C'] }).$dbName('class_db'), + subClass: text({ enum: ['B', 'D'] }).notNull(), +}); */ + +/* export const newYorkers = singlestoreView('new_yorkers') + .algorithm('merge') + .sqlSecurity('definer') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + +Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: false; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > +>; + +{ + const newYorkers = customSchema.view('new_yorkers') + .algorithm('merge') + .sqlSecurity('definer') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: false; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = singlestoreView('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }) + .algorithm('merge') + .sqlSecurity('definer') + .as( + sql`select ${users.id} as user_id, ${cities.id} as city_id from ${users} left join ${cities} on ${ + eq(cities.id, users.homeCity) + } where ${gt(users.age1, 18)}`, + ); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = customSchema.view('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }) + .algorithm('merge') + .sqlSecurity('definer') + .as( + sql`select ${users.id} as user_id, ${cities.id} as city_id from ${users} left join ${cities} on ${ + eq(cities.id, users.homeCity) + } where ${gt(users.age1, 18)}`, + ); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = singlestoreView('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }).existing(); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', true, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = customSchema.view('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }).existing(); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', true, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} */ + +{ + const customText = customType<{ data: string }>({ + dataType() { + return 'text'; + }, + }); + + const t = customText('name').notNull(); + Expect< + Equal< + { + name: 'name'; + tableName: 'table'; + dataType: 'custom'; + columnType: 'SingleStoreCustomColumn'; + data: string; + driverParam: unknown; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + identity: undefined; + generated: undefined; + brand: 'Column'; + dialect: 'singlestore'; + }, + Simplify['_']> + > + >; +} + +{ + singlestoreTable('test', { + bigint: bigint('bigint', { mode: 'bigint' }), + number: bigint('number', { mode: 'number' }), + date: date('date').default(new Date()), + date2: date('date2', { mode: 'date' }).default(new Date()), + date3: date('date3', { mode: 'string' }).default('2020-01-01'), + date4: date('date4', { mode: undefined }).default(new Date()), + datetime: datetime('datetime').default(new Date()), + datetime2: datetime('datetime2', { mode: 'date' }).default(new Date()), + datetime3: datetime('datetime3', { mode: 'string' }).default('2020-01-01'), + datetime4: datetime('datetime4', { mode: undefined }).default(new Date()), + timestamp: timestamp('timestamp').default(new Date()), + timestamp2: timestamp('timestamp2', { mode: 'date' }).default(new Date()), + timestamp3: timestamp('timestamp3', { mode: 'string' }).default('2020-01-01'), + timestamp4: timestamp('timestamp4', { mode: undefined }).default(new Date()), + }); +} + +{ + singlestoreTable('test', { + col1: decimal('col1').default('1'), + }); +} + +{ + const test = singlestoreTable('test', { + test1: singlestoreEnum('test', ['a', 'b', 'c'] as const).notNull(), + test2: singlestoreEnum('test', ['a', 'b', 'c']).notNull(), + test3: varchar('test', { length: 255, enum: ['a', 'b', 'c'] as const }).notNull(), + test4: varchar('test', { length: 255, enum: ['a', 'b', 'c'] }).notNull(), + test5: text('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test6: text('test', { enum: ['a', 'b', 'c'] }).notNull(), + test7: tinytext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test8: tinytext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test9: mediumtext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test10: mediumtext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test11: longtext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test12: longtext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test13: char('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test14: char('test', { enum: ['a', 'b', 'c'] }).notNull(), + test15: text('test').notNull(), + }); + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; +} + +/* { // All types with generated columns + const test = singlestoreTable('test', { + test1: singlestoreEnum('test', ['a', 'b', 'c'] as const).generatedAlwaysAs(sql``), + test2: singlestoreEnum('test', ['a', 'b', 'c']).generatedAlwaysAs(sql``), + test3: varchar('test', { length: 255, enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test4: varchar('test', { length: 255, enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test5: text('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test6: text('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test7: tinytext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test8: tinytext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test9: mediumtext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test10: mediumtext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test11: longtext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test12: longtext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test13: char('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test14: char('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test15: text('test').generatedAlwaysAs(sql``), + }); + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; +} */ + +{ + const getUsersTable = (schemaName: TSchema) => { + return singlestoreSchema(schemaName).table('users', { + id: int('id').primaryKey(), + name: text('name').notNull(), + }); + }; + + const users1 = getUsersTable('id1'); + Expect>; + + const users2 = getUsersTable('id2'); + Expect>; +} + +{ + const internalStaff = singlestoreTable('internal_staff', { + userId: int('user_id').notNull(), + }); + + const customUser = singlestoreTable('custom_user', { + id: int('id').notNull(), + }); + + const ticket = singlestoreTable('ticket', { + staffId: int('staff_id').notNull(), + }); + + const subq = db + .select() + .from(internalStaff) + .leftJoin( + customUser, + eq(internalStaff.userId, customUser.id), + ).as('internal_staff'); + + const mainQuery = await db + .select() + .from(ticket) + .leftJoin(subq, eq(subq.internal_staff.userId, ticket.staffId)); + + Expect< + Equal<{ + internal_staff: { + internal_staff: { + userId: number; + }; + custom_user: { + id: number | null; + }; + } | null; + ticket: { + staffId: number; + }; + }[], typeof mainQuery> + >; +} + +// TODO Implement views for SingleStore (https://docs.singlestore.com/cloud/reference/sql-reference/data-definition-language-ddl/create-view/) +/* { + const newYorkers = singlestoreView('new_yorkers') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + + await db.select().from(newYorkers).leftJoin(newYorkers, eq(newYorkers.userId, newYorkers.userId)); +} */ + +{ + const test = singlestoreTable('test', { + id: text('id').$defaultFn(() => crypto.randomUUID()).primaryKey(), + }); + + Expect< + Equal<{ + id?: string; + }, typeof test.$inferInsert> + >; +} + +{ + singlestoreTable('test', { + id: int('id').$default(() => 1), + id2: int('id').$defaultFn(() => 1), + // @ts-expect-error - should be number + id3: int('id').$default(() => '1'), + // @ts-expect-error - should be number + id4: int('id').$defaultFn(() => '1'), + }); +} +{ + const emailLog = singlestoreTable( + 'email_log', + { + id: int('id', { unsigned: true }).autoincrement().notNull(), + clientId: int('id_client', { unsigned: true }), + receiverEmail: varchar('receiver_email', { length: 255 }).notNull(), + messageId: varchar('message_id', { length: 255 }), + contextId: int('context_id', { unsigned: true }), + contextType: singlestoreEnum('context_type', ['test']).$type<['test']>(), + action: varchar('action', { length: 80 }).$type<['test']>(), + events: json('events').$type<{ t: 'test' }[]>(), + createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().onUpdateNow(), + }, + (table) => { + return { + emailLogId: primaryKey({ columns: [table.id], name: 'email_log_id' }), + emailLogMessageIdUnique: unique('email_log_message_id_unique').on(table.messageId), + }; + }, + ); + + Expect< + Equal<{ + receiverEmail: string; + id?: number | undefined; + createdAt?: string | undefined; + clientId?: number | null | undefined; + messageId?: string | null | undefined; + contextId?: number | null | undefined; + contextType?: ['test'] | null | undefined; + action?: ['test'] | null | undefined; + events?: + | { + t: 'test'; + }[] + | null + | undefined; + updatedAt?: string | null | undefined; + }, typeof emailLog.$inferInsert> + >; +} + +{ + const customRequiredConfig = customType<{ + data: string; + driverData: string; + config: { length: number }; + configRequired: true; + }>({ + dataType(config) { + Expect>; + return `varchar(${config.length})`; + }, + + toDriver(value) { + Expect>(); + return value; + }, + + fromDriver(value) { + Expect>(); + return value; + }, + }); + + customRequiredConfig('t', { length: 10 }); + customRequiredConfig({ length: 10 }); + // @ts-expect-error - config is required + customRequiredConfig('t'); + // @ts-expect-error - config is required + customRequiredConfig(); +} + +{ + const customOptionalConfig = customType<{ + data: string; + driverData: string; + config: { length: number }; + }>({ + dataType(config) { + Expect>; + return config ? `varchar(${config.length})` : `text`; + }, + + toDriver(value) { + Expect>(); + return value; + }, + + fromDriver(value) { + Expect>(); + return value; + }, + }); + + customOptionalConfig('t', { length: 10 }); + customOptionalConfig('t'); + customOptionalConfig({ length: 10 }); + customOptionalConfig(); +} + +{ + singlestoreTable('all_columns', { + bigint: bigint('bigint', { mode: 'number' }), + bigint2: bigint('bigint', { mode: 'number', unsigned: true }), + bigintdef: bigint('bigintdef', { mode: 'number' }).default(0), + binary: binary('binary'), + binary1: binary('binary1', { length: 1 }), + binarydef: binary('binarydef').default(''), + boolean: boolean('boolean'), + booleandef: boolean('booleandef').default(false), + char: char('char'), + char2: char('char2', { length: 1 }), + char3: char('char3', { enum: ['a', 'b', 'c'] }), + char4: char('char4', { length: 1, enum: ['a', 'b', 'c'] }), + chardef: char('chardef').default(''), + date: date('date'), + date2: date('date2', { mode: 'string' }), + datedef: date('datedef').default(new Date()), + datetime: datetime('datetime'), + datetime2: datetime('datetime2', { mode: 'string' }), + datetimedef: datetime('datetimedef').default(new Date()), + decimal: decimal('decimal'), + decimal2: decimal('decimal2', { precision: 10 }), + decimal3: decimal('decimal3', { scale: 2 }), + decimal4: decimal('decimal4', { precision: 10, scale: 2 }), + decimaldef: decimal('decimaldef').default('0'), + double: double('double'), + double2: double('double2', { precision: 10 }), + double3: double('double3', { scale: 2 }), + double4: double('double4', { precision: 10, scale: 2 }), + doubledef: double('doubledef').default(0), + enum: singlestoreEnum('enum', ['a', 'b', 'c']), + enumdef: singlestoreEnum('enumdef', ['a', 'b', 'c']).default('a'), + float: float('float'), + float2: float('float2', { precision: 10 }), + float3: float('float3', { scale: 2 }), + float4: float('float4', { precision: 10, scale: 2 }), + floatdef: float('floatdef').default(0), + int: int('int'), + int2: int('int2', { unsigned: true }), + intdef: int('intdef').default(0), + json: json('json'), + jsondef: json('jsondef').default({}), + mediumint: mediumint('mediumint'), + mediumint2: mediumint('mediumint2', { unsigned: true }), + mediumintdef: mediumint('mediumintdef').default(0), + real: real('real'), + real2: real('real2', { precision: 10 }), + real3: real('real3', { scale: 2 }), + real4: real('real4', { precision: 10, scale: 2 }), + realdef: real('realdef').default(0), + serial: serial('serial'), + serialdef: serial('serialdef').default(0), + smallint: smallint('smallint'), + smallint2: smallint('smallint2', { unsigned: true }), + smallintdef: smallint('smallintdef').default(0), + text: text('text'), + text2: text('text2', { enum: ['a', 'b', 'c'] }), + textdef: text('textdef').default(''), + tinytext: tinytext('tinytext'), + tinytext2: tinytext('tinytext2', { enum: ['a', 'b', 'c'] }), + tinytextdef: tinytext('tinytextdef').default(''), + mediumtext: mediumtext('mediumtext'), + mediumtext2: mediumtext('mediumtext2', { enum: ['a', 'b', 'c'] }), + mediumtextdef: mediumtext('mediumtextdef').default(''), + longtext: longtext('longtext'), + longtext2: longtext('longtext2', { enum: ['a', 'b', 'c'] }), + longtextdef: longtext('longtextdef').default(''), + time: time('time'), + timedef: time('timedef').default('00:00:00'), + timestamp: timestamp('timestamp'), + timestamp2: timestamp('timestamp2', { mode: 'string' }), + timestamp3: timestamp('timestamp3', { mode: 'string' }), + timestamp4: timestamp('timestamp4'), + timestampdef: timestamp('timestampdef').default(new Date()), + tinyint: tinyint('tinyint'), + tinyint2: tinyint('tinyint2', { unsigned: true }), + tinyintdef: tinyint('tinyintdef').default(0), + varbinary: varbinary('varbinary', { length: 1 }), + varbinarydef: varbinary('varbinarydef', { length: 1 }).default(''), + varchar: varchar('varchar', { length: 1 }), + varchar2: varchar('varchar2', { length: 1, enum: ['a', 'b', 'c'] }), + varchardef: varchar('varchardef', { length: 1 }).default(''), + vector: vector('vector', { dimensions: 1 }), + vector2: vector('vector2', { dimensions: 1, elementType: 'I8' }), + year: year('year'), + yeardef: year('yeardef').default(0), + }); +} + +{ + const keysAsColumnNames = singlestoreTable('test', { + id: int(), + name: text(), + }); + + Expect>; + Expect>; +} + +{ + singlestoreTable('all_columns_without_name', { + bigint: bigint({ mode: 'number' }), + bigint2: bigint({ mode: 'number', unsigned: true }), + bigintdef: bigint({ mode: 'number' }).default(0), + binary: binary(), + binrary1: binary({ length: 1 }), + binarydef: binary().default(''), + boolean: boolean(), + booleandef: boolean().default(false), + char: char(), + char2: char({ length: 1 }), + char3: char({ enum: ['a', 'b', 'c'] }), + char4: char({ length: 1, enum: ['a', 'b', 'c'] }), + chardef: char().default(''), + date: date(), + date2: date({ mode: 'string' }), + datedef: date('datedef').default(new Date()), + datetime: datetime(), + datetime2: datetime({ mode: 'string' }), + datetimedef: datetime('datetimedef').default(new Date()), + decimal: decimal(), + decimal2: decimal({ precision: 10 }), + decimal3: decimal({ scale: 2 }), + decimal4: decimal({ precision: 10, scale: 2 }), + decimaldef: decimal('decimaldef').default('0'), + double: double(), + double2: double({ precision: 10 }), + double3: double({ scale: 2 }), + double4: double({ precision: 10, scale: 2 }), + doubledef: double().default(0), + enum: singlestoreEnum(['a', 'b', 'c']), + enumdef: singlestoreEnum(['a', 'b', 'c']).default('a'), + float: float(), + float2: float({ precision: 10 }), + float3: float({ scale: 2 }), + float4: float({ precision: 10, scale: 2 }), + floatdef: float().default(0), + int: int(), + int2: int({ unsigned: true }), + intdef: int().default(0), + json: json(), + jsondef: json().default({}), + mediumint: mediumint(), + mediumint2: mediumint({ unsigned: true }), + mediumintdef: mediumint().default(0), + real: real(), + real2: real({ precision: 10 }), + real3: real({ scale: 2 }), + real4: real({ precision: 10, scale: 2 }), + realdef: real().default(0), + serial: serial(), + serialdef: serial().default(0), + smallint: smallint(), + smallint2: smallint({ unsigned: true }), + smallintdef: smallint().default(0), + text: text(), + text2: text({ enum: ['a', 'b', 'c'] }), + textdef: text().default(''), + tinytext: tinytext(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }), + tinytextdef: tinytext().default(''), + mediumtext: mediumtext(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }), + mediumtextdef: mediumtext().default(''), + longtext: longtext(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }), + longtextdef: longtext().default(''), + time: time(), + timedef: time().default('00:00:00'), + timestamp: timestamp(), + timestamp2: timestamp({ mode: 'string' }), + timestamp3: timestamp({ mode: 'string' }), + timestamp4: timestamp(), + timestampdef: timestamp().default(new Date()), + tinyint: tinyint(), + tinyint2: tinyint({ unsigned: true }), + tinyintdef: tinyint().default(0), + varbinary: varbinary({ length: 1 }), + varbinarydef: varbinary({ length: 1 }).default(''), + varchar: varchar({ length: 1 }), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }), + varchardef: varchar({ length: 1 }).default(''), + vector: vector({ dimensions: 1 }), + vector2: vector({ dimensions: 1, elementType: 'I8' }), + year: year(), + yeardef: year().default(0), + }); +} diff --git a/drizzle-orm/type-tests/singlestore/update.ts b/drizzle-orm/type-tests/singlestore/update.ts new file mode 100644 index 000000000..4fb5497cf --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/update.ts @@ -0,0 +1,30 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import type { SingleStoreUpdate } from '~/singlestore-core/index.ts'; +import type { SingleStoreRawQueryResult } from '~/singlestore/session.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; +import { users } from './tables.ts'; + +{ + function dynamic(qb: T) { + return qb.where(sql``); + } + + const qbBase = db.update(users).set({}).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + Expect>; +} + +{ + db + .update(users) + .set({}) + .where(sql``) + // @ts-expect-error method was already called + .where(sql``); +} + +{ + db.update(users).set({}).where(sql``).limit(1).orderBy(sql``); +} diff --git a/drizzle-orm/type-tests/singlestore/with.ts b/drizzle-orm/type-tests/singlestore/with.ts new file mode 100644 index 000000000..4233fbbf1 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/with.ts @@ -0,0 +1,80 @@ +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { gt, inArray } from '~/expressions.ts'; +import { int, serial, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; + +const orders = singlestoreTable('orders', { + id: serial('id').primaryKey(), + region: text('region').notNull(), + product: text('product').notNull(), + amount: int('amount').notNull(), + quantity: int('quantity').notNull(), + /* generated: text('generatedText').generatedAlwaysAs(sql``), */ +}); + +{ + const regionalSales = db + .$with('regional_sales') + .as( + db + .select({ + region: orders.region, + totalSales: sql`sum(${orders.amount})`.as('total_sales'), + }) + .from(orders) + .groupBy(orders.region), + ); + + const topRegions = db + .$with('top_regions') + .as( + db + .select({ + region: orders.region, + totalSales: orders.amount, + }) + .from(regionalSales) + .where( + gt( + regionalSales.totalSales, + db.select({ sales: sql`sum(${regionalSales.totalSales})/10` }).from(regionalSales), + ), + ), + ); + + const result = await db + .with(regionalSales, topRegions) + .select({ + region: orders.region, + product: orders.product, + productUnits: sql`sum(${orders.quantity})`, + productSales: sql`sum(${orders.amount})`, + }) + .from(orders) + .where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions))); + + Expect< + Equal<{ + region: string; + product: string; + productUnits: number; + productSales: number; + }[], typeof result> + >; + + const allOrdersWith = db.$with('all_orders_with').as(db.select().from(orders)); + const allFromWith = await db.with(allOrdersWith).select().from(allOrdersWith); + + Expect< + Equal<{ + id: number; + region: string; + product: string; + amount: number; + quantity: number; + /* generated: string | null; */ + }[], typeof allFromWith> + >; +} diff --git a/drizzle-orm/type-tests/sqlite/count.ts b/drizzle-orm/type-tests/sqlite/count.ts new file mode 100644 index 000000000..04350f000 --- /dev/null +++ b/drizzle-orm/type-tests/sqlite/count.ts @@ -0,0 +1,61 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, gt, ne } from '~/expressions.ts'; +import { integer, sqliteTable, text } from '~/sqlite-core/index.ts'; +import type { Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = sqliteTable('names', { + id: integer('id').primaryKey(), + name: text('name'), + authorId: integer('author_id'), +}); + +const separate = await db.$count(names); + +const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))); + +const embedded = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names).as('count1'), + }) + .from(names); + +const embeddedFilters = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'), + }) + .from(names); + +Expect>; + +Expect>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embedded + > +>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embeddedFilters + > +>; diff --git a/drizzle-orm/type-tests/sqlite/db.ts b/drizzle-orm/type-tests/sqlite/db.ts index 07af338d4..1950c7435 100644 --- a/drizzle-orm/type-tests/sqlite/db.ts +++ b/drizzle-orm/type-tests/sqlite/db.ts @@ -3,11 +3,14 @@ import { Database as BunDatabase } from 'bun:sqlite'; import { drizzle as drizzleBetterSqlite3 } from '~/better-sqlite3/index.ts'; import { drizzle as drizzleBun } from '~/bun-sqlite/index.ts'; import { drizzle as drizzleD1 } from '~/d1/index.ts'; +import { drizzle as durableSqlite } from '~/durable-sqlite/index.ts'; const client = new Database(':memory:'); const bunClient = new BunDatabase(':memory:'); declare const d1: D1Database; +declare const durableSql: DurableObjectStorage; export const db = drizzleBetterSqlite3(client); export const bunDb = drizzleBun(bunClient); export const d1Db = drizzleD1(d1); +export const durableSqliteDb = durableSqlite(durableSql); diff --git a/drizzle-orm/type-tests/sqlite/delete.ts b/drizzle-orm/type-tests/sqlite/delete.ts index fcc754740..d943077c8 100644 --- a/drizzle-orm/type-tests/sqlite/delete.ts +++ b/drizzle-orm/type-tests/sqlite/delete.ts @@ -152,3 +152,7 @@ Expect>; // @ts-expect-error method was already called .returning(); } + +{ + db.delete(users).where(sql``).limit(1).orderBy(sql``); +} diff --git a/drizzle-orm/type-tests/sqlite/generated-columns.ts b/drizzle-orm/type-tests/sqlite/generated-columns.ts index 57ffea989..f1474baed 100644 --- a/drizzle-orm/type-tests/sqlite/generated-columns.ts +++ b/drizzle-orm/type-tests/sqlite/generated-columns.ts @@ -11,12 +11,14 @@ const users = sqliteTable( firstName: text('first_name', { length: 255 }), lastName: text('last_name', { length: 255 }), email: text('email').notNull(), - fullName: text('full_name').generatedAlwaysAs(sql`concat_ws(first_name, ' ', last_name)`), + fullName: text('full_name') + .generatedAlwaysAs(sql`concat_ws(first_name, ' ', last_name)`), upperName: text('upper_name').generatedAlwaysAs( sql` case when first_name is null then null else upper(first_name) end `, ).$type(), // There is no way for drizzle to detect nullability in these cases. This is how the user can work around it }, ); + { type User = typeof users.$inferSelect; type NewUser = typeof users.$inferInsert; diff --git a/drizzle-orm/type-tests/sqlite/insert.ts b/drizzle-orm/type-tests/sqlite/insert.ts index c4566ae8f..e7d9cb77d 100644 --- a/drizzle-orm/type-tests/sqlite/insert.ts +++ b/drizzle-orm/type-tests/sqlite/insert.ts @@ -3,6 +3,7 @@ import type { Equal } from 'type-tests/utils.ts'; import { Expect } from 'type-tests/utils.ts'; import { and, eq } from '~/expressions.ts'; import { sql } from '~/sql/sql.ts'; +import { integer, QueryBuilder, sqliteTable, text } from '~/sqlite-core/index.ts'; import type { SQLiteInsert } from '~/sqlite-core/query-builders/insert.ts'; import type { DrizzleTypeError } from '~/utils.ts'; import { bunDb, db } from './db.ts'; @@ -205,3 +206,75 @@ stmt.run({ id: 1, limit: 10, offset: 20 }); // @ts-expect-error method was already called .returning(); } + +{ + const users1 = sqliteTable('users1', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + admin: integer('admin', { mode: 'boolean' }).notNull().default(false), + }); + const users2 = sqliteTable('users2', { + id: integer('id').primaryKey(), + firstName: text('first_name').notNull(), + lastName: text('last_name').notNull(), + admin: integer('admin', { mode: 'boolean' }).notNull().default(false), + phoneNumber: text('phone_number'), + }); + + const qb = new QueryBuilder(); + + db.insert(users1).select(sql`select * from users1`); + db.insert(users1).select(() => sql`select * from users1`); + + db + .insert(users1) + .select( + qb.select({ + name: users2.firstName, + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + qb.select({ + name: users2.firstName, + admin: users2.admin, + }).from(users2).where(sql``), + ); + + db + .insert(users2) + .select( + qb.select({ + firstName: users2.firstName, + lastName: users2.lastName, + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + qb.select({ + name: sql`${users2.firstName} || ' ' || ${users2.lastName}`.as('name'), + admin: users2.admin, + }).from(users2), + ); + + db + .insert(users1) + .select( + // @ts-expect-error name is undefined + qb.select({ admin: users1.admin }).from(users1), + ); + + db.insert(users1).select(db.select().from(users1)); + db.insert(users1).select(() => db.select().from(users1)); + db.insert(users1).select((qb) => qb.select().from(users1)); + // @ts-expect-error tables have different keys + db.insert(users1).select(db.select().from(users2)); + // @ts-expect-error tables have different keys + db.insert(users1).select(() => db.select().from(users2)); +} diff --git a/drizzle-orm/type-tests/sqlite/select.ts b/drizzle-orm/type-tests/sqlite/select.ts index 196dbc4de..92bb6055a 100644 --- a/drizzle-orm/type-tests/sqlite/select.ts +++ b/drizzle-orm/type-tests/sqlite/select.ts @@ -21,12 +21,15 @@ import { notLike, or, } from '~/expressions.ts'; -import { param, sql } from '~/sql/sql.ts'; +import { type InferSelectViewModel, param, sql } from '~/sql/sql.ts'; import { alias } from '~/sqlite-core/alias.ts'; import type { Equal } from 'type-tests/utils.ts'; import { Expect } from 'type-tests/utils.ts'; +import { integer, text } from '~/sqlite-core/index.ts'; import type { SQLiteSelect, SQLiteSelectQueryBuilder } from '~/sqlite-core/query-builders/select.types.ts'; +import { sqliteTable } from '~/sqlite-core/table.ts'; +import { sqliteView } from '~/sqlite-core/view.ts'; import { db } from './db.ts'; import { cities, classes, newYorkers, users } from './tables.ts'; @@ -579,3 +582,40 @@ Expect< // @ts-expect-error method was already called .offset(10); } + +{ + const table1 = sqliteTable('table1', { + id: integer().primaryKey(), + name: text().notNull(), + }); + const table2 = sqliteTable('table2', { + id: integer().primaryKey(), + age: integer().notNull(), + }); + const table3 = sqliteTable('table3', { + id: integer().primaryKey(), + phone: text().notNull(), + }); + const view = sqliteView('view').as((qb) => + qb.select({ + table: table1, + column: table2.age, + nested: { + column: table3.phone, + }, + }).from(table1).innerJoin(table2, sql``).leftJoin(table3, sql``) + ); + const result = await db.select().from(view); + + Expect< + Equal + >; + Expect>; + Expect[]>>; +} diff --git a/drizzle-orm/type-tests/sqlite/tables.ts b/drizzle-orm/type-tests/sqlite/tables.ts index 01419b27a..358b6fea6 100644 --- a/drizzle-orm/type-tests/sqlite/tables.ts +++ b/drizzle-orm/type-tests/sqlite/tables.ts @@ -4,11 +4,15 @@ import { eq, gt } from '~/expressions.ts'; import { sql } from '~/sql/sql.ts'; import { alias, + blob, check, + customType, foreignKey, index, integer, + numeric, primaryKey, + real, type SQLiteColumn, sqliteTable, text, @@ -91,11 +95,11 @@ Expect< }> >; -export const cities = sqliteTable('cities_table', { +export const cities = sqliteTable('cities_table', ({ integer, text }) => ({ id: integer('id').primaryKey(), name: text('name').notNull(), population: integer('population').default(0), -}); +})); export type City = typeof cities.$inferSelect; Expect< @@ -167,6 +171,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isAutoincrement: false; hasRuntimeDefault: false; isPrimaryKey: true; @@ -183,6 +188,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isAutoincrement: false; hasRuntimeDefault: false; isPrimaryKey: true; @@ -218,6 +224,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isAutoincrement: false; hasRuntimeDefault: false; isPrimaryKey: false; @@ -234,6 +241,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isAutoincrement: false; hasRuntimeDefault: false; isPrimaryKey: false; @@ -265,6 +273,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isAutoincrement: false; hasRuntimeDefault: false; isPrimaryKey: false; @@ -281,6 +290,7 @@ Expect< enumValues: undefined; baseColumn: never; generated: undefined; + identity: undefined; isAutoincrement: false; hasRuntimeDefault: false; isPrimaryKey: false; @@ -460,3 +470,114 @@ Expect< Expect>; Expect>; } + +{ + const customRequiredConfig = customType<{ + data: string; + driverData: string; + config: { length: number }; + configRequired: true; + }>({ + dataType(config) { + Expect>; + return `varchar(${config.length})`; + }, + + toDriver(value) { + Expect>(); + return value; + }, + + fromDriver(value) { + Expect>(); + return value; + }, + }); + + customRequiredConfig('t', { length: 10 }); + customRequiredConfig({ length: 10 }); + // @ts-expect-error - config is required + customRequiredConfig('t'); + // @ts-expect-error - config is required + customRequiredConfig(); +} + +{ + const customOptionalConfig = customType<{ + data: string; + driverData: string; + config: { length: number }; + }>({ + dataType(config) { + Expect>; + return config ? `varchar(${config.length})` : `text`; + }, + + toDriver(value) { + Expect>(); + return value; + }, + + fromDriver(value) { + Expect>(); + return value; + }, + }); + + customOptionalConfig('t', { length: 10 }); + customOptionalConfig('t'); + customOptionalConfig({ length: 10 }); + customOptionalConfig(); +} + +{ + sqliteTable('all_columns', { + blob: blob('blob'), + blob2: blob('blob2', { mode: 'bigint' }), + blobdef: blob('blobdef').default(0), + integer: integer('integer'), + integer2: integer('integer2', { mode: 'boolean' }), + integerdef: integer('integerdef').default(0), + numeric: numeric('numeric'), + numericdef: numeric('numericdef').default(''), + real: real('real'), + realdef: real('realdef').default(0), + text: text('text'), + text2: text('text2', { enum: ['a', 'b', 'c'] }), + text3: text('text3', { length: 1 }), + text4: text('text4', { length: 1, enum: ['a', 'b', 'c'] }), + text5: text('text5', { mode: 'json' }), + textdef: text('textdef').default(''), + }); +} + +{ + const keysAsColumnNames = sqliteTable('test', { + id: integer(), + name: text(), + }); + + Expect>; + Expect>; +} + +{ + sqliteTable('all_columns_without_name', { + blob: blob(), + blob2: blob({ mode: 'bigint' }), + blobdef: blob().default(0), + integer: integer(), + integer2: integer({ mode: 'boolean' }), + integerdef: integer().default(0), + numeric: numeric(), + numericdef: numeric().default(''), + real: real(), + realdef: real().default(0), + text: text(), + text2: text({ enum: ['a', 'b', 'c'] }), + text3: text({ length: 1 }), + text4: text({ length: 1, enum: ['a', 'b', 'c'] }), + text5: text({ mode: 'json' }), + textdef: text().default(''), + }); +} diff --git a/drizzle-orm/type-tests/sqlite/update.ts b/drizzle-orm/type-tests/sqlite/update.ts index aa1f8051f..a6a1b47b3 100644 --- a/drizzle-orm/type-tests/sqlite/update.ts +++ b/drizzle-orm/type-tests/sqlite/update.ts @@ -6,7 +6,7 @@ import { sql } from '~/sql/sql.ts'; import type { SQLiteUpdate } from '~/sqlite-core/query-builders/update.ts'; import type { DrizzleTypeError } from '~/utils.ts'; import { bunDb, db } from './db.ts'; -import { users } from './tables.ts'; +import { cities, users } from './tables.ts'; const updateRun = db.update(users) .set({ @@ -133,3 +133,65 @@ Expect>; // @ts-expect-error method was already called .where(sql``); } + +{ + db + .update(users) + .set({}) + .from(sql``) + .leftJoin(sql``, (table, from) => { + Expect>; + Expect>; + return sql``; + }); + + db + .update(users) + .set({}) + .from(cities) + .leftJoin(sql``, (table, from) => { + Expect>; + Expect>; + return sql``; + }); + + const citiesSq = db.$with('cities_sq').as(db.select({ id: cities.id }).from(cities)); + + db + .with(citiesSq) + .update(users) + .set({}) + .from(citiesSq) + .leftJoin(sql``, (table, from) => { + Expect>; + Expect>; + return sql``; + }); + + db + .with(citiesSq) + .update(users) + .set({ + homeCity: citiesSq.id, + }) + .from(citiesSq); +} + +{ + const result = await db.update(users).set({}).from(cities).returning(); + Expect< + Equal + >; +} + +{ + const result = await db.update(users).set({}).from(cities).returning({ + id: users.id, + }); + Expect< + Equal<{ + id: number; + }[], typeof result> + >; + db.update(users).set({}).where(sql``).limit(1).orderBy(sql``); +} diff --git a/drizzle-orm/type-tests/sqlite/with.ts b/drizzle-orm/type-tests/sqlite/with.ts index 8b5963eb6..b26e4e7d7 100644 --- a/drizzle-orm/type-tests/sqlite/with.ts +++ b/drizzle-orm/type-tests/sqlite/with.ts @@ -1,6 +1,6 @@ import type { Equal } from 'type-tests/utils.ts'; import { Expect } from 'type-tests/utils.ts'; -import { gt, inArray } from '~/expressions.ts'; +import { gt, inArray, like } from '~/expressions.ts'; import { sql } from '~/sql/sql.ts'; import { integer, sqliteTable, text } from '~/sqlite-core/index.ts'; import { db } from './db.ts'; @@ -78,4 +78,7 @@ const orders = sqliteTable('orders', { generated: string | null; }[], typeof allFromWith> >; + + const regionalSalesWith = db.$with('regional_sales_with').as(db.select().from(regionalSales)); + db.with(regionalSalesWith).select().from(regionalSalesWith).where(like(regionalSalesWith.totalSales, 'abc')); } diff --git a/drizzle-orm/type-tests/utils/neon-auth-token.ts b/drizzle-orm/type-tests/utils/neon-auth-token.ts new file mode 100644 index 000000000..5ea684ddc --- /dev/null +++ b/drizzle-orm/type-tests/utils/neon-auth-token.ts @@ -0,0 +1,5 @@ +import type { HTTPQueryOptions } from '@neondatabase/serverless'; +import { type Equal, Expect } from 'type-tests/utils.ts'; +import type { NeonAuthToken } from '~/utils'; + +Expect['authToken'], undefined>, NeonAuthToken>>; diff --git a/drizzle-seed/README.md b/drizzle-seed/README.md new file mode 100644 index 000000000..588102a5b --- /dev/null +++ b/drizzle-seed/README.md @@ -0,0 +1,105 @@ +# Drizzle Seed + +> [!NOTE] +> `drizzle-seed` can only be used with `drizzle-orm@0.36.4` or higher. Versions lower than this may work at runtime but could have type issues and identity column issues, as this patch was introduced in `drizzle-orm@0.36.4` + +## Documentation + +The full API reference and package overview can be found in our [official documentation](https://orm.drizzle.team/docs/seed-overview) + +## Overview + +`drizzle-seed` is a TypeScript library that helps you generate deterministic, yet realistic, +fake data to populate your database. By leveraging a seedable pseudorandom number generator (pRNG), +it ensures that the data you generate is consistent and reproducible across different runs. +This is especially useful for testing, development, and debugging purposes. + +#### What is Deterministic Data Generation? + +Deterministic data generation means that the same input will always produce the same output. +In the context of `drizzle-seed`, when you initialize the library with the same seed number, +it will generate the same sequence of fake data every time. This allows for predictable and repeatable data sets. + +#### Pseudorandom Number Generator (pRNG) + +A pseudorandom number generator is an algorithm that produces a sequence of numbers +that approximates the properties of random numbers. However, because it's based on an initial value +called a seed, you can control its randomness. By using the same seed, the pRNG will produce the +same sequence of numbers, making your data generation process reproducible. + +#### Benefits of Using a pRNG: + +- Consistency: Ensures that your tests run on the same data every time. +- Debugging: Makes it easier to reproduce and fix bugs by providing a consistent data set. +- Collaboration: Team members can share seed numbers to work with the same data sets. + +With drizzle-seed, you get the best of both worlds: the ability to generate realistic fake data and the control to reproduce it whenever needed. + +## Getting started + +`npm install drizzle-seed` + +You have to install `drizzle-orm` in order to use `drizzle-seed`. + +`npm install drizzle-orm` + +## Basic Usage + +In this example we will create 10 users with random names and ids + +```ts {12} +import { pgTable, integer, text } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { seed } from "drizzle-seed"; + +const users = pgTable("users", { + id: integer().primaryKey(), + name: text().notNull(), +}); + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { users }); +} + +main(); +``` + +## Options + +**`count`** + +By default, the `seed` function will create 10 entities. +However, if you need more for your tests, you can specify this in the seed options object + +```ts +await seed(db, schema, { count: 1000 }); +``` + +**`seed`** + +If you need a seed to generate a different set of values for all subsequent runs, you can define a different number +in the `seed` option. Any new number will generate a unique set of values + +```ts +await seed(db, schema, { seed: 12345 }); +``` + +## Reset databases + +With `drizzle-seed`, you can easily reset your database and seed it with new values, for example, in your test suites + +```ts +// path to a file with schema you want to reset +import * as schema from "./schema.ts"; +import { reset } from "drizzle-seed"; + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await reset(db, schema); +} + +main(); +``` + +More examples are available in our [official documentation](https://orm.drizzle.team/docs/seed-overview) \ No newline at end of file diff --git a/drizzle-seed/package.json b/drizzle-seed/package.json new file mode 100644 index 000000000..a9287eb28 --- /dev/null +++ b/drizzle-seed/package.json @@ -0,0 +1,105 @@ +{ + "name": "drizzle-seed", + "version": "0.3.0", + "main": "index.js", + "type": "module", + "scripts": { + "build": "tsx scripts/build.ts", + "pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz", + "test": "vitest --config ./vitest.config.ts", + "test:types": "cd type-tests && tsc", + "generate-for-tests:pg": "drizzle-kit generate --config=./src/tests/pg/drizzle.config.ts", + "generate-for-tests:mysql": "drizzle-kit generate --config=./src/tests/mysql/drizzle.config.ts", + "generate-for-tests:sqlite": "drizzle-kit generate --config=./src/tests/sqlite/drizzle.config.ts", + "generate": "drizzle-kit generate", + "start": "npx tsx ./src/dev/test.ts", + "start:pg": "npx tsx ./src/tests/northwind/pgTest.ts", + "start:mysql": "npx tsx ./src/tests/northwind/mysqlTest.ts", + "start:sqlite": "npx tsx ./src/tests/northwind/sqliteTest.ts", + "benchmark": "npx tsx ./src/tests/benchmarks/generatorsBenchmark.ts", + "publish": "npm publish package.tgz" + }, + "author": "Drizzle Team", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/drizzle-team/drizzle-orm/issues" + }, + "keywords": [ + "drizzle", + "orm", + "pg", + "mysql", + "postgresql", + "postgres", + "sqlite", + "database", + "sql", + "typescript", + "ts", + "drizzle-orm", + "drizzle-seed", + "seeding", + "seed" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/drizzle-team/drizzle-orm.git" + }, + "publishConfig": { + "provenance": true + }, + "sideEffects": false, + "description": "A package to seed your database using Drizzle ORM", + "exports": { + ".": { + "import": { + "types": "./index.d.mts", + "default": "./index.mjs" + }, + "require": { + "types": "./index.d.cjs", + "default": "./index.cjs" + }, + "types": "./index.d.ts", + "default": "./index.mjs" + } + }, + "peerDependencies": { + "drizzle-orm": ">=0.36.4" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": true + } + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.16.1", + "@electric-sql/pglite": "^0.2.12", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^11.1.6", + "@types/better-sqlite3": "^7.6.11", + "@types/dockerode": "^3.3.31", + "@types/node": "^22.5.4", + "@types/pg": "^8.11.6", + "@types/uuid": "^10.0.0", + "better-sqlite3": "^11.1.2", + "cpy": "^11.1.0", + "dockerode": "^4.0.2", + "dotenv": "^16.4.5", + "drizzle-kit": "workspace:./drizzle-kit/dist", + "drizzle-orm": "workspace:./drizzle-orm/dist", + "get-port": "^7.1.0", + "mysql2": "^3.3.3", + "pg": "^8.12.0", + "resolve-tspaths": "^0.8.19", + "rollup": "^4.21.2", + "tslib": "^2.7.0", + "tsx": "^4.19.0", + "uuid": "^10.0.0", + "vitest": "^2.0.5", + "zx": "^8.1.5" + }, + "dependencies": { + "pure-rand": "^6.1.0" + } +} diff --git a/drizzle-seed/rollup.config.ts b/drizzle-seed/rollup.config.ts new file mode 100644 index 000000000..c5874a54a --- /dev/null +++ b/drizzle-seed/rollup.config.ts @@ -0,0 +1,30 @@ +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig([ + { + input: 'src/index.ts', + output: [ + { + format: 'esm', + dir: 'dist', + entryFileNames: '[name].mjs', + chunkFileNames: '[name]-[hash].mjs', + sourcemap: true, + }, + { + format: 'cjs', + dir: 'dist', + entryFileNames: '[name].cjs', + chunkFileNames: '[name]-[hash].cjs', + sourcemap: true, + }, + ], + external: [/^drizzle-orm\/?/, 'pure-rand'], + plugins: [ + typescript({ + tsconfig: 'tsconfig.build.json', + }), + ], + }, +]); diff --git a/drizzle-seed/scripts/build.ts b/drizzle-seed/scripts/build.ts new file mode 100644 index 000000000..1910feac6 --- /dev/null +++ b/drizzle-seed/scripts/build.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env -S pnpm tsx +import 'zx/globals'; +import cpy from 'cpy'; + +await fs.remove('dist'); +await $`rollup --config rollup.config.ts --configPlugin typescript`; +await $`resolve-tspaths`; +await fs.copy('README.md', 'dist/README.md'); +await cpy('dist/**/*.d.ts', 'dist', { + rename: (basename) => basename.replace(/\.d\.ts$/, '.d.mts'), +}); +await cpy('dist/**/*.d.ts', 'dist', { + rename: (basename) => basename.replace(/\.d\.ts$/, '.d.cts'), +}); +await fs.copy('package.json', 'dist/package.json'); diff --git a/drizzle-seed/src/datasets/adjectives.ts b/drizzle-seed/src/datasets/adjectives.ts new file mode 100644 index 000000000..880e52636 --- /dev/null +++ b/drizzle-seed/src/datasets/adjectives.ts @@ -0,0 +1,4848 @@ +/** + * The original source for the Adjectives data was taken from https://www.kaggle.com/datasets/jordansiem/adjectives-list + */ +export default [ + 'abandoned', + 'abdominal', + 'abhorrent', + 'abiding', + 'abject', + 'able', + 'able-bodied', + 'abnormal', + 'abounding', + 'abrasive', + 'abrupt', + 'absent', + 'absentminded', + 'absolute', + 'absorbed', + 'absorbing', + 'abstracted', + 'absurd', + 'abundant', + 'abusive', + 'abysmal', + 'academic', + 'acceptable', + 'accepting', + 'accessible', + 'accidental', + 'acclaimed', + 'accommodating', + 'accompanying', + 'accountable', + 'accurate', + 'accusative', + 'accused', + 'accusing', + 'acerbic', + 'achievable', + 'aching', + 'acid', + 'acidic', + 'acknowledged', + 'acoustic', + 'acrid', + 'acrimonious', + 'acrobatic', + 'actionable', + 'active', + 'actual', + 'adhoc', + 'adamant', + 'adaptable', + 'adaptive', + 'addicted', + 'addictive', + 'additional', + 'adept', + 'adequate', + 'adhesive', + 'adjacent', + 'adjoining', + 'adjustable', + 'administrative', + 'admirable', + 'admired', + 'admiring', + 'adopted', + 'adoptive', + 'adorable', + 'adored', + 'adoring', + 'adrenalized', + 'adroit', + 'adult', + 'advanced', + 'advantageous', + 'adventurous', + 'adversarial', + 'advisable', + 'aerial', + 'affable', + 'affected', + 'affectionate', + 'affirmative', + 'affordable', + 'afraid', + 'afternoon', + 'ageless', + 'aggravated', + 'aggravating', + 'aggressive', + 'agitated', + 'agonizing', + 'agrarian', + 'agreeable', + 'aimless', + 'airline', + 'airsick', + 'ajar', + 'alarmed', + 'alarming', + 'alert', + 'algebraic', + 'alien', + 'alienated', + 'alike', + 'alive', + 'all-around', + 'alleged', + 'allowable', + 'all-purpose', + 'all-too-common', + 'alluring', + 'allusive', + 'alone', + 'aloof', + 'alterable', + 'alternating', + 'alternative', + 'amazed', + 'amazing', + 'ambiguous', + 'ambitious', + 'ambulant', + 'ambulatory', + 'amiable', + 'amicable', + 'amphibian', + 'amused', + 'amusing', + 'ancient', + 'anecdotal', + 'anemic', + 'angelic', + 'angered', + 'angry', + 'angular', + 'animal', + 'animated', + 'annoyed', + 'annoying', + 'annual', + 'anonymous', + 'another', + 'antagonistic', + 'anticipated', + 'anticlimactic', + 'anticorrosive', + 'antiquated', + 'antiseptic', + 'antisocial', + 'antsy', + 'anxious', + 'any', + 'apathetic', + 'apologetic', + 'apologizing', + 'appalling', + 'appealing', + 'appetizing', + 'applauding', + 'applicable', + 'applicative', + 'appreciative', + 'apprehensive', + 'approachable', + 'approaching', + 'appropriate', + 'approving', + 'approximate', + 'aquatic', + 'architectural', + 'ardent', + 'arduous', + 'arguable', + 'argumentative', + 'arid', + 'aristocratic', + 'aromatic', + 'arresting', + 'arrogant', + 'artful', + 'artificial', + 'artistic', + 'artless', + 'ashamed', + 'aspiring', + 'assertive', + 'assignable', + 'assorted', + 'assumable', + 'assured', + 'assuring', + 'astonished', + 'astonishing', + 'astounded', + 'astounding', + 'astringent', + 'astronomical', + 'astute', + 'asymmetrical', + 'athletic', + 'atomic', + 'atrocious', + 'attachable', + 'attainable', + 'attentive', + 'attractive', + 'attributable', + 'atypical', + 'audacious', + 'auspicious', + 'authentic', + 'authoritarian', + 'authoritative', + 'autobiographic', + 'autographed', + 'automatic', + 'autonomous', + 'available', + 'avant-garde', + 'avenging', + 'average', + 'avian', + 'avid', + 'avoidable', + 'awake', + 'awakening', + 'aware', + 'away', + 'awesome', + 'awful', + 'awkward', + 'axiomatic', + 'babbling', + 'baby', + 'background', + 'backhanded', + 'bacterial', + 'bad', + 'bad-tempered', + 'baffled', + 'baffling', + 'bald', + 'balding', + 'balmy', + 'bandaged', + 'banging', + 'bankable', + 'banned', + 'bantering', + 'barbaric', + 'barbarous', + 'barbequed', + 'barefooted', + 'barking', + 'barren', + 'bashful', + 'basic', + 'battered', + 'batty', + 'bawling', + 'beady', + 'beaming', + 'bearable', + 'beautiful', + 'beckoning', + 'bedazzled', + 'bedazzling', + 'beefy', + 'beeping', + 'befitting', + 'befuddled', + 'beginning', + 'belching', + 'believable', + 'bellicose', + 'belligerent', + 'bellowing', + 'bendable', + 'beneficial', + 'benevolent', + 'benign', + 'bent', + 'berserk', + 'best', + 'betrayed', + 'better', + 'betteroff', + 'better-late-than-never', + 'bewildered', + 'bewildering', + 'bewitched', + 'bewitching', + 'biased', + 'biblical', + 'big', + 'big-city', + 'bigger', + 'biggest', + 'big-headed', + 'bighearted', + 'bigoted', + 'bilingual', + 'billable', + 'billowy', + 'binary', + 'binding', + 'bioactive', + 'biodegradable', + 'biographical', + 'bite-sized', + 'biting', + 'bitter', + 'bizarre', + 'black', + 'black-and-blue', + 'blamable', + 'blameless', + 'bland', + 'blank', + 'blaring', + 'blasphemous', + 'blatant', + 'blazing', + 'bleached', + 'bleak', + 'bleary', + 'bleary-eyed', + 'blessed', + 'blind', + 'blindfolded', + 'blinding', + 'blissful', + 'blistering', + 'bloated', + 'blonde', + 'bloodied', + 'blood-red', + 'bloodthirsty', + 'bloody', + 'blooming', + 'blossoming', + 'blue', + 'blundering', + 'blunt', + 'blurred', + 'blurry', + 'blushing', + 'boastful', + 'bodacious', + 'bohemian', + 'boiling', + 'boisterous', + 'bold', + 'bookish', + 'booming', + 'boorish', + 'bordering', + 'bored', + 'boring', + 'born', + 'bossy', + 'both', + 'bothered', + 'bouncing', + 'bouncy', + 'boundless', + 'bountiful', + 'boyish', + 'braided', + 'brainless', + 'brainy', + 'brash', + 'brassy', + 'brave', + 'brawny', + 'brazen', + 'breakable', + 'breathable', + 'breathless', + 'breathtaking', + 'breezy', + 'bribable', + 'brick', + 'brief', + 'bright', + 'bright-eyed', + 'bright-red', + 'brilliant', + 'briny', + 'brisk', + 'bristly', + 'broad', + 'broken', + 'broken-hearted', + 'bronchial', + 'bronze', + 'bronzed', + 'brooding', + 'brown', + 'bruised', + 'brunette', + 'brutal', + 'brutish', + 'bubbly', + 'budget', + 'built-in', + 'bulky', + 'bumpy', + 'bungling', + 'buoyant', + 'bureaucratic', + 'burly', + 'burnable', + 'burning', + 'bushy', + 'busiest', + 'business', + 'bustling', + 'busy', + 'buzzing', + 'cackling', + 'caged', + 'cagey', + 'calculable', + 'calculated', + 'calculating', + 'callous', + 'calm', + 'calming', + 'camouflaged', + 'cancelled', + 'cancerous', + 'candid', + 'cantankerous', + 'capable', + 'capricious', + 'captivated', + 'captivating', + 'captive', + 'carefree', + 'careful', + 'careless', + 'caring', + 'carnivorous', + 'carpeted', + 'carsick', + 'casual', + 'catastrophic', + 'catatonic', + 'catchable', + 'caustic', + 'cautious', + 'cavalier', + 'cavernous', + 'ceaseless', + 'celebrated', + 'celestial', + 'centered', + 'central', + 'cerebral', + 'ceremonial', + 'certain', + 'certifiable', + 'certified', + 'challenged', + 'challenging', + 'chance', + 'changeable', + 'changing', + 'chanting', + 'charging', + 'charismatic', + 'charitable', + 'charmed', + 'charming', + 'chattering', + 'chatting', + 'chatty', + 'chauvinistic', + 'cheap', + 'cheapest', + 'cheeky', + 'cheerful', + 'cheering', + 'cheerless', + 'cheery', + 'chemical', + 'chewable', + 'chewy', + 'chic', + 'chicken', + 'chief', + 'childish', + 'childlike', + 'chilling', + 'chilly', + 'chivalrous', + 'choice', + 'choking', + 'choppy', + 'chronological', + 'chubby', + 'chuckling', + 'chunky', + 'cinematic', + 'circling', + 'circular', + 'circumstantial', + 'civil', + 'civilian', + 'civilized', + 'clammy', + 'clamoring', + 'clandestine', + 'clanging', + 'clapping', + 'clashing', + 'classic', + 'classical', + 'classifiable', + 'classified', + 'classy', + 'clean', + 'cleanable', + 'clear', + 'cleared', + 'clearheaded', + 'clever', + 'climatic', + 'climbable', + 'clinging', + 'clingy', + 'clinical', + 'cliquish', + 'clogged', + 'cloistered', + 'close', + 'closeable', + 'closed', + 'close-minded', + 'cloudless', + 'cloudy', + 'clownish', + 'clueless', + 'clumsy', + 'cluttered', + 'coachable', + 'coarse', + 'cockamamie', + 'cocky', + 'codified', + 'coercive', + 'cognitive', + 'coherent', + 'cohesive', + 'coincidental', + 'cold', + 'coldhearted', + 'collaborative', + 'collapsed', + 'collapsing', + 'collectable', + 'collegial', + 'colloquial', + 'colonial', + 'colorful', + 'colorless', + 'colossal', + 'combative', + 'combined', + 'comfortable', + 'comforted', + 'comforting', + 'comical', + 'commanding', + 'commemorative', + 'commendable', + 'commercial', + 'committed', + 'common', + 'communal', + 'communicable', + 'communicative', + 'communist', + 'compact', + 'comparable', + 'comparative', + 'compassionate', + 'compelling', + 'competent', + 'competitive', + 'complacent', + 'complaining', + 'complete', + 'completed', + 'complex', + 'compliant', + 'complicated', + 'complimentary', + 'compound', + 'comprehensive', + 'compulsive', + 'compulsory', + 'computer', + 'computerized', + 'concealable', + 'concealed', + 'conceited', + 'conceivable', + 'concerned', + 'concerning', + 'concerted', + 'concise', + 'concurrent', + 'condemned', + 'condensed', + 'condescending', + 'conditional', + 'confident', + 'confidential', + 'confirmable', + 'confirmed', + 'conflicted', + 'conflicting', + 'conformable', + 'confounded', + 'confused', + 'confusing', + 'congenial', + 'congested', + 'congressional', + 'congruent', + 'congruous', + 'connectable', + 'connected', + 'connecting', + 'connective', + 'conscientious', + 'conscious', + 'consecutive', + 'consensual', + 'consenting', + 'conservative', + 'considerable', + 'considerate', + 'consistent', + 'consoling', + 'conspicuous', + 'conspiratorial', + 'constant', + 'constitutional', + 'constrictive', + 'constructive', + 'consumable', + 'consummate', + 'contagious', + 'containable', + 'contemplative', + 'contemporary', + 'contemptible', + 'contemptuous', + 'content', + 'contented', + 'contentious', + 'contextual', + 'continual', + 'continuing', + 'continuous', + 'contoured', + 'contractual', + 'contradicting', + 'contradictory', + 'contrarian', + 'contrary', + 'contributive', + 'contrite', + 'controllable', + 'controlling', + 'controversial', + 'convenient', + 'conventional', + 'conversational', + 'convinced', + 'convincing', + 'convoluted', + 'convulsive', + 'cooing', + 'cooked', + 'cool', + 'coolest', + 'cooperative', + 'coordinated', + 'copious', + 'coquettish', + 'cordial', + 'corner', + 'cornered', + 'corny', + 'corporate', + 'corpulent', + 'correct', + 'correctable', + 'corrective', + 'corresponding', + 'corrosive', + 'corrupt', + 'corrupting', + 'corruptive', + 'cosmetic', + 'cosmic', + 'costly', + 'cottony', + 'coughing', + 'courageous', + 'courteous', + 'covert', + 'coveted', + 'cowardly', + 'cowering', + 'coy', + 'cozy', + 'crabby', + 'cracked', + 'crackling', + 'crafty', + 'craggy', + 'crammed', + 'cramped', + 'cranky', + 'crashing', + 'crass', + 'craven', + 'crawling', + 'crazy', + 'creaking', + 'creaky', + 'creamy', + 'creative', + 'credible', + 'creeping', + 'creepy', + 'crestfallen', + 'criminal', + 'crippled', + 'crippling', + 'crisp', + 'crispy', + 'critical', + 'crooked', + 'cropped', + 'cross', + 'crossed', + 'crotchety', + 'crowded', + 'crucial', + 'crude', + 'cruel', + 'crumbling', + 'crumbly', + 'crumply', + 'crunchable', + 'crunching', + 'crunchy', + 'crushable', + 'crushed', + 'crusty', + 'crying', + 'cryptic', + 'crystalline', + 'crystallized', + 'cuddly', + 'culpable', + 'cultural', + 'cultured', + 'cumbersome', + 'cumulative', + 'cunning', + 'curable', + 'curative', + 'curious', + 'curly', + 'current', + 'cursed', + 'curt', + 'curved', + 'curvy', + 'customary', + 'cut', + 'cute', + 'cutting', + 'cylindrical', + 'cynical', + 'daffy', + 'daft', + 'daily', + 'dainty', + 'damaged', + 'damaging', + 'damp', + 'danceable', + 'dandy', + 'dangerous', + 'dapper', + 'daring', + 'dark', + 'darkened', + 'dashing', + 'daughterly', + 'daunting', + 'dawdling', + 'day', + 'dazed', + 'dazzling', + 'dead', + 'deadly', + 'deadpan', + 'deaf', + 'deafening', + 'dear', + 'debatable', + 'debonair', + 'decadent', + 'decayed', + 'decaying', + 'deceitful', + 'deceivable', + 'deceiving', + 'decent', + 'decentralized', + 'deceptive', + 'decimated', + 'decipherable', + 'decisive', + 'declining', + 'decorative', + 'decorous', + 'decreasing', + 'decrepit', + 'dedicated', + 'deep', + 'deepening', + 'deeply', + 'defeated', + 'defective', + 'defendable', + 'defenseless', + 'defensible', + 'defensive', + 'defiant', + 'deficient', + 'definable', + 'definitive', + 'deformed', + 'degenerative', + 'degraded', + 'dehydrated', + 'dejected', + 'delectable', + 'deliberate', + 'deliberative', + 'delicate', + 'delicious', + 'delighted', + 'delightful', + 'delinquent', + 'delirious', + 'deliverable', + 'deluded', + 'demanding', + 'demented', + 'democratic', + 'demonic', + 'demonstrative', + 'demure', + 'deniable', + 'dense', + 'dependable', + 'dependent', + 'deplorable', + 'deploring', + 'depraved', + 'depressed', + 'depressing', + 'depressive', + 'deprived', + 'deranged', + 'derivative', + 'derogative', + 'derogatory', + 'descriptive', + 'deserted', + 'designer', + 'desirable', + 'desirous', + 'desolate', + 'despairing', + 'desperate', + 'despicable', + 'despised', + 'despondent', + 'destroyed', + 'destructive', + 'detachable', + 'detached', + 'detailed', + 'detectable', + 'determined', + 'detestable', + 'detrimental', + 'devastated', + 'devastating', + 'devious', + 'devoted', + 'devout', + 'dexterous', + 'diabolical', + 'diagonal', + 'didactic', + 'different', + 'difficult', + 'diffuse', + 'digestive', + 'digital', + 'dignified', + 'digressive', + 'dilapidated', + 'diligent', + 'dim', + 'diminishing', + 'diminutive', + 'dingy', + 'diplomatic', + 'dire', + 'direct', + 'direful', + 'dirty', + 'disabled', + 'disadvantaged', + 'disadvantageous', + 'disaffected', + 'disagreeable', + 'disappearing', + 'disappointed', + 'disappointing', + 'disapproving', + 'disarming', + 'disastrous', + 'discarded', + 'discernable', + 'disciplined', + 'disconnected', + 'discontented', + 'discordant', + 'discouraged', + 'discouraging', + 'discourteous', + 'discredited', + 'discreet', + 'discriminating', + 'discriminatory', + 'discussable', + 'disdainful', + 'diseased', + 'disenchanted', + 'disgraceful', + 'disgruntled', + 'disgusted', + 'disgusting', + 'disheartened', + 'disheartening', + 'dishonest', + 'dishonorable', + 'disillusioned', + 'disinclined', + 'disingenuous', + 'disinterested', + 'disjointed', + 'dislikeable', + 'disliked', + 'disloyal', + 'dismal', + 'dismissive', + 'disobedient', + 'disorderly', + 'disorganized', + 'disparaging', + 'disparate', + 'dispassionate', + 'dispensable', + 'displaced', + 'displeased', + 'displeasing', + 'disposable', + 'disproportionate', + 'disproved', + 'disputable', + 'disputatious', + 'disputed', + 'disreputable', + 'disrespectful', + 'disruptive', + 'dissatisfied', + 'dissimilar', + 'dissolvable', + 'dissolving', + 'dissonant', + 'dissuasive', + 'distant', + 'distasteful', + 'distinct', + 'distinctive', + 'distinguished', + 'distracted', + 'distracting', + 'distraught', + 'distressed', + 'distressing', + 'distrustful', + 'disturbed', + 'disturbing', + 'divergent', + 'diverging', + 'diverse', + 'diversified', + 'divided', + 'divine', + 'divisive', + 'dizzy', + 'dizzying', + 'doable', + 'documentary', + 'dogged', + 'doggish', + 'dogmatic', + 'doleful', + 'dollish', + 'domed', + 'domestic', + 'dominant', + 'domineering', + 'dorsal', + 'doting', + 'double', + 'doubtful', + 'doubting', + 'dovish', + 'dowdy', + 'down', + 'down-and-out', + 'downhearted', + 'downloadable', + 'downtown', + 'downward', + 'dozing', + 'drab', + 'drained', + 'dramatic', + 'drastic', + 'dreaded', + 'dreadful', + 'dreaming', + 'dreamy', + 'dreary', + 'drenched', + 'dress', + 'dressy', + 'dried', + 'dripping', + 'drivable', + 'driven', + 'droll', + 'drooping', + 'droopy', + 'drowsy', + 'drunk', + 'dry', + 'dual', + 'dubious', + 'due', + 'dulcet', + 'dull', + 'duplicitous', + 'durable', + 'dusty', + 'dutiful', + 'dwarfish', + 'dwindling', + 'dynamic', + 'dysfunctional', + 'each', + 'eager', + 'early', + 'earnest', + 'ear-piercing', + 'ear-splitting', + 'earthshaking', + 'earthy', + 'east', + 'eastern', + 'easy', + 'eatable', + 'eccentric', + 'echoing', + 'ecological', + 'economic', + 'economical', + 'economy', + 'ecstatic', + 'edgy', + 'editable', + 'educated', + 'educational', + 'eerie', + 'effective', + 'effervescent', + 'efficacious', + 'efficient', + 'effortless', + 'effusive', + 'egalitarian', + 'egocentric', + 'egomaniacal', + 'egotistical', + 'eight', + 'eighth', + 'either', + 'elaborate', + 'elastic', + 'elated', + 'elderly', + 'electric', + 'electrical', + 'electrifying', + 'electronic', + 'elegant', + 'elementary', + 'elevated', + 'elfish', + 'eligible', + 'elite', + 'eloquent', + 'elusive', + 'emaciated', + 'embarrassed', + 'embarrassing', + 'embattled', + 'embittered', + 'emblematic', + 'emboldened', + 'embroiled', + 'emergency', + 'eminent', + 'emotional', + 'emotionless', + 'empirical', + 'empty', + 'enamored', + 'enchanted', + 'enchanting', + 'encouraged', + 'encouraging', + 'encrusted', + 'endangered', + 'endearing', + 'endemic', + 'endless', + 'endurable', + 'enduring', + 'energetic', + 'energizing', + 'enforceable', + 'engaging', + 'engrossing', + 'enhanced', + 'enigmatic', + 'enjoyable', + 'enlarged', + 'enlightened', + 'enormous', + 'enough', + 'enraged', + 'ensuing', + 'enterprising', + 'entertained', + 'entertaining', + 'enthralled', + 'enthused', + 'enthusiastic', + 'enticing', + 'entire', + 'entranced', + 'entrepreneurial', + 'enumerable', + 'enviable', + 'envious', + 'environmental', + 'episodic', + 'equable', + 'equal', + 'equidistant', + 'equitable', + 'equivalent', + 'erasable', + 'erect', + 'eroding', + 'errant', + 'erratic', + 'erroneous', + 'eruptive', + 'escalating', + 'esoteric', + 'essential', + 'established', + 'estimated', + 'estranged', + 'eternal', + 'ethereal', + 'ethical', + 'ethnic', + 'euphemistic', + 'euphoric', + 'evasive', + 'even', + 'evenhanded', + 'evening', + 'eventful', + 'eventual', + 'everlasting', + 'every', + 'evil', + 'evocative', + 'exacerbating', + 'exact', + 'exacting', + 'exaggerated', + 'exalted', + 'exasperated', + 'exasperating', + 'excellent', + 'exceptional', + 'excessive', + 'exchangeable', + 'excitable', + 'excited', + 'exciting', + 'exclusive', + 'excruciating', + 'excusable', + 'executable', + 'exemplary', + 'exhausted', + 'exhausting', + 'exhaustive', + 'exhilarated', + 'exhilarating', + 'existing', + 'exotic', + 'expandable', + 'expanded', + 'expanding', + 'expansive', + 'expectant', + 'expected', + 'expedient', + 'expeditious', + 'expendable', + 'expensive', + 'experimental', + 'expert', + 'expired', + 'expiring', + 'explainable', + 'explicit', + 'exploding', + 'exploitative', + 'exploited', + 'explosive', + 'exponential', + 'exposed', + 'express', + 'expressionistic', + 'expressionless', + 'expressive', + 'exquisite', + 'extemporaneous', + 'extendable', + 'extended', + 'extension', + 'extensive', + 'exterior', + 'external', + 'extra', + 'extra-large', + 'extraneous', + 'extraordinary', + 'extra-small', + 'extravagant', + 'extreme', + 'exuberant', + 'eye-popping', + 'fabled', + 'fabulous', + 'facetious', + 'facial', + 'factitious', + 'factual', + 'faded', + 'fading', + 'failed', + 'faint', + 'fainthearted', + 'fair', + 'faithful', + 'faithless', + 'fallacious', + 'false', + 'falsified', + 'faltering', + 'familiar', + 'famished', + 'famous', + 'fanatical', + 'fanciful', + 'fancy', + 'fantastic', + 'far', + 'faraway', + 'farcical', + 'far-flung', + 'farsighted', + 'fascinated', + 'fascinating', + 'fascistic', + 'fashionable', + 'fast', + 'fastest', + 'fastidious', + 'fast-moving', + 'fat', + 'fatal', + 'fateful', + 'fatherly', + 'fathomable', + 'fathomless', + 'fatigued', + 'faulty', + 'favorable', + 'favorite', + 'fawning', + 'feared', + 'fearful', + 'fearless', + 'fearsome', + 'feathered', + 'feathery', + 'feckless', + 'federal', + 'feeble', + 'feebleminded', + 'feeling', + 'feigned', + 'felonious', + 'female', + 'feminine', + 'fermented', + 'ferocious', + 'fertile', + 'fervent', + 'fervid', + 'festive', + 'fetching', + 'fetid', + 'feudal', + 'feverish', + 'few,', + 'fewer', + 'fictional', + 'fictitious', + 'fidgeting', + 'fidgety', + 'fiendish', + 'fierce', + 'fiery', + 'fifth', + 'filmy', + 'filtered', + 'filthy', + 'final', + 'financial', + 'fine', + 'finicky', + 'finite', + 'fireproof', + 'firm', + 'first', + 'fiscal', + 'fishy', + 'fit', + 'fitted', + 'fitting', + 'five', + 'fixable', + 'fixed', + 'flabby', + 'flagrant', + 'flaky', + 'flamboyant', + 'flaming', + 'flammable', + 'flashy', + 'flat', + 'flattened', + 'flattered', + 'flattering', + 'flavored', + 'flavorful', + 'flavorless', + 'flawed', + 'flawless', + 'fleeting', + 'flexible', + 'flickering', + 'flimsy', + 'flippant', + 'flirtatious', + 'floating', + 'flooded', + 'floppy', + 'floral', + 'flowering', + 'flowery', + 'fluent', + 'fluffy', + 'flushed', + 'fluttering', + 'flying', + 'foamy', + 'focused', + 'foggy', + 'folded', + 'following', + 'fond', + 'foolhardy', + 'foolish', + 'forbidding', + 'forceful', + 'foreboding', + 'foregoing', + 'foreign', + 'forensic', + 'foreseeable', + 'forged', + 'forgetful', + 'forgettable', + 'forgivable', + 'forgiving', + 'forgotten', + 'forked', + 'formal', + 'formative', + 'former', + 'formidable', + 'formless', + 'formulaic', + 'forthright', + 'fortuitous', + 'fortunate', + 'forward', + 'foul', + 'foul-smelling', + 'four', + 'fourth', + 'foxy', + 'fractional', + 'fractious', + 'fragile', + 'fragmented', + 'fragrant', + 'frail', + 'frank', + 'frantic', + 'fraternal', + 'fraudulent', + 'frayed', + 'freakish', + 'freaky', + 'freckled', + 'free', + 'freezing', + 'frequent', + 'fresh', + 'fretful', + 'fried', + 'friendly', + 'frightened', + 'frightening', + 'frightful', + 'frigid', + 'frilly', + 'frisky', + 'frivolous', + 'front', + 'frosty', + 'frothy', + 'frowning', + 'frozen', + 'frugal', + 'fruitful', + 'fruitless', + 'fruity', + 'frumpy', + 'frustrated', + 'frustrating', + 'fulfilled', + 'fulfilling', + 'full', + 'fully-grown', + 'fumbling', + 'fuming', + 'fun', + 'functional', + 'fundamental', + 'fun-loving', + 'funniest', + 'funny', + 'furious', + 'furry', + 'furthest', + 'furtive', + 'fussy', + 'futile', + 'future', + 'futuristic', + 'fuzzy', + 'gabby', + 'gainful', + 'gallant', + 'galling', + 'game', + 'gangly', + 'gaping', + 'garbled', + 'gargantuan', + 'garish', + 'garrulous', + 'gaseous', + 'gasping', + 'gaudy', + 'gaunt', + 'gauzy', + 'gawky', + 'general', + 'generative', + 'generic', + 'generous', + 'genial', + 'gentle', + 'genuine', + 'geographic', + 'geologic', + 'geometric', + 'geriatric', + 'ghastly', + 'ghostly', + 'ghoulish', + 'giant', + 'giddy', + 'gifted', + 'gigantic', + 'giggling', + 'gilded', + 'giving', + 'glad', + 'glamorous', + 'glaring', + 'glass', + 'glassy', + 'gleaming', + 'glib', + 'glistening', + 'glittering', + 'global', + 'globular', + 'gloomy', + 'glorious', + 'glossy', + 'glowing', + 'gluey', + 'glum', + 'gluttonous', + 'gnarly', + 'gold', + 'golden', + 'good', + 'good-looking', + 'good-natured', + 'gooey', + 'goofy', + 'gorgeous', + 'graceful', + 'gracious', + 'gradual', + 'grainy', + 'grand', + 'grandiose', + 'graphic', + 'grateful', + 'gratified', + 'gratifying', + 'grating', + 'gratis', + 'gratuitous', + 'grave', + 'gray', + 'greasy', + 'great', + 'greatest', + 'greedy', + 'green', + 'gregarious', + 'grey', + 'grieving', + 'grim', + 'grimacing', + 'grimy', + 'grinding', + 'grinning', + 'gripping', + 'gritty', + 'grizzled', + 'groaning', + 'groggy', + 'groomed', + 'groovy', + 'gross', + 'grotesque', + 'grouchy', + 'growling', + 'grown-up', + 'grubby', + 'grueling', + 'gruesome', + 'gruff', + 'grumbling', + 'grumpy', + 'guaranteed', + 'guarded', + 'guiltless', + 'guilt-ridden', + 'guilty', + 'gullible', + 'gurgling', + 'gushing', + 'gushy', + 'gusty', + 'gutsy', + 'habitable', + 'habitual', + 'haggard', + 'hairless', + 'hairy', + 'half', + 'halfhearted', + 'hallowed', + 'halting', + 'handsome', + 'handy', + 'hanging', + 'haphazard', + 'hapless', + 'happy', + 'hard', + 'hard-to-find', + 'hardworking', + 'hardy', + 'harebrained', + 'harmful', + 'harmless', + 'harmonic', + 'harmonious', + 'harried', + 'harsh', + 'hasty', + 'hated', + 'hateful', + 'haughty', + 'haunting', + 'hawkish', + 'hazardous', + 'hazy', + 'head', + 'heady', + 'healthy', + 'heartbreaking', + 'heartbroken', + 'heartless', + 'heartrending', + 'hearty', + 'heated', + 'heavenly', + 'heavy', + 'hectic', + 'hefty', + 'heinous', + 'helpful', + 'helpless', + 'her', + 'heroic', + 'hesitant', + 'hideous', + 'high', + 'highest', + 'highfalutin', + 'high-functioning', + 'high-maintenance', + 'high-pitched', + 'high-risk', + 'hilarious', + 'his', + 'hissing', + 'historical', + 'hoarse', + 'hoggish', + 'holiday', + 'holistic', + 'hollow', + 'home', + 'homeless', + 'homely', + 'homeopathic', + 'homey', + 'homogeneous', + 'honest', + 'honking', + 'honorable', + 'hopeful', + 'hopeless', + 'horizontal', + 'hormonal', + 'horned', + 'horrendous', + 'horrible', + 'horrid', + 'horrific', + 'horrified', + 'horrifying', + 'hospitable', + 'hostile', + 'hot', + 'hotpink', + 'hot-blooded', + 'hotheaded', + 'hot-shot', + 'hot-tempered', + 'hour-long', + 'house', + 'howling', + 'huffy', + 'huge', + 'huggable', + 'hulking', + 'human', + 'humanitarian', + 'humanlike', + 'humble', + 'humdrum', + 'humid', + 'humiliated', + 'humiliating', + 'humming', + 'humongous', + 'humorless', + 'humorous', + 'hungry', + 'hurried', + 'hurt', + 'hurtful', + 'hushed', + 'husky', + 'hydraulic', + 'hydrothermal', + 'hygienic', + 'hyper-active', + 'hyperbolic', + 'hypercritical', + 'hyperirritable', + 'hypersensitive', + 'hypertensive', + 'hypnotic', + 'hypnotizable', + 'hypothetical', + 'hysterical', + 'icky', + 'iconoclastic', + 'icy', + 'icy-cold', + 'ideal', + 'idealistic', + 'identical', + 'identifiable', + 'idiosyncratic', + 'idiotic', + 'idyllic', + 'ignorable', + 'ignorant', + 'ill', + 'illegal', + 'illegible', + 'illegitimate', + 'ill-equipped', + 'ill-fated', + 'ill-humored', + 'illicit', + 'ill-informed', + 'illiterate', + 'illogical', + 'illuminating', + 'illusive', + 'illustrious', + 'imaginable', + 'imaginary', + 'imaginative', + 'imitative', + 'immaculate', + 'immanent', + 'immature', + 'immeasurable', + 'immediate', + 'immense', + 'immensurable', + 'imminent', + 'immobile', + 'immodest', + 'immoral', + 'immortal', + 'immovable', + 'impartial', + 'impassable', + 'impassioned', + 'impatient', + 'impeccable', + 'impenetrable', + 'imperative', + 'imperceptible', + 'imperceptive', + 'imperfect', + 'imperial', + 'imperialistic', + 'impermeable', + 'impersonal', + 'impertinent', + 'impervious', + 'impetuous', + 'impish', + 'implausible', + 'implicit', + 'implosive', + 'impolite', + 'imponderable', + 'important', + 'imported', + 'imposing', + 'impossible', + 'impoverished', + 'impractical', + 'imprecise', + 'impressionable', + 'impressive', + 'improbable', + 'improper', + 'improvable', + 'improved', + 'improving', + 'imprudent', + 'impulsive', + 'impure', + 'inaccessible', + 'inaccurate', + 'inactive', + 'inadequate', + 'inadmissible', + 'inadvertent', + 'inadvisable', + 'inalienable', + 'inalterable', + 'inane', + 'inanimate', + 'inapplicable', + 'inappropriate', + 'inapt', + 'inarguable', + 'inarticulate', + 'inartistic', + 'inattentive', + 'inaudible', + 'inauspicious', + 'incalculable', + 'incandescent', + 'incapable', + 'incessant', + 'incidental', + 'inclusive', + 'incoherent', + 'incomparable', + 'incompatible', + 'incompetent', + 'incomplete', + 'incomprehensible', + 'inconceivable', + 'inconclusive', + 'incongruent', + 'incongruous', + 'inconsequential', + 'inconsiderable', + 'inconsiderate', + 'inconsistent', + 'inconsolable', + 'inconspicuous', + 'incontrovertible', + 'inconvenient', + 'incorrect', + 'incorrigible', + 'incorruptible', + 'increasing', + 'incredible', + 'incredulous', + 'incremental', + 'incurable', + 'indecent', + 'indecipherable', + 'indecisive', + 'indefensible', + 'indefinable', + 'indefinite', + 'indelible', + 'independent', + 'indescribable', + 'indestructible', + 'indeterminable', + 'indeterminate', + 'indicative', + 'indifferent', + 'indigenous', + 'indignant', + 'indirect', + 'indiscreet', + 'indiscriminate', + 'indispensable', + 'indisputable', + 'indistinct', + 'individual', + 'individualistic', + 'indivisible', + 'indomitable', + 'inductive', + 'indulgent', + 'industrial', + 'industrious', + 'ineffective', + 'ineffectual', + 'inefficient', + 'inelegant', + 'ineloquent', + 'inequitable', + 'inert', + 'inescapable', + 'inevitable', + 'inexact', + 'inexcusable', + 'inexhaustible', + 'inexpedient', + 'inexpensive', + 'inexplicable', + 'inexpressible', + 'inexpressive', + 'inextricable', + 'infallible', + 'infamous', + 'infantile', + 'infatuated', + 'infected', + 'infectious', + 'inferable', + 'inferior', + 'infernal', + 'infinite', + 'infinitesimal', + 'inflamed', + 'inflammable', + 'inflammatory', + 'inflatable', + 'inflated', + 'inflexible', + 'influential', + 'informal', + 'informative', + 'informed', + 'infrequent', + 'infuriated', + 'infuriating', + 'ingenious', + 'ingenuous', + 'inglorious', + 'ingratiating', + 'inhabitable', + 'inharmonious', + 'inherent', + 'inhibited', + 'inhospitable', + 'inhuman', + 'inhumane', + 'initial', + 'injudicious', + 'injured', + 'injurious', + 'innate', + 'inner', + 'innocent', + 'innocuous', + 'innovative', + 'innumerable', + 'inoffensive', + 'inoperable', + 'inoperative', + 'inopportune', + 'inordinate', + 'inorganic', + 'inquiring', + 'inquisitive', + 'insane', + 'insatiable', + 'inscrutable', + 'insecure', + 'insensible', + 'insensitive', + 'inseparable', + 'inside', + 'insidious', + 'insightful', + 'insignificant', + 'insincere', + 'insipid', + 'insistent', + 'insolent', + 'inspirational', + 'inspired', + 'inspiring', + 'instant', + 'instantaneous', + 'instinctive', + 'instinctual', + 'institutional', + 'instructive', + 'instrumental', + 'insubordinate', + 'insufferable', + 'insufficient', + 'insulted', + 'insulting', + 'insurable', + 'insurmountable', + 'intangible', + 'integral', + 'intellectual', + 'intelligent', + 'intelligible', + 'intended', + 'intense', + 'intensive', + 'intentional', + 'interactive', + 'interchangeable', + 'interdepartmental', + 'interdependent', + 'interested', + 'interesting', + 'interior', + 'intermediate', + 'intermittent', + 'internal', + 'international', + 'interpersonal', + 'interracial', + 'intestinal', + 'intimate', + 'intimidating', + 'intolerable', + 'intolerant', + 'intravenous', + 'intrepid', + 'intricate', + 'intrigued', + 'intriguing', + 'intrinsic', + 'introductory', + 'introspective', + 'introverted', + 'intrusive', + 'intuitive', + 'invalid', + 'invaluable', + 'invasive', + 'inventive', + 'invigorating', + 'invincible', + 'invisible', + 'invited', + 'inviting', + 'involuntary', + 'involved', + 'inward', + 'irascible', + 'irate', + 'iridescent', + 'irksome', + 'iron', + 'iron-fisted', + 'ironic', + 'irrational', + 'irreconcilable', + 'irrefutable', + 'irregular', + 'irrelative', + 'irrelevant', + 'irremovable', + 'irreparable', + 'irreplaceable', + 'irrepressible', + 'irresistible', + 'irresponsible', + 'irretrievably', + 'irreverent', + 'irreversible', + 'irrevocable', + 'irritable', + 'irritated', + 'irritating', + 'isolated', + 'itchy', + 'its', + 'itty-bitty', + 'jabbering', + 'jaded', + 'jagged', + 'jarring', + 'jaundiced', + 'jazzy', + 'jealous', + 'jeering', + 'jerky', + 'jiggling', + 'jittery', + 'jobless', + 'jocular', + 'joint', + 'jolly', + 'jovial', + 'joyful', + 'joyless', + 'joyous', + 'jubilant', + 'judgmental', + 'judicious', + 'juicy', + 'jumbled', + 'jumpy', + 'junior', + 'just', + 'justifiable', + 'juvenile', + 'kaput', + 'keen', + 'key', + 'kind', + 'kindhearted', + 'kindly', + 'kinesthetic', + 'kingly', + 'kitchen', + 'knavish', + 'knightly', + 'knobbed', + 'knobby', + 'knotty', + 'knowable', + 'knowing', + 'knowledgeable', + 'known', + 'labored', + 'laborious', + 'lackadaisical', + 'lacking', + 'lacy', + 'lame', + 'lamentable', + 'languid', + 'languishing', + 'lanky', + 'larcenous', + 'large', + 'larger', + 'largest', + 'lascivious', + 'last', + 'lasting', + 'late', + 'latent', + 'later', + 'lateral', + 'latest', + 'latter', + 'laudable', + 'laughable', + 'laughing', + 'lavish', + 'lawful', + 'lawless', + 'lax', + 'lazy', + 'lead', + 'leading', + 'lean', + 'learnable', + 'learned', + 'leased', + 'least', + 'leather', + 'leathery', + 'lecherous', + 'leering', + 'left', + 'left-handed', + 'legal', + 'legendary', + 'legible', + 'legislative', + 'legitimate', + 'lengthy', + 'lenient', + 'less', + 'lesser', + 'lesser-known', + 'less-qualified', + 'lethal', + 'lethargic', + 'level', + 'liable', + 'libelous', + 'liberal', + 'licensed', + 'life', + 'lifeless', + 'lifelike', + 'lifelong', + 'light', + 'light-blue', + 'lighthearted', + 'likable', + 'likeable', + 'likely', + 'like-minded', + 'lily-livered', + 'limber', + 'limited', + 'limitless', + 'limp', + 'limping', + 'linear', + 'lined', + 'lingering', + 'linguistic', + 'liquid', + 'listless', + 'literal', + 'literary', + 'literate', + 'lithe', + 'lithographic', + 'litigious', + 'little', + 'livable', + 'live', + 'lively', + 'livid', + 'living', + 'loathsome', + 'local', + 'locatable', + 'locked', + 'lofty', + 'logarithmic', + 'logical', + 'logistic', + 'lonely', + 'long', + 'longer', + 'longest', + 'longing', + 'long-term', + 'long-winded', + 'loose', + 'lopsided', + 'loquacious', + 'lordly', + 'lost', + 'loud', + 'lousy', + 'loutish', + 'lovable', + 'loveable', + 'lovely', + 'loving', + 'low', + 'low-calorie', + 'low-carb', + 'lower', + 'low-fat', + 'lowly', + 'low-maintenance', + 'low-ranking', + 'low-risk', + 'loyal', + 'lucent', + 'lucid', + 'lucky', + 'lucrative', + 'ludicrous', + 'lukewarm', + 'lulling', + 'luminescent', + 'luminous', + 'lumpy', + 'lurid', + 'luscious', + 'lush', + 'lustrous', + 'luxurious', + 'lying', + 'lyrical', + 'macabre', + 'Machiavellian', + 'macho', + 'mad', + 'maddening', + 'magenta', + 'magic', + 'magical', + 'magnanimous', + 'magnetic', + 'magnificent', + 'maiden', + 'main', + 'maintainable', + 'majestic', + 'major', + 'makeable', + 'makeshift', + 'maladjusted', + 'male', + 'malevolent', + 'malicious', + 'malignant', + 'malleable', + 'mammoth', + 'manageable', + 'managerial', + 'mandatory', + 'maneuverable', + 'mangy', + 'maniacal', + 'manic', + 'manicured', + 'manipulative', + 'man-made', + 'manual', + 'many,', + 'marbled', + 'marginal', + 'marked', + 'marketable', + 'married', + 'marvelous', + 'masked', + 'massive', + 'master', + 'masterful', + 'matchless', + 'material', + 'materialistic', + 'maternal', + 'mathematical', + 'matronly', + 'matted', + 'mature', + 'maximum', + 'meager', + 'mean', + 'meandering', + 'meaningful', + 'meaningless', + 'mean-spirited', + 'measly', + 'measurable', + 'meat-eating', + 'meaty', + 'mechanical', + 'medical', + 'medicinal', + 'meditative', + 'medium', + 'medium-rare', + 'meek', + 'melancholy', + 'mellow', + 'melodic', + 'melodious', + 'melodramatic', + 'melted', + 'memorable', + 'menacing', + 'menial', + 'mental', + 'merciful', + 'merciless', + 'mercurial', + 'mere', + 'merry', + 'messy', + 'metabolic', + 'metallic', + 'metaphoric', + 'meteoric', + 'meticulous', + 'microscopic', + 'microwaveable', + 'middle', + 'middle-class', + 'midweek', + 'mighty', + 'mild', + 'militant', + 'militaristic', + 'military', + 'milky', + 'mincing', + 'mind-bending', + 'mindful', + 'mindless', + 'mini', + 'miniature', + 'minimal', + 'minimum', + 'minor', + 'minute', + 'miraculous', + 'mirthful', + 'miscellaneous', + 'mischievous', + 'miscreant', + 'miserable', + 'miserly', + 'misguided', + 'misleading', + 'mission', + 'mistaken', + 'mistrustful', + 'mistrusting', + 'misty', + 'mixed', + 'mnemonic', + 'moaning', + 'mobile', + 'mocking', + 'moderate', + 'modern', + 'modest', + 'modified', + 'modular', + 'moist', + 'moldy', + 'momentary', + 'momentous', + 'monetary', + 'money-grubbing', + 'monopolistic', + 'monosyllabic', + 'monotone', + 'monotonous', + 'monstrous', + 'monumental', + 'moody', + 'moral', + 'moralistic', + 'morbid', + 'mordant', + 'more', + 'moronic', + 'morose', + 'mortal', + 'mortified', + 'most', + 'mother', + 'motherly', + 'motionless', + 'motivated', + 'motivating', + 'motivational', + 'motor', + 'mountain', + 'mountainous', + 'mournful', + 'mouthwatering', + 'movable', + 'moved', + 'moving', + 'much', + 'muddled', + 'muddy', + 'muffled', + 'muggy', + 'multicultural', + 'multifaceted', + 'multipurpose', + 'multitalented', + 'mumbled', + 'mundane', + 'municipal', + 'murky', + 'muscular', + 'mushy', + 'musical', + 'musky', + 'musty', + 'mutative', + 'mute', + 'muted', + 'mutinous', + 'muttering', + 'mutual', + 'my', + 'myopic', + 'mysterious', + 'mystic', + 'mystical', + 'mystified', + 'mystifying', + 'mythical', + 'naive', + 'nameless', + 'narcissistic', + 'narrow', + 'narrow-minded', + 'nasal', + 'nasty', + 'national', + 'native', + 'natural', + 'naughty', + 'nauseating', + 'nauseous', + 'nautical', + 'navigable', + 'navy-blue', + 'near', + 'nearby', + 'nearest', + 'nearsighted', + 'neat', + 'nebulous', + 'necessary', + 'needless', + 'needy', + 'nefarious', + 'negative', + 'neglected', + 'neglectful', + 'negligent', + 'negligible', + 'negotiable', + 'neighborly', + 'neither', + 'nerve-racking', + 'nervous', + 'neurological', + 'neurotic', + 'neutral', + 'new', + 'newest', + 'next', + 'next-door', + 'nice', + 'nifty', + 'nightmarish', + 'nimble', + 'nine', + 'ninth', + 'nippy', + 'no', + 'noble', + 'nocturnal', + 'noiseless', + 'noisy', + 'nominal', + 'nonabrasive', + 'nonaggressive', + 'nonchalant', + 'noncommittal', + 'noncompetitive', + 'nonconsecutive', + 'nondescript', + 'nondestructive', + 'nonexclusive', + 'nonnegotiable', + 'nonproductive', + 'nonrefundable', + 'nonrenewable', + 'nonresponsive', + 'nonrestrictive', + 'nonreturnable', + 'nonsensical', + 'nonspecific', + 'nonstop', + 'nontransferable', + 'nonverbal', + 'nonviolent', + 'normal', + 'north', + 'northeast', + 'northerly', + 'northwest', + 'nostalgic', + 'nosy', + 'notable', + 'noticeable', + 'notorious', + 'novel', + 'noxious', + 'null', + 'numb', + 'numberless', + 'numbing', + 'numerable', + 'numeric', + 'numerous', + 'nutritional', + 'nutritious', + 'nutty', + 'oafish', + 'obedient', + 'obeisant', + 'obese', + 'objectionable', + 'objective', + 'obligatory', + 'obliging', + 'oblique', + 'oblivious', + 'oblong', + 'obnoxious', + 'obscene', + 'obscure', + 'observable', + 'observant', + 'obsessive', + 'obsolete', + 'obstinate', + 'obstructive', + 'obtainable', + 'obtrusive', + 'obtuse', + 'obvious', + 'occasional', + 'occupational', + 'occupied', + 'oceanic', + 'odd', + 'odd-looking', + 'odiferous', + 'odious', + 'odorless', + 'odorous', + 'offbeat', + 'offensive', + 'offhanded', + 'official', + 'officious', + 'oily', + 'OK', + 'okay', + 'old', + 'older', + 'oldest', + 'old-fashioned', + 'ominous', + 'omniscient', + 'omnivorous', + 'one', + 'one-hour', + 'onerous', + 'one-sided', + 'only', + 'opaque', + 'open', + 'opened', + 'openhanded', + 'openhearted', + 'opening', + 'open-minded', + 'operable', + 'operatic', + 'operational', + 'operative', + 'opinionated', + 'opportune', + 'opportunistic', + 'opposable', + 'opposed', + 'opposing', + 'opposite', + 'oppressive', + 'optimal', + 'optimistic', + 'optional', + 'opulent', + 'oral', + 'orange', + 'ordinary', + 'organic', + 'organizational', + 'original', + 'ornamental', + 'ornate', + 'ornery', + 'orphaned', + 'orthopedic', + 'ossified', + 'ostentatious', + 'other', + 'otherwise', + 'our', + 'outer', + 'outermost', + 'outgoing', + 'outlandish', + 'outraged', + 'outrageous', + 'outside', + 'outspoken', + 'outstanding', + 'outward', + 'oval', + 'overactive', + 'overaggressive', + 'overall', + 'overambitious', + 'overassertive', + 'overbearing', + 'overcast', + 'overcautious', + 'overconfident', + 'overcritical', + 'overcrowded', + 'overemotional', + 'overenthusiastic', + 'overjoyed', + 'overoptimistic', + 'overpowering', + 'overpriced', + 'overprotective', + 'overqualified', + 'overrated', + 'oversensitive', + 'oversized', + 'overt', + 'overwhelmed', + 'overwhelming', + 'overworked', + 'overwrought', + 'overzealous', + 'own', + 'oxymoronic', + 'padded', + 'painful', + 'painless', + 'painstaking', + 'palatable', + 'palatial', + 'pale', + 'pallid', + 'palpable', + 'paltry', + 'pampered', + 'panicky', + 'panoramic', + 'paradoxical', + 'parallel', + 'paranormal', + 'parasitic', + 'parched', + 'pardonable', + 'parental', + 'parenthetic', + 'parking', + 'parsimonious', + 'partial', + 'particular', + 'partisan', + 'part-time', + 'party', + 'passing', + 'passionate', + 'passive', + 'past', + 'pastoral', + 'patched', + 'patchy', + 'patented', + 'paternal', + 'paternalistic', + 'pathetic', + 'pathological', + 'patient', + 'patriotic', + 'patronizing', + 'patterned', + 'payable', + 'peaceable', + 'peaceful', + 'peculiar', + 'pedantic', + 'pedestrian', + 'peerless', + 'peeved', + 'peevish', + 'penetrable', + 'penetrating', + 'pensive', + 'peppery', + 'perceivable', + 'perceptible', + 'perceptive', + 'perceptual', + 'peremptory', + 'perennial', + 'perfect', + 'perfumed', + 'perilous', + 'period', + 'periodic', + 'peripheral', + 'perishable', + 'perky', + 'permanent', + 'permeable', + 'permissible', + 'permissive', + 'pernicious', + 'perpendicular', + 'perpetual', + 'perplexed', + 'perplexing', + 'persevering', + 'persistent', + 'personable', + 'personal', + 'persuasive', + 'pert', + 'pertinent', + 'perturbed', + 'perturbing', + 'pervasive', + 'perverse', + 'pessimistic', + 'petite', + 'pettish', + 'petty', + 'petulant', + 'pharmaceutical', + 'phenomenal', + 'philanthropic', + 'philosophical', + 'phobic', + 'phonemic', + 'phonetic', + 'phosphorescent', + 'photographic', + 'physical', + 'physiological', + 'picturesque', + 'piercing', + 'pigheaded', + 'pink', + 'pious', + 'piquant', + 'pitch-dark', + 'pitch-perfect', + 'piteous', + 'pithy', + 'pitiful', + 'pitiless', + 'pivotal', + 'placid', + 'plaid', + 'plain', + 'plane', + 'planned', + 'plastic', + 'platonic', + 'plausible', + 'playful', + 'pleading', + 'pleasant', + 'pleased', + 'pleasing', + 'pleasurable', + 'plentiful', + 'pliable', + 'plodding', + 'plopping', + 'plucky', + 'plump', + 'pluralistic', + 'plus', + 'plush', + 'pneumatic', + 'poetic', + 'poignant', + 'pointless', + 'poised', + 'poisonous', + 'polished', + 'polite', + 'political', + 'polka-dotted', + 'polluted', + 'polyunsaturated', + 'pompous', + 'ponderous', + 'poor', + 'poorer', + 'poorest', + 'popping', + 'popular', + 'populous', + 'porous', + 'portable', + 'portly', + 'positive', + 'possessive', + 'possible', + 'posthoc', + 'posthumous', + 'postoperative', + 'potable', + 'potent', + 'potential', + 'powdery', + 'powerful', + 'powerless', + 'practical', + 'pragmatic', + 'praiseworthy', + 'precarious', + 'precious', + 'precipitous', + 'precise', + 'precocious', + 'preconceived', + 'predicative', + 'predictable', + 'predisposed', + 'predominant', + 'preeminent', + 'preemptive', + 'prefabricated', + 'preferable', + 'preferential', + 'pregnant', + 'prehistoric', + 'prejudiced', + 'prejudicial', + 'preliminary', + 'premature', + 'premeditated', + 'premium', + 'prenatal', + 'preoccupied', + 'preoperative', + 'preparative', + 'prepared', + 'preposterous', + 'prescriptive', + 'present', + 'presentable', + 'presidential', + 'pressing', + 'pressurized', + 'prestigious', + 'presumable', + 'presumptive', + 'presumptuous', + 'pretend', + 'pretentious', + 'pretty', + 'prevalent', + 'preventable', + 'preventative', + 'preventive', + 'previous', + 'priceless', + 'pricey', + 'prickly', + 'prim', + 'primary', + 'primitive', + 'primordial', + 'princely', + 'principal', + 'principled', + 'prior', + 'prissy', + 'pristine', + 'private', + 'prize', + 'prized', + 'proactive', + 'probabilistic', + 'probable', + 'problematic', + 'procedural', + 'prodigious', + 'productive', + 'profane', + 'professed', + 'professional', + 'professorial', + 'proficient', + 'profitable', + 'profound', + 'profuse', + 'programmable', + 'progressive', + 'prohibitive', + 'prolific', + 'prominent', + 'promised', + 'promising', + 'prompt', + 'pronounceable', + 'pronounced', + 'proof', + 'proper', + 'prophetic', + 'proportional', + 'proportionate', + 'proportioned', + 'prospective', + 'prosperous', + 'protective', + 'prototypical', + 'proud', + 'proverbial', + 'provisional', + 'provocative', + 'provoking', + 'proximal', + 'proximate', + 'prudent', + 'prudential', + 'prying', + 'psychedelic', + 'psychiatric', + 'psychological', + 'psychosomatic', + 'psychotic', + 'public', + 'puckish', + 'puffy', + 'pugnacious', + 'pumped', + 'punctual', + 'pungent', + 'punishable', + 'punitive', + 'puny', + 'pure', + 'purified', + 'puritanical', + 'purple', + 'purported', + 'purposeful', + 'purposeless', + 'purring', + 'pushy', + 'pusillanimous', + 'putrid', + 'puzzled', + 'puzzling', + 'pyrotechnic', + 'quackish', + 'quacky', + 'quaint', + 'qualified', + 'qualitative', + 'quality', + 'quantifiable', + 'quantitative', + 'quarrelsome', + 'queasy', + 'queenly', + 'querulous', + 'questionable', + 'quick', + 'quick-acting', + 'quick-drying', + 'quickest', + 'quick-minded', + 'quick-paced', + 'quick-tempered', + 'quick-thinking', + 'quick-witted', + 'quiet', + 'quintessential', + 'quirky', + 'quivering', + 'quizzical', + 'quotable', + 'rabid', + 'racial', + 'racist', + 'radiant', + 'radical', + 'radioactive', + 'ragged', + 'raging', + 'rainbowcolored', + 'rainy', + 'rakish', + 'rambling', + 'rambunctious', + 'rampageous', + 'rampant', + 'rancid', + 'rancorous', + 'random', + 'rank', + 'rapid', + 'rapid-fire', + 'rapturous', + 'rare', + 'rascally', + 'rash', + 'rasping', + 'raspy', + 'rational', + 'ratty', + 'ravenous', + 'raving', + 'ravishing', + 'raw', + 'razor-edged', + 'reactive', + 'ready', + 'real', + 'realistic', + 'reasonable', + 'reassured', + 'reassuring', + 'rebel', + 'rebellious', + 'receding', + 'recent', + 'receptive', + 'recessive', + 'rechargeable', + 'reciprocal', + 'reckless', + 'reclusive', + 'recognizable', + 'recognized', + 'rectangular', + 'rectifiable', + 'recurrent', + 'recyclable', + 'red', + 'red-blooded', + 'reddish', + 'redeemable', + 'redolent', + 'redundant', + 'referential', + 'refillable', + 'reflective', + 'refractive', + 'refreshing', + 'refundable', + 'refurbished', + 'refutable', + 'regal', + 'regional', + 'regretful', + 'regrettable', + 'regular', + 'reigning', + 'relatable', + 'relative', + 'relaxed', + 'relaxing', + 'relentless', + 'relevant', + 'reliable', + 'relieved', + 'religious', + 'reluctant', + 'remaining', + 'remarkable', + 'remedial', + 'reminiscent', + 'remorseful', + 'remorseless', + 'remote', + 'removable', + 'renegotiable', + 'renewable', + 'rented', + 'repairable', + 'repaired', + 'repeatable', + 'repeated', + 'repentant', + 'repetitious', + 'repetitive', + 'replaceable', + 'replicable', + 'reported', + 'reprehensible', + 'representative', + 'repressive', + 'reproachful', + 'reproductive', + 'republican', + 'repugnant', + 'repulsive', + 'reputable', + 'reputed', + 'rescued', + 'resealable', + 'resentful', + 'reserved', + 'resident', + 'residential', + 'residual', + 'resilient', + 'resolute', + 'resolvable', + 'resonant', + 'resounding', + 'resourceful', + 'respectable', + 'respectful', + 'respective', + 'responsible', + 'responsive', + 'rested', + 'restful', + 'restless', + 'restored', + 'restrained', + 'restrictive', + 'retired', + 'retroactive', + 'retrogressive', + 'retrospective', + 'reusable', + 'revamped', + 'revealing', + 'revengeful', + 'reverent', + 'reverential', + 'reverse', + 'reversible', + 'reviewable', + 'reviled', + 'revisable', + 'revised', + 'revocable', + 'revolting', + 'revolutionary', + 'rewarding', + 'rhetorical', + 'rhythmic', + 'rich', + 'richer', + 'richest', + 'ridiculing', + 'ridiculous', + 'right', + 'righteous', + 'rightful', + 'right-handed', + 'rigid', + 'rigorous', + 'ringing', + 'riotous', + 'ripe', + 'rippling', + 'risky', + 'ritualistic', + 'ritzy', + 'riveting', + 'roaring', + 'roasted', + 'robotic', + 'robust', + 'rocketing', + 'roguish', + 'romantic', + 'roomy', + 'rosy', + 'rotating', + 'rotten', + 'rotting', + 'rotund', + 'rough', + 'round', + 'roundtable', + 'rousing', + 'routine', + 'rowdy', + 'royal', + 'ruddy', + 'rude', + 'rudimentary', + 'rueful', + 'rugged', + 'ruined', + 'ruinous', + 'rumbling', + 'rumpled', + 'ruptured', + 'rural', + 'rusted', + 'rustic', + 'rustling', + 'rusty', + 'ruthless', + 'rutted', + 'saccharin', + 'sacred', + 'sacrificial', + 'sacrilegious', + 'sad', + 'saddened', + 'safe', + 'saintly', + 'salacious', + 'salient', + 'salt', + 'salted', + 'salty', + 'salvageable', + 'salvaged', + 'same', + 'sanctimonious', + 'sandy', + 'sane', + 'sanguine', + 'sanitary', + 'sappy', + 'sarcastic', + 'sardonic', + 'sassy', + 'satin', + 'satiny', + 'satiric', + 'satirical', + 'satisfactory', + 'satisfied', + 'satisfying', + 'saucy', + 'savage', + 'savory', + 'savvy', + 'scalding', + 'scaly', + 'scandalous', + 'scant', + 'scanty', + 'scarce', + 'scared', + 'scarred', + 'scary', + 'scathing', + 'scattered', + 'scenic', + 'scented', + 'scheduled', + 'schematic', + 'scholarly', + 'scholastic', + 'scientific', + 'scintillating', + 'scorching', + 'scornful', + 'scrabbled', + 'scraggly', + 'scrappy', + 'scratched', + 'scratchy', + 'scrawny', + 'screaming', + 'screeching', + 'scribbled', + 'scriptural', + 'scruffy', + 'scrumptious', + 'scrupulous', + 'sculpted', + 'sculptural', + 'scummy', + 'sea', + 'sealed', + 'seamless', + 'searching', + 'searing', + 'seasick', + 'seasonable', + 'seasonal', + 'secluded', + 'second', + 'secondary', + 'second-hand', + 'secret', + 'secretive', + 'secular', + 'secure', + 'secured', + 'sedate', + 'seditious', + 'seductive', + 'seedy', + 'seeming', + 'seemly', + 'seething', + 'seismic', + 'select', + 'selected', + 'selective', + 'self-absorbed', + 'self-aggrandizing', + 'self-assured', + 'self-centered', + 'self-confident', + 'self-directed', + 'self-disciplined', + 'self-effacing', + 'self-indulgent', + 'self-interested', + 'selfish', + 'selfless', + 'self-reliant', + 'self-respect', + 'self-satisfied', + 'sellable', + 'semiconscious', + 'semiofficial', + 'semiprecious', + 'semiprofessional', + 'senior', + 'sensational', + 'senseless', + 'sensible', + 'sensitive', + 'sensual', + 'sensuous', + 'sentimental', + 'separate', + 'sequential', + 'serendipitous', + 'serene', + 'serial', + 'serious', + 'serrated', + 'serviceable', + 'seven', + 'seventh', + 'several', + 'severe', + 'shabbiest', + 'shabby', + 'shaded', + 'shadowed', + 'shadowy', + 'shady', + 'shaggy', + 'shaky', + 'shallow', + 'shamefaced', + 'shameful', + 'shameless', + 'shapeless', + 'shapely', + 'sharp', + 'sharpened', + 'shattered', + 'shattering', + 'sheepish', + 'sheer', + 'sheltered', + 'shifty', + 'shimmering', + 'shining', + 'shiny', + 'shivering', + 'shivery', + 'shocked', + 'shocking', + 'shoddy', + 'short', + 'short-lived', + 'shortsighted', + 'short-tempered', + 'short-term', + 'showy', + 'shrewd', + 'shrieking', + 'shrill', + 'shut', + 'shy', + 'sick', + 'sickened', + 'sickening', + 'sickly', + 'side-splitting', + 'signed', + 'significant', + 'silent', + 'silky', + 'silly', + 'silver', + 'silver-tongued', + 'simian', + 'similar', + 'simple', + 'simpleminded', + 'simplified', + 'simplistic', + 'simultaneous', + 'sincere', + 'sinful', + 'single', + 'single-minded', + 'singular', + 'sinister', + 'sinuous', + 'sisterly', + 'six', + 'sixth', + 'sizable', + 'sizzling', + 'skeptical', + 'sketchy', + 'skilled', + 'skillful', + 'skimpy', + 'skin-deep', + 'skinny', + 'skittish', + 'sky-blue', + 'slanderous', + 'slanted', + 'slanting', + 'sleek', + 'sleeping', + 'sleepless', + 'sleepy', + 'slender', + 'slick', + 'slight', + 'slim', + 'slimy', + 'slippery', + 'sloped', + 'sloping', + 'sloppy', + 'slothful', + 'slow', + 'slow-moving', + 'sluggish', + 'slushy', + 'sly', + 'small', + 'smaller', + 'smallest', + 'small-minded', + 'small-scale', + 'small-time', + 'small-town', + 'smarmy', + 'smart', + 'smarter', + 'smartest', + 'smashing', + 'smeared', + 'smelly', + 'smiling', + 'smoggy', + 'smoked', + 'smoky', + 'smooth', + 'smothering', + 'smudged', + 'smug', + 'snapping', + 'snappish', + 'snappy', + 'snarling', + 'sneaky', + 'snide', + 'snippy', + 'snobbish', + 'snoopy', + 'snooty', + 'snoring', + 'snotty', + 'snow-white', + 'snug', + 'snuggly', + 'soaked', + 'soaking', + 'soakingwet', + 'soaring', + 'sober', + 'sociable', + 'social', + 'socialist', + 'sociological', + 'soft', + 'softhearted', + 'soggy', + 'solar', + 'soldierly', + 'sole', + 'solemn', + 'solicitous', + 'solid', + 'solitary', + 'somatic', + 'somber', + 'some,', + 'sonic', + 'sonly', + 'soothed', + 'soothing', + 'sophisticated', + 'sordid', + 'sore', + 'sorrowful', + 'sorry', + 'soulful', + 'soulless', + 'soundless', + 'sour', + 'south', + 'southeasterly', + 'southern', + 'southwestern', + 'spacious', + 'spare', + 'sparing', + 'sparkling', + 'sparkly', + 'sparse', + 'spasmodic', + 'spastic', + 'spatial', + 'spattered', + 'special', + 'specialist', + 'specialized', + 'specific', + 'speckled', + 'spectacular', + 'spectral', + 'speculative', + 'speechless', + 'speedy', + 'spellbinding', + 'spendthrift', + 'spherical', + 'spicy', + 'spiffy', + 'spiky', + 'spinal', + 'spineless', + 'spiral', + 'spiraled', + 'spirited', + 'spiritless', + 'spiritual', + 'spiteful', + 'splashing', + 'splashy', + 'splattered', + 'splendid', + 'splintered', + 'spoiled', + 'spoken', + 'spongy', + 'spontaneous', + 'spooky', + 'sporadic', + 'sporting', + 'sportsmanly', + 'spotless', + 'spotted', + 'spotty', + 'springy', + 'sprite', + 'spry', + 'spurious', + 'squalid', + 'squandered', + 'square', + 'squashed', + 'squashy', + 'squatting', + 'squawking', + 'squealing', + 'squeamish', + 'squeezable', + 'squiggly', + 'squirming', + 'squirrelly', + 'stable', + 'stackable', + 'stacked', + 'staggering', + 'stagnant', + 'stained', + 'stale', + 'stanch', + 'standard', + 'standing', + 'standoffish', + 'starched', + 'star-crossed', + 'stark', + 'startled', + 'startling', + 'starving', + 'stately', + 'static', + 'statistical', + 'statuesque', + 'status', + 'statutory', + 'staunch', + 'steadfast', + 'steady', + 'stealth', + 'steaming', + 'steamy', + 'steel', + 'steely', + 'steep', + 'stereophonic', + 'stereotyped', + 'stereotypical', + 'sterile', + 'stern', + 'sticky', + 'stiff', + 'stifled', + 'stifling', + 'stigmatic', + 'still', + 'stilled', + 'stilted', + 'stimulating', + 'stinging', + 'stingy', + 'stinking', + 'stinky', + 'stirring', + 'stock', + 'stodgy', + 'stoic', + 'stony', + 'stormy', + 'stout', + 'straggly', + 'straight', + 'straightforward', + 'stranded', + 'strange', + 'strategic', + 'streaked', + 'street', + 'strenuous', + 'stressful', + 'stretchy', + 'strict', + 'strident', + 'striking', + 'stringent', + 'striped', + 'strong', + 'stronger', + 'strongest', + 'structural', + 'stubborn', + 'stubby', + 'stuck-up', + 'studied', + 'studious', + 'stuffed', + 'stuffy', + 'stumbling', + 'stunned', + 'stunning', + 'stupendous', + 'sturdy', + 'stuttering', + 'stylish', + 'stylistic', + 'suave', + 'subconscious', + 'subdued', + 'subject', + 'subjective', + 'sublime', + 'subliminal', + 'submissive', + 'subordinate', + 'subsequent', + 'subservient', + 'substantial', + 'substantiated', + 'substitute', + 'subterranean', + 'subtitled', + 'subtle', + 'subversive', + 'successful', + 'successive', + 'succinct', + 'succulent', + 'such', + 'sudden', + 'suffering', + 'sufficient', + 'sugary', + 'suggestive', + 'suitable', + 'sulky', + 'sullen', + 'sumptuous', + 'sunny', + 'super', + 'superabundant', + 'superb', + 'supercilious', + 'superficial', + 'superhuman', + 'superior', + 'superlative', + 'supernatural', + 'supersensitive', + 'supersonic', + 'superstitious', + 'supple', + 'supportive', + 'supposed', + 'suppressive', + 'supreme', + 'sure', + 'sure-footed', + 'surgical', + 'surly', + 'surmountable', + 'surprised', + 'surprising', + 'surrealistic', + 'survivable', + 'susceptible', + 'suspected', + 'suspicious', + 'sustainable', + 'swaggering', + 'swanky', + 'swaying', + 'sweaty', + 'sweeping', + 'sweet', + 'sweltering', + 'swift', + 'swimming', + 'swinish', + 'swishing', + 'swollen', + 'swooping', + 'syllabic', + 'syllogistic', + 'symbiotic', + 'symbolic', + 'symmetrical', + 'sympathetic', + 'symptomatic', + 'synergistic', + 'synonymous', + 'syntactic', + 'synthetic', + 'systematic', + 'taboo', + 'tacit', + 'tacky', + 'tactful', + 'tactical', + 'tactless', + 'tactual', + 'tainted', + 'take-charge', + 'talented', + 'talkative', + 'tall', + 'taller', + 'tallest', + 'tame', + 'tamed', + 'tan', + 'tangential', + 'tangible', + 'tangled', + 'tangy', + 'tanned', + 'tantalizing', + 'tapered', + 'tardy', + 'targeted', + 'tarnished', + 'tart', + 'tasteful', + 'tasteless', + 'tasty', + 'tattered', + 'taunting', + 'taut', + 'taxing', + 'teachable', + 'tearful', + 'tearing', + 'teasing', + 'technical', + 'technological', + 'tectonic', + 'tedious', + 'teenage', + 'teensy', + 'teeny', + 'teeny-tiny', + 'telegraphic', + 'telekinetic', + 'telepathic', + 'telephonic', + 'telescopic', + 'telling', + 'temperamental', + 'temperate', + 'tempestuous', + 'temporary', + 'tempted', + 'tempting', + 'ten', + 'tenable', + 'tenacious', + 'tender', + 'tenderhearted', + 'ten-minute', + 'tense', + 'tentative', + 'tenth', + 'tenuous', + 'tepid', + 'terminal', + 'terrestrial', + 'terrible', + 'terrific', + 'terrified', + 'terrifying', + 'territorial', + 'terse', + 'tested', + 'testy', + 'tetchy', + 'textual', + 'textural', + 'thankful', + 'thankless', + 'that', + 'the', + 'theatrical', + 'their', + 'thematic', + 'theological', + 'theoretical', + 'therapeutic', + 'thermal', + 'these', + 'thick', + 'thievish', + 'thin', + 'thinkable', + 'third', + 'thirsty', + 'this', + 'thorny', + 'thorough', + 'those', + 'thoughtful', + 'thoughtless', + 'thrashed', + 'threatened', + 'threatening', + 'three', + 'thriftless', + 'thrifty', + 'thrilled', + 'thrilling', + 'throbbing', + 'thumping', + 'thundering', + 'thunderous', + 'ticking', + 'tickling', + 'ticklish', + 'tidal', + 'tidy', + 'tight', + 'tightfisted', + 'time', + 'timeless', + 'timely', + 'timid', + 'timorous', + 'tiny', + 'tipsy', + 'tired', + 'tireless', + 'tiresome', + 'tiring', + 'tolerable', + 'tolerant', + 'tonal', + 'tone-deaf', + 'toneless', + 'toothsome', + 'toothy', + 'top', + 'topical', + 'topographical', + 'tormented', + 'torpid', + 'torrential', + 'torrid', + 'torturous', + 'total', + 'touched', + 'touching', + 'touchy', + 'tough', + 'towering', + 'toxic', + 'traditional', + 'tragic', + 'trainable', + 'trained', + 'training', + 'traitorous', + 'tranquil', + 'transcendent', + 'transcendental', + 'transformational', + 'transformative', + 'transformed', + 'transient', + 'transitional', + 'transitory', + 'translucent', + 'transparent', + 'transplanted', + 'trapped', + 'trashed', + 'trashy', + 'traumatic', + 'treacherous', + 'treasonable', + 'treasonous', + 'treasured', + 'treatable', + 'tremendous', + 'tremulous', + 'trenchant', + 'trendy', + 'triangular', + 'tribal', + 'trick', + 'tricky', + 'trim', + 'tripping', + 'trite', + 'triumphant', + 'trivial', + 'tropical', + 'troubled', + 'troublesome', + 'troubling', + 'truculent', + 'true', + 'trusted', + 'trustful', + 'trusting', + 'trustworthy', + 'trusty', + 'truthful', + 'trying', + 'tumultuous', + 'tuneful', + 'tuneless', + 'turbulent', + 'twinkling', + 'twinkly', + 'twisted', + 'twitchy', + 'two', + 'typical', + 'tyrannical', + 'tyrannous', + 'ubiquitous', + 'ugly', + 'ultimate', + 'ultraconservative', + 'ultrasensitive', + 'ultrasonic', + 'ultraviolet', + 'unabashed', + 'unabated', + 'unable', + 'unacceptable', + 'unaccompanied', + 'unaccountable', + 'unaccustomed', + 'unacknowledged', + 'unadorned', + 'unadulterated', + 'unadventurous', + 'unadvised', + 'unaffected', + 'unaffordable', + 'unafraid', + 'unaggressive', + 'unaided', + 'unalienable', + 'unalterable', + 'unaltered', + 'unambiguous', + 'unanimous', + 'unannounced', + 'unanswerable', + 'unanticipated', + 'unapologetic', + 'unappealing', + 'unappetizing', + 'unappreciative', + 'unapproachable', + 'unashamed', + 'unassailable', + 'unassertive', + 'unassisted', + 'unattached', + 'unattainable', + 'unattractive', + 'unauthorized', + 'unavailable', + 'unavailing', + 'unavoidable', + 'unbalanced', + 'unbearable', + 'unbeatable', + 'unbeaten', + 'unbecoming', + 'unbelievable', + 'unbelieving', + 'unbendable', + 'unbending', + 'unbiased', + 'unblemished', + 'unblinking', + 'unblushing', + 'unbounded', + 'unbreakable', + 'unbridled', + 'uncanny', + 'uncaring', + 'unceasing', + 'unceremonious', + 'uncertain', + 'unchangeable', + 'unchanging', + 'uncharacteristic', + 'uncharitable', + 'uncharted', + 'uncivil', + 'uncivilized', + 'unclassified', + 'unclean', + 'uncluttered', + 'uncomely', + 'uncomfortable', + 'uncommitted', + 'uncommon', + 'uncommunicative', + 'uncomplaining', + 'uncomprehending', + 'uncompromising', + 'unconcerned', + 'unconditional', + 'unconfirmed', + 'unconquerable', + 'unconscionable', + 'unconscious', + 'unconstitutional', + 'unconstrained', + 'unconstructive', + 'uncontainable', + 'uncontrollable', + 'unconventional', + 'unconvinced', + 'unconvincing', + 'uncooked', + 'uncooperative', + 'uncoordinated', + 'uncouth', + 'uncovered', + 'uncreative', + 'uncritical', + 'undamaged', + 'undated', + 'undaunted', + 'undeclared', + 'undefeated', + 'undefined', + 'undemocratic', + 'undeniable', + 'undependable', + 'underdeveloped', + 'underfunded', + 'underhanded', + 'underprivileged', + 'understandable', + 'understanding', + 'understated', + 'understood', + 'undeserved', + 'undesirable', + 'undetected', + 'undeterred', + 'undeveloped', + 'undeviating', + 'undifferentiated', + 'undignified', + 'undiminished', + 'undiplomatic', + 'undisciplined', + 'undiscovered', + 'undisguised', + 'undisputed', + 'undistinguished', + 'undivided', + 'undoubted', + 'unearthly', + 'uneasy', + 'uneducated', + 'unemotional', + 'unemployed', + 'unencumbered', + 'unending', + 'unendurable', + 'unenforceable', + 'unenthusiastic', + 'unenviable', + 'unequal', + 'unequaled', + 'unequivocal', + 'unerring', + 'uneven', + 'uneventful', + 'unexceptional', + 'unexcited', + 'unexpected', + 'unexplainable', + 'unexplored', + 'unexpressive', + 'unfailing', + 'unfair', + 'unfaithful', + 'unfaltering', + 'unfamiliar', + 'unfashionable', + 'unfathomable', + 'unfavorable', + 'unfeeling', + 'unfettered', + 'unfilled', + 'unflagging', + 'unflappable', + 'unflattering', + 'unflinching', + 'unfocused', + 'unforeseeable', + 'unforgettable', + 'unforgivable', + 'unforgiving', + 'unfortunate', + 'unfriendly', + 'unfulfilled', + 'ungallant', + 'ungenerous', + 'ungentlemanly', + 'unglamorous', + 'ungraceful', + 'ungracious', + 'ungrateful', + 'unguarded', + 'unhandsome', + 'unhappy', + 'unharmed', + 'unhealthy', + 'unheated', + 'unheeded', + 'unhelpful', + 'unhesitating', + 'unhurried', + 'uniform', + 'unilateral', + 'unimaginable', + 'unimaginative', + 'unimpeachable', + 'unimpeded', + 'unimpressive', + 'unincorporated', + 'uninformed', + 'uninhabitable', + 'uninhibited', + 'uninitiated', + 'uninjured', + 'uninspired', + 'uninsurable', + 'unintelligent', + 'unintelligible', + 'unintended', + 'unintentional', + 'uninterested', + 'uninterrupted', + 'uninvited', + 'unique', + 'united', + 'universal', + 'unjust', + 'unjustifiable', + 'unkempt', + 'unkind', + 'unknowing', + 'unknown', + 'unlawful', + 'unlicensed', + 'unlikable', + 'unlikely', + 'unlivable', + 'unloved', + 'unlucky', + 'unmanageable', + 'unmanly', + 'unmanned', + 'unmarketable', + 'unmasked', + 'unmatched', + 'unmemorable', + 'unmentionable', + 'unmerciful', + 'unmistakable', + 'unmitigated', + 'unmodified', + 'unmotivated', + 'unnatural', + 'unnecessary', + 'unnerved', + 'unnerving', + 'unnoticeable', + 'unobserved', + 'unobtainable', + 'unobtrusive', + 'unofficial', + 'unopened', + 'unopposed', + 'unorthodox', + 'unostentatious', + 'unpalatable', + 'unpardonable', + 'unpersuasive', + 'unperturbed', + 'unplanned', + 'unpleasant', + 'unprecedented', + 'unpredictable', + 'unpretentious', + 'unprincipled', + 'unproductive', + 'unprofessional', + 'unprofitable', + 'unpromising', + 'unpronounceable', + 'unprovoked', + 'unqualified', + 'unquantifiable', + 'unquenchable', + 'unquestionable', + 'unquestioned', + 'unquestioning', + 'unraveled', + 'unreachable', + 'unreadable', + 'unrealistic', + 'unrealized', + 'unreasonable', + 'unreceptive', + 'unrecognizable', + 'unrecognized', + 'unredeemable', + 'unregulated', + 'unrelenting', + 'unreliable', + 'unremarkable', + 'unremitting', + 'unrepentant', + 'unrepresentative', + 'unrepresented', + 'unreserved', + 'unrespectable', + 'unresponsive', + 'unrestrained', + 'unripe', + 'unrivaled', + 'unromantic', + 'unruffled', + 'unruly', + 'unsafe', + 'unsalvageable', + 'unsatisfactory', + 'unsatisfied', + 'unscheduled', + 'unscholarly', + 'unscientific', + 'unscrupulous', + 'unseasonable', + 'unseemly', + 'unselfish', + 'unsettled', + 'unsettling', + 'unshakable', + 'unshapely', + 'unsightly', + 'unsigned', + 'unsinkable', + 'unskilled', + 'unsociable', + 'unsolicited', + 'unsolvable', + 'unsolved', + 'unsophisticated', + 'unsound', + 'unsparing', + 'unspeakable', + 'unspoiled', + 'unstable', + 'unstated', + 'unsteady', + 'unstoppable', + 'unstressed', + 'unstructured', + 'unsubstantial', + 'unsubstantiated', + 'unsuccessful', + 'unsuitable', + 'unsuited', + 'unsupervised', + 'unsupported', + 'unsure', + 'unsurpassable', + 'unsurpassed', + 'unsurprising', + 'unsuspected', + 'unsuspecting', + 'unsustainable', + 'unsympathetic', + 'unsystematic', + 'untainted', + 'untamable', + 'untamed', + 'untapped', + 'untenable', + 'untested', + 'unthinkable', + 'unthinking', + 'untidy', + 'untimely', + 'untitled', + 'untouchable', + 'untraditional', + 'untrained', + 'untried', + 'untroubled', + 'untrustworthy', + 'untruthful', + 'unused', + 'unusual', + 'unverified', + 'unwary', + 'unwashed', + 'unwatchable', + 'unwavering', + 'unwholesome', + 'unwieldy', + 'unwilling', + 'unwise', + 'unwitting', + 'unworkable', + 'unworldly', + 'unworthy', + 'unwritten', + 'unyielding', + 'upbeat', + 'upmost', + 'upper', + 'uppity', + 'upright', + 'uproarious', + 'upset', + 'upsetting', + 'upstairs', + 'uptight', + 'up-to-date', + 'up-to-the-minute', + 'upward', + 'urbane', + 'urgent', + 'usable', + 'used', + 'useful', + 'useless', + 'usual', + 'utilitarian', + 'utopian', + 'utter', + 'uttermost', + 'vacant', + 'vacillating', + 'vacuous', + 'vagabond', + 'vagrant', + 'vague', + 'vain', + 'valiant', + 'valid', + 'valorous', + 'valuable', + 'vanishing', + 'vapid', + 'vaporous', + 'variable', + 'varied', + 'various', + 'varying', + 'vast', + 'vegetable', + 'vegetarian', + 'vegetative', + 'vehement', + 'velvety', + 'venal', + 'venerable', + 'vengeful', + 'venomous', + 'venturesome', + 'venturous', + 'veracious', + 'verbal', + 'verbose', + 'verdant', + 'verifiable', + 'verified', + 'veritable', + 'vernacular', + 'versatile', + 'versed', + 'vertical', + 'very', + 'vexed', + 'vexing', + 'viable', + 'vibrant', + 'vibrating', + 'vicarious', + 'vicious', + 'victorious', + 'vigilant', + 'vigorous', + 'vile', + 'villainous', + 'vindictive', + 'vinegary', + 'violent', + 'violet', + 'viperous', + 'viral', + 'virtual', + 'virtuous', + 'virulent', + 'visceral', + 'viscous', + 'visible', + 'visionary', + 'visual', + 'vital', + 'vitriolic', + 'vivacious', + 'vivid', + 'vocal', + 'vocational', + 'voiceless', + 'volatile', + 'volcanic', + 'voluminous', + 'voluntary', + 'voluptuous', + 'voracious', + 'vulgar', + 'vulnerable', + 'wacky', + 'wailing', + 'waiting', + 'wakeful', + 'wandering', + 'wanting', + 'wanton', + 'warlike', + 'warm', + 'warmest', + 'warning', + 'warring', + 'wary', + 'waspish', + 'waste', + 'wasted', + 'wasteful', + 'watchful', + 'waterlogged', + 'waterproof', + 'watertight', + 'watery', + 'wavering', + 'wax', + 'waxen', + 'weak', + 'weakened', + 'weak-willed', + 'wealthy', + 'wearisome', + 'weary', + 'wee', + 'weedy', + 'week-long', + 'weekly', + 'weightless', + 'weighty', + 'weird', + 'welcoming', + 'well', + 'well-adjusted', + 'well-argued', + 'well-aware', + 'well-balanced', + 'well-behaved', + 'well-built', + 'well-conceived', + 'well-considered', + 'well-crafted', + 'well-deserved', + 'well-developed', + 'well-done', + 'well-dressed', + 'well-educated', + 'well-equipped', + 'well-established', + 'well-founded', + 'well-groomed', + 'well-heeled', + 'well-honed', + 'well-informed', + 'well-intentioned', + 'well-kempt', + 'well-known', + 'well-liked', + 'well-lit', + 'well-made', + 'well-maintained', + 'well-mannered', + 'well-meaning', + 'well-off', + 'well-placed', + 'well-planned', + 'well-prepared', + 'well-qualified', + 'well-read', + 'well-received', + 'well-rounded', + 'well-spoken', + 'well-suited', + 'well-thought-of', + 'well-thought-out', + 'well-to-do', + 'well-traveled', + 'well-used', + 'well-versed', + 'well-worn', + 'well-written', + 'west', + 'western', + 'wet', + 'what', + 'wheezing', + 'which', + 'whimpering', + 'whimsical', + 'whining', + 'whispering', + 'whistling', + 'white', + 'whole', + 'wholehearted', + 'wholesale', + 'wholesome', + 'whooping', + 'whopping', + 'whose', + 'wicked', + 'wide', + 'wide-eyed', + 'wide-ranging', + 'widespread', + 'wiggly', + 'wild', + 'willful', + 'willing', + 'wily', + 'windy', + 'winning', + 'winsome', + 'winter', + 'wintery', + 'wiry', + 'wise', + 'wishful', + 'wispy', + 'wistful', + 'withering', + 'witless', + 'witty', + 'wizardly', + 'wobbly', + 'woeful', + 'wolfish', + 'wonderful', + 'wondrous', + 'wonted', + 'wood', + 'wooden', + 'wooing', + 'wool', + 'woolen', + 'woozy', + 'wordless', + 'wordy', + 'work', + 'workable', + 'working', + 'work-oriented', + 'worldly', + 'worn', + 'worndown', + 'wornout', + 'worried', + 'worrisome', + 'worrying', + 'worse', + 'worshipful', + 'worst', + 'worth', + 'worthless', + 'worthwhile', + 'worthy', + 'wounding', + 'wrathful', + 'wrenching', + 'wretched', + 'wriggling', + 'wriggly', + 'wrinkled', + 'wrinkly', + 'written', + 'wrong', + 'wrongful', + 'wry', + 'yawning', + 'yearly', + 'yearning', + 'yellow', + 'yelping', + 'yielding', + 'young', + 'younger', + 'youngest', + 'youthful', + 'yummy', + 'zany', + 'zealous', + 'zestful', + 'zesty', + 'zippy', + 'zonked', + 'zoological', +]; + +export const maxStringLength = 22; diff --git a/drizzle-seed/src/datasets/cityNames.ts b/drizzle-seed/src/datasets/cityNames.ts new file mode 100644 index 000000000..3ea80747e --- /dev/null +++ b/drizzle-seed/src/datasets/cityNames.ts @@ -0,0 +1,42861 @@ +/** + * The original source for cities names data was taken from https://www.kaggle.com/datasets/juanmah/world-cities + * We've excluded a few countries and their cities from this list because we don't think they should ever appear in any list + */ +export default [ + 'Humpata', + 'Qunghirot', + 'Erdek', + 'Asenovgrad', + 'Payyoli', + 'Pidhorodne', + 'Clawson', + 'Kala Diara', + 'Kadan', + 'Tumut', + 'Bayat', + 'Gangoli', + 'Nuqui', + 'Lochau', + 'Albignasego', + 'Narayanganj', + 'Novo Lino', + 'Regeneracao', + 'Belvedere Marittimo', + 'Santana de Parnaiba', + 'Miyako', + 'Aniche', + 'Andrijevica', + 'Buggenhout', + 'Kurikka', + 'Milanowek', + 'Liaquatpur', + 'Khamaria', + 'Jora Khurd', + 'Petal', + 'Pak Tin Pa', + 'Xiantangcun', + 'Bovingdon', + 'Lianmuqin Kancun', + 'Sainte-Maxime', + 'Campinorte', + 'Vammanal', + 'Yabelo', + 'Wani', + 'Kaita', + 'Monkey Bay', + 'Ianapera', + 'Mariestad', + 'Bukomansimbi', + 'Kavaje', + 'Amahai', + 'Luis Alves', + 'Altmunster', + 'Spring Lake', + 'Niemasson', + 'Douala', + 'Naranjos', + 'Madhubani', + 'Atotonilco el Alto', + 'Palmar de Varela', + 'Hanerik', + 'Wadhraf', + 'Terre Neuve', + 'Libungan', + 'Mattigiri', + 'Prieto Diaz', + 'Barjora', + 'Kagamino', + 'Pakokku', + 'Bol', + 'Prienai', + 'Tan Phong', + 'Kali', + 'Rauenberg', + 'Chios', + 'Jerico', + "Qal'acha", + 'Biwong', + 'Albi', + 'Plantation', + 'Nargund', + 'Yamakita', + 'Ghota Fatehgarh', + 'Vredenburg', + 'Sherman', + 'Penal', + 'Varkaus', + 'Zhongwei', + 'Miri', + 'Guarapuava', + 'Bolszewo', + 'Park Forest Village', + 'Cianjur', + 'Kempsey', + 'Guaraci', + 'Odorheiu Secuiesc', + 'Alytus', + 'Mulanje', + 'Evinayong', + 'Bukkapatnam', + 'Berd', + 'Kisaran', + 'Bitung', + 'Selcuk', + 'Xikeng', + 'Cantanhede', + 'Pasuquin', + 'Steinheim am der Murr', + 'Mahem', + 'Gloria', + 'Benbrook', + 'Beforona', + 'Borken', + 'Ouled Rahmoun', + 'Pedras de Maria da Cruz', + 'Al Fuhays', + 'Titiribi', + 'Kien Luong', + 'Shediac', + 'Villasanta', + 'Guelph', + 'Duzkoy', + 'Narkher', + 'Tateyama', + 'Penzance', + 'Pasay City', + 'San Lucas Sacatepequez', + 'Skardu', + 'Maharajapuram', + 'Al `Asharah', + 'Tanant', + 'Kawaguchi', + 'Madhuban Bediban', + 'Bridge City', + 'Greenbelt', + 'Iara', + 'Baton Rouge', + 'Yopal', + 'Moribila', + 'Ciudad-Rodrigo', + 'Madira', + 'Bobonong', + 'Fort Hood', + 'Amboasary', + 'Sostanj', + 'Fnidq', + 'Vikasnagar', + 'Yangping', + 'Arraias', + 'Goslar', + 'Shuixi', + 'Lagoa da Canoa', + 'Baiersbronn', + 'Forest', + 'Sedavi', + 'Kosonsoy', + 'Trelleborg', + 'Bucheya', + 'Kidricevo', + 'Gokulgarh', + 'Coswig', + 'Marechal Taumaturgo', + 'Sidi Akkacha', + 'Kouarfa', + 'Finspang', + 'Timberlane', + 'Colotenango', + 'Puerto Ayora', + 'Goytapa', + 'Tarlapalli', + 'Acandi', + 'General Luna', + 'Calamba', + 'Miqiao', + 'Linxia Chengguanzhen', + 'Nizamabad', + 'Doria Sonapur', + 'Chongoroi', + 'Oskaloosa', + 'Prevalje', + 'Lubang', + 'Ladispoli', + 'Coite do Noia', + 'Sarjapur', + 'Panuco', + 'Jendouba', + 'Nayakanhatti', + "'Ain Mouilah", + 'Chapeco', + 'Stanton', + 'Balve', + 'Libante', + 'Etah', + 'Yarim', + 'Litchfield Beach', + 'Tsingoni', + 'Kendall Park', + 'Karkala', + 'Vlaardingen', + 'Torrente', + 'Periyanegamam', + 'Senhor do Bonfim', + 'Fleet', + 'Geita', + 'Chettipalaiyam', + 'New Washington', + 'Vlore', + 'San Juan Ermita', + 'Lagonglong', + 'Shwebo', + 'Hattiesburg', + 'Mering', + 'Basibasy', + 'Sarahs', + 'Nadendla', + 'Palaiyam', + 'Porto de Pedras', + 'Porto Novo', + 'Westampton', + 'Oriximina', + 'Al Jaghbub', + 'Hajjah', + 'Cutler Bay', + 'Toyokawa', + 'Tehuipango', + 'Catabola', + 'Ivanic-Grad', + 'Caudete', + 'Del Rio', + 'Clarence-Rockland', + 'Morrisville', + 'Divisopolis', + 'Chigasaki', + 'West Auckland', + 'Suzu', + 'Blythe', + 'Khunti', + 'Putussibau', + 'Ourikela', + 'Ambano', + 'Calpe', + 'Ambohitralanana', + 'Longjin', + 'Sao Filipe', + 'San Jose de Gracia', + 'Bura', + 'Ziarat', + 'Termoli', + 'Vohindava', + 'Xinbu', + 'Biliaivka', + 'Sahneh', + 'Rutherglen', + 'Naolinco de Victoria', + 'Windlesham', + 'Manchester', + 'Orinda', + 'South Yarmouth', + 'Tabaco', + 'Kayseri', + 'Ibiapina', + 'Murcia', + 'Swieqi', + 'Valencia', + 'Chirala', + 'Eloxochitlan', + 'Dembeni', + 'Fardis', + 'Misaki', + 'Nagar', + 'Miyajima', + 'Brikama', + 'Umurlu', + 'Brierley Hill', + 'Khodabandeh', + 'Ozgon', + 'Reze', + 'Tinajeros', + 'Novomyrhorod', + 'Fully', + 'Biak', + 'Colider', + 'Kashiwara', + 'Sjenica', + 'Hednesford', + 'Madukkarai', + 'Villaflores', + 'Chiautla de Tapia', + 'Taormina', + 'Huwei', + 'Seiyo', + 'Washim', + 'Benton', + 'Venturina', + 'Kakalur', + 'Andamooka', + 'Anghad', + 'West Hempfield', + 'Turnhout', + 'Markkleeberg', + 'Poko', + 'Wealdstone', + 'Ban Na Pa', + 'Cerklje na Gorenjskem', + 'Bombon', + 'Arimalam', + 'Jangy-Kyshtak', + 'Isny im Allgau', + 'Braga', + 'Kawadgaon', + 'Bonate di Sopra', + 'Tonneins', + 'Munagala', + 'Chinna Mushidivada', + 'Talagutong', + 'Neihuzhai', + 'Agua de Dios', + 'Herceg Novi', + "Nerk'in Getashen", + 'Botucatu', + 'Arroio do Meio', + 'Villafranca del Panades', + 'Madikeri', + 'Serik', + 'San Sebastian Huehuetenango', + 'Kyzyl-Adyr', + 'Somarasampettai', + 'Surmene', + 'Turbana', + 'Chilas', + 'Marinette', + 'The Hammocks', + 'McPherson', + 'General MacArthur', + 'Strasbourg', + 'Tibana', + 'Polatli', + 'Sur', + 'Lye', + 'West Carson', + 'Uludere', + 'Moroni', + 'Mata de Sao Joao', + 'Pachmir', + 'Saskatoon', + 'Nina Rodrigues', + 'Chinnakkavundanur', + 'Norton Shores', + 'Abu', + 'Santo Angelo', + 'Pianezza', + 'Wythenshawe', + 'Mabeskraal', + 'Fort Drum', + 'Gobindpur', + 'Kostopil', + 'Calca', + 'Nonkon', + 'Kolno', + 'Tati', + 'Glastonbury', + 'Itakura', + 'Payyanadam', + 'Puerto Caimito', + 'Thionville', + 'Wezep', + 'Llandudno', + 'Antequera', + 'Camp Perrin', + 'Sao Luis do Paraitinga', + 'Kabatas', + 'Volta Redonda', + 'Beni Mered', + 'Kaffrine', + 'Reutlingen', + 'Fort Morgan', + 'Naucalpan de Juarez', + 'Ebo', + 'Dargaz', + 'Vina del Mar', + 'Ilam', + 'Kyzyl-Suu', + 'Perinton', + 'Nenmini', + 'La Mirada', + 'Sumbe', + 'Jicin', + 'Indian Trail', + 'Nova Bassano', + 'Batam Centre', + 'Tocache Nuevo', + 'Tambaram', + 'Kikugawa', + 'Sari', + 'Borca', + 'Beja', + 'Feltre', + 'Godo', + 'Oshawa', + 'German Flatts', + 'Schaffhausen', + 'Albino', + 'Wejherowo', + 'Plav', + 'Beccar', + 'Pitsea', + 'Lauro de Freitas', + 'Gogrial', + 'Ferros', + 'Tipp City', + 'Waynesboro', + 'Khost', + 'Golungo Alto', + 'Tamesis', + 'Almondbury', + 'Bernardo de Irigoyen', + 'Vairampatti', + 'Neiafu', + 'Everswinkel', + 'Garden Acres', + 'Yufu', + 'Coxim', + 'Dilra', + 'Guwahati', + 'Tan Phu', + "Khairpur Mir's", + 'Poza Rica de Hidalgo', + 'Trancoso', + 'Koytendag', + 'At Tafilah', + 'Ranohira', + 'Legionowo', + 'Romitan Shahri', + 'The Villages', + 'Carroll', + 'Mogi Mirim', + 'Mo i Rana', + 'San Miguel Duenas', + 'Dardoq', + 'Abaetetuba', + 'New Milford', + 'Palda', + 'Tsinjomitondraka', + 'Pisz', + 'Otaru', + 'Caldas Novas', + 'Pampanito', + 'Panuco de Coronado', + 'Lanyi', + 'Mayoyao', + 'Tlagasana', + 'Chetouane', + 'Grand-Popo', + 'Nagaoki', + 'Chong Nonsi', + 'Emmiganur', + 'Looc', + 'Vila Muriqui', + 'Frattamaggiore', + 'Ormoz', + 'Goynucek', + 'Arles', + 'Laungowal', + 'Jalhay', + 'Wan Tau Tong', + 'Villa Angela', + 'Mariyammanahalli', + 'Pivka', + 'Firmat', + 'Patsanda', + 'Colle Salvetti', + 'Gyula', + 'Valparai', + 'Issaquah', + 'Jeronimo Monteiro', + 'Bugasong', + 'Broxbourne', + 'Tsundupalle', + 'Jacupiranga', + 'San Juan Ixcoy', + 'Ambatomirahavavy', + 'Lanco', + 'Monsenhor Gil', + 'Sabaa Aiyoun', + 'Narnaul', + 'Carutapera', + 'Altay', + 'Ambodiampana', + 'Kouvola', + 'Harahan', + 'Paoskoto', + 'Vargem Grande Paulista', + 'Schwarzenbruck', + 'Bellheim', + 'Oak Ridge', + 'Chimaltenango', + 'Issaba', + 'Tiri', + 'Taroudannt', + 'Nonahi', + 'Ranquitte', + 'Tala Yfassene', + 'Meybod', + 'Muchamiel', + 'Antohobe', + 'Axochiapan', + 'Malakanagiri', + 'Whitchurch', + 'Lake in the Hills', + 'San Mateo del Mar', + 'Curuzu Cuatia', + 'Schweizer-Reineke', + 'Babusnica', + 'Montgomeryville', + 'Gundugolanu', + 'Abi al Khasib', + 'Pillanallur', + 'Soulei', + 'Ayas', + 'Azuqueca de Henares', + 'Chodov', + 'Mingjiujie', + 'Patar', + 'Porto Murtinho', + 'Jafra', + 'El Progreso', + 'Rulin', + 'Penetanguishene', + 'Ban Khek Noi', + 'Tubao', + 'Calw', + 'Gaundra', + 'Nasik', + 'Gaolingcun', + 'Kalol', + 'Meadowbrook', + 'Kidapawan', + 'Rudsar', + 'Maddaloni', + 'Kabul', + 'Karlskoga', + 'Yazd', + 'Podalakur', + 'Asakura', + 'Teranikallu', + 'Keokuk', + 'Uyuni', + 'Yorosso', + 'Pfaffikon', + 'Arniquet', + 'Kyzyl-Kyshtak', + 'Kambur', + 'Mburucuya', + 'Mingacevir', + 'Zvolen', + 'Cabaceiras do Paraguacu', + 'Tamworth', + 'Guantanamo', + 'Darregueira', + 'Ezequiel Montes', + 'Fomboni', + 'Quintanar de la Orden', + 'Siachoque', + 'Kericho', + 'Mineral de Angangueo', + 'Peka', + 'Fenglin', + 'Quetzaltenango', + 'Sevanagala', + 'Port Pirie', + 'Tengyue', + 'Novoyavorovskoye', + 'Forli', + 'Suchitoto', + 'Hosdurga', + 'Arcoverde', + 'Vale de Cambra', + 'San Pablo Atlazalpan', + 'Hockenheim', + 'Dandu Mailaram', + 'Tadjmout', + 'Port of Spain', + 'Nanaimo', + 'Colonia del Sol', + 'Primeiro de Maio', + 'Valletta', + 'Horishni Plavni', + 'Kingswood', + 'Furstenwalde', + 'Grosse Pointe Farms', + 'Bang Kruai', + 'Haiger', + 'Chansolme', + 'Xiaoganzhan', + 'Mulaikkaraippatti', + 'Nandazhang', + 'Hambuhren', + 'Medellin', + 'Canapolis', + 'Corozal', + 'Bignona', + 'Nova Vodolaha', + 'Shedbal', + 'Maojiatang', + 'Greentree', + 'Mahayag', + 'Rajendranagar', + 'Guararema', + 'Pirane', + 'Tall Dhahab', + 'Sipoo', + 'Aiquile', + 'Kish', + 'Taltal', + 'Kushmanchi', + 'Santa Genoveva de Docordo', + 'Tapiales', + 'Yunak', + 'Floirac', + 'Parbata', + 'Zhangzhengqiao', + 'Zossen', + 'Caselle Torinese', + 'Zaliohouan', + 'Waldkirchen', + 'Sao Jose da Coroa Grande', + 'Pasinler', + 'Gaspe', + 'Soubakaniedougou', + 'Nabire', + 'Sieyik', + 'Sao Jose de Piranhas', + 'Gioia del Colle', + 'Villanueva de Arosa', + 'Marihatag', + 'Daugavpils', + 'Saint Helens', + 'Jiannan', + 'Gross Kreutz', + 'Ganapathivattam', + 'Santiago de Cuba', + 'Katanning', + 'Marotolana', + 'Dingzhou', + 'Spoleto', + 'San Agustin Chahal', + 'Calheta de Sao Miguel', + 'Aragua de Barcelona', + 'Vammala', + 'Sao Goncalo do Amarante', + 'Shahba', + 'Cornedo Vicentino', + 'Kew Green', + 'Kuzuculu', + 'Tobati', + 'Currais Novos', + 'Mounds View', + 'Wetzlar', + 'Nesoddtangen', + 'Bosilovo', + 'Marolambo', + 'Horodyshche', + 'Aleksandrow Kujawski', + 'Sao Jose do Cedro', + 'Dhabauli', + 'Macka', + 'Oelsnitz', + 'Kiambu', + 'Mugutkhan Hubli', + 'Jalkaura', + "Bo'ao", + 'Emmiganuru', + 'Manglur', + 'Shenmu', + 'Kerch', + 'Paluan', + 'Jaguaribe', + 'Palmeira dos Indios', + 'Zorgo', + 'Chagne', + 'Ugrinovci', + 'Feuchtwangen', + 'Mitake', + 'Pacora', + "Rui'an", + 'West Hanover', + 'Buca', + 'Sam Son', + 'Matsuyama', + 'Gouka', + 'Tuchin', + 'Tonantins', + 'Narangba', + 'Inami', + 'Wolf Trap', + 'Kumharsan', + 'Gundi', + 'Rubiera', + 'Balia', + 'Estarreja', + 'Golkoy', + 'Nordestina', + 'Dattapulia', + 'Wapienica', + 'Unao', + 'Vasterhaninge', + 'Wenxian Chengguanzhen', + 'Hanover Park', + 'Urrugne', + 'San Antonio Ilotenango', + 'San Vicente dels Horts', + "Sao Miguel d'Oeste", + 'Caerfyrddin', + 'Sidi Rahhal', + 'Olongapo', + 'Jasol', + 'Castelnuovo Rangone', + 'Don Benito', + 'Thung Song', + 'Zhanibek', + 'Male', + 'Anpachi', + 'Buford', + 'Kwekwe', + 'Urmston', + 'Fernan-Nunez', + 'Rio das Ostras', + 'Zhmerynka', + 'Shahrud', + 'Las Lomitas', + 'Curuca', + 'Ar Ramadi', + 'Jamundi', + 'Skalica', + 'Taixing', + 'Huron', + 'Basse-Goulaine', + 'Arteche', + 'Roxbury', + 'Nawnghkio', + 'Terryville', + 'Eastlake', + 'Guarabira', + 'Zionsville', + 'Yala', + 'Os', + 'Tuquerres', + 'Zlatograd', + 'Harnai', + 'Cherukara', + 'Numancia', + 'Negotino', + 'Sparta', + 'Buzen', + 'Karuvelampatti', + 'Monte San Pietro', + 'Rosario do Catete', + 'Thamaga', + 'Johnstown', + 'Manaira', + 'Zabrat', + 'Tebourba', + 'Cornelius', + 'Al Mu`addamiyah', + 'Epinal', + 'Haria', + 'Surrey', + 'Tarapoto', + 'Morecambe', + 'Maribojoc', + 'Savda', + 'Calbiga', + 'Ayodhyapattanam', + 'Dulmen', + 'Porteirinha', + 'Armstrong', + 'Darton', + 'Poco Redondo', + 'Yoboki', + 'Maevka', + 'Pihra', + 'Nong Khae', + 'Mascoutah', + 'Ukrainka', + 'Ivrea', + 'Vadavalam', + 'Huaraz', + 'Gjakove', + 'Penablanca', + 'Lishuping', + 'Kiso', + 'Guaranda', + 'Palliman', + 'Rawtenstall', + 'Atchampeta', + 'Tarazona de Aragon', + 'Vincent', + 'Longtang', + 'Muragacha', + 'Gwadar', + 'Aguilas', + 'Karttigappalli', + 'Rahway', + 'Churi', + 'Leini', + 'Ragan Sur', + 'Hongliuwan', + 'Bayang', + 'Plattsburgh', + 'Anagni', + 'Gidan Ider', + 'Ait Bouchta', + 'Sidi Abdelkader', + 'Salaverry', + "Ma'erkang", + 'Niena', + 'Santamaguluru', + 'Buxtehude', + 'Ugep', + 'Zelezniki', + 'Emmendingen', + 'Monforte de Lemos', + 'Tourza', + 'Rivalta di Torino', + 'Orange Cove', + 'Bijbiara', + 'Jardim do Serido', + 'Kajaani', + 'Barros Cassal', + 'Shengang', + 'Candido de Abreu', + 'Ashdod', + 'Bhit Bhagwanpur', + 'Sajoszentpeter', + 'Ambolotarakely', + 'Viota', + 'Muskegon', + 'Hanau', + 'Madhupur', + 'Popesti-Leordeni', + 'Coyhaique', + 'Hojai', + 'Nehe', + 'Maddagiri', + 'Bodrum', + 'Mountain View', + 'Tash-Komur', + 'Cili', + 'Kristianstad', + 'Pehuajo', + 'Jam', + 'Frederickson', + 'Aywaille', + 'Heule', + 'Kasungu', + 'Koge', + 'Mukdahan', + 'Dialakoroba', + 'Les Sorinieres', + 'Al Kiswah', + 'Dongxiaozhai', + 'Mirandela', + 'Mochizuki', + 'Hanumantanpatti', + 'Bregenz', + 'Kongjiazhuangcun', + 'Xianxi', + 'Kovin', + 'Konan', + 'La Serena', + 'Menomonie', + 'Zemmouri', + 'Nakagusuku', + 'Gharbara', + 'Oro-Medonte', + 'Qiloane', + 'Larantuka', + 'Nonoai', + 'Silvarpatti', + 'Kharagbani', + 'Nghia Lo', + 'Kissimmee', + 'Higuey', + 'Susques', + 'Pyalakurti', + 'Sleaford', + 'Barranco de Loba', + 'Rattihalli', + 'Standish', + 'Holyoke', + 'Nettadahalli', + 'Torrejon de la Calzada', + 'Nidzica', + 'Sovata', + 'Ilha Solteira', + 'Rampur Khajuriya', + 'Pappinissheri', + 'Sint Willebrord', + 'Benito Soliven', + "Sek'ot'a", + 'Jhang City', + 'Vadakkanandal', + 'Kolo', + 'Montecorvino Rovella', + 'Mancio Lima', + 'Kashkar-Kyshtak', + 'Mohania', + 'Kochgawan', + 'Honiara', + 'Brejoes', + 'Krasnyy Yar', + 'Otrokovice', + 'Saidpur', + 'Nainijor', + 'Jhanjharpur', + 'Yarm', + 'Caransebes', + 'Santa Teresa del Tuy', + 'Banate', + 'Altadena', + 'Sihu', + 'Velten', + 'San Pablo', + 'Chbar Mon', + 'Baraderes', + 'Baraon', + 'Telsang', + 'Saint Budeaux', + 'Candelaria', + 'Silva Jardim', + 'Udburu', + 'Mastic', + 'Itapora', + 'Iklin', + 'Vlasotince', + 'Svay Pak', + 'Havelock North', + 'Kalipatnam', + 'Mozarlandia', + 'Kamarhati', + 'Strehaia', + 'Joshimath', + 'Bridgnorth', + 'Batticaloa', + 'Pallisa', + 'Coronel Dorrego', + 'Chelmek', + 'Hinthada', + 'Nola', + 'Little Rock', + 'New Braunfels', + 'Havre', + 'Lavezares', + 'Lamphun', + 'Karajgi', + 'Maheshram', + 'Rangasamudram', + 'Cuango', + 'Tonse East', + 'Morlanwelz-Mariemont', + 'Otavalo', + 'Nerkunram', + 'Jaque', + 'Okha', + 'Bhamo', + 'Gadabay', + 'Timbauba', + 'Andapafito', + 'Punta Umbria', + 'Vanj', + 'Kun Puhal', + 'Timmendorfer Strand', + 'Pau dos Ferros', + 'Matsuzaka', + 'Columbus', + 'Putina', + 'Kotdwara', + 'Tagana-an', + 'Ajdovscina', + 'Siegen', + 'Sangin', + 'Bihpuriagaon', + 'Biswanath Chariali', + 'Chiva', + 'Paszto', + 'Ban Wang Daeng', + 'Karlsfeld', + 'El Alamo', + 'Lelystad', + 'Chand Chaur', + 'Nioaque', + 'Yargatti', + 'Hilzingen', + 'Ayvacik', + 'Suceava', + 'Padova', + 'Carneys Point', + 'Winkfield', + 'Absecon', + 'Guntakal Junction', + 'Bhadwar', + 'Iwaka', + 'San Pedro de Lloc', + 'Key Largo', + 'Monterotondo', + 'Al Hillah', + 'Zaouia Ait Ishak', + 'Floriana', + 'Ubajara', + 'Ouled Fayet', + 'Novo Cruzeiro', + 'Selwyn', + 'Phitsanulok', + 'Amarzgane', + 'Punakha', + 'Liperi', + 'Biblis', + 'Pavlodar', + 'Salalah', + 'Tysvaer', + 'Yapqan', + 'Vetapalem', + 'Baisari', + 'Boryeong', + 'Dhanauli', + 'Atenco', + 'Little Chute', + 'Mimasaka', + 'Champapur', + 'Itamonte', + 'Marple', + 'El Callao', + 'Whanganui', + 'Korschenbroich', + 'Zhuqi', + 'Asfarvarin', + 'Kodayattur', + 'Yozyovon', + 'Thompson', + 'Wingles', + 'Karliova', + 'Radyvyliv', + 'Mollendo', + 'Jagdalpur', + 'Nandgaon', + 'Toyono', + 'Gryfino', + 'Ventspils', + 'Sibiti', + 'Shishi', + 'Lulhaul', + 'Bhalpatti', + 'Norman', + 'Halesowen', + 'Al Midhnab', + 'Viroflay', + 'Nether Providence', + 'Anglet', + 'Seven Corners', + 'Kurort Steinbach-Hallenberg', + 'Eden Prairie', + 'Firestone', + 'Shanywathit', + 'North Wantagh', + 'Grenzach-Wyhlen', + 'Chiari', + 'Keningau', + 'Miranda do Corvo', + 'Tenambakkam', + 'Tilburg', + 'Budipuram', + 'Nallamada', + 'San Giovanni la Punta', + 'Middle', + 'Nanzhai', + 'Castelfiorentino', + 'Mont-Royal', + 'Khasbalanda', + 'Zirara', + 'Carluke', + 'Kawatana', + 'Idangansalai', + 'Mandi', + 'Sabanitas', + 'Balangir', + 'Phillipsburg', + 'Poniatowa', + 'Jizzax', + 'Corumbaiba', + 'Sampues', + 'Muynoq', + 'Valga', + 'Daharia', + 'Mundakkal', + 'Qarqin', + 'Gandu', + 'Los Vilos', + 'Arkonam', + 'Kananya', + 'South Middleton', + 'Devsar', + 'Kamin-Kashyrskyi', + 'Hauterive', + 'Raitar', + 'Swords', + 'Elche', + 'Shakhtinsk', + 'Meadow Woods', + 'Kadiapattanam', + 'Sherrelwood', + 'Altamura', + 'Bloomingdale', + 'Hesperia', + 'Marathon', + 'Sogndal', + 'Timberlake', + 'Upper Gwynedd', + 'Saint-Andre-de-Cubzac', + 'Chelmsford', + 'Udaipur Bithwar', + 'Provins', + 'Prievidza', + 'Loiyo', + 'Dabou', + 'Kastav', + 'Vellarivalli', + 'Orchards', + 'Summerfield', + 'Ilidza', + 'Badiadka', + 'Ypsilanti', + 'Warsaw', + 'Randers', + 'Aqtobe', + 'Rosendael', + 'New Paltz', + 'Port Neches', + 'Cranford', + 'Hadamar', + 'Bergenfield', + 'Orland Park', + 'Ba Chuc', + 'Fomento', + 'Tegueste', + 'Omagh', + 'Maria Pinto', + 'Ban Suan', + 'Cuernavaca', + 'Leominster', + 'Eidsvold', + 'Sohtha', + 'Todi', + 'Choszczno', + 'Yamato', + 'Lengquancun', + 'Sultanpur Mazra', + 'Boumalne', + 'Manises', + 'Xiaoli', + 'Zolochiv', + 'Pyapon', + 'Lake Ronkonkoma', + 'Hasselt', + 'El Minie', + 'Ambinanindovoka', + 'Trittau', + 'Mulavur', + 'Loyish Shaharchasi', + 'Bembereke', + 'Antratsyt', + 'Sayalkudi', + 'Chorrocho', + 'Ipiau', + 'Pembroke Pines', + 'Palghar', + 'Frodsham', + 'Sullana', + 'Sunamganj', + 'Finike', + 'Qiryat Mozqin', + 'Correia Pinto', + 'Macheng', + 'Ban Du', + 'Agde', + 'Santa Quiteria', + 'Voyenno-Antonovka', + 'Kapellen', + 'An Chau', + 'Valera', + 'Glen Parva', + 'Mahikeng', + 'Gland', + 'Northallerton', + 'Su-ngai Kolok', + 'Ash Shaykhan', + 'Roztoky', + 'Kokomo', + 'Duren', + 'Iver', + 'Campanha', + 'Befody', + 'Vaisampakkal', + 'Ucar', + 'Janesville', + 'Aksehir', + 'Buur Gaabo', + 'Ouled Fares', + 'Eseka', + 'Cossimbazar', + 'Abingdon', + 'Andrychow', + 'Calimesa', + 'Manati', + 'Hannoversch Munden', + 'Nalakadoddi', + 'Xiaoba', + 'Suo-Oshima', + 'Beaver Dam', + 'Oulad Ouchchih', + 'Analavory', + 'Lopez Jaena', + 'Midland', + 'Zhongbu', + 'Tarrytown', + 'Brighton', + 'Vannes', + 'Rajgir', + 'Kuppam', + 'Kottakota', + 'Onesti', + 'Massa', + 'Sebastian', + 'Benahavis', + 'Gigmoto', + 'Probolinggo', + 'Valkenswaard', + 'Momostenango', + 'Det Udom', + 'Myrza-Ake', + 'Bou Saada', + 'Maur Kalan', + 'Cholargos', + 'Senglea', + 'Baranzate', + 'Point Fortin', + 'Wutiancun', + 'South Whittier', + 'Tiran', + 'Fitchburg', + 'Eyvan', + 'Burbaliq', + 'Heanor', + 'Rancharia', + 'Karumattampatti', + 'Piagapo', + 'Rishivandiyam', + 'Amposta', + 'Tultepec', + 'Cabaret', + 'Cloquet', + 'Plouzane', + 'Wilnecote', + 'Presidente Epitacio', + 'Mossaka', + 'Piano di Sorrento', + 'Colomiers', + 'Kaithal', + 'Cotoca', + 'Kofele', + 'Nangong', + 'Yhu', + 'Chepo', + 'Kyoto', + 'Sorocaba', + 'Phagwara', + 'Ludenscheid', + 'Guotang', + 'Vayakkalattu', + 'Mianeh', + 'Closepet', + 'Kronshagen', + 'Unye', + 'Vale de Cavalos', + 'Zitorada', + 'Ramnicu Valcea', + 'Samastipur', + 'West Hempstead', + 'Saunshi', + 'Atamyrat', + 'Morfelden-Walldorf', + 'Okahandja', + 'Shaoyu', + 'Mirpur Mathelo', + 'Odendaalsrus', + 'Jisrayn', + 'Elankur', + 'Facatativa', + 'Leshan', + 'Halle', + 'Paharpur', + 'Dialafara', + 'Villeneuve-Loubet', + 'Regidor', + 'Pukkulam', + 'Torre Boldone', + 'Tirukkalikkunram', + 'Xihuangcun', + 'Artemida', + 'Lorrach', + 'Governador Valadares', + 'Virton', + 'Xochiatipan de Castillo', + 'Ehingen an der Donau', + 'Carlosama', + 'Zarzal', + 'Terni', + 'Tomaszow Mazowiecki', + 'Villa Union', + 'North Lakes', + 'Lospalos', + 'Wuhan', + 'La Puebla del Rio', + 'Caraga', + 'Drolshagen', + 'Mahad', + 'Charleville', + 'Dhorimanna', + 'Hakodate', + 'Soumagne', + 'Magra', + 'Chulumani', + 'Alloa', + 'Montegranaro', + 'Singhara Buzurg', + 'Errahalli', + 'Sakleshpur', + 'Serris', + 'Zell am See', + 'Danbury', + 'Yoshioka', + 'Eruh', + 'Kinshasa', + 'Mellieha', + 'Ibadan', + 'Filottrano', + 'Dongjiangshui', + 'Mariehamn', + 'Mbalmayo', + 'Cobija', + 'Cairo', + 'Jatara', + 'Famalicao', + 'Bayonet Point', + 'Trang Bang', + 'Fandriana', + 'Copceac', + 'Nhamunda', + 'Sriperumbudur', + 'Kitamoto', + 'Le Grand-Quevilly', + 'Higuera de Zaragoza', + 'Sa Dec', + 'Plougastel-Daoulas', + 'IJsselstein', + 'Rivoli', + 'Neu Bleckede', + 'Am-Timan', + 'Jobabo', + 'Lerik', + 'Chaiyo', + 'Bergen op Zoom', + 'Niederzier', + 'Sesimbra', + 'Jagta', + 'Levis', + "Stara L'ubovna", + 'Horbury', + 'Bialy Kamien', + 'Alamo', + 'Palaio Faliro', + 'Roselle Park', + 'Rudewa', + 'Kotma', + 'Cojedes', + 'Guangming', + 'Shanwei', + 'Vanimo', + 'Alesd', + 'Sidmouth', + 'Hamah', + 'Alejandro Korn', + 'Emsworth', + 'Shenjiabang', + 'Calatayud', + 'Furano', + 'Moca', + 'Viraghattam', + 'Farshut', + 'Nachod', + 'Patzun', + 'Southborough', + 'Hopetown', + 'Regina', + 'Estavayer-le-Lac', + 'Castellammare di Stabia', + 'Marabut', + 'Wegrow', + 'Moncton', + 'Pochuta', + 'Yumbe', + 'Raynes Park', + 'Kaiserslautern', + 'Ixtahuacan', + 'Montemor-o-Novo', + 'Palanisettipatti', + 'Baaqline', + 'Pen-y-Bont ar Ogwr', + 'Altinekin', + 'Villaba', + 'Cudahy', + 'Pazhanji', + 'Icononzo', + 'Yaozhuangcun', + 'Sanson', + 'Mol', + 'Lejiangxiang', + 'Prien am Chiemsee', + 'Heerde', + 'Nabatiye', + 'Dobre Miasto', + 'Franklin', + 'Ban Khi Lek', + 'Calumpang', + 'Levelland', + 'San Jose de Bocay', + 'Xichang', + 'Uravakonda', + 'Madinat Zayid', + 'Udayagiri', + "Jeppe's Reef", + "Nek'emte", + 'Fajardo', + 'Ratodero', + 'South Elgin', + 'Gogogogo', + 'Awjilah', + 'Kantharalak', + 'La Trinidad', + 'Nakanoto', + 'Paamiut', + 'Augustdorf', + 'Ferizli', + 'Belauncha', + 'Elurupadu', + 'Abuna', + 'Kuantan', + 'Casimiro de Abreu', + 'Battle Creek', + 'Ohara', + 'Knottingley', + 'Varzedo', + 'Ouled Slama Tahta', + 'Sorrento', + 'Lalmatie', + 'Hemavati', + 'Lewisboro', + 'Gary', + 'Hartbeespoort', + 'Piriyapatna', + 'Fulacunda', + 'Coatzacoalcos', + 'Puliyara', + 'Busca', + 'Jamkhed', + 'Targu Lapus', + "Ra's al Ma`arrah", + 'Nakhtarana', + 'Monte Plata', + 'Gravenhurst', + 'Jingcheng', + 'Starnberg', + 'Sa al Hajar', + 'Whitehouse', + 'Batatais', + 'Ciempozuelos', + 'Bingmei', + 'Divinopolis', + 'Como', + 'Sirgora', + 'Ban Bang Yai', + 'Tejucuoca', + 'Soavina Antanety', + 'Grapevine', + 'Waseca', + 'Sindelfingen', + 'Ulhasnagar', + 'Tulcan', + 'Ararica', + 'Camoapa', + 'Zoubiria', + 'Dour', + 'Terku Narippaiyur', + 'Llagostera', + 'Qo`rg`ontepa', + 'San Rafael La Independencia', + 'Sandino', + 'Dasoguz', + 'Bankra', + 'Mauguio', + 'Viagrande', + 'Bay Village', + 'Galivedu', + 'Oulad Fares', + 'Patapatnam', + 'Swinoujscie', + 'Dharmkot', + 'Recklinghausen', + 'General Viamonte', + 'Cyangugu', + 'Monteux', + 'Ozalp', + 'Quintero', + 'Baocheng', + 'Marhaura', + 'La Solana', + 'Simpang Renggam', + 'Bad Durkheim', + 'Sao Bento do Sapucai', + 'Mitzic', + 'Etajima', + 'Volos', + 'Hailey', + 'Weinsberg', + 'Dahi', + 'Braselton', + 'Miro Khan', + 'Qujingpu', + 'Ince-in-Makerfield', + 'Spanish Springs', + 'Montanhas', + 'Presevo', + 'San Carlos Centro', + 'Nandnawan', + 'Nowy Sacz', + 'View Royal', + 'West Rembo', + 'Paragould', + 'Tortoli', + 'Guapiacu', + 'Gijon', + 'Ilesa', + 'Tongjiang', + 'Belakvadi', + 'Santa Sylvina', + 'Adelfia', + 'Mandya', + 'Bay City', + 'Rosny-sous-Bois', + 'Angono', + 'Uzhhorod', + 'Ap Phu Hai', + 'Bembe', + 'Palma di Montechiaro', + 'Shanhe', + 'Oued el Djemaa', + 'Atturkuppam', + 'Adwa', + 'Zabbar', + 'Bhagalpur', + 'West Valley City', + 'Ruislip', + 'Talatamaty', + 'Ban Wang Krachae', + 'Iskapalem', + 'Kashmar', + 'Bouchagroun', + 'Timissa', + 'Xizi', + 'Elmwood Park', + 'Santa Ana Huista', + 'Hickory Hills', + 'Rayong', + 'Zambrow', + 'Diamantina', + 'Carmen de Areco', + 'Kodigenahalli', + 'Jamikunta', + 'Soyaux', + 'Juraqan', + 'Burgess Hill', + 'Piskent', + 'Wloclawek', + 'Meerssen', + 'Cruz Alta', + 'Huitzilan', + 'Melo', + 'Takelsa', + 'Xiaoyi', + 'Jhabua', + 'Jagna', + 'Sondiha', + 'Sopiste', + 'Koungou', + 'Yeldurti', + 'Dois Irmaos', + 'Jaru', + 'Kulasekharapuram', + 'Shinshiro', + 'Qaanaaq', + 'Cangucu', + 'Rosas', + 'Westtown', + 'Kaka', + 'Gobardanga', + 'Fangyuan', + 'Kallupatti', + 'Raiganj', + 'Chitapur', + 'Benevides', + 'Miercurea-Ciuc', + 'Massapequa', + 'Kallakkudi', + 'Dinuba', + 'Ouagadougou', + 'Hanford', + 'Mfou', + 'Mandalapalle', + 'Alvand', + 'Fort Campbell North', + 'Kueneng', + 'Bunnik', + 'Radnor', + 'Ikoto', + 'Blankenburg', + 'Santa Marinella', + 'Snizhne', + 'Zerakpur', + 'Caetite', + 'Boechout', + 'Gulbarga', + 'Southern Pines', + 'Nanakuli', + 'Miarinarivo', + 'Lakshmeshwar', + 'Gaocheng', + 'Laheji', + 'Bagumbayan', + 'Goshen', + 'Neopolis', + 'Baohe', + 'Diss', + 'Hangu', + 'Muyinga', + 'Apahida', + 'Miagao', + 'Videm pri Ptuju', + 'Kuysinjaq', + 'Kalfou', + 'Danilovgrad', + 'Ambalatany', + 'Katkol', + 'Forio', + 'Chaohucun', + 'Manki', + 'Capulhuac', + 'White Rock', + 'Ban Pa Tueng', + 'Marahom', + 'Constanta', + 'Ntoum', + 'Los Minas', + 'Nabaruh', + 'Mahuver', + 'Crosia', + 'Bilenke', + 'Miranda de Ebro', + 'Oshakati', + 'Kochugaon', + 'Paralimni', + 'Ramon Santana', + 'Villa Bisono', + 'Ichikai', + 'Nefasit', + 'Sangmelima', + 'Jasmine Estates', + 'Marion Oaks', + 'Ahmedabad', + 'Bello', + 'Koporo-Kenie-na', + 'Attard', + 'Kamargani', + 'Greenwood Village', + 'Dubacherla', + 'Ad Dabyah', + 'Katosi', + 'Nhandeara', + 'Thap Than', + 'Andranambolava', + 'Totutla', + 'Gambettola', + 'Hatoyama', + 'Shanyincun', + 'Pinjranwan', + 'Yadiki', + 'Ceelbuur', + 'Petrovec', + 'Tighenif', + 'Berwick-Upon-Tweed', + 'Pakhtaobod', + 'Sentilj', + 'Barahra', + 'Pingdeng', + 'Zunil', + 'Darwen', + 'Trang', + 'Napier', + 'Oroshaza', + 'Sao Tiago', + 'Sipe Sipe', + 'Gera', + 'Sadhoa', + 'Sungo', + 'Papagaios', + 'Yahaba', + 'Tassera', + 'Cuyotenango', + 'Doumanaba', + 'Tortuguitas', + 'La Prairie', + 'Enfida', + 'Nafpaktos', + 'Chhapra', + 'Hoenheim', + 'Bondoufle', + 'La Virginia', + 'Guneysu', + 'Champaign', + 'Kampala', + 'Sundararaopeta', + 'Uppidamangalam', + 'Ikkadu', + 'Valjevo', + 'Xidiancun', + 'Bangkinang', + 'Ermenek', + 'Bayaguana', + 'Bitburg', + 'North Smithfield', + 'Lagunas', + 'Chuquicamata', + 'Kannamangalam', + 'Taunggyi', + 'Bibipet', + 'Fangcun', + 'Baikatpur', + 'Sultanpur', + 'Rio de Mouro', + 'Dongcun', + 'Yali', + 'Gora Kalwaria', + 'Ottawa', + 'Achankovil', + 'Przemysl', + 'Ambohinamboarina', + 'Sangalbahita', + 'Ban Pet', + 'Segaon', + 'Watertown', + 'Baghmaria', + 'Meissen', + 'Chebli', + 'Santo Antonio do Descoberto', + 'Santa Rosa de Rio Primero', + 'Gold Canyon', + 'Marburg', + 'Bhadas', + 'Arcos', + 'Kediri', + 'Kasaoka', + 'Teisendorf', + 'Tadikombu', + 'Enger', + 'Hajan', + 'Fenggeling', + 'Guroymak', + 'Kolanpak', + 'Magam', + 'Marsberg', + 'Panagar', + 'Nove Mesto nad Vahom', + 'Koluszki', + 'Tan-Tan', + 'Dehdasht', + 'Karimganj', + 'Tekman', + 'Dudelange', + 'Remscheid', + 'Xiaolongtan', + 'Srisailain', + 'Pike Road', + 'Villaquilambre', + 'Neustadt', + 'Anserma', + 'Prerov', + 'Mandleshwar', + 'Entrerrios', + 'Andranopasy', + 'Ambohibe', + 'Eagle Mountain', + 'Kadimetla', + 'Obala', + 'Nichinan', + 'Kouloum', + 'Karumba', + 'Sagamihara', + 'Multi', + 'Imabari', + 'Vizela', + 'Chiquimula', + 'Okkerneutboom', + 'Faribault', + 'Seyhan', + 'Vinces', + 'Vijayapuri North', + 'Santa Clara del Cobre', + 'Yorkville', + 'Purisima de Bustos', + 'Belhi', + 'Balarampur', + 'Buluan', + 'Jefferson Valley-Yorktown', + 'Buchs', + 'Agua Boa', + 'Karayazi', + 'Raipur Buzurg', + 'Sisa', + 'Adigala', + 'Demba', + 'Tangerhutte', + 'Sidi Moussa Ben Ali', + 'Alpharetta', + 'Shangping', + 'Sabha', + 'Iguatemi', + 'Ragampet', + 'Andranovao', + 'Chitaga', + 'Olalapadi', + 'Chenalho', + 'Kent', + 'Gorele', + 'Fiumicino', + 'Carrboro', + 'Sihanoukville', + 'Cepin', + 'Mawlaik', + 'Melmuri', + 'Mapai', + 'Ciudad Lopez Mateos', + 'Santaluz', + 'Leninskoe', + 'Esbiaat', + 'Shterpce', + 'Sidi Abdelkarim', + 'Meoqui', + 'Wajimazakimachi', + 'Sulya', + 'Kangersuatsiaq', + 'Telpur', + 'Osmanabad', + 'Diourbel', + 'Waycross', + 'Hohenmolsen', + 'Mukhtarpur Salkani', + 'Campo Grande', + 'Willebroek', + 'Bamaur', + 'Vico Equense', + 'Andipalaiyam', + 'Peraia', + 'Ashtown', + 'Biro', + 'Weddington', + 'Arboletes', + 'Tauramena', + 'Bayserke', + 'Toretsk', + 'Ha Tinh', + 'Redondela', + 'Patcham', + 'Hilden', + 'New Corella', + 'San Francisco del Rincon', + 'Tlacolulan', + 'Letlhakane', + 'Rubanda', + 'Bohechio', + 'Boujediane', + 'Hacienda Heights', + 'Alderwood Manor', + 'Hendrik-Ido-Ambacht', + 'Dhangadhi', + 'Chanute', + 'Paraippatti', + 'Senftenberg', + 'Remich', + 'Nimbahera', + 'Kadrabad', + 'Taperoa', + 'Dharan', + 'Bidar', + 'Jiaozuo', + 'Waterford', + 'Nanao', + 'Verona', + 'Bawali', + 'Vellakkovil', + 'Lakkavaram', + 'Lyss', + 'Punnavalli', + 'Martil', + 'Melut', + 'Newburgh', + 'Qaskeleng', + 'Crevillente', + 'Assemini', + 'Kilankundal', + 'Rades', + 'Beidao', + 'Leiden', + 'Phu Ly', + 'Antehiroka', + 'Ouolodo', + 'Vaddepalli', + 'Andalusia', + 'Marechal Candido Rondon', + 'Malaut', + 'Port Shepstone', + 'Julich', + 'Spitak', + 'Si Wilai', + 'Oblesevo', + 'Nantes', + 'Totana', + 'Godhavn', + 'Augusto Correa', + 'Mirchpur', + 'Yzeure', + 'Pelabuhanratu', + 'Werneuchen', + 'Dospat', + 'Ikkarai Boluvampatti', + 'Sareh Mowndeh', + 'Ramiriqui', + 'Sorriso', + 'Wasi', + 'Indi', + 'Talavera de la Reina', + 'Lichuan', + 'Cachoeiro de Itapemirim', + 'Kariba', + 'Narita', + 'Dazhou', + 'Marmeleiro', + 'Wollongong', + 'Alwaye', + 'Fazilka', + 'Tynemouth', + 'Aramari', + 'Bantay', + 'Helmond', + 'Talegaon Dabhade', + 'Dowbaran', + 'Babak', + 'Country Walk', + 'Irara', + 'Simoes', + 'Surakarta', + 'Carapegua', + 'Lal Khatanga', + 'Nowshera', + 'Sao Leopoldo', + 'Pinrang', + 'Bouchegouf', + 'Kipili', + 'Yuzhne', + 'Narasimharajapura', + 'Halls', + 'Paco', + 'Tarija', + 'Vergina', + 'Sheridan', + 'Bastrop', + 'Cota', + 'Jinghong', + 'Aylestone', + 'Tlalixcoyan', + 'Tiszakecske', + 'Blackwells Mills', + 'Serarou', + 'Kherameh', + 'Kampung Baharu Nilai', + 'Gomboussougou', + 'Mulheim-Karlich', + 'Oshnaviyeh', + 'Ocnita', + 'Thornton Heath', + 'Enfield', + 'Castricum', + 'Rio dos Cedros', + 'West Chester', + 'Beanana', + 'Wiener Neudorf', + 'Takkolam', + 'Santo Antonio de Posse', + 'Les Abricots', + 'Higashi-Matsuyama', + 'Burutu', + 'Yingtan', + 'Valkeala', + 'Council Bluffs', + 'North Decatur', + 'Gaozhou', + 'Ghabrah', + 'Barun', + 'Mugla', + 'Olgiy', + 'Pramatam', + 'Ituacu', + 'Hihya', + 'Faridkot', + 'Zarand', + 'Escada', + 'Ambaguio', + 'Sao Joao Nepomuceno', + 'Bardoli', + 'Murakami', + 'Leinefelde', + 'Zawyat an Nwacer', + 'Banjul', + 'Genthin', + 'Susono', + 'Buin', + 'Vryheid', + 'El Tocuyo', + 'Pompton Lakes', + 'Ibipitanga', + 'Magwe', + 'Ocuilan de Arteaga', + 'Solleftea', + 'Areal', + 'Casal di Principe', + 'Hyvinkaa', + 'Kukdeshwar', + 'Ludlow', + 'Klamath Falls', + 'Husnabad', + 'Belwara', + 'Tonisvorst', + 'Shekhupur', + 'Yokaichiba', + 'Amlash', + 'Usumatlan', + 'Mascota', + 'Hemau', + 'Bhawana', + 'Dongshi', + 'Siriari', + 'Gulf Hills', + 'Khromtau', + 'Jalal-Abad', + 'Ayvalik', + 'Baza', + 'Knowsley', + 'Gibsonville', + 'Tondangi', + 'Uzunkopru', + 'Pedda Penki', + 'New Rochelle', + 'Limache', + 'Nasiyanur', + 'Pansemal', + 'Ottendorf-Okrilla', + 'Karukurti', + 'Xiangjiaba', + 'Varennes', + 'Del Gallego', + 'Riviera Beach', + 'Chicacole', + 'Marignane', + 'Nova Vas', + 'Dammaj', + 'Kayunga', + 'Wallaceburg', + 'Bangkalan', + 'Kandi', + 'Corman Park No. 344', + 'Lavinia', + 'Estelle', + 'Parthenay', + 'Datiana', + 'Tamzoura', + 'Banapur', + 'Campia Turzii', + 'Pylaia', + 'Las Heras', + 'Diamantino', + 'Dachepalle', + 'Vegarai', + 'Campbell', + 'Sao Cristovao', + 'Nossa Senhora do Socorro', + 'Ocotal', + 'Del Carmen', + 'Mangalvedha', + 'Bamba', + 'Yaojia', + 'Ecully', + 'Tadjourah', + 'Flanders', + 'Nordhragota', + 'Stutterheim', + 'Sher', + 'Seferhisar', + 'Lieshout', + 'Brignoles', + 'Alcoa', + 'Senta', + 'Isola della Scala', + 'Ekchari', + 'Formello', + 'Rio Bananal', + 'Uppalapadu', + 'Apatzingan de la Constitucion', + 'Yuancun', + 'Mandi Burewala', + 'Baraka', + 'Ban Bang Khu Lat', + 'Tradate', + 'Fangliao', + 'Santa Ana de Yacuma', + 'Castel San Giovanni', + 'Aachen', + 'Ginan', + 'Riviere-du-Loup', + 'Blain', + 'Alawandi', + 'Xo`jayli Shahri', + 'Samut Prakan', + 'Ban Wang Nok Aen', + 'Triprangottur', + 'Tumbes', + 'Vandam', + 'Bistan', + 'Punnaikkayal', + 'St. Francis', + 'Garforth', + 'Sultanbeyli', + 'Jalpaiguri', + 'Nelas', + 'Embrun', + 'Karia Ba Mohamed', + 'Guasavito', + 'Chiroqchi', + 'Leander', + 'Adrano', + 'Boudjima', + 'Ostroh', + 'Kodriva', + 'Meriden', + 'Nova Laranjeiras', + 'Barnoldswick', + 'Urbiztondo', + 'Marapanim', + 'Jiangshan', + 'Jamshoro', + 'Aki', + 'Lackawanna', + 'Kafr Nubl', + 'Siroda', + 'Whittier', + 'Junqueiro', + 'Karavaram', + 'Jaguariaiva', + 'Fort Stewart', + 'Alvarado', + 'Vicopisano', + 'Raseiniai', + 'Zolotonosha', + 'Arbutus', + 'Parsahi Sirsia', + 'Alcora', + 'Canutama', + 'Edmonton', + 'Kalaa Srira', + 'Shirayamamachi', + 'Orito', + 'Mahadebnagar', + 'Wallaroo', + 'Ciampino', + 'Sirumugai', + 'Zhangshicun', + 'Fanambana', + 'Los Guayos', + 'Santa Gertrudes', + 'Godome', + 'Formby', + 'Paramankurichi', + 'Caratinga', + 'Dayalpur Sapha', + 'Mahuakheraganj', + 'Pine Hill', + 'Zhosaly', + 'Kousseri', + 'Jabalpur', + 'Bad Lauchstadt', + 'Julu', + 'Cortazar', + 'Drexel Heights', + 'Bolzano', + 'Plano', + 'Grossenhain', + 'Ventaquemada', + 'Bechem', + 'Masaki', + 'Denpasar', + 'Gaggiano', + 'Roxas', + 'Wickede', + 'Gomoh', + 'Arklow', + 'Fossano', + 'Stralsund', + 'Kresek', + 'Pohrebyshche', + 'Siruvalur', + 'Karpi', + 'Adra', + 'Rafiganj', + 'Oftringen', + 'Ilirska Bistrica', + 'Anazzou', + 'Jonnagiri', + 'Norcross', + 'Sidi Bennour', + 'Berkine', + 'Ueckermunde', + 'Walsall', + 'Dingle', + 'Raxaul', + 'Chard', + 'Cataingan', + 'Ervadi', + 'Santiago', + 'Dallgow-Doberitz', + 'Poperinge', + 'Shankarpur', + 'Felton', + 'Nesebar', + 'Junction City', + 'Socorro', + 'Horb am Neckar', + 'Slavonski Brod', + 'Samaniego', + 'Bolingbrook', + 'Sombrerete', + 'Pastrana', + 'Bertoua', + 'Zungeru', + 'Ataleia', + 'Ath Thawrah', + 'Palisades Park', + 'Biatorbagy', + 'Perches', + 'Ashqelon', + 'Janjanbureh', + 'Stendal', + 'Quixabeira', + 'San Giovanni Valdarno', + 'Madepalli', + 'Bubanza', + 'Fatehpur Shahbaz', + 'Vigonza', + 'Pallikapuzha', + 'Stepney', + 'Sibiu', + 'Bodagudipadu', + 'Isidro Casanova', + 'Chatia', + 'Angor', + 'Ouro Verde', + 'Markala', + 'Sapkyo', + 'Amarapuuram', + 'Kannadiparamba', + 'Koula', + 'Laksar', + 'Uonuma', + 'Mainpuri', + 'Liberia', + 'Zarah Sharan', + 'Alcaniz', + 'Lakhaura', + 'Bansbari', + 'Kurikuppi', + 'Muan', + 'Sandrandahy', + 'Corail', + 'Sardrud', + 'Ambositra', + 'Zhengtun', + 'Ijebu-Ode', + 'Athol', + 'Menderes', + 'Shirbadgi', + 'Oaxaca', + 'Nogales', + 'Tekkattur', + 'Mobile', + 'Pazaryeri', + 'Hirakud', + 'Oyama', + 'Anrochte', + 'Mashiko', + 'Vobkent Shahri', + 'Neumunster', + 'Lingbao Chengguanzhen', + 'Elbistan', + 'Hakka', + 'Safranbolu', + 'Goulds', + 'Kadriye', + 'Komae', + 'Mahdia', + 'Peer', + 'Mbulungu', + 'Prince George', + 'Bir Ben Laabed', + 'Manukau City', + 'Isfana', + 'Buxton', + 'Hassfurt', + 'Paro', + 'Andimeshk', + 'Mockmuhl', + 'Leonora', + 'Healdsburg', + 'Pyrgos', + 'Dardilly', + 'Richardson', + 'Espiritu', + 'Dipolog', + 'Oyodo', + 'Concord', + 'Rye Brook', + 'Komoro', + 'Liedekerke', + 'Nishon Tumani', + 'Wyke', + 'Padangsidempuan', + 'Milton', + 'Princes Town', + 'Bandar-e Deylam', + 'Perry Hall', + "Ahmer el 'Ain", + 'Kampong Cham', + 'Vijayawada', + 'Kendal', + 'Sabinas', + 'Sihanamaro', + 'Kanye', + 'George Town', + 'Piquete', + 'Mullach Ide', + 'Fukushima', + 'Hohen Neuendorf', + 'Antsahabe', + 'Navoiy', + 'Riehen', + 'Castel Bolognese', + 'Virovitica', + 'Solihull', + 'Galaat el Andeless', + 'Endwell', + 'Langeloop', + 'Tuban', + 'Quaregnon', + 'Mastchoh', + 'La Chapelle-sur-Erdre', + 'Tapes', + 'Bac Ninh', + 'Tustin', + 'Leava', + 'Nagai', + 'Huatusco', + 'Kyjov', + 'Duque Bacelar', + 'Tanbei', + 'Copiague', + 'Langenzenn', + 'Iaciara', + 'Bimbo', + 'Guri', + 'Lake Ridge', + 'Star Dojran', + 'Nivala', + 'Damme', + 'Ambohitompoina', + 'Slovenj Gradec', + 'Najibabad', + 'Ziguinchor', + 'Chatenay-Malabry', + 'Mulanay', + 'Nakasi', + 'Guadalajara', + 'Bad Hersfeld', + 'Raciborz', + 'Sesto San Giovanni', + 'Walker', + 'Nonnweiler', + 'Dujiashigou', + 'Akune', + 'Malabang', + 'Konarka', + 'Chaves', + 'Yueqing', + 'Goias', + 'Isbergues', + 'Esenler', + 'Jovellanos', + 'Hajin', + 'Forest Acres', + 'Duchcov', + 'Keza', + 'Summit View', + 'Sokotindji', + 'Chitvel', + 'Tunis', + 'Sarkoy', + 'Nerang', + 'Berlaar', + 'Lukovica', + 'Bilthoven', + 'Muddebihal', + 'Thi Tran Mau A', + 'Fano', + 'Samtse', + 'Litian Gezhuang', + 'Dundalk', + 'Silistra', + 'Jangamguda', + 'Borna', + 'Bassar', + 'Gerstungen', + 'Talevad', + 'Worksop', + 'Sidlaghatta', + 'Chapulhuacan', + 'Guasdualito', + 'Garhi', + 'Cugir', + 'Epitacio Huerta', + 'Sankhavaram', + 'Sabotsy', + 'Nausori', + 'Purnea', + 'Ponnamaravati', + 'Sarkikaraagac', + 'Bourem', + 'Quepos', + 'South San Jose Hills', + 'Changchunpu', + 'Wissen', + 'Nova Canaa', + 'Whitburn', + 'Wysokie Mazowieckie', + 'Soacha', + 'Nagathan', + 'Daly City', + 'Antanimasaka', + 'Pura', + 'Petersfield', + 'Overlea', + 'Patjirwa', + 'Weldiya', + 'Iringa', + 'Guaruja', + 'Sitges', + 'Paola', + 'West End', + 'Nasice', + 'Mullanwala', + 'Nanjangud', + 'Jozefoslaw', + 'Zalau', + 'Petorca', + 'Zandhoven', + 'Xiashi', + 'Jaspur', + 'Pasuruan', + 'Bergen', + 'Gangneung', + 'Stargard Szczecinski', + 'President Roxas', + 'El Achir', + 'Beltangadi', + 'Iida', + 'Ardooie', + 'Swallownest', + 'Bairi Chak', + 'Zhijiang', + 'Shiyan', + 'Huzurabad', + 'Kampene', + 'Norden', + 'Chumpak', + 'Kokrajhar', + 'Arbelaez', + 'Mendig', + 'Independent Hill', + 'Etimesgut', + 'White Settlement', + 'Pottanur', + 'Shisui', + 'Mukkudal', + 'Mellila', + 'Pinagkaisahan', + 'Alcorta', + 'Dhantola', + 'San Carlos de Bariloche', + 'New Kingman-Butler', + 'Lawang', + 'Taki', + 'Khanpur', + 'Pirallahi', + 'Amatlan de los Reyes', + 'Kottapuram', + 'Mount Magnet', + 'Hunedoara', + 'Urayasu', + 'Provadia', + 'Siddarampuram', + 'Warfield', + 'Lemery', + 'Lidkoping', + 'Kalikiri', + 'Cravinhos', + 'Mukkanur', + 'Lakeland Highlands', + 'Pilani', + 'Al Mayadin', + 'Port Charlotte', + 'Jinshan', + 'Zeydabad', + 'Cantonment', + 'Nakhon Thai', + 'Dingras', + 'Kirippatti', + 'Haverford', + 'Rufisque', + 'Alagarai', + 'Bang Ban', + 'Querencia do Norte', + 'Puebloviejo', + 'Helena Valley Southeast', + 'Mezitli', + 'Pryor Creek', + 'Hem', + 'Antadinga', + 'Costa de Caparica', + 'Ghardimaou', + 'Shuinancun', + 'Shelek', + 'Saldanha', + 'Maues', + 'Apaseo el Grande', + 'Gendou', + 'Conceicao da Aparecida', + 'Burtonsville', + 'Eagle Pass', + 'Babhangawan', + 'Halle-Neustadt', + "Bahla'", + 'Matelandia', + 'Muddanuru', + 'Coroaci', + 'Morro Agudo', + 'Lipjan', + 'Mizusawa', + 'Cangola', + 'Montanha', + 'Rethymno', + 'Westwood Lakes', + 'Valpovo', + 'Cayambe', + 'Itapicuru', + 'Bourdoud', + 'Polasara', + 'Beltinci', + 'Armentieres', + 'Nuriston', + 'Hattian Bala', + 'Torre del Campo', + 'Hormigueros', + 'Tan An', + 'Sao Jose dos Campos', + 'Belem de Maria', + 'Ksar Lmajaz', + 'Ossining', + 'Tikota', + 'Matsumoto', + 'Reina Mercedes Viejo', + 'New Plymouth', + 'Ocean Pointe', + 'Zogbodome', + 'Ahlaf', + 'Southfield', + 'Nijverdal', + 'Mumias', + 'Vac', + 'Hevie', + 'Malumfashi', + 'Merignac', + 'Agua Prieta', + 'Parabcan', + 'Kako', + 'Chestermere', + 'Nijar', + 'Esanboy', + 'Loango', + 'Boulder City', + 'Pedda Mupparam', + 'Bender', + 'Snellville', + 'Aibongo', + 'Geraardsbergen', + 'Skillounta', + 'Zalingei', + 'Argyroupoli', + 'Steiner Ranch', + 'Meghraj', + 'Midrand', + 'Akola', + 'Sacacoyo', + 'Osogbo', + 'Kaukhali', + 'Cotui', + 'Nzalat Laadam', + 'Bunji', + 'East Meadow', + 'Yangasso', + 'Saint-Cyprien', + 'Banda Aceh', + 'Shipley', + 'Nelamangala', + 'Sidi Redouane', + 'Mahomet', + 'El Abadia', + 'Kanagicho', + 'Port Elgin', + 'Parkway', + 'Kashiwazaki', + 'Belabo', + 'Rajauli', + 'Vlist', + 'Lucheng', + 'Chania', + 'Garching bei Munchen', + 'Taurianova', + 'Kahhale', + 'Novellara', + 'Cerejeiras', + 'Maintal', + 'Old Orchard Beach', + 'Lecco', + 'Oirase', + 'Kailahun', + 'Tres Valles', + 'Sao Jose de Ribamar', + 'Pala', + 'Galikesh', + 'Phularwan', + 'Santo Tomas de los Platanos', + 'Sher Chakla', + 'Rumia', + 'Almasi', + 'Hasanpur Juned', + 'Acambaro', + 'Market Warsop', + 'Diankabou', + 'Yelahanka', + 'Florissant', + 'Daegu', + 'Neckarsulm', + 'Suan', + 'Abadan', + 'Piritu', + 'Tulancingo', + 'Al Malikiyah', + 'Hazel Grove', + 'Zhanlicun', + 'Shoreline', + 'Sutihar', + 'Kopa', + 'Conshohocken', + 'Cuilco', + 'Budaun', + 'Makhar', + 'Kondrukota', + 'South Huron', + 'Jambukuttaippatti', + 'Plabennec', + 'Benoni', + 'Maddikera', + 'Vredendal', + 'Aci Castello', + 'Solotvyno', + 'Tarhzout', + 'Webb City', + 'Shagamu', + 'Svendborg', + 'Manokwari', + 'Lalian', + 'Kings Mountain', + 'Sambhar', + 'Kovacica', + 'Sumperk', + 'Barra Bonita', + 'Cedeno', + 'El Paraiso', + 'Zeghanghane', + 'Parihara', + 'Chosica', + 'Allur', + 'Al Qatif', + 'Wadlakonda', + 'Itoshima', + 'Huckelhoven', + 'Lempaala', + 'Fleming Island', + 'Sewai', + 'Mahesh Khunt', + 'Cranbourne', + 'Bahia Honda', + 'Sanderstead', + 'Munagapaka', + 'Kamata', + 'Arakere', + 'Nasirabad', + 'Tennala', + 'Sumbal', + 'Mandaluyong City', + 'Sunne', + 'Skierniewice', + 'Dilarpur', + 'Qujing', + 'Minneapolis', + 'Abalessa', + 'Roosevelt', + 'Balatonfured', + 'Mel Palaiyam', + 'Maitum', + 'Umm el Fahm', + 'Nanpala', + 'Isahara', + 'Qantir', + 'Boucherville', + 'Port Townsend', + 'Sassuolo', + 'Lipno', + 'Vahdat', + 'Orastie', + 'Oakland', + 'Reus', + 'Arpacay', + 'Andenne', + 'Kafr Takharim', + 'Wakasa', + 'Rutigliano', + 'Marianao', + 'Norderstedt', + 'Orobo', + 'Helsinki', + 'Bria', + 'Wehr', + 'Melsungen', + 'Saint-Basile-le-Grand', + 'Keisen', + 'Khorramshahr', + 'Brighouse', + 'Mount Dora', + 'Bogor', + 'Ayni', + 'Karoi', + 'Kujri', + 'Codo', + 'Police', + 'Ascension', + 'Chaguaramas', + 'Montauban', + 'Kukichuo', + 'Ruti', + 'Abbottabad', + 'Zgornje Jezersko', + 'Bushkill', + 'Ilobu', + 'Allison Park', + 'Abreu e Lima', + 'Magdalena', + 'Natividade do Carangola', + 'Chiclayo', + 'Hard', + 'Singrauliya', + 'Thanh Phu', + 'Kraljevo', + 'Grossos', + 'Algonquin', + 'Nowgong', + 'San Pietro in Casale', + 'Si Racha', + 'Anjoma-Ramartina', + 'Somain', + 'Ronkonkoma', + 'Draper', + 'Gudikallu', + 'Anandnagar', + 'Bouknadel', + 'Torri di Quartesolo', + 'Kaluvaya', + 'Benato-Toby', + 'Bormujos', + 'Xinzhou', + 'Qingyuan', + 'Tiruppattur', + 'Rotherham', + 'Alawalpur', + 'West Perrine', + 'Sahab', + 'Brejo do Cruz', + 'Uchiko', + 'Lichfield', + 'Schwaz', + 'Prevost', + 'Srimushnam', + 'Isalo', + 'Malinagar', + 'Tucson Estates', + 'Santiago do Cacem', + 'Neuenhaus', + 'Sa`dah', + 'Diebougou', + 'Karmah an Nuzul', + 'Muradpur', + 'Hov', + 'Duffel', + 'Ankeny', + 'Selim', + 'San Juan Bautista', + 'La Lisa', + 'Upper Arlington', + 'Chagallu', + 'West Wickham', + 'Godhra', + 'Bilaspur', + 'Bijeraghogarh', + 'Misterbianco', + 'Immingham', + 'Lawndale', + 'Ham Lake', + 'Bytow', + 'Lahnstein', + 'Illertissen', + 'Carpi', + 'Uch Sharif', + 'Pariconia', + 'Teixeira de Freitas', + 'Newstead', + 'Chegga', + 'Potsdam', + 'Korinthos', + 'Palmital', + 'Undavalli', + 'Onteniente', + 'Ayutuxtepeque', + 'Parbhani', + 'Ban Noen Kum Nueng', + 'Sucha Beskidzka', + 'Pieve di Soligo', + 'Sioux City', + 'Bni Darkoul', + 'Mae O', + 'Nova Sintra', + 'Piratininga', + 'Massantola', + 'Alice Springs', + 'Tidili Masfiywat', + 'Mahaly', + 'Baglar', + 'Heartland', + 'Kuyucak', + 'Pili', + 'Katri', + 'Saladas', + 'Samarinda', + 'Okhtyrka', + 'Sulagiri', + 'Pluzine', + 'Di Linh', + 'Kabacan', + 'Koprubasi', + 'Bayanauyl', + 'Murray Bridge', + 'Sunchales', + 'Holzminden', + 'Solon', + 'Orosi', + 'Ofaqim', + 'Vadakkangara', + 'Tooele', + '`Adra', + 'Nagercoil', + 'Berriche', + 'Hyuga', + 'Santos Reyes Nopala', + 'Sezana', + 'Halifax', + 'Ambodisakoana', + 'Booneville', + 'Kirkenes', + 'Mahmutlar', + 'Bad Mergentheim', + 'Tnine Lgharbia', + 'Tanamarina-Sakay', + 'Escaudain', + 'Paterno', + 'Lepe', + 'Marui', + 'Lalitpur', + 'Las Veredas', + 'Pocos de Caldas', + 'Guasave', + 'Canterbury', + 'Rakhwari', + 'Benbutucun', + 'Harrisburg', + 'Casalgrande', + 'Mercaderes', + 'Celorico de Basto', + 'Goiania', + 'Thiruvananthapuram', + 'Padrauna', + 'Navarro', + 'Minturno', + 'Sarari', + 'Carugate', + 'Math Lohiyar', + 'Vairichettipalaiyam', + 'Leo', + 'Saposoa', + 'Vasa', + 'Heggadadevankote', + 'Po', + 'Rock Springs', + 'Kantilo', + 'Jedeida', + 'Pignon', + 'Izunokuni', + 'Shiyeli', + 'Mandiavato', + 'San Jose del Monte', + 'Ouenou', + 'Bouabout', + 'Kangaba', + 'Ulm', + 'Gwalior', + 'Recke', + 'Puruk Cahu', + 'Leonia', + 'Zapala', + 'Ban Na San', + 'Morant Bay', + 'Teonthar', + 'Douar Bou Tlelis', + 'Gondia', + 'Pulur', + 'Andonabe', + 'Santo Antonio do Jacinto', + 'Poldokhtar', + 'Setubal', + 'Mogalturru', + 'Tlahuelilpan', + 'Strood', + 'Pirapetinga', + 'Hamina', + 'Pompeia', + 'Pistoia', + 'Varre-Sai', + 'Tonosho', + 'Cisneros', + 'Bad Sackingen', + 'Yinggen', + 'Barwa Sagar', + 'Ayorou', + 'Chandhaus', + 'Mehsari', + 'Bobrovytsia', + 'McKinney', + 'Gernsbach', + 'Huejutla de Reyes', + 'Cintalapa de Figueroa', + 'Terra Alta', + 'Isemi-Ile', + 'Nishiwaki', + 'Siruma', + 'Leinfelden-Echterdingen', + 'Bernissart', + 'Dharampur', + 'Golden', + 'Beek', + 'Noordwijk', + 'Elixku', + "Piest'any", + 'Kilvisharam', + 'Andria', + 'Nanjakkad', + 'Sankaridrug', + 'Ondorhaan', + 'Campo Novo do Parecis', + 'Castellon de la Plana', + 'Houthulst', + 'Choi Hung', + 'Zabrze', + 'Madaya', + 'Alfafar', + 'Nili', + 'Robertsonpet', + 'Yedapalli', + 'Cidade de Nacala', + 'Mathibestad', + 'Oued Tlelat', + 'Cuetzalan', + 'Juli', + 'Xixinzhuangzhen', + 'Hanwell', + 'Pomezia', + 'Lucani', + 'Fastiv', + 'Bela Palanka', + 'Kumarkhali', + 'Golubac', + 'Svencionys', + 'Sesena', + 'Savigny-sur-Orge', + 'Cal', + 'Brown Deer', + 'Thessaloniki', + 'Jiroft', + 'Taoyang', + 'Beziers', + 'Pathraha', + 'Minalabac', + 'Genova', + 'Sarea Khas', + 'Mahabe', + 'Bela Vista', + 'Zawyat Ahancal', + 'Speyer', + 'Cicekdagi', + 'Ouled Brahim', + 'Yinying', + 'Aguas Vermelhas', + 'Lille', + 'Dillingen', + 'Silang', + 'Monsey', + 'Kalakada', + 'Comarapa', + 'Tit Mellil', + 'Sergio Osmena Sr', + 'Tabapua', + 'Nata', + 'Redlands', + 'Jaggisettigudem', + 'Loma de Cabrera', + 'Kasongo', + 'Kincardine', + 'Aukstieji Paneriai', + 'Sabana Grande', + 'Barwadih', + 'Eragny', + 'Mochudi', + 'New Ulm', + 'La Chorrera', + 'Teresa', + 'Pingtan', + 'Caete', + 'Wieruszow', + 'Torrelavega', + 'Waltham Cross', + 'Yaupi', + 'Becancour', + 'Sao Fidelis', + 'Koch', + 'San Martin de Valdeiglesias', + 'Akron', + 'Cruzeiro do Sul', + 'Halabjah', + 'Channapatna', + 'Aliquippa', + 'Tilehurst', + 'Sabaoani', + 'Berck-sur-Mer', + 'Marquetalia', + 'Shepparton', + 'Stalybridge', + 'Banos', + 'Rengam', + 'Andradina', + 'Andicun', + 'Changde', + 'Vacaville', + 'Sattegalam', + 'Dadukou', + 'Machida', + 'Quisqueya', + 'Solcava', + 'Annecy', + 'Golakpur', + 'Altenburg', + 'Vahdattiyeh', + 'Damoh', + 'Mojo', + 'Vercelli', + 'Simijaca', + 'Chainpur', + 'Esopus', + 'Dimasalang', + 'Grand Rapids', + 'Bowdon', + 'Socastee', + 'Sibila', + 'Miches', + 'Kalkuni', + 'Kananga', + 'Ain Legdah', + 'Songo', + 'Huandacareo', + 'Panazol', + 'Iacu', + 'Kelandis', + 'Miragoane', + 'Pote', + 'Shimabara', + 'Mariluz', + 'Anyuan', + 'Guisborough', + 'Hashtgerd', + 'La Esperanza', + 'Gitarama', + 'San Miguel de Tucuman', + 'Hialeah', + 'Lake Wales', + 'Orihuela', + 'Lakhzazra', + 'Marivan', + 'Whitehorse', + 'Arawa', + 'Ono', + 'Sidi Bou Ali', + 'Papa', + 'Guankou', + 'Marghita', + 'Takarazuka', + 'Sao Joao do Manhuacu', + "G'ijduvon Shahri", + 'Puerto Heath', + 'San Juanito de Escobedo', + 'Mattenhof', + 'Amriswil', + 'Semara', + 'Lokeren', + 'Nevers', + 'Ken Caryl', + 'Hungen', + 'Kehl', + 'Tysmenytsia', + 'Khaur', + 'Hawsh al Bahdaliyah', + 'Palasa', + 'Vaduz', + 'Munnuru', + 'Oranjestad', + 'Tallkalakh', + 'Elwood', + 'Tocaima', + 'Ostringen', + 'Fontoura Xavier', + 'Palau', + 'Eastleigh', + 'Ferry Pass', + 'Renigunta', + 'Gig Harbor', + 'Guma', + 'Angadikkal Tekkekara', + 'Pathri', + 'Neya', + 'Santa Ana Maya', + 'Campobello di Mazara', + 'Zhongshu', + 'Lorton', + 'Wankaner', + 'Claremont', + 'Casca', + 'Izumi', + 'Lodhwe', + 'Sidi Boushab', + 'Karcag', + 'Siyabuswa', + 'Simeria', + 'Nemby', + 'Tema', + 'Komatsushimacho', + 'Canhotinho', + 'Ubstadt-Weiher', + 'Mill Hill', + 'Stange', + 'Rincon', + 'Leteri', + 'Cavite City', + 'Msaken', + 'Maputsoe', + 'Martorell', + 'Villiers-le-Bel', + 'San Esteban', + 'Fruita', + 'Sagunto', + 'Monkayo', + 'Maurepas', + 'Jianguang', + 'Zetel', + 'Dordrecht', + 'Kangbao', + 'Linkou', + 'Virginia', + 'Freital', + 'Tut', + 'Tashi', + 'Lang Son', + 'Falun', + 'Tentena', + 'Cristino Castro', + 'Langwedel', + 'Joaima', + 'Maraiyur', + 'Mata Grande', + 'Estanzuela', + 'Rosemere', + 'Kloof', + 'Or `Aqiva', + 'Cerro Grande', + 'Villa Berthet', + 'Buena Park', + 'Balkanabat', + 'Yaguate', + 'Katagon', + 'Cantaura', + 'Stung Treng', + 'Shambu', + 'Froha', + 'Anzio', + 'Trichur', + 'Bhatranha', + 'Sideropolis', + 'Herbrechtingen', + 'Gent', + 'Burhaniye', + 'Odienne', + 'Hendon', + 'Rustenburg', + 'Ninohe', + 'Godollo', + 'Muelle de los Bueyes', + 'Astorga', + 'Nowy Dwor Mazowiecki', + 'Pilibangan', + 'Rapu-Rapu', + 'Ingham', + 'Sable-sur-Sarthe', + 'Andre Fernandes', + 'Khomeyni Shahr', + 'Languyan', + 'Schwyz', + 'Terranuova Bracciolini', + 'Sangan', + 'Nirpur', + 'Palmeira', + 'Aziylal', + "Vel'ky Krtis", + 'Babhnoul', + 'Ait Yaazem', + 'Usti nad Orlici', + 'Bonham', + 'Achampet', + 'San Baudilio de Llobregat', + 'Tres Arroyos', + 'Suva', + 'Hameln', + 'Oued Taria', + 'Tamalameque', + 'Aguinaldo', + 'Kitajima', + 'Bilhorod-Dnistrovskyi', + 'Glocester', + 'Nuevo Ideal', + 'Rialma', + 'Tukums', + 'Calhoun', + 'Ambalavero', + 'Idappadi', + 'Benevento', + 'Lormont', + 'Anndevarapeta', + 'Upton', + 'Zamalka', + 'Sasaram', + 'Bucksburn', + 'Segamat', + 'Zywiec', + 'Ribnitz-Damgarten', + 'Marly', + 'Mogwase', + 'Senaki', + 'Arroio Grande', + 'Grobbendonk', + 'Clarendon Hills', + 'Vyshneve', + 'Lomas del Mirador', + 'Royal Palm Beach', + 'Koloriang', + 'Niort', + 'Tazhakara', + 'Papanasam', + 'Arroio do Tigre', + 'Los Bajos', + 'Ostuncalco', + 'Jastrzebie-Zdroj', + 'Bejucal', + 'St. Clements', + 'Itanhem', + 'Bayamo', + 'Vejle', + 'Kishanpur Ratwara', + 'Chok Chai', + 'Oberhausen-Rheinhausen', + 'Karawang', + 'Lengir', + 'Katori', + 'Karapinar', + 'Talcahuano', + 'Ciudad de Huajuapam de Leon', + 'Hindupur', + 'Keller', + 'Fagaras', + 'Wodonga', + 'Tanakkangulam', + 'Pereira', + 'Clonmel', + 'Eunice', + 'Eberswalde', + 'Tottiyam', + 'Iquira', + 'Ergani', + 'Santa Clara', + 'Jaitpur', + 'Anzin', + 'Toumodi', + 'Sitio do Mato', + 'Drabar', + 'Srikhanda', + 'Ban Mae Hia Nai', + 'Shimubi', + 'Ozd', + 'Mucur', + 'Burdeos', + 'Hawthorne', + 'El Aouana', + 'Kirkuk', + 'Mekele', + 'Barguna', + 'Brzeg Dolny', + 'Santa Maria Tonameca', + 'San Luis', + 'Les Ponts-de-Ce', + 'Helong', + 'Marneuli', + 'Anjiamangirana I', + 'Punjai Kalamangalam', + 'Phan Thiet', + 'Beni Mellal', + 'Jose Panganiban', + 'Boxley', + 'Tamalpais-Homestead Valley', + 'Raisari', + 'Calanasan', + 'Kazanlak', + 'Buved', + 'Amarpatan', + 'Quang Tri', + 'Premia de Mar', + 'Nandimandalam', + 'Leeuwarden', + 'Difficult Run', + 'Bergamo', + 'Kuwana', + 'Paignton', + 'Puduparambubhagam', + 'Tranqueras', + 'Haren', + 'Wertingen', + 'Castelar', + 'Glenrothes', + 'Bilimora', + 'Funes', + 'Longview', + 'Afdem', + 'Soyalo', + 'Nasatta', + 'Takkali', + 'Downpatrick', + 'Khotyn', + 'Sidi Brahim', + 'Halewood', + 'Lawton', + 'Surampatti', + 'Arroio dos Ratos', + 'Ain Zohra', + 'Barking', + 'Sucre', + 'Amboanjo', + 'Lubalo', + 'Chachapoyas', + 'Villa Yapacani', + 'Vettakkaranpudur', + 'Montemarciano', + 'Aracatu', + 'Avigliano', + 'Roanne', + 'Nadur', + 'Weinan', + 'Heyuan', + 'Tabontabon', + 'Peterborough', + 'Dieli', + 'El Ach', + 'Ezanville', + 'Gore', + 'Bougoula', + 'Porlamar', + 'Sedona', + 'Saint-Louis', + 'Cebazat', + 'Nueva Guinea', + 'Sao Francisco de Assis', + 'Achi', + 'Mummidivaram', + 'Antsirabe', + 'Samalapuram', + 'Chandler', + 'Shuanghe', + 'Santana do Paraiso', + 'Ampondra', + 'Delavan', + 'Cacoal', + 'Abangaritos', + 'South Londonderry', + 'Triesenberg', + 'Amantea', + 'Bou Arkoub', + 'Tamponala', + 'Sogamoso', + 'Verdun', + 'Mali Zvornik', + 'La Democracia', + 'El Prat de Llobregat', + 'Borgloon', + 'Morwa', + 'Jarville-la-Malgrange', + 'Evans', + 'Peto', + 'Forde', + 'Grants', + 'Maglie', + 'Altagracia de Orituco', + 'Budaors', + 'El Affroun', + 'Azhikkod', + 'Hiramandalam', + 'Botad', + 'Rye', + 'Broadstairs', + 'Tilmi', + 'Harleysville', + 'Moncao', + 'Usti nad Labem', + 'Maracha', + 'Gaojiayingcun', + 'Castelnuovo di Verona', + 'Versmold', + 'Luzilandia', + 'Vernier', + 'Southold', + 'Nkhata Bay', + 'Chavkandak', + 'Paniqui', + 'South Milwaukee', + 'Mauranwan', + 'Sidi Ladjel', + 'Mozirje', + 'Mthatha', + 'Novotroitske', + 'Aracinovo', + 'Fulin', + 'Tatalon', + 'Carpentersville', + 'Vaijapur', + 'Zhongcheng', + 'Glendale', + 'Saugeen Shores', + 'Saint-Paul-les-Dax', + 'Kobeliaky', + 'Pai Bigha', + 'Bay St. Louis', + 'Bluffdale', + 'Zagora', + 'Careiro', + 'Karahrud', + 'Mudakkal', + 'Amuru', + 'Ismailia', + 'Chodavaram', + 'Kiyose', + 'Cerritos', + 'Ambondrona', + 'Penn Hills', + 'Le Taillan-Medoc', + 'Girua', + 'Rio Linda', + 'Poas', + 'Lucas do Rio Verde', + 'Pratapgarh', + 'Daddi', + 'Lifford', + 'Vandikarai', + 'Westhoughton', + 'Rajepur', + 'Monreal', + 'Loviisa', + 'Juanacatlan', + 'Hankey', + 'Amboise', + 'Bozkir', + 'Ha Tien', + 'Kucukcekmece', + 'Posse', + 'Buerarema', + 'Leixlip', + 'Huanren', + 'Bawgalegyi', + 'Manage', + 'Dumanjog', + 'Mulifanua', + 'Traun', + 'Pantelho', + 'Gundur', + 'Mirialguda', + 'Russelsheim', + 'Quixelo', + 'Chipata', + 'Macaubas', + 'Fujino', + 'Craig', + 'Iskapalli', + 'Solhan', + 'Costessey', + 'La Vega', + 'Espejo', + 'Perevalsk', + 'Middle Island', + 'Zavora', + 'Lorain', + 'Leninskiy', + 'Rossdorf', + 'Pembroke', + 'Weirton', + 'Villa Gonzalez', + 'Multan', + 'Patamundai', + 'Wicklow', + 'Florence', + 'Tibati', + 'Steti', + 'Ugento', + 'Messstetten', + 'Pestel', + 'Sujangarh', + 'Camanducaia', + 'Acarau', + 'Balagam', + 'Sungal', + 'Linyi', + 'Tukuyu', + 'Faetano', + 'Batgram', + 'Saundatti', + 'Roux', + 'Hermosa Beach', + 'Mavelikara', + 'Canapi', + 'Lovendegem', + 'Busogo', + 'Villeneuve-les-Avignon', + 'Phokeng', + "Sant'Arpino", + 'Ambasamudram', + 'Gobernador Virasora', + 'Langley Park', + 'Mungo', + 'Carrick on Shannon', + 'Warangal', + 'Haan', + 'Markt Indersdorf', + 'Ngororero', + 'Pavannur', + 'Boekel', + 'Sotteville-les-Rouen', + 'Sarai Alamgir', + 'Kutiyana', + 'Great Cornard', + 'Oiapoque', + 'Diondiori', + 'Xiangshui', + 'Benesov', + 'Borja', + 'Traunstein', + 'Cardito', + 'Evreux', + 'Camponogara', + 'Kladovo', + 'Oncativo', + 'Nazret', + 'Cachoeiras de Macacu', + 'Bayport', + 'Mandaguari', + 'Katha', + 'Scarborough', + 'Hotan', + 'Dinangorou', + 'Kilimli', + 'Killamarsh', + 'Bifoun', + 'Gorom-Gorom', + 'Reddish', + 'Wildomar', + 'Tlalmanalco', + 'Grafenhainichen', + "Sama'il", + 'Touwu', + 'Castillos', + 'Sipacapa', + 'Dionisio Cerqueira', + 'Maqsuda', + 'Iranduba', + 'Bharweli', + 'Irakleio', + 'Shima', + 'Zandvoort', + 'Edremit', + 'Vitomirice', + 'Laoaoba', + 'Kocakoy', + 'Barnet', + 'Majitha', + 'Minnehaha', + 'Holbaek', + 'Panchgram', + 'San Michele al Tagliamento', + 'San Agustin de Guadalix', + 'Fanjakana', + 'Piat', + 'Marjampad', + 'River Falls', + 'Gualan', + 'Capannori', + 'Takashima', + 'Siripur', + 'Antakotako', + "Qa'en", + 'Agadir Melloul', + 'West Jordan', + 'Dipalpur', + 'Ar Rudayyif', + 'New Hyde Park', + 'Rasdhoo', + 'Amakusa', + 'Jember', + 'North Richland Hills', + 'Andrainjato', + "Bo'ness", + 'Curridabat', + 'Wangaratta', + 'Mirante', + 'Pulppatta', + 'Prestwick', + 'El Tarra', + 'Ambodimanga II', + 'Beausoleil', + 'Raisio', + 'Puttur', + 'Diriamba', + 'Davyhulme', + 'Murukondapadu', + 'North Union', + 'Guayaramerin', + 'Mingguang', + 'Milaor', + 'Dongluocun', + 'Ghuenke', + 'Market Harborough', + 'Sainte-Savine', + 'Makaha', + 'Security-Widefield', + 'Donabate', + 'Wigan', + 'Tasquillo', + 'Villasagar', + 'Buikwe', + "Ouro Preto d'Oeste", + 'Roldanillo', + "Ait I'yach", + 'Camliyayla', + 'Cinderford', + 'Maraial', + 'Santo Stefano di Magra', + 'Anaiyur', + 'Kushk', + 'Amontada', + 'Marituba', + 'Cambe', + 'Horjul', + 'Seguela', + 'Wagin', + 'Schwandorf', + 'Lawaan', + 'Kadirli', + 'Vaughan', + 'East Retford', + 'Hinatuan', + 'Shoranur', + 'Rayen', + 'Bistrica ob Sotli', + 'Acharnes', + 'Rajsamand', + 'Aipe', + 'Faxinal', + 'Ollioules', + 'Ismaning', + 'Smartno', + 'Lamhadi', + 'Lecce', + 'Bredbury', + 'Adjarra', + 'Lichana', + 'La Troncal', + 'Sao Caetano do Sul', + 'Koppal', + 'Hansa', + 'Sirinhaem', + 'Weissenfels', + 'Aloha', + 'Tailai', + 'Jucuaran', + 'Summerstrand', + 'Amnat Charoen', + 'Kornwestheim', + 'Sorum', + 'Iarpur', + 'Minquan', + 'Srivaikuntam', + 'Almunecar', + 'Ban Don Thong', + 'Targu Jiu', + 'Tamparan', + 'Oyabe', + 'Stadtlohn', + 'Hazorasp', + 'Torredembarra', + 'Kalavad', + 'Ochakiv', + 'Williamsburg', + 'North Greenbush', + 'Victor Larco Herrera', + 'Karasu', + 'Sabnima', + 'Iramala', + 'Hoa Binh', + 'Naregal', + 'Concordia Sagittaria', + 'Portishead', + 'Zhaltyr', + 'Liberty Triangle', + 'Chegurumomadi', + 'Vila Junqueiro', + 'Bitlis', + "Tajura'", + 'Ambodivoara', + 'Highbury', + 'Nizza Monferrato', + 'Kesariya', + 'Walur', + 'Turbat', + 'Kars', + 'Colorado Springs', + 'Lashkar Gah', + 'Lavaur', + 'Nova Petropolis', + 'Goalpara', + 'Padra', + 'Fallbrook', + 'Imotski', + 'Domnesti', + 'Ilami', + 'Edewecht', + 'Playas de Rosarito', + 'Palo Negro', + 'Ban Song', + 'Batna', + 'Galt', + 'Ambatolava', + 'Le Cannet', + 'Urumita', + 'Ankily', + 'Missouri City', + 'Partap Tanr', + 'Margarita', + 'Vandiperiyar', + 'Kaminokawa', + 'Goldach', + 'Viamao', + 'Nainpur', + 'Ya`bad', + 'Palhano', + 'Chhatapur', + 'Commune Sidi Youssef Ben Ahmed', + 'Jucati', + 'Con Dao', + "Al Bayda'", + 'Badhoevedorp', + 'San Rafael Oriente', + 'Ndulo', + 'Az Zulfi', + 'Ban San Pong', + 'Antioch', + 'Stoke-on-Trent', + 'Sahuli', + 'Asagi Ayibli', + 'Palukudoddi', + 'Chorfa', + 'Campos Novos', + 'Borim', + 'Hueytown', + 'Anuppur', + 'Port Lincoln', + 'Sertanopolis', + 'Cradock', + 'Winsford', + 'Bakhchysarai', + 'Spreitenbach', + 'Arese', + 'Buug', + 'Paruchuru', + 'Lower Swatara', + 'Vinica', + 'Pottsville', + 'Moncalieri', + 'Lamzoudia', + 'Crestwood', + 'Damongo', + 'Greece', + 'Utinga', + 'Niamtougou', + 'Kallayi', + 'Trogir', + 'Osmaneli', + 'Friendly', + 'Kanchanpur', + 'Hawera', + 'Carupano', + 'Nador', + 'Grand-Couronne', + 'Ogbomoso', + 'Sahagun', + 'Bridgwater', + 'Xicotepec de Juarez', + 'Durham', + 'Jerez de los Caballeros', + 'North Hykeham', + 'Stannington', + 'Ibanda', + 'Batac', + 'Logten', + 'Hoorn', + 'Roussillon', + 'Minzhu', + 'Blindio', + 'Mainz', + 'Catarman', + 'Lumberton', + 'Payabon', + 'Sangeorz-Bai', + "Pau d'Alho", + 'Honggu', + 'Sauk Rapids', + 'Ortaklar', + 'Thousand Oaks', + 'Kocani', + 'Kudangulam', + 'Chicoloapan', + 'De Meern', + 'Qiblai', + 'Montevista', + 'San Felipe Orizatlan', + 'Lakatoro', + 'Badia Polesine', + 'Bad Kissingen', + 'Bom Jesus do Itabapoana', + 'Erenler', + 'Katwijk', + 'Sao Francisco do Sul', + 'Lila', + 'Na Sceiri', + 'Perumbakkam', + 'Leling', + 'Gladstone', + 'Al `Aqabah', + 'Waregem', + 'Mulgund', + 'Ambolomadinika', + 'Osorno', + 'Gorgab', + 'Udaipur', + 'Khomam', + 'Sindalakkundu', + 'Toukountouna', + 'Chebba', + 'Pettampalaiyam', + 'Baile Atha Luain', + 'Nurobod Shahri', + 'Kropyvnytskyi', + 'Guarapari', + 'Caidat Sidi Boubker El Haj', + 'Ala-Buka', + 'Turin', + 'Bucharest', + 'Gingoog', + 'Youngsville', + 'Angwali', + 'Orikhiv', + 'Hofn', + 'Varzea da Palma', + 'Lower Makefield', + 'Chillicothe', + 'Seabrook', + 'Phetchaburi', + 'Fujisawa', + 'Matara', + 'Kepno', + 'Alden', + 'Omigawa', + 'Bayombong', + 'Toride', + 'Mirabela', + 'Tullukuttinayakkanur', + 'Ugong', + 'Zhangye', + 'Mangha', + 'Krsko', + 'Comrat', + 'Sudbury', + 'Almaty', + 'Sonamukhi', + 'Terrabona', + 'Windsor', + 'Coralville', + 'Capela', + 'Qufu', + 'Stallings', + 'Kingaroy', + 'Downham Market', + 'Calapan', + 'Aizuwakamatsu', + 'Kaintragarh', + 'Inhuma', + 'Pangururan', + 'Scotchtown', + 'La Marque', + 'Iwata', + 'Zrece', + 'Waldheim', + 'Ajmer', + 'Pamekasan', + 'Mafune', + 'Weiyuan', + 'Gunzburg', + 'Drohobych', + 'Damal', + 'Sigtuna', + 'Campulung', + 'Patancheruvu', + 'Gavardo', + 'Acushnet', + 'Poiana Mare', + 'Tarsus', + 'Betanzos', + 'Chau Doc', + 'Cacequi', + 'Lalsaraia', + 'Aweitancun', + 'Gia Rai', + 'Corat', + 'Arcos de la Frontera', + 'Akassato', + "Samch'ok", + 'Lake Tapps', + 'Aine Draham', + 'Vertou', + 'Neenah', + 'Tanhacu', + 'Shimla', + 'Keal', + 'Rani', + 'Kadaiyanallur', + 'Sefwi Wiawso', + 'Shankarpur Khawas', + 'Veliyangod', + 'Senador Guiomard', + 'Danzao', + 'Ndele', + 'Pornic', + 'Nowy Tomysl', + 'Scenic Oaks', + 'Ursulo Galvan', + 'Freudenberg', + 'Araruna', + 'Popayan', + 'Centereach', + 'Kamaishi', + 'Sainte-Marthe-sur-le-Lac', + 'Imilchil', + 'Zimapan', + 'Meschede', + 'Puthuppariyaram', + 'Paso de los Libres', + 'Longbenton', + 'Kampong Speu', + 'Novy Jicin', + 'Jinggang', + 'Asahi', + 'Khokri Kalan', + 'Kaveripatnam', + 'Lviv', + 'Bowling Green', + 'Braganca', + 'Panjgirain Kalan', + 'Emeryville', + 'Ruhango', + 'Tarim', + 'Castiglione del Lago', + 'Kyiv', + 'Mineral Wells', + 'Burnsville', + 'Franeker', + 'Teignmouth', + 'Canas', + 'Ajjampur', + 'Barhauna', + 'Zacharo', + 'Fujimi', + 'Billerica', + 'Falconara Marittima', + 'Alto Araguaia', + 'Tak Bai', + 'Bishops Cleeve', + 'Orhangazi', + 'Spokane Valley', + 'Kovel', + 'Walworth', + 'Zushi', + 'Aglasun', + 'Romainville', + 'Sorsogon', + 'Castellamonte', + 'Des Plaines', + 'Xai', + "'Ain Abid", + 'Plattekill', + 'Sinzheim', + 'Cipanas', + 'Paliaturutu', + 'Katwe', + 'Lorenskog', + 'San Tung Chung Hang', + 'Abrego', + 'Viana', + 'Ishtixon Shahri', + 'Kharika', + 'Sautron', + 'San Narciso', + 'Hilton Head Island', + 'Lingolsheim', + 'Bornheim', + 'Koregaon', + 'Matao', + 'Sumida', + 'Ketsch', + 'Gopalnagar', + 'Rodez', + 'Canelones', + 'Tolten', + 'Crawley', + 'Tillsonburg', + 'Wislane', + 'Mortsel', + 'Akitakata', + 'Halasztelek', + 'Herselt', + 'Kartal', + 'Balabac', + 'Ungaran', + 'Da', + 'Sao Joao del Rei', + 'Ghanipur Bejha', + 'Al Quway`iyah', + 'Zalaegerszeg', + 'Carquefou', + 'Huanimaro', + 'Ponte Nova', + 'Monte Azul', + 'Melito di Porto Salvo', + 'Shimenzhai', + 'Pinehurst', + "K'ebri Beyah", + 'Molsheim', + 'Konigstein im Taunus', + 'Taranagar', + 'Farciennes', + 'Bhilai', + 'Mathurapur', + 'Port-a-Piment', + 'Kyegegwa', + 'Ortakoy', + 'Antonibe', + 'Mercedes Umana', + 'Coronel', + 'Pedrinhas', + 'Suhagi', + 'Jaboatao dos Guararapes', + 'Oxon Hill', + 'Jalolquduq', + 'Morros', + 'Sumbas', + 'Chingola', + 'Rada Tilly', + 'Kolachel', + 'Bandarawela', + 'Pallattur', + 'Carauari', + 'Colares', + 'Santa Fe do Sul', + 'Musapur', + 'Arni', + 'Negage', + 'Graham', + 'Lake Grove', + 'Tolosa', + 'Marlboro', + 'Kandrawan', + 'Ponte Serrada', + 'Shanghai', + 'Ouedeme', + 'Saki', + 'Chembra', + 'Syston', + 'Palmeirais', + 'Borlange', + 'Bordj Bounaama', + 'Montceau-les-Mines', + 'Zhufeng', + 'Frei Paulo', + 'Western Springs', + 'Hemmingen', + 'Santa Cruz del Sur', + 'Pleasantville', + 'Chandla', + 'Maihma Sarja', + 'Sacaba', + 'Reynoldsburg', + 'Highland', + 'Sunyani', + 'Ajnala', + 'Ourem', + 'Zaltan', + 'Vannikkonendal', + 'Suchindram', + 'Metouia', + 'Bandar-e Lengeh', + 'Sestu', + 'Troisdorf', + 'Tibasosa', + "Villenave-d'Ornon", + 'Huseni', + 'Komorniki', + 'Ankerana', + 'Planura', + 'Saguenay', + 'Xiangping', + 'Almendralejo', + 'Slawno', + 'Bir Ghbalou', + 'Luna', + 'Tiaret', + 'Pulakurti', + 'General Santos', + 'Shibancun', + 'Lansing', + 'Lake Los Angeles', + 'Hala', + 'Port Washington', + 'Cortalim', + 'Apostoles', + 'Radnevo', + 'Reina Mercedes', + 'Tivoli', + 'Gomishan', + 'Maigh Nuad', + 'Gujiao', + 'Sorvagur', + 'Milpitas', + 'Ramnagar Bankat', + 'Longyearbyen', + 'Sarauni', + 'Angel R. Cabada', + 'Brooksville', + 'Bochaha', + 'Alguazas', + 'Tigaraksa', + 'Guiguinto', + 'Granbury', + 'Aiyetoro Gbede', + 'Zenica', + 'Itsoseng', + "Al Madrah Sama'il", + 'Murtajapur', + 'Gyongyos', + 'Kabale', + 'Armiansk', + 'Bandar-e Kong', + 'Makhambet', + 'Naval', + 'Issoudun', + 'Valmiera', + 'Virapandiyanpattanam', + 'Beirut', + 'Tagbilaran City', + 'El Factor', + 'Banikane', + 'Khartoum', + 'Jiangna', + 'Bhawanipatna', + 'Aungban', + 'Leonding', + 'Limeil-Brevannes', + 'Obernkirchen', + 'Nova Trento', + 'Zambezi', + 'Leith', + 'Gessate', + 'Huguan Nongchang', + 'Tirupati', + 'Cilimli', + 'Brie-Comte-Robert', + 'Cebu City', + 'Patti', + 'Germi', + 'Jaleshwar', + 'Piliscsaba', + 'Devipattinam', + 'Chato', + 'Khartoum North', + 'Brushy Creek', + 'Vlagtwedde', + 'Amacuzac', + 'Kosigi', + 'Aranjuez', + 'Leama', + 'Roanoke', + 'Limpio', + 'Kemalpasa', + 'Haisyn', + 'Peoria', + 'Stade', + 'In Guezzam', + 'Gashua', + 'Nerubaiske', + 'Ratlam', + 'Mahmutlu', + 'Srirampuram', + 'Veldurti', + 'Wemmel', + 'Phichit', + 'Dibaya-Lubwe', + 'Madurai', + 'Sao Goncalo do Para', + 'Vitry-le-Francois', + 'Anantarazupeta', + 'Boudinar', + 'Zschopau', + 'Quellon', + 'Longonjo', + 'Hexham', + 'Mangqu', + 'Hoxut', + 'Twickenham', + 'Gueckedou', + 'Labbaikkudikkadu', + 'Mittenwalde', + 'Hatton', + 'Feldbach', + 'Lahij', + 'Sasan', + 'Miastko', + 'Waitakere', + 'Shizuoka', + 'West Warwick', + 'Galati', + 'Biu', + 'Kudatini', + 'Viradouro', + 'Saffle', + 'Gyumai', + 'Zhongshan', + 'Picayune', + 'Antaly', + 'Anse-a-Veau', + 'Saint Peter Port', + 'Ambohitrolomahitsy', + 'Avadattur', + 'Calasiao', + 'Bambang', + 'Warder', + 'Koforidua', + 'Tangier', + 'Duggirala', + 'Glenville', + 'Bagrinagar', + 'Ingelmunster', + 'Carrigaline', + 'Ashtabula', + 'Galeras', + 'Ulchin', + 'Paarl', + 'Bandung', + 'Roxana', + 'Gulu', + 'Kesavapuram', + 'Beantake', + 'Yokoshiba', + 'Kaohsiung', + 'Piranguinho', + 'Port-de-Bouc', + 'Lakri', + 'Cadereyta Jimenez', + 'Merad', + 'Centralia', + 'Putte', + 'Baozhong', + 'Tougan', + 'Knutsford', + 'Estancia Velha', + 'Nawada', + 'Gangelt', + 'Frontignan', + 'Chiconcuac', + 'Gunnedah', + 'Lingen', + 'Bozmargi', + 'Porac', + 'St. Peter', + 'East Hempfield', + 'Tapalpa', + 'Jolfa', + 'Photharam', + '`Ajman', + 'Altunhisar', + 'Staufenberg', + 'Fgura', + 'Heist-op-den-Berg', + 'Colima', + 'Hoogstraten', + 'Oi', + 'Rustington', + 'Vandamettu', + 'Manzanares el Real', + 'Laohekou', + 'Szigethalom', + 'Colindres', + 'Harrow', + 'Kishanganj', + 'Brejo Santo', + 'Dalfsen', + 'Werdau', + 'Rijkevorsel', + 'Coondapoor', + 'Kagizman', + 'Pardi', + 'Paombong', + 'Pitseng', + 'Chikwawa', + 'Togou', + 'Akanavaritota', + 'Ojus', + 'Buhl', + 'Ganjing', + 'Sona', + 'Vayalpad', + 'Taishacho-kizukikita', + 'Ocoee', + 'Noda', + 'Nurdagi', + 'Maidan Shahr', + 'Culleredo', + 'Paravurkambolam', + 'Olintepeque', + 'Kien Giang', + 'Kunoy', + 'Zawyat Sidi al Mekki', + 'Aleksinac', + 'Mandalay', + 'La Laguna', + 'Kanabur', + 'San Miguel Panan', + 'Batumi', + 'Gambolo', + 'Cibitoke', + 'Swatara', + 'Ganga', + 'Cotorro', + 'Skofljica', + 'Piranhas', + 'Aricak', + 'Darb-e Behesht', + 'Ampana', + 'North Bellmore', + 'La Roche-sur-Yon', + 'Challapata', + 'Binghamton', + 'Hatod', + 'Gouveia', + 'Gardendale', + 'Newnan', + 'Le Haillan', + 'Lencois Paulista', + 'Dangriga', + 'Mallan', + 'Forest Park', + 'Chiyoda', + 'Pirenopolis', + 'Sao Joao do Piaui', + 'Mahavanona', + 'Hanzhong', + 'Madalag', + 'Los Osos', + 'Guatajiagua', + 'Berri', + 'Lardero', + 'El Jicaral', + 'Ter Apel', + 'Kola', + 'Vasteras', + "Al 'Attawia", + 'Pamplona', + 'Tomobe', + 'Bejaia', + 'Kullorsuaq', + 'Arganil', + 'Comapa', + 'Dehqonobod', + 'Gaoping', + 'Buntok', + 'Somerton', + 'Herstal', + 'Igualada', + 'Tumaco', + 'Saint Joseph', + 'Mandeville', + 'Monterrey', + 'Jamindan', + 'Nguti', + 'Sidi Lahssen', + 'Redruth', + 'Akcakoca', + 'Jankampet', + 'Tataouine', + 'Manizales', + 'Narasannapeta', + 'Karempudi', + 'St. Augustine', + 'Cumanayagua', + 'Montesilvano', + 'Gracias', + 'Congaz', + 'Poso', + 'Humenne', + 'Shalqar', + 'Lago Ranco', + 'Morogoro', + 'Qingan', + 'Anini-y', + 'Marche-en-Famenne', + 'Erseke', + 'Presidente Venceslau', + 'Abomey', + 'Dorohoi', + 'Talipparamba', + 'Bonito', + 'Algarrobo', + 'Yamoussoukro', + 'Lafey', + 'Kadi', + 'Beberibe', + 'Yurihama', + 'Viana do Castelo', + 'Assen', + 'Sniatyn', + 'Spanish Fort', + 'Xintianfeng', + 'Bukungu', + 'Xiaqiaotou', + 'Peshtera', + 'Hueyapan de Ocampo', + 'Marreddipalli', + 'Kaimati', + 'Isabela', + 'Selfoss', + 'Delhi', + 'Fujikawaguchiko', + 'Marutharod', + 'Bom Repouso', + 'Jagadhri', + 'El Valle', + 'Zlotoryja', + 'Grover Beach', + 'Hashtpar', + 'San Alejo', + 'Nazarje', + 'Hillcrest', + 'Sananduva', + 'Peissenberg', + 'Bada Barabil', + 'Watampone', + 'Nove Mesto nad Metuji', + 'Karankot', + 'Pucon', + 'Guangshui', + 'Falls Church', + 'Danihe', + 'Mudakkiraye', + 'Ceuti', + 'Fussen', + 'Zaghouan', + 'Yongbei', + 'Hong Ngu', + 'Conchas', + 'Manivilundan', + 'Tadhwa Nandpur', + 'Monistrol-sur-Loire', + 'Chateaubriant', + 'Sangar', + 'Fafe', + 'Shoshong', + 'Madang', + 'Marcy', + 'Puttlingen', + 'Groves', + 'Al Bahah', + 'Gorham', + 'Afogados da Ingazeira', + 'Knokke-Heist', + 'Pariyaram', + 'Zagreb', + 'Winton', + 'Les Iles-de-la-Madeleine', + 'Kusterdingen', + 'Paraguari', + 'Kuknur', + 'Black River', + 'Baishaling', + 'Sigulda', + 'Uppsala', + 'Selm', + 'Muy Muy', + 'Paracale', + 'Luleburgaz', + 'La Llagosta', + 'Demirci', + 'Kalasin', + 'Ibajay', + 'Ceska Lipa', + 'Camocim de Sao Felix', + 'Ogose', + 'Menlo Park', + 'Ouedo-Agueko', + 'Karakurisshi', + 'Franklin Lakes', + 'Ismayilli', + 'Gottingen', + 'Melmadai', + 'Meulaboh', + 'Ogre', + 'Bad Abbach', + 'Longmen', + 'Awka', + 'Braco do Norte', + 'Bigadic', + 'General Pacheco', + 'Sint-Oedenrode', + 'Annamalainagar', + 'Angamali', + 'Julio de Castilhos', + 'Capao Bonito', + 'Poynton', + 'Santa Cruz Atizapan', + 'Ekeren', + 'Gandara West', + 'Metairie', + 'Katsuren-haebaru', + 'Dupnitsa', + 'Tricase', + 'Mokpo', + 'Dassari', + 'Givatayim', + 'Jambai', + 'Hamsavaram', + 'Foggia', + 'Scott', + 'Inkhil', + 'Catamayo', + 'Nana', + 'Ntchisi', + 'Olomouc', + 'Madison Heights', + 'Genzano di Roma', + 'Vieux Fort', + 'Kagvad', + 'Motegi', + 'Breves', + 'Myitkyina', + 'Aqkol', + 'Al `Ashir min Ramadan', + 'Ariano Irpino', + 'Restrepo', + 'Jeumont', + 'Drazinda', + 'San Pedro de Ribas', + 'Chintakommadinne', + 'Tank', + 'Preili', + 'Konakli', + 'Julita', + 'Meda', + 'Ciudad Obregon', + 'Barabai', + 'Gizycko', + 'Boureit', + 'Arambagh', + 'Bonito Oriental', + 'Mbanga', + 'Chemmanam', + 'Kakira', + 'Mundka', + 'Noniya', + 'Marianske Lazne', + 'Khanaqin', + 'Pajacuaran', + 'Ain el Bya', + 'Bacliff', + 'Sewari', + 'Altusried', + 'Zinapecuaro', + 'Matsuo', + 'Vasylivka', + 'Peda-Houeyogbe', + 'Nossa Senhora dos Remedios', + 'Chaltabaria', + 'Hailakandi', + 'Poznan', + 'Taoudenni', + 'Sidhwan', + 'Guadalajara de Buga', + 'Rozhyshche', + 'Caracarai', + 'Suwannaphum', + "Bur Fu'ad", + 'Fderik', + 'Mezotur', + 'Las Vegas', + 'Limassol', + 'Jursinci', + 'Taggia', + 'Poco das Trincheiras', + 'Douar Oulad Naoual', + 'Calarca', + 'Mont-Tremblant', + 'Belterra', + 'Sheikhpura', + 'North Olmsted', + 'Ramdurg', + 'Orumanayur', + 'Villafranca de los Barros', + 'Esztergom', + 'Payakaraopeta', + 'Ramchandrapur', + 'Khajamahalpur', + 'Najafgarh', + 'Athni', + 'Mikkelin Maalaiskunta', + 'Bijnor', + 'Ighram', + 'Dragash', + 'Thargomindah', + 'Castel Maggiore', + 'Belambo', + 'Nueva Palmira', + 'Kurabur', + 'Yahsihan', + 'Chesterton', + 'Konduru', + 'Tordesillas', + 'Biala Podlaska', + 'Katsuragi', + 'South Bradenton', + 'Naranja', + 'Okhmalynka', + 'Newtownards', + 'Pimenta Bueno', + 'Naraura', + 'Craponne', + 'Auta', + 'Kumaravadi', + 'Anajas', + 'Mouiat Ouennsa', + 'Norvenich', + 'Jaruco', + 'Stockelsdorf', + 'Moron de la Frontera', + 'Dalmine', + 'Le Pont-de-Claix', + 'Ambahy', + 'Fianga', + 'Pinecrest', + 'San Bonifacio', + 'Carpinteria', + 'Stip', + 'Ergolding', + 'Lokossa', + 'Siniloan', + 'Seeheim-Jugenheim', + 'Nottingham', + 'Abrantes', + 'San Felipe Jalapa de Diaz', + 'Boninal', + 'Digor', + 'Hosur', + 'Trepuzzi', + 'Koidu', + 'Kumta', + 'Paracatu', + 'Shinkai', + 'Thuan An', + 'Pokaran', + 'Betzdorf', + 'Binde', + 'Ghazni', + 'Vembur', + 'Pontivy', + 'Juterbog', + 'Sheerness', + 'Chhindwara', + 'Alagoinhas', + 'Itabira', + 'San Jose Ojetenam', + 'Mazatenango', + 'Nabinagar', + 'Manavgat', + 'Ovenden', + 'Lagkadas', + 'Gaildorf', + 'Chirpan', + 'Vidalia', + 'Maracaju', + 'Pindwara', + 'Nova Dubnica', + 'Mokarrampur', + 'Bocaue', + 'Warsop', + 'Florida', + 'Gangajalghati', + 'Monte Alegre do Sul', + 'Kibi', + 'Pirakkad', + 'Bani `Ubayd', + 'Tiberias', + 'Palombara Sabina', + 'Rongat', + 'Wabagai', + 'Maceio', + 'Agliana', + 'Bell', + 'Yosano', + 'Potiragua', + 'Mannamangalam', + 'Yerevan', + 'Valledupar', + 'Faversham', + 'Xavantes', + 'Embrach', + 'Desanagi', + 'Ciudad de Ceuta', + 'Contagem', + 'As Sib', + 'Kulgam', + 'Kanigiri', + 'Kuljibrin', + 'Palm Beach Gardens', + 'Mugnano di Napoli', + 'Chunian', + 'Merzig', + 'Dhilwan Kalan', + 'Stainz', + 'Betera', + 'Vallur', + 'Seva', + 'Puerto Lopez', + 'Inekar', + 'Suong', + 'Tiruvottiyur', + 'Baghlan', + 'Ilo', + 'Bautzen', + 'Koutiala', + 'Allahabad', + 'Lhokseumawe', + 'San Juan del Sur', + 'Taua', + 'Sapa Sapa', + 'Otuke', + 'Sabra', + 'Kutahya', + 'Coccaglio', + 'Mamun', + 'Billerbeck', + 'Weil am Rhein', + 'Lajia', + 'Argentan', + 'Ninheira', + 'Aranya Kalan', + 'Lumding', + 'Comendador Levy Gasparian', + 'Lilongwe', + 'Cerro Largo', + 'Oosterhout', + 'Oswestry', + 'Gaspar', + 'Videle', + 'Maumelle', + 'Azezo', + 'Osuna', + 'Pulla', + 'Yangshuling', + 'Khirkiyan', + 'Hamilton', + 'Kalgoorlie', + 'Rejiche', + 'Aylesford', + 'Nagaoka', + 'Monterey Park', + 'Potavaram', + 'Jequeri', + 'Boisbriand', + 'Meliana', + 'Quilenda', + 'Santa Leopoldina', + 'Lakeland', + 'Eatontown', + 'Basilisa', + 'Wernau', + 'Sassenheim', + 'Twistringen', + 'Talitay', + 'East Wenatchee', + 'Jinbi', + '`Anbarabad', + 'Shimanto', + 'Caputira', + 'Goppingen', + 'Turuttikkara', + 'Zhongtai', + 'San Francisco Zapotitlan', + 'Hazebrouck', + 'Clemencia', + 'Surandai', + 'Khijri', + 'Jacarezinho', + 'Lunsar', + 'Ouled Sidi Brahim', + 'Chaqra', + 'Distraccion', + 'Eppelborn', + 'Vakhrusheve', + 'San Carlos del Zulia', + 'Khairpur Tamewah', + 'Shongzhy', + 'Ban Bang Kaeo', + 'Murillo', + 'Ban Mai', + 'Lakoucun', + 'Kottaikuppam', + 'Sini', + 'Karditsa', + 'Penumur', + 'Ramos', + 'Arauco', + 'Chatayamangalam', + 'Nieuw-Lekkerland', + 'Ixhuatlan del Sureste', + 'Bog Walk', + 'Daltonganj', + 'Guamo', + 'Kiklah', + 'Bongouanou', + 'Las Pinas City', + 'Narni', + 'Olesa de Montserrat', + 'Barrancabermeja', + 'Souama', + 'Sokolow Podlaski', + 'Katakwi', + 'Sunset', + 'Dilawarpur', + 'Telagh', + 'Caapiranga', + 'Pavittiram', + 'Yunzhong', + 'Paicandu', + 'Farafangana', + 'Zawyat Sidi Ben Hamdoun', + 'Bosilegrad', + 'Cabanaquinta', + 'Eaubonne', + 'El Paujil', + 'Matca', + 'Rawdon', + 'Tongoma', + 'Monsefu', + 'Itaguara', + 'Mihama', + 'Alengad', + 'Zhumadian', + 'Schlieren', + 'Murree', + 'Seto', + 'Yenipazar', + 'Kharak Kalan', + 'Ambares-et-Lagrave', + 'Kawlin', + 'Sainte-Julienne', + 'Khagaul', + 'Dyersburg', + 'Ekuvukeni', + 'Virginopolis', + 'Zhenzhou', + 'Jiashizhuangcun', + 'Changchong', + 'Notodden', + 'Stephenville', + 'Rellingen', + 'Keolari', + 'Los Alcarrizos', + 'Brake', + 'Yuzawa', + 'Cabatuan', + 'Kanegasaki', + 'Vohiposa', + 'Arequipa', + 'Antseza', + 'Puspokladany', + 'Natick', + 'Astrea', + 'Auchel', + 'Aquidauana', + 'Mohlin', + 'Redcar', + 'Brookline', + 'Jekabpils', + 'Melville', + 'Pintadas', + 'Manfalut', + 'Aasiaat', + 'Sao Borja', + 'Ransiki', + 'Ibipetuba', + 'Kuala Lumpur', + "Tong'anyi", + 'Eklahra', + 'Satara', + 'Pupri', + 'Gualcince', + 'Friedberg', + 'Sinnai', + 'Saynshand', + 'Bupoto', + 'Kalanjur', + 'Hilversum', + 'Shobara', + 'Mahaplag', + 'Puturge', + 'Oued Jdida', + 'Melnik', + 'Pointe-Noire', + 'Tipton', + 'Tekari', + 'Ounagha', + 'Cavriago', + 'Lupon', + 'Paoay', + 'Follonica', + 'Bhachhi Asli', + 'Hofheim', + 'Kasukabe', + 'Viligili', + 'Tinton Falls', + 'Limay', + 'Pauktaw', + 'Sedziszow Malopolski', + 'Citrus', + 'Chirongui', + 'Mitane', + 'Payimattam', + 'Pajaros', + 'Gelves', + 'Tupi Paulista', + 'Pokhuria', + 'Thetford Mines', + 'Iselin', + 'Bandar Murcaayo', + 'Taungoo', + 'Hikawadai', + 'Los Santos', + 'Marbella', + 'Vranje', + 'Squamish', + 'Mahthi', + 'Toura', + 'Cicciano', + 'Wernigerode', + 'Xiaobazi', + 'Reforma', + 'Ispir', + 'Ntcheu', + 'Samboan', + 'Kelamangalam', + 'Diglipur', + 'Malkhaid', + 'Tomakomai', + 'Vidhareidhi', + 'Rio Grande City', + 'Puerto Libertador', + 'Ariccia', + 'Mashhad', + 'Renkum', + 'Sakabansi', + 'Dandeli', + 'Gahanna', + 'Waupun', + 'Sonoma', + 'Kutchan', + 'Phetchabun', + 'Bischwiller', + 'Barotac Viejo', + 'Giresun', + 'Caieiras', + 'Rodeiro', + 'Guabo', + 'Kusti', + 'Ketou', + 'Oestrich-Winkel', + 'Cayce', + 'Sume', + 'Yaese', + 'Zemamra', + 'Spring Garden', + 'Sheron', + 'Karnawad', + 'Neu-Ulm', + 'Grafelfing', + 'Costa Mesa', + 'Wangjia', + 'Joaquim Gomes', + 'Al Qurnah', + 'Naivasha', + 'Borgerhout', + 'Santa Rosalia', + 'Corocoro', + 'Liskeard', + 'Kruibeke', + 'Mingaora', + 'Latauna', + 'Sepolno Krajenskie', + 'Madison', + 'Berkane', + 'San Giovanni Rotondo', + 'Siwah', + 'Aldeia de Paio Pires', + 'Krosno Odrzanskie', + 'Zriba-Village', + 'Tampico', + 'Gastre', + 'Katrineholm', + 'Joao Pinheiro', + 'Martensville', + 'Karakocan', + 'San Antero', + 'Pocinhos', + 'Erravaram', + 'Ogaki', + 'Ezzhiliga', + 'Dimmbal', + 'Torul', + 'Darlaston', + 'Itapissuma', + 'Mragowo', + 'Buton', + 'Villach', + 'Xinzhancun', + 'Rusape', + 'Ialoveni', + 'Biltine', + 'Alukkuli', + 'Panpuli', + 'Barili', + 'Kempen', + "`Alem T'ena", + 'Wustermark', + 'Murnau am Staffelsee', + 'El Ghomri', + 'Kambhaladinne', + 'Ciego de Avila', + 'Khoragachhi', + 'Tiburon', + 'Ohata', + 'San Martin Zapotitlan', + 'Priego de Cordoba', + "Mun'gyong", + 'Placetas', + 'Comodoro Rivadavia', + 'Goluwali', + 'Janglot', + 'Ammanford', + 'Juquia', + 'Parapua', + 'Mitchell', + 'Lincoln', + 'Kafr Sa`d', + 'Dhanwada', + 'Vergiate', + 'Son Servera', + 'Uto', + 'Gesuba', + 'Jamaat Shaim', + 'Basirhat', + 'Dalain Hob', + 'Bishenpur', + 'Regensburg', + 'Altensteig', + 'Kekem', + 'Sanwal', + 'Palankottai', + 'Raquira', + 'Antsahanoro', + 'Cerignola', + 'Beesel', + 'Burj al `Arab', + 'Rantepao', + 'Ankadindambo', + 'Jinotega', + 'Hechingen', + 'Ingersoll', + 'Vitoria da Conquista', + 'Parma', + 'Vaciamadrid', + 'Daita', + 'Santa Catarina Juquila', + 'Kozje', + 'Hagen', + 'Pargi', + 'Porto Nacional', + 'Krishnarajpur', + 'Huazangsi', + 'Blida', + 'Majra', + 'Sahasoa', + 'Inzai', + 'Murrysville', + 'Jalalpur', + 'Belakoba', + 'Ichalkaranji', + 'Almagro', + 'Vassouras', + 'Taubate', + 'Sarrat', + 'Jupiter', + 'Icara', + 'Avrankou', + 'Brick', + "Giv`at Shemu'el", + 'Bargarh', + 'Wang Tau Hom', + 'Teixeira', + 'Darmstadt', + 'Kasuya', + 'Periya Pattanam', + 'Dangbo', + 'El Viejo', + 'Xapuri', + 'Woodbury', + 'Pampatar', + 'Waspan', + 'Samabouro', + 'Al Ahad al Masarihah', + 'Koath', + 'Gwanda', + 'Vinhais', + 'Islahiye', + 'Mogpog', + 'Bayt Jala', + 'Nagireddipalli', + 'Miami Beach', + 'Conversano', + 'Quartier Militaire', + 'Cauto Cristo', + 'Arhribs', + "Villa O'Higgins", + 'Orange Walk', + 'Dharmavaram', + 'Paraiso do Tocantins', + 'Calolziocorte', + 'Capitan Sarmiento', + 'Tyagadurgam', + 'Vendram', + 'Harstad', + 'Caldogno', + 'Tlaxcoapan', + 'Jiangjiafan', + 'Arta', + 'Jacona de Plancarte', + 'Sarpol-e Zahab', + 'Nahuala', + 'Boa Esperanca', + 'Mealhada', + 'Troutdale', + 'Wharton', + 'Tournon-sur-Rhone', + 'Casino', + 'Saint-Lin--Laurentides', + 'Jaco', + 'Evander', + 'Figeac', + 'Elukone', + 'Camocim', + 'Sahpur', + 'Lower Tungawan', + 'Vengur', + 'Miraima', + 'Ximeicun', + 'Hoogland', + 'Weimar', + 'Kokkola', + 'Atiquizaya', + 'Oldenburg in Holstein', + 'Kulattuppalaiyam', + 'Ramada', + 'Itampolo', + 'Bommarbettu', + 'Piove di Sacco', + 'Kanavaypatti', + 'Ise', + "Oktyabr'sk", + 'Tigzirt', + 'Oulad Amrane', + 'Kundal', + 'Cheria', + 'Kalchini', + 'Dschang', + 'Bentiu', + 'Emboscada', + 'Sangram', + 'Plerin', + 'Kezi', + 'Grobenzell', + 'Tomino', + 'Santa Cruz do Rio Pardo', + 'Isorana', + 'Al Qbab', + 'Sleepy Hollow', + 'Dehloran', + 'Taixi', + 'Noamundi', + 'Ankilimalinika', + 'Hwasu-dong', + 'San Bruno', + 'Dijon', + 'Yiyang', + 'Guntakal', + 'Suluova', + 'Kottacheruvu', + 'Ludwigshafen', + 'Ghargaon', + 'Rosa', + 'Sual', + 'Miller Place', + 'Dronten', + 'Dobbs Ferry', + 'Santa Marta', + 'Khopoli', + 'Kelilalina', + 'Bela Cruz', + 'Klela', + 'Santomera', + 'Chortoq', + 'Kemp Mill', + 'Mae Rim', + 'Yalvac', + 'Meridiala', + 'Itaporanga', + 'Macerata Campania', + 'Maxeville', + 'Sierra Colorada', + 'Ginatilan', + 'Fortaleza', + 'Tenmalai', + 'Oyonnax', + 'Taishi', + 'Krapkowice', + 'Sojitra', + "Alamat'a", + 'Flemalle-Haute', + 'Jeannette', + 'Edessa', + 'Fairview Park', + 'Pabianice', + 'An Nayrab', + 'Gussago', + 'Aridagawa', + 'Inebolu', + 'Ponte Buggianese', + 'Comallo', + 'Handeni', + 'Kadogawa', + 'Vesala', + 'Santana do Manhuacu', + 'Vicente Guerrero', + 'Trnovska Vas', + 'Franklin Square', + 'Hessle', + 'Tuvur', + 'Les Mureaux', + 'Rikuzen-Takata', + 'Shahmirzad', + 'Irshava', + 'Almirante Tamandare', + 'Bhairapura', + 'Grand Forks', + 'Adolfo Gonzales Chaves', + 'Donmatias', + 'Jobat', + 'Jami', + 'Norfolk', + 'Kasagi', + 'Senador Canedo', + 'Bhabanipur', + 'Danville', + 'Guaraciaba do Norte', + 'Perez', + 'Akaiwa', + 'Santa Cruz do Monte Castelo', + 'Deerlijk', + 'Key West', + 'Somandepalle', + 'Bartoszyce', + 'Tsuiki', + 'Hillegom', + "Qa'emiyeh", + 'Sahidganj', + 'Terrier Rouge', + 'Marikina City', + 'Vinzons', + 'Makouda', + 'Conceicao da Barra', + 'Tebesbest', + 'Hachinohe', + 'Carora', + 'Luckeesarai', + 'Castelo', + 'Bommagondanahalli', + 'Kutavur', + 'Bedworth', + 'Laarne', + 'East Grand Rapids', + 'Itatiba', + 'Pineville', + 'Banhatti', + 'Thiotte', + 'San Martin de las Piramides', + 'Puconci', + 'Saidpur Dabra', + 'Xaghra', + 'Zhuhai', + 'Altamonte Springs', + 'Warminster', + 'Zollikon', + 'Patiala', + 'Oxted', + 'Gornja Radgona', + 'Bade', + 'Dahe', + 'Risca', + 'Puxinana', + 'Mixtla de Altamirano', + 'Haderslev', + 'Sun Village', + 'Mahazoarivo', + 'Asara', + 'Tortosa', + 'Bellmead', + 'San Marino', + 'Khrestivka', + 'Usuki', + 'Yasynuvata', + 'North Valley', + 'Chennur', + 'Bishnupur', + 'Fanzhao', + 'Oltinko`l', + 'El Carmen', + 'Lagawe', + 'Argentona', + 'Torgau', + 'Tiflet', + 'Xincun', + 'Thap Khlo', + 'Havant', + 'Sulingen', + 'Apt', + 'Sabana de Torres', + 'Qyzylorda', + 'Hebburn', + 'Katerini', + 'Werota', + 'Taishan Houcun', + 'Zefyri', + 'Akseki', + 'Nkawkaw', + 'Sabangan', + 'West Sacramento', + 'Calandala', + 'Charcas', + 'Ash Shaykh `Uthman', + 'Mahajeran-e Kamar', + 'Toundout', + 'Mainvilliers', + 'Kronach', + 'Jayal', + 'Ruthen', + 'Kamenz', + 'Cayirova', + 'Temple', + 'Sammamish', + 'Agstafa', + 'Gleno', + 'Sattahip', + 'Kichha', + 'Tartagal', + 'Pingliang', + 'Ganvie', + 'Zapotiltic', + 'Temsamane', + 'Gyegu', + 'Shangqiu', + 'Niquen', + 'Baotou', + 'Orzesze', + 'Ghanzi', + 'Manikkal', + 'Zaraza', + 'Sarbisheh', + 'Mabehiri', + 'Clitheroe', + 'Jastrebarsko', + 'Frydek-Mistek', + 'Taxila', + 'Valencia West', + 'Ambalaroka', + 'Fouriesburg', + 'Ojuelos de Jalisco', + 'Saurimo', + 'Bereket', + 'Ledbury', + 'Lenexa', + 'Bakhor', + 'Come', + 'Tonk Khurd', + 'Chak Jhumra', + 'Qarah', + 'Macaparana', + 'Dormagen', + 'Kamirenjaku', + 'Nhlangano', + 'Loughton', + 'Carcarana', + 'Bahawalpur', + 'Hengshui', + 'Pointe-a-Pitre', + 'Louvain-la-Neuve', + 'Lindong', + 'Elsfleth', + 'Gangavalli', + 'Aguelmous', + 'Termas de Rio Hondo', + 'Nueva Paz', + 'Karuvellur', + 'Dholka', + 'Dishashah', + 'Neropolis', + 'Ourilandia do Norte', + 'Gorna Oryahovitsa', + 'Dangyang', + 'Raiganj Bazar', + 'Piliv', + 'Chosei', + 'Chambas', + 'Ringwood', + 'Bongaigaon', + 'Polignano a Mare', + 'Naranda', + 'Mondai', + 'Babadag', + 'Nattam', + 'Crosne', + 'Ananipalle', + 'Sorata', + 'Collipulli', + 'Famenin', + 'Waddinxveen', + 'Koratgi', + 'Mojokerto', + 'Moreno Valley', + 'Baltit', + 'Wokha', + 'Salzwedel', + 'Shangtangcun', + 'Stahnsdorf', + 'Jetpur', + 'Vellallur', + 'Drouin', + 'Tochigi', + 'Kaniyambadi', + 'Aran Bidgol', + 'Park Ridge', + 'Ammon', + 'Guipavas', + 'Miaoli', + 'Monchengladbach', + 'Sulahpet', + 'McMinns Lagoon', + 'Palangotu Adwar', + 'Delray Beach', + 'Singura', + 'Oakland Park', + 'Goonellabah', + 'Drezdenko', + 'Athurugiriya', + 'Aizumi', + 'Maria la Baja', + 'Toshloq', + 'Pale', + 'Burhar', + 'Murrieta', + 'Loufan', + 'Kibungo', + 'Doi Lo', + 'New Bedford', + 'Srinivaspur', + 'Atru', + 'Mataili Khemchand', + 'Ashibetsu', + 'Hansi', + 'Bacong', + 'Rukhae', + 'Kosching', + 'Addison', + 'Rangpur', + 'Toppenish', + 'Eyvanekey', + 'Girardot', + 'Piravanthur', + 'Korem', + 'Porto Torres', + 'Lagoa Grande', + 'Aratuba', + 'Hadithah', + 'Bel Air South', + 'Buluko', + 'Ambala', + 'Baie du Tombeau', + 'El Cairo', + 'Caluire-et-Cuire', + 'Caluya', + 'Ponedera', + 'Ban Bang Mae Nang', + 'Leer', + 'Kissing', + 'Shamalgan', + "'s-Heerenberg", + 'Miguel Calmon', + 'Bar', + 'Birak', + 'Igana', + 'Onnaing', + 'Sveta Trojica v Slovenskih Goricah', + 'Boca do Acre', + 'Amaravati', + 'Dhulkot', + 'Joubb Jannine', + 'Edeia', + 'Toda', + 'Okinoshima', + 'Murra', + 'East Renton Highlands', + 'Badshahpur', + 'Lalgudi', + 'Bomareddipalli', + 'Curaca', + 'Zetale', + 'Yuci', + 'Levittown', + 'Linbian', + 'Toyooka', + 'Krugersdorp', + 'Northfleet', + 'McKee City', + 'Atescatempa', + 'Dundo', + 'Sarableh', + 'Santo Domingo', + 'Lucca', + 'Le Chambon-Feugerolles', + 'Kanyato', + 'Sokoura', + 'Ponte de Lima', + 'Kafr Laha', + 'Covenas', + 'Shchastia', + 'Bamenda', + 'Tolmin', + 'Ottakkadai', + 'Palmetto Bay', + 'Castrop-Rauxel', + 'Barra dos Coqueiros', + 'Mahdishahr', + 'Angra do Heroismo', + 'Chulucanas', + 'Haslev', + 'Cahul', + 'Luocheng', + 'Asyut', + 'Muping', + 'Country Club Hills', + 'Ambondro', + 'Nakayama', + 'Boljevac', + 'Chaman', + 'Birqash', + 'Ghat', + 'Cungus', + 'Cuyapo', + 'Kissane Ltouqi', + 'Mexborough', + 'Mansinghpur Bijrauli', + 'Komlo', + 'Boissy-Saint-Leger', + 'Sabinopolis', + 'Talkhvoncheh', + "'s-Gravendeel", + 'Malkara', + 'Glace Bay', + 'San Andres Sajcabaja', + 'Pforzheim', + 'Wombwell', + 'New Hanover', + 'Schneverdingen', + 'Vidisha', + 'Neubiberg', + 'Banu Chhapra', + 'Pamidi', + 'Benkovac', + 'Cocoa', + 'Stawell', + 'Saint-Quentin', + 'Northdale', + 'Clermont', + 'Don Torcuato', + 'Hanchuan', + 'Hessisch Lichtenau', + 'Jena', + 'Tursunzoda', + 'Avintes', + 'Burela de Cabo', + 'Gladenbach', + 'Myszkow', + 'Quiroga', + 'Azove', + 'Sao Geraldo', + 'Penrith', + 'Arenapolis', + 'Miyada', + 'Jurbarkas', + 'Matias Olimpio', + 'Bindki', + 'Ditzingen', + 'Babaeski', + 'Lyngdal', + 'Baarn', + 'Xinmin', + 'Sondho Dullah', + 'San Onofre', + 'Halawah', + 'Soran', + 'Suihua', + 'Perere', + 'Ban Thum', + 'Massy', + 'Delta', + 'Qom', + 'Eldoret', + 'Tournavista', + 'Cangas', + 'Juchen', + 'Pattikonda', + 'Cacuaco', + 'Kamisato', + 'Ilarionove', + 'Spijkenisse', + 'Anavatti', + 'Seringueiras', + 'Beipiao', + 'Kouribga', + 'Kirangur', + 'Bismarck', + 'Ahaus', + 'Barajor', + 'Canta', + 'Sierre', + 'Gopalpur', + 'Fremont', + 'Baixa Grande', + 'Apolo', + 'Xigujing', + 'Vevcani', + 'Madhura', + 'Feucht', + 'Fehmarnsund', + 'Cesme', + 'Nerviano', + 'Brownsweg', + 'Baynala', + 'Ky Anh', + 'Khunti Dhanaili', + 'Arjona', + 'Ciudad Sandino', + 'Lyantonde', + 'Pililla', + "Santa Croce sull' Arno", + 'Liangshi', + 'Pirkkala', + 'Saligrama', + 'Odugattur', + 'Pullanvidudi', + 'Fort Irwin', + 'Veliko Gradiste', + 'Culiacan', + 'Singa', + "Conde-sur-l'Escaut", + 'North Fort Myers', + 'Martinez', + 'Raisinghnagar', + 'Heubach', + 'Accokeek', + 'Citta di Castello', + 'Sint-Genesius-Rode', + 'Iruma', + 'Curico', + 'Pitlam', + 'Niamey', + 'Araci', + 'Zaozhuang', + 'Az Zahiriyah', + 'Takouta', + 'Douar Bouchfaa', + 'Mirna', + 'Aron', + 'Firminopolis', + 'Niederwerrn', + 'Chandi', + 'Parana', + 'Bom Jesus do Galho', + 'Boussu', + 'General Nakar', + 'Elancourt', + 'Tirodi', + 'Cosenza', + 'Sooke', + 'Alga', + 'Duruma', + '`Amuda', + 'Aburi', + 'Short Hills', + 'Haripura', + 'Granada', + 'The Mumbles', + 'Yadavolu', + 'East Honolulu', + 'Vellalapuram', + 'Boufarik', + 'Oer-Erkenschwick', + 'Terkuvenganallur', + 'Orodara', + 'Didouche Mourad', + 'Stapleford', + 'Iles', + 'Cantilan', + 'Kennebunk', + 'Urasoe', + 'Singosari', + 'Onda', + 'Rushall', + 'Bukhara', + 'Ibbenburen', + 'Epsom', + 'Glendale Heights', + 'Ubai', + 'Chatra', + 'Ranavav', + 'Chevilly-Larue', + 'Kemijarvi', + 'Ungoofaaru', + 'Zaouiat Moulay Bouchta El Khammar', + 'Vinaninkarena', + 'Zaleze', + 'Zelenikovo', + 'Newfane', + 'Langedijk', + 'Orly', + 'Berchha', + 'Guimba', + 'Gamboula', + 'Alakamisy Itenina', + 'Alotau', + 'Sothgaon', + 'Bockum', + 'Sawai Madhopur', + 'Lahan', + 'Santa Ignacia', + 'Modesto', + 'Jales', + 'Baragaon', + 'Huarmey', + 'North Ridgeville', + 'Kolomyia', + 'San Martin de los Andes', + 'Saboeiro', + 'Bucay', + 'Xexeu', + 'Santo Stino di Livenza', + 'Boca Chica', + 'Keskin', + 'Namtu', + 'Nyenga', + 'Carlow', + 'Medemblik', + 'Cadoneghe', + 'Hagondange', + 'Zozocolco de Hidalgo', + 'Desamparados', + 'Bengonbeyene', + 'Bou Zemou', + 'Bellary', + 'Bremen', + 'Fitzgerald', + 'Finale Emilia', + 'Bilgi', + 'Starobilsk', + 'Korangal', + 'Ingeniero Maschwitz', + 'Akat Amnuai', + 'Afua', + 'Dondon', + 'Dhangaraha', + 'Avellaneda', + 'Castelfranco di Sopra', + 'Bang Racham', + 'Bisaul', + 'Persan', + 'Palaw', + 'Manja', + 'Lindesberg', + 'Halton Hills', + 'Mandaon', + 'Ratingen', + 'Staszow', + 'Sitakili', + 'Niebull', + 'Malabo', + 'Berceni', + 'Floha', + 'Wiltz', + 'Anamoros', + 'Banqiao', + 'Afgooye', + 'Druzhba', + 'Montevrain', + 'Socopo', + 'Logatec', + 'Prestatyn', + 'Lodhikheda', + 'Phuket', + 'Berriozabal', + 'Puan', + 'Brooklyn', + 'Casma', + 'New Square', + 'Lake Station', + 'San Juan de Rio Coco', + 'Korydallos', + 'Cubatao', + 'Stidia', + 'Kalamansig', + 'Amstetten', + 'Ventersburg', + 'Khovaling', + 'Millburn', + 'Halba', + 'Tenggarong', + 'Stelle', + 'Bacup', + 'Cacak', + 'Khamanon Kalan', + 'Fanyang', + 'Patikul', + 'Igarassu', + 'Lakeland Village', + 'Sanxing', + 'Kongsberg', + 'Harelbeke', + 'Chintalapudi', + 'Chinna Ganjam', + 'Utena', + 'Carai', + 'Calcoene', + 'Salgar', + 'Maardu', + 'Tarhunah', + 'Breaza', + 'Villamontes', + 'Vadakakarai', + 'Jaidte Lbatma', + 'Chautapal', + 'Dois Corregos', + 'Mahmudabad Nemuneh', + 'Raul Soares', + 'Walker Mill', + 'Qiaoyang', + 'Alachua', + 'Nailsea', + 'Ipaumirim', + 'Wakabadai', + 'Sebt Labrikiyne', + 'Anupshahr', + 'Calinog', + 'Hammamet', + 'Famagusta', + 'Norwich', + 'Little Falls', + 'Arealva', + 'Bad Schwalbach', + 'Dovzhansk', + 'Amiens', + 'Floridia', + 'Sukma', + 'Zoeterwoude', + 'Narahia', + 'Santo Nino', + 'Acate', + 'Zuromin', + 'Kagithane', + 'Quarrata', + 'Ivankiv', + 'Sougueur', + 'Ain Harrouda', + 'Temsia', + 'Yangjiang', + 'Baja', + 'Ampasina-Maningory', + 'Porto', + 'Kolavallur', + 'Dianbu', + 'Sakuragawa', + "L'Union", + 'Prattipadu', + 'Mogosoaia', + 'Lija', + 'Dornbirn', + 'Madera Acres', + 'Munro Turuttu', + 'Ban Ao Nang', + 'Catubig', + 'Kibungan', + 'Cienfuegos', + 'Taza', + 'Zoma-Bealoka', + 'Zabid', + 'Sandare', + 'Labuleng', + 'Dresden', + 'The Valley', + 'Le Kram', + 'Shihe', + 'Zira', + 'Kurume', + 'Wengtiancun', + 'Mucuri', + 'Burnley', + 'Aborlan', + 'Allen', + 'Diriomo', + 'Metro', + 'Mandalgovi', + 'Etten-Leur', + 'Almeria', + 'Wootton', + 'Doukouya', + 'Ban Tha Khlong', + 'Trikala', + 'Hasanpura', + 'Gisborne', + 'Cooper City', + 'Mukasi Pidariyur', + 'Oulad Yaich', + 'Ibitita', + 'Majarhat', + 'Naas', + 'Sete Barras', + 'Ongata Rongai', + 'El Parral', + 'Hayrabolu', + 'Kasipalaiyam', + 'Jamaame', + 'Tripoli', + 'Ettapur', + 'Gurnee', + 'Ofunato', + 'Statesville', + 'Taiwa', + 'Boekenhouthoek', + 'Bandar-e Khamir', + 'Meningie', + 'Valle de La Pascua', + 'Vinhedo', + 'Ksar', + 'Al Mindak', + 'Blackrock', + 'Kaua Kol', + 'Ban Yaeng', + 'Kodaimangalam', + 'Brownsville', + 'Zhushan', + 'Vrede', + 'Pombas', + 'Albatera', + 'Puerto Narino', + 'Pakpattan', + 'Awfouss', + 'Santa Cruz de Los Taques', + 'Thi Tran Ngai Giao', + 'Pakkam', + 'Chibok', + 'Wollert', + 'Berisso', + 'Pocone', + 'Eindhoven', + 'Eramala', + 'Alphen aan den Rijn', + 'Kamuli', + 'Puerto Quijarro', + 'Manaus', + 'Burleson', + 'Topsham', + 'Maribondo', + 'Buta', + 'Tampamolon Corona', + 'Alagoinha', + 'Valljkkod', + 'Nilo Pecanha', + 'Chachahuantla', + 'Dax', + 'Texcatepec', + 'Horizontina', + 'Serra Negra', + 'Kolda', + 'Bee Ridge', + 'Nekarikallu', + 'Wasilla', + 'Vatana', + 'Bestobe', + 'Fatsa', + "Sao Joao d'Alianca", + 'Sondrio', + 'Gorgonzola', + 'Nongstoin', + 'Yaguajay', + 'Solapur', + 'Abington', + 'Star', + 'Asuncion', + 'Valdosta', + 'Zhongtanying', + 'Rewahi', + 'Ankarana-Miraihina', + 'Debar', + 'Playa Grande', + 'Guabiruba', + 'Del Aire', + 'Tumen', + 'Perket', + 'Sinor', + 'Canford Cliffs', + 'Khorramdarreh', + 'Latehar', + 'Guerouma', + 'Pine Ridge', + 'Alba Iulia', + 'Qalansuwa', + 'Orocue', + 'Taraz', + 'Jesus Maria', + 'Langsa', + 'Trikkunnapuzha', + 'Nosy Varika', + 'Nishi', + 'Anjangaon', + 'Trebisacce', + 'Taucha', + 'Grosshansdorf', + 'Shirone', + 'Swakopmund', + "Bog'ot", + 'Antenor Navarro', + 'Parsippany-Troy Hills', + 'East Northport', + 'Sur Singh', + 'Huy', + 'Gandikunta', + 'Tamarakulam', + 'Cerknica', + 'Hindaun', + 'Vichy', + 'Hilo', + 'Branson', + 'Kalimala', + 'Othello', + 'Dol', + 'Nanga Eboko', + 'Angical', + 'Plains', + 'Culpeper', + 'Famjin', + 'Watrap', + 'Basrur', + 'Aracoiaba da Serra', + 'Bretten', + 'Gavere', + 'Lingxi', + 'Doctor Juan Eulogio Estigarribia', + 'Aracatuba', + 'Fredonia', + 'Wittmund', + 'Hirao', + 'Elmshorn', + 'Pfullingen', + 'Temse', + 'Betim', + 'Bhiraha', + 'Taquara', + 'Orivesi', + 'Thanh Pho Uong Bi', + 'Kamalganj', + 'Manamadurai', + 'Sint-Kruis', + 'Sankaramangalam', + 'Evergreen Park', + 'Catigbian', + 'Auhar Sheikh', + 'Gornji Vakuf', + 'Obanazawa', + 'Casandrino', + 'Saquisili', + 'Narsinghgarh', + 'Mitcham', + 'Carmelo', + 'Green', + 'Ringsted', + 'Timmins', + 'Wobulenzi', + 'Haddington', + 'Lohna', + 'West Mifflin', + 'Al Musayyib', + 'Brugg', + 'Jyvaskyla', + 'Al Muharraq', + 'Heroica Caborca', + 'Mapandan', + 'Osimo', + 'Srem', + 'Caxito', + 'Tantega', + 'Mahajamba', + 'Stara Tura', + 'Palo Alto', + 'Miskolc', + 'Dharmapuri', + 'Krefeld', + 'Sibutao', + 'Itinga', + 'Kuacjok', + 'Zhaodong', + 'Ambikapur', + 'Cologno al Serio', + 'Agua Branca', + 'Barskoon', + 'Jora', + 'Ciudadela', + 'Yenice', + 'Anderlecht', + 'Sunny Isles Beach', + 'Pilikodu', + 'San Marzano di San Giuseppe', + 'Hunenberg', + 'Khandpara', + 'Bieber', + 'Timezgana', + 'Sam Phran', + 'Yuquan', + '`Akko', + 'Ittikara', + 'Louveira', + 'Kalingiyam', + 'La Grande-Motte', + 'Maebara', + 'Billinghurst', + 'Bou Adel', + 'Leszno', + 'Inhumas', + 'Atoka', + 'Kerou', + 'Saint-Benoit', + 'Zhangzhou', + 'Belazao', + 'Senapparetti', + 'Cua', + 'Tangerang', + 'Zebbug', + 'Levanger', + 'Liminka', + 'Punceres', + 'Mavinpalli', + 'Jharsugra', + 'Hassan Abdal', + 'Karaburun', + 'Zazafotsy', + 'Simri Bakhriarpur', + 'Caprino Veronese', + 'Yorito', + 'Makaya', + 'Tenango de Doria', + 'Minamata', + 'Fujimino', + 'Vecchiano', + 'Tavas', + 'Punta Indio', + 'San Gregorio Atzompa', + 'Ambohimiarivo', + 'Hessisch Oldendorf', + 'Geretsried', + 'Calabozo', + 'Kara-Balta', + 'Pieta', + 'Grande Prairie', + 'Los Angeles', + 'Dingalan', + 'Potengi', + 'San Jose de Ocoa', + 'Suncheon', + 'Fornaka', + 'Rehti', + 'Bandixon', + 'Virapperumanallur', + 'North Lindenhurst', + 'Sainte-Julie', + 'Los Reyes de Salgado', + 'Pinamar', + 'Mayfield Heights', + 'Santa Apolonia', + 'Harpur Bhindi', + 'Campodarsego', + 'Urbana', + "Braine-l'Alleud", + 'Wukari', + 'Martahalli', + 'Leppavirta', + 'Kastel Stari', + 'Sansare', + 'Braganca Paulista', + 'Bridgeport', + 'Cecina', + 'Ossett', + 'Chepica', + 'Koratagere', + 'Muthuswamipuram', + 'Kamigori', + 'Tuchola', + 'Fukang', + 'Balanga', + 'Isola Capo Rizzuto', + 'Galmaarden', + 'Heinola', + 'Gioia Tauro', + 'Zelfana', + 'Quarai', + 'Frauenfeld', + 'Makole', + 'Puerto Lempira', + 'Brandenburg', + 'Kot Radha Kishan', + 'Armazem', + 'Mahaboboka', + 'Jingjiazhuang', + 'Ongole', + 'Magalhaes de Almeida', + 'Darat `Izzah', + 'Longtian', + 'Ahualulco de Mercado', + 'Alexander City', + 'Iwai', + 'Coolbaugh', + 'Khambhaliya', + 'Narayanavanam', + 'Littleton', + 'Ciudad Lerdo', + 'Martin Coronado', + 'Vialonga', + 'Hamminkeln', + 'Arrah', + 'Pianco', + 'Villa Elisa', + 'Tawaramoto', + 'Skien', + 'Santa Clara La Laguna', + 'Takad Sahel', + 'Giardini', + 'Barbosa Ferraz', + 'Brevnov', + 'Fuenlabrada', + 'Birhana', + 'Kaberamaido', + 'Siocon', + 'Loja', + 'Midori', + 'Sambhu Chak', + 'Federacion', + 'Masan', + 'Lousada', + 'Laguna Salada', + 'Pooler', + 'Geisenfeld', + 'Orlandia', + 'El Castillo de La Concepcion', + 'Koriyama', + 'Altagracia', + 'Minalin', + 'San Feliu de Llobregat', + 'Uithoorn', + 'Rivadavia', + 'Castello de Ampurias', + 'Zhur', + 'Greenock', + 'Criuleni', + 'Nangola', + 'Claymont', + 'Sihecun', + 'Palmers Green', + 'Guatica', + 'Mandaue City', + 'Pharkia', + 'Cambui', + 'Placerville', + 'Junin de los Andes', + 'Omachi', + 'Gaspar Hernandez', + 'Valinda', + 'Fort Walton Beach', + 'Arttuvattala', + 'Mahela', + 'Dowa', + 'Kelme', + 'Chippenham', + 'Metahara', + 'Marovantaza', + "Saint David's", + 'West Windsor', + 'Cleburne', + 'Itupiranga', + 'Trikarpur North', + 'Miyanaga', + 'Gandhari', + 'Tatarbunary', + 'Zholymbet', + 'Springwater', + 'Seekonk', + 'Groningen', + 'Chocaman', + 'Lianzhou', + 'Siggiewi', + 'Ig', + 'Sarh', + 'Al Hayy', + 'Chum Phae', + 'Meridjonou', + 'El Copey', + 'Salerno', + 'Antsampandrano', + 'Zahana', + 'Stuttgart', + 'Razan', + 'Hexiang', + 'North Merrick', + 'Hicksville', + 'Palmares Paulista', + 'Kalpi', + 'Sariyer', + 'Nandamuru', + 'Guidel', + 'Morinda', + 'Jinhe', + 'Yasinia', + 'Nova Friburgo', + 'Buriti Alegre', + 'Shahin Shahr', + 'Chivolo', + 'Gosforth', + 'Hunchun', + 'Gexianzhuang', + 'Mareno di Piave', + 'Montville', + 'Kikuyu', + 'Wauwatosa', + 'Dhalaa', + 'Villa del Carbon', + 'Tigbao', + 'Aidlingen', + 'Mekla', + 'Nieuwegein', + 'Geldern', + 'Pepillo Salcedo', + 'Belampalli', + 'Karakol', + 'Saint-Louis du Nord', + 'Bartabwa', + 'Zardab', + 'Kanakpur', + 'Dock Sur', + 'Todos Santos Cuchumatan', + 'Sunbat', + 'Matmata', + 'Le Passage', + 'Panr', + 'Galdacano', + 'Wa', + 'West Freehold', + 'Merville', + 'Sonderborg', + 'Corovode', + 'Tizgane', + 'Kadinhani', + 'Penalva', + 'Piqua', + 'Wenwu', + 'Castanuelas', + 'Jazan', + 'Nowe Miasto Lubawskie', + 'Velletri', + 'Cativa', + 'Niihama', + 'Carapebus', + 'Jai', + 'Sao Jose da Laje', + 'Southgate', + 'Ottaikkalmantapam', + 'Ladhuka', + 'Kulkent', + 'Mobetsu', + 'Pincourt', + 'Tagoloan', + 'Kiseljak', + 'Maryport', + 'Flores da Cunha', + 'Sao Jeronimo', + 'Ban Nong Han', + 'Kirk of Shotts', + 'Diyadin', + 'Uchti', + 'Marlboro Village', + 'El Consejo', + 'Dasraha Bhograjpur', + 'Hagenow', + 'Beniajan', + 'Halfway', + 'Jiming', + 'Antibes', + 'Manushmuria', + 'Nantucket', + 'La Mision', + 'Limbang', + 'Rubano', + 'Tchaourou', + 'Repatriacion', + 'Danyang', + 'Ad Duraykish', + 'Osterode', + 'Karvetnagar', + 'Secunda', + 'Kuhbanan', + 'Poissy', + 'Itupeva', + 'Liuhe', + 'New Smyrna Beach', + 'Okuta', + 'Shuangqiao', + 'Irati', + 'Lampa', + 'Gautampura', + 'Cacahoatan', + 'Mooirivier', + 'Spanaway', + 'Abu Hardub', + 'Bor', + 'Dingjiagouxiang', + 'Rehburg-Loccum', + 'Ambajogai', + 'Detmold', + 'Yuncheng', + 'Boyarka', + 'Eden', + 'Penne', + 'Barghat', + 'Ampahimanga', + 'Turkmenabat', + 'Zarqan', + 'Dover', + 'Sindirgi', + 'Panabo', + 'Holliston', + 'Tambolaka', + 'Ciudad Arce', + 'Nadbai', + 'Nova Era', + 'Bekodoka', + 'South Fayette', + 'Ampanavoana', + 'Mahbubabad', + 'Palkonda', + 'Alasehir', + 'Dongshicuo', + 'Uniondale', + 'Bhakua', + 'Damavand', + 'Kami-kawabe', + 'Deutschlandsberg', + 'Domahani', + 'Pallippatti', + 'Kaolack', + 'Bolhrad', + 'Murfreesboro', + 'Sankt Georgen im Schwarzwald', + 'Antanambe', + 'Macajuba', + 'Thies', + 'Tarlac City', + 'Taquaritinga', + 'City of Orange', + 'Glendora', + 'Tufanganj', + 'Claremore', + 'Zejtun', + 'Figueira da Foz', + 'Holmen', + 'Aiea', + 'Runkel', + 'Forbe Oroya', + 'Ondjiva', + 'Tosu', + 'Surinam', + 'Rahatgarh', + 'Winterswijk', + 'Sakiet ed Daier', + 'Kolonnawa', + 'Wawizaght', + 'Imisli', + 'Horokhiv', + 'Shenjiatun', + 'Lenguazaque', + 'Newington', + 'Lecheng', + 'Gamboma', + 'Manubolu', + 'Perumbalam', + 'Bougaribaya', + 'Dryden', + 'Kielce', + 'Edeleny', + 'Ouidah', + 'Comalapa', + 'Harrogate', + 'Qianwu', + 'Nguru', + 'South Daytona', + 'Corbin', + 'Abnub', + 'Saren', + 'Beydag', + 'Monthey', + 'Bonheiden', + 'August', + 'North Miami Beach', + 'Aulnay-sous-Bois', + 'Aaley', + 'Talne', + 'Kodungallur', + 'San Buenaventura', + 'Gerlingen', + 'West Earl', + 'Pacatu', + 'Hallein', + 'Nueva Ocotepeque', + 'Biassono', + 'Garfield Heights', + 'Motozintla', + 'Gerpinnes', + 'Mnasra', + 'Riohacha', + 'Etacheri', + 'Pudur', + "Sant'Antonio Abate", + 'Steinhagen', + 'Sfax', + 'San Miguel Chicaj', + 'El Congo', + 'Falimari', + 'Pinhal Novo', + 'Ystrad Mynach', + 'Wangen im Allgau', + 'Qazmalar', + 'Severinia', + 'Nonea', + 'Dargecit', + 'Sukumo', + 'Cittadella', + 'Yamba', + 'Sao Luis de Montes Belos', + 'Suleswaranpatti', + 'Abu Tisht', + 'Ploermel', + 'Arvika', + 'Warud', + 'Mazatlan Villa de Flores', + 'Inga', + 'Jaworzno', + 'Canalete', + 'Balayan', + 'Ankazomborona', + 'Nadimpalem', + 'Illintsi', + 'Laives', + 'Juchitan de Zaragoza', + 'Ponot', + 'Annan', + 'Fiorentino', + 'Lambarkiyine', + 'Linluo', + 'Neu-Anspach', + 'Rio Maria', + 'South Venice', + 'Moranbah', + 'North Haledon', + 'Orchard Park', + 'Pico Truncado', + 'Gomez Palacio', + 'I-n-Amenas', + 'Barreiros', + 'San Mateo', + 'Harran', + 'Toccoa', + 'Ghoradal', + 'Medgidia', + 'Roermond', + 'Madinat al Habbaniyah', + 'San Agustin Tlaxiaca', + 'Nakodar', + 'La Baneza', + 'Citrus Park', + 'Guttal', + 'Lovington', + 'Carahue', + 'Vicosa do Ceara', + 'Saitama', + 'Whistler', + 'Amjhera', + 'Rio Brilhante', + 'Mora', + 'I-n-Salah', + 'Sao Joaquim do Monte', + 'Huanian', + 'Alcalde Diaz', + 'Bijaynagar', + 'Lowton', + 'Anjahamarina', + 'Tohoue', + 'Birkenhead', + 'Ghogaon', + 'Kadachchinallur', + 'Lal-lo', + 'Colorado do Oeste', + 'Boukoumbe', + 'La Source', + 'Knittelfeld', + 'Guangping', + 'Torrington', + 'Yako', + 'Batesville', + 'Dhobipet', + 'Ain Kechera', + 'Prey Veng', + 'Webster Groves', + 'Huai Yot', + 'Thandewala', + 'Koksijde', + 'Vosselaar', + 'Ashtarak', + 'Timaru', + 'Myrtle Beach', + 'Ban Na Sai', + 'Masbate', + 'Buloqboshi', + 'Trzebnica', + 'Biskra', + 'Sidi Bousber', + 'Jiangjiehe', + 'Qinhe', + 'Kunnur', + 'Palmerston North', + 'Kodanad', + 'Gulyam', + 'Gulou', + 'Fort Dodge', + "Mek'i", + 'Mount Olive', + 'Marco de Canavezes', + 'Ucuma', + 'Kucukkuyu', + 'Sendamaram', + 'Minacu', + 'Quezon City', + 'Caxias do Sul', + 'Banjarbaru', + 'Analamisampy', + 'Zhongwangzhuang', + 'Morroa', + 'Laghzawna', + 'Talakulam', + 'Pailin', + 'Clearfield', + 'Soumpi', + 'Drochtersen', + 'Lourinha', + 'Zerbst', + 'Beohari', + 'Marina di Carrara', + 'Sherpur Khurd', + 'Ploufragan', + 'Sucat', + 'Nadikude', + 'Live Oak', + 'Puerto Casado', + 'Mortara', + 'Beichengqu', + 'Iwanuma', + 'Melle', + 'Guney', + 'Geneva', + 'Whitley Bay', + 'Chintalavadi', + 'Buwama', + 'Midlothian', + 'Guatire', + 'Baghauni', + 'Chiang Mai', + 'East Ham', + 'Lenyenye', + 'Shirakawa-tsuda', + 'Dibulla', + 'Nocera Superiore', + 'Paispamba', + 'Puerto Princesa', + 'Ban Ton Thong Chai', + 'Markham', + 'Bileh Savar', + 'Timmapuram', + 'Kone', + 'Tchindjendje', + 'Bir el Djir', + 'Saldus', + 'Coalville', + 'Waterville', + 'North Canton', + 'Dalandzadgad', + 'New Franklin', + 'Presidencia de la Plaza', + "M'Chedallah", + 'Heves', + 'Brodnica', + 'Kicevo', + 'Niaogho', + 'Sao Pedro', + 'Connahs Quay', + 'Renningen', + 'Kaiwen', + 'Frejus', + 'Nobeji', + 'Le Bardo', + 'North Fair Oaks', + 'Metzingen', + 'Thonex', + 'Argos Orestiko', + 'Erdington', + 'Takasaki', + 'Storm Lake', + 'Ronnenberg', + 'Freetown', + 'Neuenburg', + 'Pinotepa', + 'Wood Buffalo', + 'Usme', + 'Kitaakita', + 'Nagykoros', + 'Midleton', + 'South Ockendon', + 'Brahmana Periya Agraharam', + 'Mantua', + 'Hayange', + 'Qarshi', + 'Komono', + 'Yasuj', + 'Cuartero', + 'Fuding', + 'Oberderdingen', + 'Consaca', + 'Calatrava', + 'San Vicente de Baracaldo', + 'Yakakent', + 'Sowan', + 'Major Isidoro', + 'Dongou', + 'Monza', + 'Millbrae', + 'Panshi', + 'Roquebrune-sur-Argens', + 'Satravada', + 'Visby', + 'Iseyin', + 'Kesarimangalam', + 'Greensburg', + 'Nova Paka', + 'Butterworth', + 'Fort Liberte', + 'Hongseong', + 'Anand', + 'Kalamasseri', + 'Kazhukambalam', + 'Beian', + 'Derik', + 'Malalag', + 'Prakhon Chai', + 'Laje', + 'Upper Uwchlan', + 'Sun Prairie', + 'Maghaghah', + 'Al Qaryatayn', + 'Ghonchi', + 'Masabdisa', + 'Luan Chau', + 'Czersk Pomorski', + 'Poggibonsi', + 'Privas', + 'Sa`adat Shahr', + 'Divinolandia', + 'Frontino', + 'Nurota', + 'Kpandae', + 'Kamitonda', + 'Molln', + 'Rogozno', + 'Kavital', + 'Sarlat-la-Caneda', + 'Reserva', + 'Ankazotsifantatra', + 'Shiraoka', + 'Baisha', + 'Sodertalje', + 'Sao Mateus do Sul', + 'Cimahi', + 'Nirakpurpali', + 'Baluntaicun', + 'Aloran', + 'Usmat Shaharchasi', + 'Jalpura', + 'Prairieville', + 'Neuenburg am Rhein', + 'Pangzawl', + 'Oshkosh', + 'Kalaidasht', + 'El Milia', + 'Williams Lake', + 'Lillebonne', + 'Kalyves Polygyrou', + 'Nova Europa', + 'Kawit', + 'Grande Saline', + 'Shuili', + 'Khanda', + 'Sa`idi', + 'Mount Eliza', + 'Queensferry', + 'Gibara', + 'Bathurst', + 'Itri', + 'Cosquin', + 'Dakovo', + 'Jimbolia', + 'Parsad', + 'Tartar', + 'Santarem', + 'Ingenio', + 'Az Za`ayin', + 'Avola', + 'Campiernagar', + 'Hioki', + 'Gaizhou', + 'Amancio', + 'Fort Salonga', + 'Ramhormoz', + 'Es Senia', + 'Konosu', + 'Tallaght', + 'San Giovanni in Marignano', + 'Al Mahallah al Kubra', + 'Nashville', + 'Nkongsamba', + 'Cazenga', + 'Teutonia', + 'Asthanwan', + 'Genappe', + 'Neuenstadt am Kocher', + 'Grammichele', + 'Latteri', + 'Aragarcas', + 'Thevur', + 'Yaglidere', + 'Radomir', + 'Bearsden', + 'Novo Airao', + 'Bierun Stary', + 'Lagunia Surajkanth', + 'Dengtangcun', + 'Schwechat', + 'Kalkar', + 'Cukurca', + 'Xiedian', + 'Teramo', + 'Yonago', + 'Samakhiali', + 'Pemba', + 'Rancho Palos Verdes', + 'Barclayville', + 'Bridgeton', + 'Eslov', + 'Shahpur', + 'Mandalgarh', + 'Nordstemmen', + 'Portlethen', + 'Opmeer', + 'Gemona del Friuli', + 'Askim', + 'Sidi Moussa', + 'Josefina', + 'Oskarshamn', + 'Kaujalgi', + 'Vicente Lopez', + 'Mandasa', + 'Bordj Menaiel', + 'Melgaco', + 'Beaufort', + 'Catskill', + 'Madaba', + 'Savoy', + 'Rahta', + 'Chiran', + 'London Grove', + 'Malov', + 'Serra Caiada', + 'Arico el Nuevo', + 'Dongping', + 'Elumalai', + 'Montelimar', + 'Weingarten', + 'Talbahat', + 'Bima', + 'Frouzins', + 'Kanur', + 'Beavercreek', + 'Vange', + 'Tianwei', + 'Westbury', + 'Donduseni', + 'Teotepeque', + 'Felidhoo', + 'McCordsville', + 'Catamarca', + 'Gurpinar', + 'Matola', + 'Lezajsk', + 'Chinna Gollapalem', + 'Fervedouro', + 'Ambodihara', + 'Bagalkot', + 'Quillabamba', + 'Boundiali', + 'Ankara', + 'Montfoort', + 'Union', + 'Pulheim', + 'Itako', + 'Kampong Tunah Jambu', + 'Bandar `Abbas', + 'Ghotki', + 'Phek', + 'Calabar', + 'Taft', + 'Nong Bua Lamphu', + 'Yenkuvarigudem', + 'Sebeta', + 'Freeport', + 'Gehrden', + 'Doctor Phillips', + 'Leers', + 'Mannedorf', + 'Linz', + 'Carvin', + 'Zhengdong', + 'Itabera', + 'Fujita', + 'Fazakerley', + 'Rillieux-la-Pape', + 'Xikou', + 'Grain Valley', + 'Chia', + 'Taiobeiras', + 'Kamianske', + 'Novyi Svit', + 'Pasni', + 'Nawanagar', + 'Zwickau', + 'Albstadt', + 'Tawau', + 'Sandnes', + 'Hilsa', + 'Trollhattan', + 'Le Mee-sur-Seine', + 'Villa del Rosario', + 'Barlinek', + 'Dacheng', + 'Ardestan', + 'Morbegno', + 'Bujaru', + 'Oued el Abtal', + 'Catio', + 'Mabalacat', + 'Venkidanga', + 'Leshou', + 'Makedonska Kamenica', + 'Pipra Latif', + 'Lae', + 'Myeik', + 'Lisburn', + 'Curralinho', + 'Cunen', + 'Wanparti', + 'Marica', + 'Muscle Shoals', + 'Nykobing Mors', + 'Rwamagana', + "Jin'e", + 'Quitandinha', + 'Nilufer', + 'Camisano Vicentino', + 'Masera di Padova', + 'Brockville', + 'Ashiya', + 'Velenje', + 'Chencha', + 'Pongotan', + 'Rogaska Slatina', + 'Barasat', + 'Lognes', + "N'Goussa", + 'Bordeaux', + 'Huaquillas', + 'Puvalur', + 'Pasraha', + 'Belladere', + 'Tonghae', + 'Montebelluna', + 'Nihonmatsu', + 'Mariano Roque Alonso', + 'Doura', + 'Xiangxiang', + 'Mang La', + 'Dumjor', + 'Jedrzejow', + 'Butler', + 'Rebola', + 'Douar Hammadi', + 'Sala Consilina', + 'Alassio', + 'Fenoarivobe', + 'Pueblo Rico', + 'Bramhapuri', + 'Mejillones', + 'Arcore', + 'Wiefelstede', + 'Wick', + 'Porsa', + 'Urdinarrain', + 'Seabra', + 'Pompton Plains', + 'Wells', + 'Paete', + 'Thorne', + 'Moss Vale', + 'Kortrijk', + 'San Giovanni in Fiore', + 'Dhar', + 'Bonneuil-sur-Marne', + 'Beregadougou', + 'Khandaich', + 'Arrigorriaga', + 'Glossop', + 'Neusass', + 'Olivenza', + 'Chaodongcun', + 'Lichtenburg', + 'Perupalem', + 'Pulimathu', + 'Buharkent', + 'Lancing', + 'Arendal', + 'Mpessoba', + 'Massillon', + 'Lordelo do Ouro', + 'Tlaltenango de Sanchez Roman', + 'Berkley', + 'Tracunhaem', + 'Avitanallur', + 'Livron-sur-Drome', + 'Badoc', + 'Salman Bak', + 'Vaihingen an der Enz', + 'Las Torres de Cotillas', + 'Claxton Bay', + 'Popasna', + 'Astana', + 'Fairhope', + 'Barras', + 'Acala', + 'Balti', + 'San Bartolome Jocotenango', + 'Capalonga', + 'Probistip', + 'Candoi', + 'Dapa', + 'Yei', + 'Roznov pod Radhostem', + "Oulad 'Azzouz", + 'Sherobod', + 'Andondabe', + 'Gedera', + 'Mandi Dabwali', + 'Le Blanc-Mesnil', + 'Elambalur', + 'Altlandsberg', + 'Chouafaa', + 'Sao Jose da Tapera', + 'Loimaa', + 'Concepcion', + 'Dhrol', + 'Kulpahar', + 'Mililani Town', + 'Goondiwindi', + 'Varzea Nova', + 'Sampgaon', + 'Dibraghani', + 'Wiwili', + 'Elk Grove', + 'Iskenderun', + 'Tandarampattu', + 'Bani Mazar', + 'Parramos', + 'Northwood', + 'Charabidya', + 'Villa Sandino', + 'Karsiyang', + 'Loughborough', + 'Tetari', + 'Nigde', + 'Yarmouth', + 'Bonen', + 'Monserrato', + 'Echirolles', + 'Iglesias', + 'Senges', + 'Tacambaro de Codallos', + 'Zwolle', + 'Baradero', + 'Belize City', + 'Ponmundam', + 'Vaterstetten', + 'Tiddas', + 'Uracoa', + 'Nandigaon', + 'Greven', + 'Vila Franca do Campo', + 'Dhumnagar', + 'Yiwu', + 'Casoria', + 'Tovuz', + 'Yakossi', + 'Domont', + 'Bai Chay', + 'Grimsby', + 'Manzini', + 'Tiszaujvaros', + 'Sapele', + 'Lihuzhuang', + 'Michalovce', + 'Tanamarina', + 'Apartado', + 'Albu Kamal', + 'Griffith', + 'Zhashkiv', + 'Oroco', + 'Falkirk', + 'Derassi', + 'Vipava', + 'Berriane', + 'Kiskunhalas', + 'Gradiska', + 'Doesburg', + 'Jamestown', + 'Kingston upon Hull', + 'Djibloho', + 'Sachin', + '`Aliabad-e Katul', + 'Bouanri', + 'Chandpur', + 'Godalming', + 'Toamasina', + 'Basarh', + 'Taviano', + 'Apricena', + 'Takayama', + 'Nossen', + 'Hashima', + 'Staines-upon-Thames', + 'Lachen', + 'Ecclesfield', + 'Kiraz', + 'Cercado Abajo', + 'Mangalpur Gudaria', + 'Anolaima', + 'Ban Mae Chedi', + 'Namur', + 'Bragadiru', + 'Cusano Milanino', + 'Shirosato', + 'Khilchipur', + 'Lymington', + 'Kuala Kurun', + 'Santa Cruz Balanya', + 'North Babylon', + 'Thogaduru', + 'Shirin', + 'El Ghourdane', + 'Qanliko`l', + 'Jangalapalli', + 'Mariakerke', + 'Kukkundur', + 'Coyula', + 'Meghauna', + 'Ain el Hadjar', + 'Sen Monorom', + 'Madakasira', + 'Shepton Mallet', + 'Biggleswade', + 'Llallagua', + 'Birzai', + 'Beneditinos', + "Kattaqo'rg'on Shahri", + 'La Crucecita', + 'Tall Abyad', + 'Ar Rahmaniyah', + 'Milnrow', + 'Shasta Lake', + 'San Giovanni Lupatoto', + 'Lovejoy', + 'Fo-Boure', + 'Konigswinter', + 'Ironton', + 'Sabirabad', + 'Sai Wan Ho', + 'Ueda', + 'Yinchengpu', + 'Miandrarivo', + 'Horley', + 'Djidian', + 'Koundara', + 'Dereli', + 'Sidi Merouane', + 'Alwal', + 'Debre Tabor', + 'Beltsville', + 'San Francisco de los Romo', + 'Singkawang', + 'Kryvyi Rih', + 'Cisnadie', + 'Altotting', + 'Cape Canaveral', + 'Mitsuke', + 'Baroueli', + 'Abrandabad-e Shahediyeh', + 'Merkanam', + 'Huazalingo', + "L'Hay-les-Roses", + 'Riorges', + 'Serra Dourada', + 'Woensdrecht', + 'Castelli Calepio', + 'Kiruna', + 'Fatima', + 'Bensonville', + 'Weissenhorn', + 'San Jose El Idolo', + 'Racconigi', + 'Lluchmayor', + 'Inabe', + 'Amgachia', + 'Aswapuram', + 'Wilmette', + 'Zwonitz', + 'Haji Shah', + 'Nava', + 'El Cerrito', + 'Tabira', + 'Treuchtlingen', + 'Ponte da Barca', + 'Poitiers', + 'Watari', + 'As Safirah', + 'Handsworth', + 'Muniz Freire', + 'God', + 'Nova Crixas', + 'Tujg', + 'Jakobstad', + 'San Bartolo Tutotepec', + 'Kennewick', + 'Baianopolis', + 'Las Navas', + 'Paracelis', + 'Moniquira', + 'Porumamilla', + 'Arroyo Seco', + 'Bom Sucesso', + 'West Haven', + 'Kinik', + 'Nangavalli', + 'Draksharama', + 'Wells Branch', + 'Shumanay', + 'Zlocieniec', + 'Monte di Procida', + 'Novodnistrovsk', + 'Chitungwiza', + 'San Pedro de Macoris', + 'Mombin Crochu', + 'Dumbarton', + 'Ostersund', + 'Le Grand-Saconnex', + 'Uijeongbu', + 'Lomianki', + 'Piazza Armerina', + 'Butebo', + 'Kirsehir', + 'Huamachuco', + 'Ismailpur', + 'North Amityville', + 'Lanquin', + 'Rio Negrinho', + 'Ukrainsk', + 'Rockingham', + 'Zarasai', + 'Terneuzen', + 'Musiri', + 'Badanahatti', + 'Edayikunnam', + 'Caridade', + 'Jensen Beach', + 'Kuriyama', + 'Antarvedi', + 'Amos', + 'Byatarayanpur', + 'Sao Domingos do Maranhao', + 'Tarutung', + 'Worthington', + 'Fujiidera', + 'Gerd Faramarz Shahediyeh', + 'Buchach', + 'Igarape', + 'Lincolnton', + 'Ameskroud', + 'Bad Oldesloe', + 'Yorii', + 'Juticalpa', + 'Lakshmaneswaram', + 'Catanduvas', + 'Ryhope', + 'Wesselsbron', + 'As Suwayhirah as Sahil', + 'Selvazzano Dentro', + 'Tanxia', + 'East Hampton', + 'Agua Fria', + 'Garanhuns', + 'Saint-Lazare', + 'Char Fasson', + 'Malanguan', + 'Waco', + 'Kanaya', + 'Totolapan', + 'Bloemhof', + 'Tokkavadi', + 'Nogent-sur-Marne', + 'Zhuolan', + 'Nandyalampeta', + 'Invercargill', + 'Trabia', + 'Boromo', + 'Blackfalds', + 'General Trias', + 'Kalakkadu', + 'Ellesmere Port', + 'Gampola', + 'Macclesfield', + 'Bachhauta', + 'Unzen', + 'Balbalan', + 'Maimon', + 'Minanba', + 'Andalucia', + 'Baclaran', + 'Sumqayit', + 'Amargosa', + 'Ghatawan', + 'Alajuela', + 'Maroaloka', + 'Bongandanga', + 'Papagos', + 'Bairia', + 'Ghagga', + 'Lillehammer', + 'Iizuna', + 'Kifosso', + 'Crailsheim', + 'Zvornik', + 'Devgadh Bariya', + 'Imola', + 'Estreito', + 'Haga', + 'Toyoyama', + 'Abeokuta', + 'Laoang', + 'West Seneca', + 'Kharhial', + 'Menzel Temime', + 'Jankinagar', + 'El Carmen de Chucuri', + 'Huari', + 'Maua', + 'Pekalongan', + 'Coltauco', + 'Carmen de Apicala', + 'Cacador', + 'Andohajango', + 'Yangiqo`rg`on', + 'Yesagyo', + 'Sahuayo de Morelos', + 'Nijmegen', + 'West Whittier-Los Nietos', + 'Santa Rosa del Sur', + 'Tharial', + 'Boves', + 'Bodhan', + 'Bagula', + 'Gobabis', + 'Ouahigouya', + 'Maubeuge', + 'Eldorado', + 'Bakhtawarpur', + 'Ternivka', + 'Kasserine', + 'Sayula de Aleman', + 'Fallanden', + 'Fonseca', + 'Lipari', + 'Bou Djeniba', + 'Jagraon', + 'Parrita', + 'Erongaricuaro', + 'Mizumaki', + 'Dieramana', + 'Moyamba', + 'La Linea de la Concepcion', + 'Kiffa', + 'Silleda', + 'Port Said', + 'Gross-Gerau', + 'Ilakatra', + 'Wooster', + 'Sicasica', + 'Eureka', + 'Dodworth', + 'Hereford', + '`Amran', + "La Chapelle d'Armentieres", + 'Pelsall', + 'Borshchiv', + 'Kaiyuan', + 'Pandag', + 'Mortagua', + 'Errachidia', + 'Gaya', + 'Dietzenbach', + 'An Najaf', + 'Dungarpur', + 'Oued Seguin', + 'Sampaloc', + 'Traralgon', + 'Andalatanosy', + 'Gillingham', + 'Mudon', + 'Deville-les-Rouen', + 'Xicheng', + 'Afonso Claudio', + 'Daanbantayan', + 'Horqueta', + 'Bahraigh', + 'North Brunswick', + 'Marotsiraka', + 'Ribnita', + 'North Royalton', + 'Kalemie', + 'El Oro de Hidalgo', + 'Woods Cross', + 'Tsiamalao', + 'Shankarampet', + 'Tecpan de Galeana', + 'Humlebaek', + 'Phatthalung', + 'Meddappakkam', + 'Raha', + 'Sensuntepeque', + 'Paidiipalli', + 'Itatira', + 'Siddapur', + 'Catemaco', + 'Joue-les-Tours', + 'Amal', + 'Bara Belun', + 'Hidalgotitlan', + 'Algeciras', + 'Carbonita', + 'Kulrian', + 'Kasaishi', + 'Pasir Gudang', + 'Yingchuan', + 'Owo', + 'Saanich', + 'Altos', + 'Dana Point', + 'Morro da Fumaca', + 'Copparo', + 'Gerze', + 'Werneck', + 'Athis-Mons', + 'Yanam', + 'Palmilla', + 'Port St. John', + 'Kapay', + 'Pottipuram', + 'Vaniyambadi', + 'Glenmont', + 'Ocara', + 'Huasuo', + 'Raver', + 'Manouba', + 'Volklingen', + 'Calcado', + 'Kattiyeri', + 'West Boldon', + 'Asaita', + 'Maradah', + 'Nagasu', + 'Mundelein', + 'Boerne', + 'Kuji', + 'Voi', + 'Hafnarfjordhur', + 'Huatabampo', + 'Juliaca', + 'Parkland', + 'Kedougou', + 'Suqian', + 'Caicedonia', + 'Dhing', + 'Azambuja', + 'Sekimachi', + 'Jasauli', + 'Benxi', + 'Ifanhim', + 'Sparti', + 'Reichelsheim', + 'Beni Khalled', + 'Salkhua', + 'Rotenburg an der Fulda', + 'Cheticheri', + 'Dorking', + 'Al Majma`ah', + 'Ghajnsielem', + 'Yomou', + 'Monsummano', + 'Richton Park', + 'Capbreton', + 'Curitibanos', + 'East Brandywine', + 'Karmana', + 'As Salamiyah', + 'Pallippurattuseri', + 'Mareeba', + 'Port Sudan', + 'Jumilla', + 'Schleswig', + 'Cedar Lake', + 'Richmond West', + 'Puqi', + 'Sbeitla', + 'Vaddapalli', + 'River Grove', + 'Northbridge', + 'Erkoklu', + 'Miliana', + 'Autun', + 'Lohja', + 'Ixtaczoquitlan', + 'Tczew', + "Shaqra'", + 'Djugu', + "Ben N'Choud", + 'East Kilbride', + 'Ciudad Melchor Muzquiz', + 'Tezpur', + 'Aldershot', + 'Tankara', + 'Honganur', + 'Nikshahr', + 'Jeffersonville', + 'Portage', + 'Waremme', + 'Bacuag', + 'Bhajani', + 'Kutno', + 'Kinston', + 'Macabebe', + 'Greater Napanee', + 'Isla Vista', + 'Santona', + 'Rimavska Sobota', + 'Sultanganj', + 'Mittweida', + 'Humayingcun', + 'Buon Ma Thuot', + 'Meaux', + 'Beibu', + 'La Vergne', + 'Warri', + 'Huaibaijie', + 'Yasica Arriba', + 'Zayda', + 'East Whittier', + 'Wasquehal', + 'Elkton', + 'Pasupatikovil', + 'Leuna', + 'Jamtara', + 'Baildon', + 'Lobo', + 'Karor', + 'Havsa', + 'Puente de Piedra', + 'Santana do Ipanema', + 'Strombeek-Bever', + 'Egg', + 'Charter Oak', + 'Romit', + 'Sevenoaks', + 'Toshima', + 'Lashio', + 'Tocina', + 'Phu My', + 'El Fula', + 'Dimona', + 'Leopoldshohe', + 'Marungur', + 'Goleniow', + 'Ouangani', + 'Dumalinao', + 'Sulz am Neckar', + 'Colchester', + 'Tarin Kot', + 'Bhairahawa', + 'Jaguaretama', + 'Shin', + "Pallazzolo sull'Oglio", + 'Rajnagar', + 'Massi', + 'Redlynch', + 'Badin', + 'Paredes de Coura', + 'Malambo', + 'Abjij', + 'Bekasi', + 'Shaomi', + 'Salzano', + 'Harlow', + 'Flower Hill', + 'Soasio', + 'Oakton', + 'Lukula', + 'Garhakota', + 'Monteforte Irpino', + 'Shamsabad', + 'Marsala', + 'Lauri', + 'Lala', + 'Siniscola', + 'Huixtla', + 'Taxisco', + 'Pesaro', + 'Khapalu', + 'Oyster Bay', + 'Ivanhoe', + 'Xai-Xai', + 'Medulla', + 'Naini', + 'Santa Cruz del Quiche', + 'Saint-Gratien', + 'Sante Bennur', + 'Al Brouj', + 'Raje', + 'Perladka', + 'Ban Laem Chabang', + 'Gora', + 'Monitos', + 'Panchanandapur', + 'Pandacan', + 'Surbiton', + 'Sandy', + 'Wittenheim', + 'Gujranwala', + 'Zaria', + 'Hirayama', + 'Neyveli', + 'Guane', + 'Oak Forest', + 'Suzhou', + 'Tulle', + 'Prantij', + 'Rocca di Papa', + 'Tielt', + 'Bleicherode', + 'Ponte San Pietro', + "'Ain el Turk", + 'Bagou', + 'Taragi', + 'Ukal', + 'Ruy Barbosa', + 'Siroki Brijeg', + 'Pabellon de Arteaga', + 'Granite City', + 'Ait Majdane', + 'Hirehaluhosahalli', + 'Pinabacdao', + 'Kawa', + 'Phaltan', + 'Piombino Dese', + 'Belmopan', + 'Kodad', + 'Bozova', + 'Quibala', + 'Nakur', + 'Berkovitsa', + 'Chiredzi', + 'Sanmenxia', + 'Bugdayli', + 'Nersingen', + 'Palma Campania', + 'Paceco', + 'Isesaki', + 'Lopatcong', + 'Pecan Grove', + 'Thomaston', + 'Sri Jayewardenepura Kotte', + 'Quang Yen', + 'Greeley', + 'Omidiyeh', + 'Namakkal', + 'Macuro', + 'Neuenkirchen', + 'Goure', + 'Kehen', + 'Dongyangshi', + 'Fiumefreddo di Sicilia', + 'Alta Floresta', + 'Ban Na Chom Thian', + 'Viet Tri', + 'Kashgar', + 'Shawinigan', + 'Danghara', + 'Belgrade', + 'Puerto Tejada', + 'Tulin', + 'Agblangandan', + 'Poranga', + 'Lagos', + 'Mallig', + 'Ciftlik', + 'Sao Francisco de Paula', + 'Kiyama', + 'Crespo', + 'Bad Salzungen', + 'Doganhisar', + 'Indargarh', + 'Miandrivazo', + 'Mondovi', + 'Quthing', + 'Guipos', + 'Dikhil', + 'Polakala', + 'Ambaliha', + 'Anuppampattu', + 'Aquitania', + 'La Rinconada', + 'Paramaribo', + "Ma'anshan", + 'Uniao da Vitoria', + 'Cuihua', + 'Areka', + 'Huldenberg', + 'Gudlavalleru', + 'Alasandigutta', + 'Changtoushang', + 'Segni', + 'Durlesti', + 'Chivhu', + 'Carmen de Carupa', + 'Prachin Buri', + 'Buzhum', + 'Phalodi', + 'Munak', + 'Rewari', + 'La Gomera', + 'Yuhuan', + 'Woburn', + 'Aghbal', + 'Santa Maria a Vico', + 'Bogo', + 'Mendon', + 'Kong', + 'Benapol', + 'Sakoueba', + 'Uruana', + 'Chengam', + 'Freising', + 'Nobsa', + 'Ciechocinek', + 'Chengjiao', + 'Cerca Carvajal', + 'Matawan', + 'Al Madamud', + 'Mettmann', + 'Yakumo', + 'Bir Mogrein', + 'Puerto Acosta', + 'Nunna', + 'Gunjapalle', + 'Collier', + 'Mateszalka', + 'Bento Goncalves', + 'Umbrete', + 'Kapakli', + 'Padada', + 'Oud-Turnhout', + 'Karukh', + 'Fort Meade', + 'Domanic', + 'Emsburen', + 'Braunsbedra', + 'Sacramento', + 'Vidauban', + 'Ranomafana', + 'Chone', + 'Malapannanagudi', + 'Siralkoppa', + 'Mequon', + 'Rottingdean', + 'Lindenwold', + 'Chanal', + 'Wakuya', + 'Malaba', + 'Chaabat el Leham', + 'Huinan', + 'Port-Vila', + 'Silopi', + 'Tanabi', + 'Yelmalla', + 'Siteki', + 'Casiguran', + 'Kennedale', + 'Kenduadih', + 'Intibuca', + 'Fos-sur-Mer', + 'Nueva Era', + 'Jicheon', + 'Rovigo', + 'Acipayam', + 'Bajala', + 'Drama', + 'Shiv', + 'San Giorgio a Cremano', + 'Antonio Dias', + 'Jalajala', + 'Ocsa', + 'Oviedo', + 'University City', + 'Boquira', + 'Gaura', + 'Naaldwijk', + 'Manteswar', + 'Farmingville', + 'Bokonbaev', + 'Qingxicun', + 'Scharbeutz', + 'Hamrun', + 'Lake Barcroft', + 'Adeje', + 'Saint Austell', + 'Jarrow', + 'Kandira', + 'Lincang', + 'Orsay', + 'Affoltern am Albis', + 'Bilozerka', + 'Lishaocun', + 'Ochanthururtha', + 'Koekelare', + 'Vengikkal', + 'Dysselsdorp', + 'New Scotland', + 'Isfara', + 'Cuers', + 'Derry', + 'Tampere', + 'Debiapur', + 'Yandian', + 'Awan Patti', + 'Kara-Kyshtak', + 'Oguz', + 'Qualiano', + 'Jose Maria Ezeiza', + 'Pfaffenhofen', + 'Sidi Ali', + 'Arayat', + 'Carvajal', + 'Palakollu', + 'Jingzhou', + 'Owerri', + 'Bato', + 'Yanyan', + 'Dasmarinas', + 'Srifa', + 'Duzhuang', + 'Qazi Ahmad', + 'Brook Park', + 'Market Drayton', + 'Kitahiroshima', + 'Swindon', + 'Pilibhit', + 'Hoyland', + 'Georgsmarienhutte', + 'Chigorodo', + 'Abu Qir', + 'Marysville', + 'Singerei', + "Town 'n' Country", + 'Jangany', + 'Atwater', + 'Fuchucho', + 'Rutana', + 'Shamgarh', + 'Cotegipe', + 'Atascadero', + 'Maisons-Alfort', + 'Bela Vista de Minas', + 'Kopervik', + 'Madugula', + 'Semra', + 'Binh Hoa', + 'Krnov', + 'Itapaci', + 'Sao Raimundo Nonato', + 'Sint-Joost-ten-Node', + 'Maski', + 'Regen', + 'Folkestone', + 'Rardhu', + 'Toyohashi', + 'Duragan', + 'Guelendeng', + 'Bugongi', + 'Montecchio Emilia', + 'Mahwah', + 'Dayrut', + 'Papeete', + 'Worsley', + 'Mponela', + 'Barakpur', + 'Broken Hill', + 'Sakiet ez Zit', + 'Glen Burnie', + 'Negapatam', + 'Itahari', + 'Kambainellur', + 'Cienega de Flores', + 'Jau', + 'Xalatlaco', + 'Dalaman', + 'Thi Tran Dong Dang', + 'Zacatlan', + 'Changbang', + 'Chikugo', + 'Uden', + 'Kansas City', + 'Sremska Mitrovica', + 'Pizhou', + 'Fannuj', + 'Slany', + 'Soye', + 'Painal', + 'Station des Essais M.V.A.', + 'Egg Harbor', + 'Morsi', + 'Araioses', + 'Escondido', + 'Castelnuovo Berardenga', + 'Ijra', + 'Ndora', + 'Ain Taya', + 'Essaouira', + 'Kimba', + 'Taher', + 'Bouar', + 'Kyrenia', + 'Chilca', + 'Quatis', + 'Badulla', + 'Kalpatta', + 'Turffontein', + 'Potunuru', + 'Karuhatan', + 'Amalfi', + 'Wuppertal', + 'Cayo Mambi', + 'Riva del Garda', + 'Boucau', + 'Sejong', + 'Elesbao Veloso', + 'Jiaojiangcun', + 'Pullanpallikonam', + 'Borborema', + 'Corella', + 'Schkeuditz', + 'Gurgenpinari', + 'Buddayyakota', + "Zd'ar nad Sazavou", + 'Jiangmen', + 'Tallahassee', + 'Ciudad Lazaro Cardenas', + 'Peabiru', + 'Ghomrassen', + 'Sultan Kudarat', + 'Brunswick', + 'Brookes Point', + 'Jihlava', + 'Kotharpettai', + 'Koumia', + 'Hasanpur', + 'Riachao do Dantas', + 'Tinqueux', + 'Stillwater', + 'Tempoal de Sanchez', + 'Weiterstadt', + 'Ichikikushikino', + 'Atlapadu', + 'Kiashahr', + 'Bertioga', + 'Centro Habana', + 'Makassar', + 'Rajakheri', + 'Kuhbil', + 'Baldwin Park', + 'Chunchura', + 'Shepshed', + 'Yakima', + 'Rayagada', + 'Saltpond', + 'Karamadai', + 'Sainte-Suzanne', + "St. John's", + 'Peddannavaripalle', + 'Ban Houayxay', + 'Pir Maker', + 'Bagewadi', + 'Chemancheri', + 'San Luis Rio Colorado', + 'Fullerton', + 'Khargram', + 'Yacuiba', + 'Rancho Grande', + 'Giurgiu', + 'San Blas Atempa', + 'Koulamoutou', + 'Jwaneng', + 'Kamalapuram', + 'Crna Trava', + 'Brooklyn Park', + 'Singapperumalkovil', + 'Kiban', + 'Budalur', + 'Yeonil', + 'Nsele', + 'Cumra', + 'Baie-Comeau', + 'Avila', + 'Chantilly', + 'Tongyangdao', + 'Izamal', + 'Yalaguina', + 'Obersiggenthal', + 'Sadalgi', + 'Cuprija', + 'Laufenburg (Baden)', + 'Bukan', + 'Siyana', + 'Kafue', + 'Portmore', + 'Perchtoldsdorf', + 'Catemu', + 'Kaimana', + 'Demir Hisar', + 'Tabor', + 'Alakamisy', + 'Bayt Lahya', + 'Rosiori de Vede', + 'Sarnia', + 'Venustiano Carranza', + 'Nunchia', + 'Kankakee', + 'Chittur', + 'Duma', + 'Bou Arada', + 'Burton', + 'Itatiaiucu', + 'Mason', + 'Araputanga', + 'San Juan Tecuaco', + 'Meghaul', + 'Abhwar', + 'Zhujiacun', + 'Saline', + 'Fethiye', + 'Leeds', + 'Ogori', + 'Keffi', + 'Karanja', + 'Heiligenhaus', + 'Haci Zeynalabdin', + 'Gubin', + 'Chenab Nagar', + 'Gormi', + 'Lydenburg', + 'Neuhaus am Rennweg', + 'Kings Grant', + 'Umi', + 'Dengka', + 'Gangaur', + 'Atsugicho', + 'Ibshaway', + 'Mananasy-Tsitakondaza', + 'Itacarambi', + 'Minowa', + 'Corzuela', + 'Vanduvancheri', + 'Bohl-Iggelheim', + 'Shinile', + 'Wilde', + 'Montlucon', + 'Latham', + 'Picana', + 'Tuao', + 'Drapetsona', + 'Mountain Brook', + 'Montechiarugolo', + 'Kasba Tanora', + 'Rajghat Garail', + 'Kasumpti', + 'Devirammanahalli', + 'Singalandapuram', + 'Chevy Chase', + 'Aspropyrgos', + "'Ain Taghrout", + 'Delft', + 'Beining', + 'Blainville', + 'Krasnohrad', + 'Siem Reap', + 'Betamcherla', + 'Labin', + 'Pompei', + 'Gospic', + 'Hakui', + 'Gomaringen', + 'Saint-Brevin-les-Pins', + 'Langtang', + 'Mangalapadi', + 'Santa Lucia del Camino', + 'East Whiteland', + 'Morioka', + 'Bouira', + 'Coria del Rio', + 'Varzea Paulista', + 'Araira', + 'Thana Bhawan', + 'Ain Taoujdat', + 'Mumbwa', + 'Comandante Luis Piedra Buena', + 'Powell River', + 'Nampa', + 'Nisang', + 'Piran', + 'Bude', + 'Merosina', + 'Kamez', + 'Fairborn', + 'Abhar', + 'Giffoni Valle Piana', + 'Jose Bonifacio', + 'Andhana', + 'Kisanuki', + 'Landover', + 'Dekanme', + 'Raposos', + 'Columbio', + 'Flieden', + 'Antalaha', + 'Impruneta', + 'Pyapali', + 'Butuan', + 'Hlevakha', + 'Braslovce', + 'Kalajoki', + 'Minami-Boso', + 'Tocumen', + 'Matan', + 'Zapopan', + 'Calbayog City', + 'Liloy', + 'Durgi', + 'Byadgi', + 'Vero Beach South', + 'Hongtuliang', + 'Baharu', + 'Hounslow', + 'Joquicingo', + 'Cranleigh', + 'Cassilandia', + 'Burton Latimer', + 'Poco Fundo', + 'Starogard Gdanski', + 'Paikpara', + 'Calera', + 'Gyumri', + 'Higashine', + 'Hialeah Gardens', + 'Moravce', + 'Lac', + 'Poruvakara', + 'Cadaval', + 'Pasig City', + 'Montpellier', + 'Nantang', + 'Baruni', + 'Imani', + 'Rommerskirchen', + 'Orizaba', + "Saint Ann's Bay", + "Olho d'Agua das Flores", + 'Amvrosiivka', + "M'dhilla", + 'Abulug', + 'Helmstedt', + 'La Chapelle-Saint-Luc', + 'Delitzsch', + 'Gurmaila', + 'Sainte-Anne-des-Plaines', + 'Gardhabaer', + 'Sodankyla', + 'Chandrakona', + 'Leczyca', + 'Madhipura', + 'Khargapur', + 'Cruz do Espirito Santo', + 'Gloucester City', + 'Latisana', + 'Doranda', + 'Holly Springs', + 'Mechtras', + 'Grenoble', + 'Red Bluff', + 'Titu', + 'Karavalur', + 'Chiquimulilla', + 'Kirkcaldy', + 'Soledad de Doblado', + 'Ben Nasseur', + 'Miyato', + 'Temecula', + 'Araripe', + 'Ambatoharanana', + 'Union Park', + 'Cuemba', + 'Birkirkara', + 'Ambohitsilaozana', + 'La Louviere', + 'Enniscorthy', + 'Ishkashim', + 'Touna', + 'Presidente Olegario', + 'Maranguape', + 'Mati', + 'Sainte-Catherine', + 'Nerja', + 'Freiburg im Breisgau', + 'Cairo Montenotte', + 'Uchchangidurgam', + 'Jhansi', + 'Elmali', + 'Al Jabin', + 'Orono', + 'Wickliffe', + 'Lontras', + 'Mound', + 'Cosio', + 'Drummondville', + 'Royal Tunbridge Wells', + 'Dolneni', + 'Tabogon', + 'Sosnowiec', + 'Badrashni', + 'Papanduva', + 'Kalayaan', + 'Zottegem', + 'General Salgado', + 'Ozatlan', + 'Southchase', + 'Santa Adelia', + 'Aribinda', + 'Nakanojomachi', + 'Coroata', + 'Marovatolena', + 'Saubara', + 'Pingtiancun', + 'Syosset', + 'Dinslaken', + 'Carpentras', + 'San Martino di Lupari', + 'Tabarka', + 'Yulara', + 'Ratzeburg', + 'Punta Gorda', + 'San Rafael del Yuma', + 'Arima', + 'Jannat Shahr', + 'El Escorial', + 'Honwada', + 'Bhagwanpur Khurd', + 'Ayutla de los Libres', + 'Guaiba', + 'Garzon', + 'Tarangnan', + 'Marktheidenfeld', + 'Ban Chang Phuak', + 'Aurad', + 'Epazoyucan', + 'Tone', + 'Seforong', + 'Lichtervelde', + 'Seriate', + 'Suileng', + 'Viraganur', + 'Novi Banovci', + 'Knaresborough', + 'Bulisa', + 'Shaqlawah', + 'Kingstowne', + 'Zalec', + 'Siano', + 'Flexeiras', + 'Ar Raqqah', + 'Kercem', + 'El Kef', + 'Cicero Dantas', + 'Schonefeld', + 'Neves Paulista', + 'Fish Hawk', + 'Akjoujt', + 'La Courneuve', + 'Biliran', + 'Telkap', + 'Sher Muhammadpuram', + 'Arapoti', + 'Laupheim', + 'Boden', + 'Ratau', + 'Rudesheim am Rhein', + 'Xintai', + 'Temerin', + 'Rebordosa', + 'Bernolakovo', + 'Sulat', + 'Ellwangen', + 'Gates', + 'Merano', + 'Nacozari de Garcia', + 'Camp Springs', + 'Taraka', + 'Yutiancun', + 'Soanpeta', + 'Navgilem', + 'Nagakute', + 'Ipetumodu', + 'Minnetonka', + 'The Acreage', + 'American Canyon', + 'Tripurantakam', + 'Acara', + 'Greer', + 'Conception Bay South', + 'Oiwa', + 'Ukhai Purbari Patti', + 'My Hoa', + 'Miklavz na Dravskem Polju', + 'Porbandar', + 'As Samawah', + 'Tamba', + 'Namangan', + 'Alluru', + 'Zinzana', + 'Karasburg', + "Ich'on", + 'Vojnik', + 'San Miguel Siguila', + 'Lome', + 'Vieiro', + 'Hardas Bigha', + "Jalawla'", + 'Belfast', + 'Sotik Post', + 'Hejiaji', + 'Lovech', + 'Rokupr', + 'Yalda', + 'Mangaldai', + 'Goodyear', + 'Kolakaluru', + 'Bonney Lake', + 'Alcobaca', + 'Bideford', + 'Godawari', + 'Parnaiba', + 'Eskilstuna', + 'Conceicao do Almeida', + 'San Carlos Park', + 'Dolyna', + 'East Liverpool', + 'Ceylanpinar', + 'Adachi', + 'Pottassheri', + 'Kauhajoki', + 'Rio Paranaiba', + 'Patos de Minas', + 'Saint-Jean-de-Monts', + 'Irapuato', + 'Zhuolu', + 'Miadanandriana', + 'Inashiki', + 'Hildesheim', + 'Cold Springs', + 'Merksplas', + 'Engen', + 'LaSalle', + 'Uyo', + 'Vinh Chau', + 'Parsuram', + 'Yoshimi', + 'Jaypul', + 'Minooka', + 'Mehtar Lam', + 'Corsico', + 'Wanding', + 'Corbas', + 'Shaogang', + 'Lusanga', + 'Xining', + 'Navalgund', + 'Binh Minh', + 'Dindigul', + 'Doddanahalli', + 'Tefenni', + 'Samdrup Jongkhar', + 'Taylorsville', + 'Bulicun', + 'Lisieux', + 'Valley Falls', + 'Salpazari', + 'Pianoro', + 'Kedia', + 'Green Valley', + 'Cape Breton', + 'Rencun', + 'Bandar-e Genaveh', + 'Beaverton', + 'Purranque', + 'My Tho', + 'Wendlingen am Neckar', + 'Mohon', + 'Ngaoundere', + 'Boryslav', + 'Antonio Prado', + 'Ramgarh', + 'Gdynia', + 'Hoyland Nether', + 'Cori', + 'Adakli', + 'Na Wa', + 'Ramla', + 'Ellicott', + 'Nowa Sol', + 'Laakirchen', + 'Jalalpur Bhattian', + 'Jaragua do Sul', + 'Ilic', + 'Guimar', + 'Bullas', + 'Dalawarpur', + 'Anuradhapura', + 'Carnaubal', + 'Yokosuka', + 'Lake Mohawk', + 'Phulwar', + 'Bohemia', + 'Imzouren', + 'Tibigan', + 'Apostolove', + 'Cantley', + 'Mbuji-Mayi', + 'Uarini', + "Qarah Zia' od Din", + 'Cortes', + 'Cullercoats', + 'Reggio di Calabria', + 'Figline Valdarno', + 'Mutsu', + 'Maiquinique', + 'New Panamao', + 'Odder', + 'Mar de Espanha', + 'Bukedea', + 'Rath To', + 'Choppington', + 'Irugur', + 'Padang Besar', + 'Cine', + 'Beckett Ridge', + 'Baghuz Fawqani', + 'Campos del Puerto', + 'Bandar-e Torkaman', + 'Sakon Nakhon', + 'San Juan de Alicante', + 'Benicarlo', + 'Beuningen', + 'Vrable', + 'Hennebont', + 'Mamobihat', + 'Three Springs', + "Saint-Cyr-l'Ecole", + 'Brod', + 'Taourirt', + 'Sliven', + 'Stanytsia Luhanska', + 'Ruston', + 'Plauen', + 'Sarasota Springs', + 'Sao Gotardo', + 'Beloeil', + 'Liuguang', + 'Bani Suhayla', + 'Pilar', + 'Sao Joao do Araguaia', + 'Teseney', + 'Domzale', + 'Feira', + 'Ambatoria', + "Guang'an", + 'Carice', + 'Loha', + 'Carmo do Rio Claro', + 'Calle Larga', + 'Ban Yang Hom', + 'Ilha de Mocambique', + 'Barwani', + 'Lowes Island', + 'Liaocheng', + 'Shergarh', + 'La Loggia', + 'Halden', + 'Khowrzuq', + 'Sahavalanina-Antenina', + 'Alum Rock', + 'Kilakkarai', + 'Finsterwalde', + 'Ouedo', + 'Tlalnepantla', + 'Tafi Viejo', + 'Minnampalli', + 'Santa Comba', + 'Sant Joan de Vilatorrada', + 'Marl', + 'Hulkoti', + 'Libourne', + 'Orasje', + 'Svolvaer', + 'Villa Curuguaty', + 'Velden am Worthersee', + 'Riverton', + 'Selmane', + 'Jamshedpur', + 'Sinsina', + 'Amity', + 'Kani-Bonzon', + 'Ilopango', + 'Kiliia', + 'Trelew', + 'Kowdalli', + 'Sao Martinho do Bispo', + 'Bayabas', + 'Marcolandia', + 'Hemel Hempstead', + 'Titlagarh', + 'Thathupur', + 'Kavieng', + 'Pepa', + 'Mangarwara', + 'Wanderlandia', + 'Sokolka', + 'Nara', + 'Koror', + 'Mbamba Bay', + 'Wezembeek-Oppem', + 'Tanjombato', + 'Eichstatt', + 'Independence', + 'Kagoshima', + 'Velingara', + 'Ouistreham', + 'St. Stephens', + 'Altinova', + 'Juchipila', + 'Jiaxing', + 'Millcreek', + 'Ameca', + 'Palmacia', + 'Fiano Romano', + 'Costa Volpino', + 'Rioja', + 'Kalkara', + 'Zanica', + 'Husainabad', + 'Naters', + 'Mistrato', + 'Ambahive', + 'Savanna-la-Mar', + 'Irinjalakuda', + 'Padappakara', + 'Chiantla', + 'Cidade Gaucha', + 'Walton-on-the-Naze', + 'Habiganj', + 'Irukanni', + 'Tekpanja', + 'Ellisville', + 'Luancheng', + 'Rudauli', + 'Abu al Matamir', + 'Nabas', + 'Kagadi', + 'Los Llanos', + 'Ilovaisk', + 'Ar Rumaythah', + 'Melun', + 'Catral', + 'Canico', + 'Pandharpur', + 'Hiroshima', + 'Don Bosco', + 'Billericay', + 'Mentana', + 'Sandravinany', + 'Pacos de Ferreira', + 'Darsi', + 'Tuineje', + 'Medinipur', + 'Mensora', + 'Nikaia', + 'Tan Chau', + 'Ma`alot Tarshiha', + "Huai'an", + 'Spinea', + 'Silvia', + 'Alcarraz', + 'Dongxishan', + 'Putrajaya', + 'Two Rivers', + 'Inacio Martins', + 'Kharahara', + 'Jakarta', + 'Hurth', + 'Giengen an der Brenz', + 'New Delhi', + 'Morlenbach', + 'Krus na Ligas', + 'Tornquist', + 'Hacine', + 'Yanji', + 'Fraiburgo', + 'Gursu', + 'San Giljan', + 'Verin', + 'Moissac', + 'Baghambarpur', + 'Bhatpara', + 'Pontinha', + 'Cunha Alta', + 'Riacho das Almas', + 'Yadwad', + 'St. Albert', + 'Agno', + 'Sitarganj', + 'Budhni', + 'Comayagua', + 'Matagua', + 'Remagen', + 'Aigle', + 'Forest Hills', + 'Wangtan', + 'Avanhandava', + 'Karnobat', + 'Chanp', + 'Amherst', + 'Duiven', + 'Bilohirsk', + 'Pleternica', + 'Marogong', + 'Kefar Sava', + 'Quilmes', + 'Conchagua', + 'Al Khankah', + 'Dulken', + 'Lobatse', + 'Utehia', + 'Sotomayor', + 'Traunreut', + 'Aleksandrovka', + 'Hetane', + 'Bad Oeynhausen', + 'Pescara', + 'Ashkezar', + 'Seiro', + 'Torrejon de Ardoz', + 'Antanimora Atsinanana', + 'Yiewsley', + 'Fojnica', + 'Iharana', + 'Dutse', + 'Ban Lao Yao', + 'Dirusumarru', + 'Tame', + 'Beledweyne', + 'San Andres Xecul', + 'Formia', + 'Mengla', + 'Croix', + 'Kraslava', + 'Pedagadi', + 'Denbigh', + 'Barharwa Kalan', + 'Alexania', + 'Sakhnin', + 'Yenagoa', + 'Kaya', + 'Karlstad', + 'Bundibugyo', + 'Harrai', + 'Dragor', + 'San Jose de Las Matas', + 'Gabela', + 'Alpu', + 'Karema', + 'Yevlax', + 'Allanmyo', + 'Ghafurov', + 'Brandis', + 'Pulupandan', + 'Eidsvoll', + 'Hinunangan', + 'Manmad', + 'Guledagudda', + 'Bangshang', + 'Kujwa', + 'Fairview', + 'Arani', + 'Spoltore', + 'Peiting', + 'Panj', + 'Sahavato', + 'Cramlington', + 'Poggio a Caiano', + 'Sisauna', + 'Mamushe', + 'Tsevie', + 'Barikot', + 'Tecax', + 'Sivapuram', + 'Kalbacar', + 'Mitry-Mory', + 'Biguacu', + 'Trowbridge', + 'Hasanganj', + 'Chamblee', + 'Sid', + 'Alboraya', + 'Pinar del Rio', + 'Calvillo', + 'Mercedes', + 'Chapel en le Frith', + 'Kalat', + "Ra's al Khaymah", + 'Mapoteng', + 'Ponta de Pedras', + 'Glenfield', + 'Temple Terrace', + 'Tlokweng', + 'Raun', + 'Aquin', + 'Boiling Springs', + 'Slobozhanske', + 'Truckee', + 'Nuremberg', + 'Yanghe', + 'Charqueadas', + 'Clarkston', + 'Wallingford Center', + 'Colleferro', + 'Luxor', + 'Gadsden', + 'Dautphe', + 'Macedo de Cavaleiros', + 'Venissieux', + 'Shamva', + 'Chesapeake', + 'Felsberg', + 'East Los Angeles', + 'Monte Caseros', + 'Misawa', + 'Souaflia', + 'Bouguirat', + 'Talara', + 'Iarinarivo', + 'Atalaia do Norte', + 'Pangunattam', + 'Little Ferry', + 'Oulad Tayeb', + 'Masar', + 'Melena del Sur', + 'Miura', + 'Panihati', + 'Andresy', + 'Ramayampet', + 'Basoko', + 'Thala', + 'Chifeng', + 'Majhaul', + 'Nediyanad', + 'Chaumont', + 'Belen de Escobar', + 'Karak', + 'Araxa', + 'Canteleu', + 'Vanrhynsdorp', + 'Achaguas', + 'Norwood', + 'Kandahar', + 'Santa Teresa', + 'Salmon Arm', + 'Kalutara', + 'Greene', + 'Harrisonburg', + 'Lohur', + 'Dingman', + 'Phon Charoen', + 'Farakka', + 'College Place', + 'Wodzislaw Slaski', + 'Alimodian', + 'Kemi', + 'Khapdeh', + 'Xaafuun', + 'Pongalur', + 'Chandwara', + 'Tecali', + 'Villacanas', + 'Kalluru', + 'Bouarouss', + 'Misantla', + 'Mazinde', + 'Rixensart', + 'Satellite Beach', + 'Jinhua', + 'Reedsburg', + "Togoch'ale", + 'Marchena', + 'Wan Long', + "Lyuboml'", + 'Ivancice', + 'Wendelstein', + 'Helston', + 'Manduria', + 'Shama', + 'Sedriano', + 'Cheruvaranam', + 'Nechi', + 'Mandu', + 'Binefar', + 'Sha Kok Mei', + 'Muta', + 'Caripito', + 'Petrolina de Goias', + 'Lianhe', + 'Brock', + 'Schonaich', + 'Aloguinsan', + 'Acapulco de Juarez', + 'Ayt Mohamed', + 'Poplar Bluff', + 'Limburgerhof', + 'Jaggampeta', + 'Ostroleka', + 'Kalamboli', + 'Ash Shihr', + 'Sasaima', + 'Vesele', + 'Lac-Brome', + 'Matnog', + 'Skokie', + "Ha'il", + 'Tzaneen', + 'Greenford', + 'Savanur', + 'Permet', + 'Mallapur', + 'Ladyzhyn', + 'Dama', + 'Angers', + 'Montigny-le-Bretonneux', + 'Xiaodian', + 'Narok', + 'Eching', + 'Castel San Pietro Terme', + 'Itondy', + 'Alcudia', + 'Periyanayakkanpalaiyam', + 'Artigues-pres-Bordeaux', + 'Marikina Heights', + 'Ben Zakkay', + 'Cachipay', + 'Hertzogville', + 'Daman', + 'Wilsdruff', + 'Ben Guerir', + 'Sciacca', + 'Signa', + 'Tuy', + 'Kireka', + 'Reston', + 'Sarab-e Taveh-ye `Olya', + 'Singida', + 'Waldorf', + 'Ain Jemaa', + 'Al Ghat', + 'Deodrug', + 'Kotmale', + 'Guemar', + 'Skoura', + 'Tuaran', + 'Outa Bouabane', + 'Talaivasal', + 'Jerusalem', + 'Hegang', + 'Daskasan', + 'Hannan', + 'Murowana Goslina', + 'Bomlo', + 'Nova Veneza', + 'Coppell', + 'Miguelopolis', + 'Stadtallendorf', + 'Kishundaspur', + 'Yuxiaguan', + 'Aleksandrovac', + 'Mianwali', + 'Mujikharf', + 'Develi', + 'Khawr Fakkan', + 'Billapadu', + 'Cacu', + 'Oldenburg', + 'Lens', + 'Veternik', + 'Canet de Mar', + 'Vellaturu', + 'Colmar', + 'Guadalupe Nuevo', + 'Hulin', + 'Talwandi Bhai', + 'Sighisoara', + 'Santa Monica', + 'Alessandria', + 'Idku', + 'Barsaun', + 'Podebrady', + 'Dona Ines', + 'Eppelheim', + 'Lonavale', + 'Comitan', + 'Darby', + 'Billingham', + 'Saint-Maximin-la-Sainte-Baume', + 'Volterra', + 'Kombissiri', + 'San Lorenzo de Guayubin', + 'Mashyal', + 'Cogua', + 'Parral', + 'Coaticook', + 'Bekobod', + 'Ribadeo', + 'Bhanukumari', + 'Kampong Chhnang', + 'Vrbovec', + 'South Sioux City', + 'Kalaki', + 'Panama City', + 'Mustafabad', + 'Morur', + 'Zurbatiyah', + 'Gamba', + 'Toukoroba', + 'Bully-les-Mines', + 'Nattakkadaiyur', + 'Sand Springs', + 'Turek', + 'Nejapa', + 'Woodridge', + 'Laranjeiras', + 'Xiaozui', + 'Kottaiyur', + 'Sebaste', + 'Penn Forest', + 'La Esmeralda', + 'Nohsa', + 'Eskisehir', + 'Chillan Viejo', + 'Kani', + 'Terdal', + 'Sarta', + 'Jinan', + 'Oborniki', + 'Taketoyo', + 'Badnor', + 'Panchkula', + 'Tres Passos', + 'Cartaya', + 'Barra de Santa Rosa', + 'Ganapavaram', + 'Mulampilli', + 'Damaishan', + 'Meihuacun', + 'Pahsara', + 'Atessa', + 'Toucheng', + 'Chamba', + 'Kranuan', + 'Pasaul', + 'Westview', + 'Grimari', + 'San Jacinto de Buena Fe', + 'Porecatu', + 'Ihtiman', + 'Misilmeri', + 'Friesenheim', + 'Wentang', + 'Bageshwar', + 'Mahtha', + 'Tenes', + 'Eraclea', + 'Evesham', + 'Elmhurst', + 'Nahorkatiya', + 'Oil City', + 'Sidi Jaber', + 'Laxou', + 'Royal Kunia', + 'Dong Xoai', + 'Hathauri', + 'Ambinanintromby', + 'Jurovski Dol', + 'Erwitte', + 'Jerada', + 'Costas de Cao', + 'Douetire', + 'Terku Valliyur', + 'Samesi', + 'Halgeri', + 'Huichapan', + 'Bolpur', + 'Ban Bu Sung', + 'Dandong', + 'Bucaramanga', + 'Bhavnagar', + 'Walia', + 'Puerto Vallarta', + 'Rishikesh', + 'Solebury', + 'Maratturai', + 'Lindlar', + 'Bariarpur Kandh', + 'Yerbas Buenas', + 'Ottur', + 'Torpa', + 'Debre Zeyit', + 'Lemgo', + 'Piritiba', + 'San Lorenzo de El Escorial', + 'Paterson', + 'Gonzaga', + 'Calabasas', + 'Miahuatlan', + 'Castelo do Piaui', + 'Sprockhovel', + 'Adelaide', + 'Clay Cross', + "Do'stlik Shahri", + 'Carrieres-sous-Poissy', + 'Hizan', + 'Mios', + 'Edenburg', + 'Worplesdon', + 'Le Kremlin-Bicetre', + 'Nanjundapuram', + 'Muratli', + 'Massena', + 'Girona', + 'Arifwala', + 'Chillum', + 'Salcedo', + 'Caoayan', + 'Kolonodale', + 'Curacautin', + 'Ixchiguan', + 'Dubnica nad Vahom', + 'Careiro da Varzea', + 'Meckenbeuren', + 'Lacombe', + 'Chifubu', + 'Kostinbrod', + 'Lipova', + 'Sao Joao Evangelista', + 'Messini', + 'Santangpai', + 'Viransehir', + 'Ait Ali', + 'Harsova', + 'Mamou', + 'Denkendorf', + 'Kalardasht', + 'Santo Antonio do Leverger', + 'Qusar', + 'San Tan Valley', + 'Kushima', + 'West Norriton', + 'Brasilia de Minas', + 'Hassleholm', + 'Shahdol', + 'Faqus', + 'Abbots Langley', + 'Zanandore', + 'Canon City', + 'Manjeri', + 'New Milton', + 'Regensdorf', + 'Flower Mound', + 'Dharphari', + 'Chapra', + 'Tazert', + 'Hirehadagalli', + 'Walkden', + 'Hatti', + 'Ohangaron', + 'Bujanovac', + 'Dharmapuram', + 'Munchenstein', + 'Ishigaki', + 'Cocody', + 'Karimpur', + 'Appukkudal', + 'New Kensington', + 'Palmas de Monte Alto', + 'Baghduma', + 'Sorisole', + 'Qarazhal', + 'Shemonaikha', + 'Korfez', + 'Mbale', + 'Shahar Telpa', + 'Gangwuzhen', + 'Qianzhou', + 'Potosi', + 'Monte Chingolo', + 'Baba Bakala', + 'Nottuln', + 'Talakad', + 'Dearborn Heights', + 'Mulakumud', + '`Ali Shahr', + 'Nashtifan', + 'Buriti', + 'Veliko Tarnovo', + 'Forest Lake', + 'Mizuho', + 'Birni Lafia', + 'Honda', + 'Fortin de las Flores', + 'Corral de Bustos', + 'Dracut', + 'Dighwa', + 'La Riche', + 'Karsaut', + 'Mito', + 'Xunjiansi', + 'Gandia', + 'Sidi Makhlouf', + 'Xibeijie', + 'Peritoro', + 'Sanjiangkou', + 'Bounaamane', + 'Barja', + 'San Felipe del Progreso', + 'Daisen', + 'Avadi', + 'North Bay Shore', + 'Dagohoy', + 'Fuying', + 'Dabas', + 'Koipadi', + 'Bloomington', + 'Mecheria', + 'Sundargarh', + 'Rajod', + 'Serramazzoni', + 'Kokofata', + 'Urlati', + 'Temixco', + 'Barbadanes', + 'Peddapalli', + 'Le Mont-sur-Lausanne', + 'Berthoud', + 'Navrongo', + 'Brockworth', + 'West Goshen', + 'Arcos de Valdevez', + 'Lalgola', + 'Bakhmut', + 'Majibacoa', + 'Yangfang', + 'Malisheve', + 'Cloverdale', + 'Petah Tiqwa', + 'Yamanashi', + 'Riofrio', + 'Merta', + 'Yoro', + 'Trinidad', + 'Sobreda', + 'Sidi Allal el Bahraoui', + 'Naranammalpuram', + 'Baicoi', + 'Rolling Meadows', + 'Linjiang', + 'Aralam', + "Citta Sant'Angelo", + 'Kaous', + 'Zacualpa', + 'Rivas', + 'Vellipalaiyam', + 'Ciudad Santa Catarina', + 'Chaungtha', + 'Los Lunas', + 'Crna na Koroskem', + 'San Miguel Ixtahuacan', + 'Pantukan', + 'Tsushima', + 'Donggangli', + 'Nalbach', + 'Begles', + 'Addlestone', + 'Maluso', + 'Atchison', + 'Vertentes', + 'Perth', + 'Deori Khas', + 'Cho Phuoc Hai', + 'Pacifica', + 'Belhatti', + 'Audincourt', + 'Baraidih', + 'Tsiningia', + 'Ampary', + 'Drobeta-Turnu Severin', + 'Tsumeb', + 'Atlantic City', + 'Villaguay', + 'Gyangze', + 'Wokingham', + 'Cowdenbeath', + 'Tehuacan', + 'Babanusah', + 'Kolobrzeg', + 'Iguala de la Independencia', + 'Shanklin', + 'Nasriganj', + 'Haql', + 'Bonab', + 'Ratnanagar', + 'Ermoupoli', + 'Besalampy', + 'Mannar', + 'Vigo', + 'Wolverhampton', + 'Idfu', + 'Zacapa', + 'Malacacheta', + 'Jersey City', + 'Tanmpegre', + 'Gemlik', + 'Bilovodsk', + 'Putyvl', + 'Memmelsdorf', + 'Ar Rustaq', + 'Munster', + 'Dano', + 'Barhan', + 'San Giorgio Ionico', + 'Shevington', + 'Larnaca', + 'Mawu', + 'Rio Verde de Mato Grosso', + 'Arroyos y Esteros', + 'Thoubal', + 'Breisach am Rhein', + 'Aizawl', + 'Bamessing', + 'Nurmo', + 'Lewistown', + 'Marseille', + 'Hyde Park', + 'Antsahalava', + 'Gardelegen', + 'Ramena', + 'Centerville', + 'Berdiansk', + 'Contla', + 'Lunavada', + 'Schwetzingen', + 'Vilangurichchi', + 'Lijiaxiang', + 'North Tustin', + 'Sycow', + 'San Severo', + 'Tugaya', + 'Luziania', + 'Bay', + 'Sete', + 'Torbat-e Heydariyeh', + 'Lerum', + 'Las Tunas', + 'Chalala', + 'Surat', + 'Tobias Barreto', + 'Svilengrad', + 'Viswanathaperi', + 'Duartina', + 'Basantpur', + 'Chikodi', + 'Joplin', + 'Hilongos', + 'Whitefish Bay', + 'Sisian', + 'Grimbergen', + 'Soham', + 'Bage', + 'Senlis', + 'Matungao', + 'Roche-a-Bateau', + 'Belfort', + 'Asarcik', + 'Alotenango', + 'Santa Catarina Ayotzingo', + 'Villa Tunari', + 'Dangcheng', + 'Sievierodonetsk', + 'Vigan', + 'Anahuac', + 'Iesolo', + 'Chandili', + 'Aleksandrow Lodzki', + 'Palkur', + 'Parwaha', + 'Machali', + 'Corleone', + 'Heemstede', + 'Vallehermoso', + 'Ermezinde', + 'Sedalia', + 'Guanxi', + 'Valea lui Mihai', + 'Montescaglioso', + 'Heerlerbaan', + 'Seika', + 'Tongxiao', + 'Ronneby', + 'Badou', + 'Andilana Avaratra', + 'Hope Mills', + 'Taiynsha', + 'Borgampad', + 'Gonder', + 'Weigelstown', + 'Boukhralfa', + 'Kurshab', + 'Bad Liebenwerda', + 'Hodal', + 'Franco da Rocha', + 'Gangtok', + 'Kamianka-Dniprovska', + 'Acilia', + 'Paks', + 'Dastgerd', + 'Montbeliard', + 'Molteno', + 'Lakshmipuram', + 'El Tabo', + 'North Massapequa', + 'San Juan Cotzal', + 'Buftea', + 'Freystadt', + 'Chiltiupan', + 'Jabera', + 'Ikizce', + 'Khurmi', + '`Ajlun', + 'Kalpitiya', + 'Idlib', + 'Yuza', + 'Gaohucun', + 'Lavis', + 'Olesnica', + 'Parnera', + 'Windorah', + 'Munnarkod', + 'Tuckahoe', + 'Schotten', + 'Blansko', + 'Carregal do Sal', + 'Okemos', + 'Central Coast', + 'Anjehalli', + 'West Hollywood', + 'Chanthaburi', + 'Barauna', + 'Chettinayakkanpatti', + 'Aomori', + 'Sukurhutu', + 'Tiruvalam', + 'Lisala', + 'Al Bajur', + 'Santa Filomena', + 'Padre Paraiso', + 'Boura', + 'Teocuitatlan de Corona', + 'Inowroclaw', + 'Nova Milanese', + 'Yalta', + 'Lukulu', + 'Ambila', + 'Oulad Hassoune', + 'Folignano', + 'Salaman', + 'Belem de Sao Francisco', + 'Khvaf', + 'Rio Bravo', + 'Kotri', + 'Bom Jesus', + 'Ambodibonara', + 'Balvi', + 'Jiaozishan', + 'Cubuk', + 'Cagnes-sur-Mer', + 'Harrow Weald', + 'Nantingcun', + 'Choctaw', + 'Adalaj', + 'Hoi An', + 'Chintamani', + 'Ajka', + 'Havelock', + 'Votuporanga', + 'Uta', + 'Incirliova', + 'Ankazondandy', + 'Ash Shunah ash Shamaliyah', + 'Skegness', + 'Kedu', + 'Petite-Synthe', + 'Mungeli', + 'Anjahabe', + 'Maceira', + 'Sakurai', + 'Talya', + 'Ochtrup', + 'Polur', + 'Wake Forest', + 'Bushenyi', + 'Yihezhuang', + 'Vega Baja', + 'East Goshen', + 'Serra Talhada', + 'Ribeira Grande', + 'Branquinha', + 'Chimakurti', + 'Smyrna', + 'Lamesa', + 'Tabuelan', + 'Tuxpam de Rodriguez Cano', + 'Satyamangalam', + 'El Sauce', + 'Nasrullahganj', + 'Acerra', + 'Horlivka', + 'Beloit', + 'Adigaratti', + 'Puraini', + 'Gueltat Sidi Saad', + 'Egra', + 'Comendador', + 'Verrieres-le-Buisson', + 'Novoberde', + 'Onchan', + 'Puri', + 'Oudtshoorn', + 'Ab Pakhsh', + 'Melouza', + 'Schoonhoven', + 'Quilicura', + 'Arlon', + 'Steinkjer', + 'Port Macquarie', + 'Inca', + 'Erkelenz', + 'Lebrija', + 'Mandla', + 'Brus Laguna', + 'Deniliquin', + 'Schramberg', + 'Mokhotlong', + 'Walbrzych', + 'Azarshahr', + 'Highland Springs', + 'Ambodilazana', + 'Timurni', + 'Corbera de Llobregat', + 'Sanand', + 'Lugu', + 'Plovdiv', + 'Nokia', + 'Emden', + 'Biganos', + 'Sao Joao do Triunfo', + 'Carrollton', + 'Qadian', + 'Lowenberg', + 'Hongshandian', + 'Sultanpur Mor', + 'Uusikaupunki', + 'Manosque', + 'Itapevi', + 'Saudade', + 'Russi', + 'Brecksville', + 'Abaran', + 'Abu Suwayr', + 'Laranjeiras do Sul', + 'Krasnyi Luch', + 'Chekkal', + 'Grimma', + 'Aksum', + 'Arari', + 'Bromborough', + 'Yufle', + 'Indio', + 'Xonobod', + 'Trairi', + 'The Pinery', + 'Wehrheim', + 'Martuni', + 'Burbank', + 'Muritiba', + 'Nieuw-Vennep', + 'Cannock', + 'Neuhausen am Rheinfall', + 'Rostamabad', + 'Broome', + 'Voru', + 'Asilah', + 'Fazendinha', + 'West Orange', + 'Sirigeri', + 'Gurmatkal', + 'Simplicio Mendes', + 'Ede', + 'Rolesville', + 'Ashtian', + 'Saint-Genis-Laval', + 'Morada Nova', + 'Rohri', + 'Easton', + 'Tonacatepeque', + 'Marmande', + 'Neratovice', + 'Mildenhall', + 'Telaprolu', + 'North Perth', + 'Vengat', + 'Shirako', + 'Khanewal', + 'Malitbog', + 'Uherske Hradiste', + 'Sinsheim', + 'Baguanos', + 'Guigang', + 'Kumarankari', + 'Minudasht', + 'Bilauri', + 'Sihushan', + 'El Dovio', + "Sant'Anastasia", + 'Puertollano', + 'Rotorua', + 'Douar Azla', + 'Tostedt', + 'Silkeborg', + 'Velakalnattam', + 'Malvik', + 'Redentora', + 'Kimhae', + 'Cankuzo', + 'Turuvanur', + 'Siliancun', + 'Davie', + 'Ouled Rahou', + 'Pederneiras', + 'Aviano', + 'Kwale', + 'Knik-Fairview', + 'Baiyashi', + 'Pendekallu', + 'Sarai Ranjan', + 'Appleton', + 'San Pedro', + 'Bautista', + 'Maurilandia', + 'San Giovanni in Persiceto', + 'East Bethel', + 'Slovenska Bistrica', + 'Caerdydd', + 'Satana', + 'Fakola', + 'Waukee', + 'Lindsay', + 'Bailleul', + 'Ventimiglia', + 'Zaltbommel', + 'Arua', + 'Mansingha', + 'Amauna', + 'Muridke', + 'Burgthann', + 'Kayapinar', + 'Kawachicho', + 'Jiuru', + 'Hartland', + 'Oberwingert', + 'Shiddapur', + 'Sherbrooke', + 'Divaca', + 'Tayum', + 'Trecate', + 'El Menzel', + 'Rawson', + 'Imaculada', + 'Mindat', + "L'Epiphanie", + 'Ambarimaninga', + 'Oststeinbek', + 'Kuala Terengganu', + 'Sankt Polten', + 'Pageralam', + 'Carmen de Viboral', + 'Kety', + 'Inhambupe', + 'Nonantola', + 'South Lake Tahoe', + 'Hiji', + 'Erbaocun', + 'Millis', + 'Chervonohrad', + 'Williamsport', + 'Son La', + 'Leon de los Aldama', + 'Kraulshavn', + 'Figuil', + 'Ritterhude', + 'Sahel', + 'Karaga', + 'Rosarno', + 'Chudamani', + 'Stonehaven', + 'Jataizinho', + 'Surdulica', + 'Veghel', + 'Oki', + 'Hoxter', + 'Memari', + 'Holubivske', + 'Texcoco', + 'Aja', + 'Bayyavaram', + 'Pueblo', + 'Fengcheng', + 'Mossingen', + 'Arinos', + 'Bedesa', + 'Dolenjske Toplice', + 'Shadegan', + 'Pontecorvo', + 'Camano', + 'Fairburn', + 'Villefranche-sur-Saone', + 'Maiduguri', + 'Roxas City', + 'Biddeford', + 'Sokone', + 'Aravelli', + 'Palikir', + 'Herford', + 'Otsego', + 'Kamiita', + 'Brambleton', + 'Hongsi', + 'Bamafele', + 'Al Qurayn', + 'Naousa', + 'Urdorf', + 'Esira', + 'Cachoeira do Sul', + 'Botevgrad', + 'Jamjamal', + 'Sowerby Bridge', + 'Remada', + 'Vistahermosa', + 'Los Lagos', + 'Abengourou', + 'Jean-Mermoz', + 'Parker', + 'Hisor', + 'Oulunsalo', + 'Aguilar', + 'Yuscaran', + 'Sedro-Woolley', + 'Lankaran', + 'Zaniena', + 'Lucerne', + 'Tortum', + 'Lynbrook', + 'Rasiari', + 'Trappes', + 'Kirkel', + 'Balakrishnanpatti', + 'Getafe', + 'Bad Vilbel', + 'Mankayan', + 'Trindade', + 'La Ceja', + 'Tacuba', + 'Tagbina', + 'Severna Park', + 'Canosa di Puglia', + 'Gasan', + 'Daiwanishi', + 'Polohy', + 'Qaraghandy', + 'Ziri', + 'San Antonio de Padua', + 'Rarott', + 'Itami', + 'Carapo', + 'La Cruz de Rio Grande', + 'Ruskin', + 'Camooweal', + 'Barika', + 'Tohoku', + 'Pante Macassar', + 'Vocklabruck', + 'Erutukada', + 'Koili Simra', + 'Edea', + 'Targoviste', + 'Antanankambano', + 'Bangzha', + 'Bankass', + 'Podstrana', + 'Kakuma', + 'Quezaltepeque', + 'Schaan', + 'Daxincun', + 'Trikodi', + 'Yawatahama-shi', + 'Pefki', + 'The Hague', + 'Tamu', + 'Guli', + 'Shing', + 'Bakersfield', + 'Lanxi', + 'Singaparna', + 'Aljustrel', + 'Chirchiq', + 'Rio Acima', + 'Halstenbek', + 'Pandhurna', + 'Boa Nova', + 'Cerkvenjak', + 'Saint-Jean-le-Blanc', + 'Largo', + 'Basse-Terre', + 'Jimalalud', + 'Bhanas Hivre', + "Sant'Ilario d'Enza", + 'Santa Lucia Utatlan', + 'Parasurampur', + 'New Providence', + 'Sofiivka', + 'Ban Muang Kham', + 'Pingdu', + 'Kyle', + 'Nellutla', + 'Hualqui', + 'New City', + 'Tangancicuaro de Arista', + 'Brawley', + 'Rucheng', + 'Bendorf', + 'Steinen', + 'Welby', + 'Valentigney', + 'Jacinto City', + 'Mweka', + 'Baler', + 'Sasso Marconi', + 'Fanzeres', + 'Sunrise', + 'Ovidiopol', + 'Canarana I', + 'Jaitwar', + 'Settat', + 'Villa Paranacito', + 'Lerdo de Tejada', + 'Westerstede', + 'Rangapukur', + 'Vinukonda', + 'Mastaga', + 'Terrasini Favarotta', + 'El Fuerte', + 'Heydarabad', + 'Rapperswil-Jona', + 'Preston', + 'Mahasamund', + 'Bossier City', + 'Kotamobagu', + 'Sowme`eh Sara', + 'Charala', + 'Gayeri', + 'Calape', + 'Villa Rica', + 'Panying', + 'Khandauli', + 'Oristano', + 'Ar Ramtha', + 'Ben Gardane', + 'Donnacona', + 'Bagamoyo', + 'Caras', + 'Chikha', + 'Kalikavu', + 'Lewiston', + 'Maseru', + 'Huanchaco', + 'Higuerote', + 'Yangshe', + 'Salinas Victoria', + 'Marofoty', + 'Yamen', + 'Hyderabad City', + 'Gusang', + 'Mexicali', + 'Yedtare', + 'Salgado de Sao Felix', + 'Gaffney', + 'Torcy', + 'Hodos', + 'Sangerhausen', + 'Tsuruta', + 'Bladensburg', + "Sach'on", + 'Sabanozu', + 'Agustin Codazzi', + 'Kings Park West', + 'Periyamuttur', + 'Varnsdorf', + 'Akbez', + 'The Woodlands', + 'Puente Nacional', + 'Golcuk', + 'Sandbach', + 'Tullamore', + 'Chimalhuacan', + 'Kula', + 'Oliveira de Frades', + 'Yuasa', + 'Jieyang', + 'Conceicao de Jacuipe', + 'Pezinok', + 'Malgrat de Mar', + 'Ceske Budejovice', + 'Qapshaghay', + 'Membakut', + 'General Acha', + 'Studenka', + 'Cleveland', + 'Mehnajpur', + 'Darasuram', + 'Rubengera', + 'Kusadasi', + 'Buddh Gaya', + "Clermont-l'Herault", + 'Bifeng', + 'Nakama', + 'Kuiju', + 'Scheessel', + 'Kajo Kaji', + 'Medina Estates', + 'Pointe-Claire', + 'Savannakhet', + 'Yarumal', + 'Saint-Egreve', + 'Sokoto', + 'Timonium', + 'Tire', + 'Ota', + 'Otwock', + 'Pretoria-Noord', + 'Iziaslav', + 'Bell Gardens', + 'Ecija', + 'Khunays', + 'Kottaipatti', + 'Garin', + 'Tuktukan', + 'Ibara', + 'Tyre', + 'Casteldaccia', + 'Gulgam', + 'Jaffna', + 'Gariadhar', + 'Zoetermeer', + 'Kasulu', + 'Santo Antonio dos Lopes', + 'Pannawonica', + 'Cajuru', + 'Belchertown', + 'Sonseca', + 'Non Sung', + 'Kardla', + 'Furstenfeldbruck', + 'Ban Tat', + 'Bydgoszcz', + 'Semere', + 'Fortuna', + 'Corralillo', + 'Coin', + 'Siegburg', + 'Comacchio', + 'Bellavista', + 'Mission Bend', + 'Avezzano', + 'Rancho Mission Viejo', + 'Mechraa Bel Ksiri', + 'Shirebrook', + 'Kizhariyur', + 'Bueng Kan', + 'Matigou', + 'Misratah', + 'Deventer', + 'Luau', + 'Kiratpur Rajaram', + 'Huanuni', + 'Kannamanayakkanur', + 'Kluczbork', + 'North Battleford', + 'Lukaya', + 'Bo`ka', + 'Baicheng', + 'Channahon', + 'Terrace Heights', + 'Uniao', + 'Kunigal', + "Porto Sant'Elpidio", + 'Marin', + 'Bimun', + 'Dilijan', + 'Satkhira', + 'Erie', + 'Hemsworth', + 'Gueppi', + 'Urubici', + 'Daraw', + 'Kastoria', + 'Yutz', + 'Lichtenstein', + 'Unquillo', + 'Homewood', + 'Accrington', + 'Kalyanpur', + 'Diego de Almagro', + 'Beeville', + 'Koila', + 'Hathiakan', + 'Coka', + 'Bouake', + 'Veyrier', + 'Akhaltsikhe', + 'Tinaquillo', + 'Moncada', + 'Gladbeck', + 'Telpaneca', + 'Central Elgin', + 'Vari', + 'Plonsk', + 'Chateau-Thierry', + 'Schkopau', + 'Nawalpur', + 'Jagatpur', + 'La Ceiba', + 'Omuthiya', + 'Soalandy', + 'Kimberley', + 'Kappiyara', + 'Hyattsville', + 'Shichigahama', + 'Koronowo', + 'Modra', + 'Manolo Fortich', + 'Neuilly-sur-Marne', + 'San Juanito', + 'Sedhiou', + 'Pailon', + 'Vechta', + 'Schortens', + "Qia'erbagecun", + 'Coalinga', + 'Plankstadt', + 'Cachoeira Alta', + 'Grand Prairie', + 'Ulsan', + 'Bouguenais', + 'Campina da Lagoa', + 'Sorong', + 'Churumuco de Morelos', + 'Byron Bay', + 'Sarny', + 'Bilbays', + 'Patiali', + 'Milagro', + 'Kothri Kalan', + 'Idaho Falls', + 'Bombo', + 'Muttunayakkanpatti', + 'Nidamanuru', + 'Countryside', + 'Buu Long', + 'Capitan Bado', + 'Surpur', + 'Reggane', + 'Campi Bisenzio', + 'Anantasagaram', + 'Salto Grande', + 'Baduria', + 'Echague', + 'Hollywood', + 'Rahat', + 'Lower Paxton', + 'Trebbin', + 'Wardha', + 'Beek en Donk', + 'Vilhena', + 'Bergeijk', + 'Chhapia', + 'Ganaram', + 'Chhanera', + 'Kapangan', + 'Roncade', + 'Walcourt', + 'Waltenhofen', + 'Bahar', + 'Oiba', + 'Willoughby', + 'Babol', + 'Alashankou', + 'Groton', + 'Ban Talat Rangsit', + 'Neijiang', + 'Johvi', + 'Ulao', + 'Nagold', + 'Lacey', + 'Marigot', + 'Zabljak', + 'Atushi', + 'Pileru', + 'Borodyanka', + 'Guaymate', + 'Alakamisy Anativato', + 'Montecchio Maggiore', + 'Umea', + 'Mekambo', + 'Jericho', + 'Urrao', + 'Conceicao do Mato Dentro', + 'Cutro', + 'Narayankher', + 'Ocoyoacac', + 'Las Margaritas', + 'North Cornwall', + 'Katiena', + 'Liangwancun', + 'Tarawan', + 'Tianjin', + 'Eltham', + 'Ampitasimo', + 'Patzcuaro', + 'Boortmeerbeek', + 'Zary', + 'New Tecumseth', + 'Izki', + 'Faradonbeh', + 'Ezine', + 'Mushie', + 'Kuvango', + 'La Quiaca', + 'Tamgrout', + 'Buenos Aires', + 'Khowrhesht', + 'Muskegon Heights', + 'Wangsicun', + 'Kimwanyi', + 'Narra', + 'Belton', + 'Khlong Luang', + 'Barhadashi', + 'Ixtlahuacan de los Membrillos', + 'Lauterbach', + 'Mohda', + 'Chepstow', + 'Blegny', + 'Reykjavik', + 'Jangalapalle', + 'Cumayeri', + 'Tarashcha', + 'Cervera', + 'Pouso Redondo', + 'Bunde', + 'Bariyarpur', + 'Zweibrucken', + 'Alzenau in Unterfranken', + 'Santo Tomas', + 'Taxtako`pir', + 'Colinas do Tocantins', + 'Vryburg', + 'Hooksett', + 'Rahui', + 'Daejeon', + 'Chinaval', + 'Pornichet', + 'Hazelwood', + 'Sherkot', + 'Shuishang', + 'Palmitos', + 'Erin', + 'Lagoa do Itaenga', + 'Uthal', + 'Whippany', + 'Zamboanguita', + 'Kasugai', + 'Kuurne', + 'Embarcacion', + 'Alsfeld', + 'Saint-Avertin', + 'Lokwabe', + 'Bronte', + 'Gemerek', + 'Diamond Bar', + 'Germantown', + 'Tengampudur', + 'Myanaung', + 'Amritpur', + 'Manhattan Beach', + 'Spring Valley Lake', + 'Niangoloko', + 'Crevalcore', + 'Bamble', + 'Hollabrunn', + 'Furtwangen im Schwarzwald', + 'Ariquemes', + 'Baruari', + 'Paura Madan Singh', + 'Bracknell', + 'Le Bouscat', + 'Pirapozinho', + 'Puyappalli', + 'Zhengjiatun', + 'Garibaldi', + 'Aydin', + 'Sestri Levante', + 'Panjampatti', + 'Chengbin', + 'Ambohitrambo', + 'Olympia Heights', + 'Fuxin', + 'Jupiter Farms', + 'Birjand', + 'Sugauna', + 'Villa La Angostura', + 'Abu Hummus', + 'Igreja Nova', + 'Sanghera', + 'Tremembe', + 'Bhopalia', + 'Vila Velha', + 'Papampeta', + 'Dhanga', + 'Sheyban', + 'Jaltenango', + 'Tagapul-an', + 'Kulusuk', + 'Montesson', + 'Chascomus', + 'Guernica y Luno', + 'Itamarati', + 'Metepec', + 'Wadersloh', + 'Costa Marques', + 'Barod', + 'Bhaisalotan', + 'Velez-Malaga', + 'Raunds', + 'Hushihacun', + 'Sindgi', + 'Quivican', + 'Ivoti', + 'Maroteza', + 'Bazar-Korgon', + 'Baduriatola', + 'Tysons', + 'Saidapet', + 'Busto Arsizio', + 'Cabanatuan City', + 'Sami', + 'Logansport', + 'Besozzo', + 'Sonaguera', + 'Acevedo', + 'Harbiye', + 'Prairie Ridge', + 'Kasaragod', + "Hosa'ina", + 'Borgo San Dalmazzo', + 'Cockeysville', + 'Kabirpur', + 'Mikuszowice', + 'Kamiichi', + 'Arsali', + 'Enugu', + 'Sahanivotry-Manandona', + 'Savar', + 'Al Khubar', + 'Haddon', + 'Allahdurg', + 'Rodynske', + 'Puduru', + 'Jiquirica', + 'Kirishima', + 'Skive', + 'Aj Jourf', + 'Uedem', + 'Cananea', + 'Stryzhavka', + 'Bankapur', + '`Afrin', + 'Saint-Colomban', + 'Imperia', + 'Valsequillo de Gran Canaria', + 'Jette', + 'Hamm', + 'Piranga', + 'Rixheim', + 'Currimao', + 'Opuwo', + 'Herohalli', + 'Larreynaga', + 'Gravina in Puglia', + 'Benton Harbor', + 'Afragola', + 'Castelldefels', + 'Lubao', + 'Brahmanbaria', + 'Barkly West', + 'San Rafael del Norte', + 'Abu Dhabi', + 'Mamburao', + 'Chartres', + 'Hawick', + 'Shiruru', + 'Butajira', + 'Newcastle', + 'Igarape-Miri', + 'San Mariano', + 'Statesboro', + 'Cachoeira do Arari', + 'Goumori', + 'Kassaro', + 'Karaisali', + 'Yoshida', + 'Stekene', + 'Walajabad', + 'Alcorcon', + 'Seneca', + 'Cabagan', + 'Mansfield', + 'Valsad', + 'Monaragala', + 'Barro', + 'Assebroek', + 'Al Mubarraz', + "L'Isle-d'Abeau", + 'Cajueiro', + 'Wanstead', + 'Iten', + 'Salamina', + 'Jacinto Machado', + 'Kuyganyor', + 'Ar Rusayfah', + 'Morgantown', + 'Balta', + 'Anse-a-Foleur', + 'Crnomelj', + 'Dagbe', + 'Nubl', + 'Isumi', + 'Arumbavur', + 'Lohuti', + 'Backa Palanka', + 'Marktoberdorf', + 'Massango', + 'Gamay', + 'Asbury Park', + 'Piracicaba', + 'Tadas', + 'Malanvadi', + 'Ranko', + 'Falam', + 'Kerava', + 'Brevik', + 'Fort Madison', + 'San Pablo Villa de Mitla', + 'La Resolana', + 'Irun', + 'Santa Juana', + 'Senirkent', + 'Colbun', + 'Chasiv Yar', + 'Bailleston', + 'Mairwa', + 'Ba Don', + 'Caetanopolis', + 'Popovo', + 'Bandipura', + 'Brewer', + 'Raesfeld', + 'Chityal', + 'Bois-Guillaume', + 'Miahuatlan de Porfirio Diaz', + 'Eupen', + 'Riemst', + 'Reims', + 'Menen', + 'Wundanyi', + 'Guarenas', + 'Biankouma', + 'Vila Bela da Santissima Trindade', + 'Pallappalaiyam', + 'Farmington', + 'Bradenton', + 'Singoli', + 'Cainta', + 'Farahalana', + 'Simoes Filho', + 'San Zenon', + 'Planadas', + 'Catole do Rocha', + 'Solrod Strand', + 'Rovinj', + 'Dracena', + 'Jablonec nad Nisou', + 'Chhagalnaiya', + 'Ampataka', + 'Lohagara', + 'Zarautz', + 'Tiquisio', + 'Epinay-sous-Senart', + 'Soka', + 'Gandarbal', + 'Los Muermos', + 'Akyaka', + 'Az Zaqaziq', + 'Ibaretama', + 'San Jose Guayabal', + 'Phulparas', + 'Unity', + 'Anderson', + 'Post Falls', + 'Bandio', + 'Quiindy', + 'Mazoe', + 'Gokarna', + 'Weston-super-Mare', + 'Donihue', + 'Maliano', + 'Sahibganj', + 'Stropkov', + 'Williston', + 'Khutauna', + 'Mishrikot', + 'Rice Lake', + 'Boa Viagem', + 'Ashkhaneh', + 'Wabag', + 'Ban Mangkon', + 'Bognor Regis', + 'Alabel', + 'Hoor', + 'Perote', + 'Sotkamo', + 'Mukondapalli', + 'Kenzingen', + 'El Alia', + 'Barssel', + 'Libon', + 'Pljevlja', + 'Karumulaikkal', + 'Atlatlahucan', + 'Puteaux', + 'Ramnicu Sarat', + 'Karariya', + 'Floro', + 'Richard-Toll', + 'Semuto', + 'Jilotlan de los Dolores', + 'Nellikkuppam', + 'Malsch', + 'Khowrasgan', + 'Ambodimahabibo', + 'Safidon', + 'Canoinhas', + 'Ban Na Yang', + 'Kuttur', + 'Makubetsu', + 'Tsavo', + 'Guape', + 'Mapleton', + 'Sao Sebastiao da Grama', + 'Cannanore', + 'Sama', + 'Vail', + 'Alwar', + 'Cherlagandlapalem', + 'Zyryanovsk', + 'Kopong', + 'Wabrzezno', + 'Antsampanimahazo', + 'Forney', + 'Obock', + 'Banstead', + 'Kovurupalli', + 'Bientina', + 'Le Creusot', + 'Bontang', + 'Pierre', + 'Sidi Aissa', + 'San Ignacio Cerro Gordo', + 'Lancon-Provence', + 'Sao Bento do Sul', + 'Arkadelphia', + 'Lalru', + 'Neriyamangalam', + 'Kwai Chung', + 'Matsubara', + 'Metapan', + 'Hopatcong', + 'Baniyas', + 'Song Phi Nong', + 'Labason', + 'Kashima', + 'Bafanji', + 'Isola del Liri', + 'Las Rozas de Madrid', + 'Herdecke', + 'Ait Bousarane', + 'Itanhandu', + 'Odemis', + 'Mohgaon', + 'Bilgoraj', + 'Poquoson', + 'Dilolo', + 'Soliman', + 'Videira', + 'Sankt Augustin', + 'Hachimantai', + 'Oneida', + 'Medeiros Neto', + 'Sakri', + 'Chestnut Ridge', + 'Burubaytal', + 'Oruro', + 'Beauraing', + 'Mauli', + 'Curanilahue', + 'Great Neck', + 'Skofja Loka', + 'Aiken', + 'Roncador', + 'Temuco', + 'Oros', + 'Timmapur', + 'Kununurra', + 'Naqadeh', + 'Clarin', + 'Podgorica', + 'Beldanga', + 'Mutluru', + 'South Whitehall', + 'Prince Rupert', + 'Nirasaki', + 'Hodatsushimizu', + 'Trani', + 'Voghera', + 'Purkersdorf', + 'Medapadu', + 'Puerto Francisco de Orellana', + 'Fatao', + 'Sidi Yahia El Gharb', + 'Spiez', + 'Pujili', + 'Renk', + 'Quemado de Guines', + 'Antenetibe', + 'Rueil-Malmaison', + 'Saimbeyli', + 'Asagi Quscu', + 'Tsarahasina', + 'Hunasagi', + 'Chilpancingo', + 'Goycay', + 'Republic', + 'Bocholt', + 'Montgeron', + 'Dodji-Bata', + 'Panzgam', + 'Joinville-le-Pont', + 'Fergus', + 'Tenkasi', + 'Groveland', + 'El Carmen de Atrato', + 'Garou', + 'Sangao', + 'Buffalo', + 'Oulad Amrane el Mekki', + 'Bethune', + 'Sidfa', + 'Cuichapa', + 'Maria Aurora', + 'Sanzana', + 'Vaxjo', + 'Tsrar Sharif', + 'Samut Sakhon', + 'Novate Milanese', + 'Mirdaul', + 'Broadview Heights', + 'Domaa-Ahenkro', + 'Herseh Chhina', + 'Tupa', + 'Porec', + 'Mount Evelyn', + 'Balboa Heights', + 'Xuqiaocun', + 'Sumbawa Besar', + 'Nahiyat Khan Bani Sa`d', + 'Danli', + 'Bartin', + 'Feyzin', + 'Peruvanthanam', + 'Chene-Bougeries', + 'Malakal', + 'North Middleton', + 'Hauzenberg', + 'Datteln', + 'Goiatuba', + 'Solan', + 'Ol Kalou', + 'Kaedi', + 'Sakib', + 'Reyes', + 'Freudenstadt', + 'Anamalais', + 'Chengxiang', + 'Ely', + 'Monmouth', + 'Fukude', + 'Nepanagar', + 'Bousse', + 'Emba', + 'Ghusiya', + 'Noisiel', + 'Barharia', + 'Techiman', + 'Castilho', + 'Higashi-osaka', + 'Kerugoya', + 'Bouaiche', + 'Laurel', + 'Suluktu', + 'Hanzviur', + 'Rosrath', + 'Ciro Marina', + 'Cabanillas del Campo', + 'Qahjavarestan', + 'Zuidhorn', + 'Beilen', + 'Minami-Soma', + 'Douentza', + 'Florianopolis', + 'Darende', + 'Catanduva', + 'Wao', + 'Rasingapuram', + 'Huodoushancun', + 'Fontaine-les-Dijon', + 'Shinyanga', + 'Sargodha', + 'Penfield', + 'Dunaujvaros', + 'Ube', + 'Ain Dfali', + 'Andrembesoa', + 'Kochas', + 'Kondalampatti', + 'Khejroli', + 'Manassas', + 'Macaiba', + 'Uson', + 'Langelsheim', + 'Kottukal', + 'Sharan', + 'Longueuil', + 'Parappur', + 'Kurivikod', + 'Pindi Bhattian', + 'Buuhoodle', + 'Karjat', + 'Befandriana', + 'Alto Santo', + 'Pinneli', + 'Mlawa', + 'Cortez', + 'Katteragandla', + 'Badalona', + 'Zibo', + 'Keizer', + 'Marijampole', + 'Loncoche', + 'Mpika', + 'Hobro', + 'Mancora', + 'Bosconia', + 'Frohburg', + 'Weilerswist', + 'Las Charcas', + 'Carney', + 'Bhansia', + 'Simrahi', + 'Guildford', + 'Fuquay-Varina', + 'Braunau am Inn', + 'Pine Hills', + 'Franconville', + 'Noordwijkerhout', + 'Bezons', + 'Sliema', + 'Pinellas Park', + 'Kafr Qasim', + 'Dundee', + 'Valley', + 'Bromsgrove', + 'Coacalco', + 'Kralupy nad Vltavou', + 'Lafrayta', + 'Tunapuna', + 'Barmstedt', + 'Upper Saucon', + 'Palaiya Ayakkudi', + 'Mladenovac', + 'Qaratau', + 'Hurtgenwald', + 'Mailapur', + 'Tucurui', + 'Australia', + 'Cidelandia', + 'Holly Hill', + 'Kannandahalli', + 'Gilgit', + 'Roetgen', + 'Jirkov', + 'Madavur', + 'Eastham', + 'Kachavaram', + 'Qal`at an Nakhl', + 'Pearl River', + 'Oberstdorf', + 'Andilamena', + 'Tittagudi', + 'Ceel Baraf', + 'Meerbusch', + 'Jaunpur', + 'Terrell', + 'Ban Nong Kula', + 'Sarmiento', + 'Coaldale', + 'Baneh', + 'Leh', + 'Mirai', + 'Ambodisikidy', + 'Amtar', + 'Boise', + 'Solsona', + 'Maizuru', + 'Villa Luvianos', + 'Twentynine Palms', + 'Murapaka', + 'Monclova', + 'Miharu', + 'Krumbach', + 'Haldensleben', + 'Inaja', + 'Iguatu', + 'Ponto Novo', + 'Batabano', + 'Itape', + 'Poggiomarino', + 'Gayaspur', + 'Monte San Juan', + 'Kostiantynivka', + 'Bagalvad', + 'Pozoblanco', + 'Gaunivaripalle', + 'Gandhidham', + 'Pingxiangcheng', + 'Flers-lez-Lille', + 'Owasso', + 'Summerville', + 'Ban Tap Tao', + 'Mahisanrh', + 'Podaturpeta', + 'Kalanchak', + 'Lira', + 'Yaguaron', + 'Saharanpur', + 'Guthrie', + 'Tirat Karmel', + 'Sagala', + 'Griesheim', + 'Riesa', + 'Anaikal', + 'Kontiolahti', + 'Betania', + 'Varjota', + 'Pisaflores', + 'Encarnacion de Diaz', + 'Tafeng', + 'Ioannina', + 'Ha Giang', + 'Ash Shinan', + 'Maromiandra', + 'Kampel', + 'Seyyedan', + 'Elk', + 'Marcos Juarez', + 'Timri', + 'Ewarton', + 'Betul Bazar', + 'Sao Jose do Belmonte', + 'Amboahangibe', + 'Isser', + 'Yellayapalem', + 'Bhiwani', + 'Ad Dakhla', + 'Edmond', + 'Lourosa', + 'Aslanapa', + 'Ilsede', + 'Phoenix', + 'Negrine', + 'Prabhat Pattan', + 'Tapaktuan', + 'Camalig', + 'Upper Montclair', + 'Binh Long', + 'Trincomalee', + 'Yellowknife', + 'Turbo', + 'Kashaf', + 'Rapur', + 'Jamiltepec', + 'Zaggota', + 'Serui', + 'Ban Nam Dip Luang', + 'Sursee', + 'Miyoshidai', + 'Chichiriviche', + 'Kariat Ben Aouda', + 'Tranomaro', + 'Torokbalint', + 'Eiheiji', + 'Fartura', + 'Lonate Pozzolo', + 'Lixingcun', + 'Purwakarta', + "Fu'an", + 'Havi Bhauar', + 'Livinjipuram', + 'Sherwood', + 'Cayenne', + 'Hamme', + 'Bao Loc', + 'Cameta', + 'San Fructuoso de Bages', + 'Mawlamyine', + 'Yonkers', + 'Doudian', + 'Orebro', + 'Longvic', + 'Penaballi', + 'Sinzig', + 'Jambi', + 'Bandundu', + 'Poshkent', + 'Dhamdaha', + 'Springettsbury', + 'Savage', + 'Iga', + 'Narsimlapet', + 'Hatwans', + 'Marco Island', + 'Mallikkundam', + 'Loharda', + 'Aguada de Pasajeros', + 'Jining', + 'Dhana', + 'Raceland', + 'Wroclaw', + 'Sao Lourenco da Mata', + 'Kiblawan', + 'Alagoa Nova', + 'Trentham', + 'Alofi', + 'Missoula', + 'Pecanha', + 'Atok', + 'Paipa', + 'Kouroussa', + 'Arnavutkoy', + 'Ankaran', + 'Jorhat', + 'Susari', + 'Higashi-Hiroshima', + 'Incline Village', + 'Rafael Calzada', + 'Thetford', + 'Jelgava', + 'Manono', + 'Tuncheng', + 'Potrerillos', + 'Lopon', + 'Moline', + 'Cornelio Procopio', + 'Dali', + 'Agropoli', + 'Inazawa', + 'Korosavada', + 'Elne', + 'Karur', + 'Vasudevanallur', + 'Viti', + 'Rezina', + 'Paju', + 'Thohoyandou', + 'Enniskillen', + 'Bad Fallingbostel', + 'Sokobanja', + 'Pleven', + 'Bang Phongphang', + 'Danga', + 'Tahuna', + 'Belsara', + 'Battalgazi', + 'Paravada', + 'General Belgrano', + 'Qingping', + 'Jinsha', + 'Malmedy', + 'Santa Cruz Mulua', + 'Lindenberg im Allgau', + 'Karpenisi', + 'Dondo', + 'Viralippatti', + 'Tado', + 'Cimanggis', + 'Barra do Bugres', + 'Kudahuvadhoo', + 'Badru Khan', + 'Samorin', + 'Tiruverumbur', + 'Taloda', + 'Roding', + 'Bante', + 'La Independencia', + 'Basseterre', + 'Ati', + 'Cavinti', + 'Chitembo', + 'Ryugasaki', + 'Eschborn', + 'Vertientes', + 'Bobingen', + 'Haliyal', + 'Sao Gabriel da Cachoeira', + 'Dielheim', + 'Siirt', + 'Oosterend', + 'Aich', + 'Ilkhchi', + 'Labrador City', + 'Chioggia', + 'Neuquen', + 'Upper Allen', + 'Itajobi', + 'Baltimore', + 'Nagra', + 'Wegorzewo', + 'Sansa', + 'Suaita', + 'Purral', + 'Narwar', + 'Denan', + 'Kandla Port', + 'Koubel-Koundia', + 'Airdrie', + 'Karumandi Chellipalaiyam', + 'Parol', + 'San Francisco la Union', + 'Kocasinan', + 'Sidi Abdallah', + 'Merzenich', + 'Correntina', + 'Saunda', + 'Paducah', + 'Keve', + 'Nandiala', + 'Indramayu', + 'Qusmuryn', + 'Aramangalam', + 'Mavorano', + 'Koppaka', + 'Corroios', + 'Thornbury', + 'Palm City', + 'Sao Felix da Marinha', + 'Giffnock', + 'Cardenas', + 'Kyaunggon', + 'Zhoukou', + 'Red Deer', + 'Constanza', + 'Libertador General San Martin', + 'Ducheng', + 'Orsova', + 'Bures-sur-Yvette', + 'Tixtla de Guerrero', + 'Tlachichilco', + 'Kangan', + 'Lyndhurst', + 'Wexford', + 'Chitipa', + 'Palagonia', + 'Olsberg', + 'Antongomena-Bevary', + 'Wayaobu', + 'Irosin', + "Sa'ada", + 'Lamwo', + 'Bibemi', + 'Bartow', + 'Floresti', + 'Kemin', + 'Baramandougou', + 'Baybay', + 'Nansang', + 'Frenstat pod Radhostem', + 'Mizhhiria', + 'Zelino', + 'Beni Abbes', + 'Tarkwa', + 'Ezhipram', + 'Vicente Noble', + 'Hitachi-ota', + 'Muhembo', + 'Ksar el Hirane', + 'Lauchhammer', + 'Agboville', + 'Big Bear City', + 'Oradea', + 'Mabitac', + 'Trashigang', + 'An Nasiriyah', + 'Belo Oriente', + 'Pampas', + 'Schifferstadt', + 'Guatemala City', + 'Pithampur', + 'I`zaz', + 'Poranki', + 'Arcola', + 'Perugia', + 'Betmangala', + 'Haqqulobod', + 'Wujiaying', + 'Ankazoabokely', + 'Agrestina', + 'Rodenbach', + 'Nowshahr', + 'Sierning', + 'Santa Maria de Jesus', + 'Madalena', + 'Tarrafal', + 'Mitoma', + 'Roseaux', + 'Wichita', + 'Grudziadz', + 'Zarnesti', + 'Findikli', + 'Sarnen', + 'Irpin', + 'Rouen', + 'Qishe', + 'Dammartin-en-Goele', + 'Hazel Dell', + 'Charikar', + 'Ostrzeszow', + 'Tunari', + 'Kingri', + 'La Reina', + 'Boriziny', + 'Nangavaram', + 'Jahangirpur Salkhani', + 'Mangobandar', + 'Gustavia', + 'Alangudi', + 'Naumburg', + 'Sarikishty', + 'Dania Beach', + 'Budaka', + 'Tillmans Corner', + 'Ramotswa', + 'Menges', + 'Dasuya', + 'Darreh Shahr', + 'Perumkulam', + 'Moorreesburg', + 'El Ejido', + 'Lingayen', + 'Fontana', + 'Ubaporanga', + 'Shoo', + 'Heroica Matamoros', + 'Bayog', + 'Marikal', + 'Da Nang', + 'Lampertheim', + 'Basbiti', + 'Ramonville-Saint-Agne', + 'Kuwait City', + 'Manakana', + 'Mandra', + 'Tehata', + 'Stein bei Nurnberg', + 'Crigglestone', + 'Brofodoume', + 'Trnava', + 'Thimphu', + 'Gurmia', + 'Cacem', + 'Alahina', + 'Mongo', + 'Khayelitsha', + 'Sadri', + 'Fukui', + 'Dharmaj', + 'Kaatsheuvel', + 'Dammarie-le-Lys', + 'Matsakabanja', + 'Ait Ouaoumana', + 'Kirovsk', + 'Savur', + 'Baranivka', + 'Kodaikanal', + 'Lantana', + 'Shache', + 'Jacobabad', + 'President Quirino', + 'Kozienice', + 'Coatetelco', + 'Tha Muang', + 'Fanlu', + 'Ejutla de Crespo', + 'Senhora da Hora', + 'Bhadarwah', + 'Almazora', + 'Americus', + 'Schaesberg', + 'Liulin', + 'Shahrak-e Ja`fariyeh', + 'Pathankot', + 'Kirkkonummi', + 'Koyulhisar', + 'Adamankottai', + 'Ketama', + 'Irineopolis', + 'Kavali', + 'Sambalhera', + 'Nanjian', + 'San Juan de Uraba', + 'Tola Khadda', + 'Mulheim', + 'Berhoum', + 'Hawaiian Paradise Park', + 'Marg`ilon', + 'Eilendorf', + 'Oostzaan', + 'Evanston', + 'Mantasoa', + 'Ixtlahuaca', + 'Capao da Canoa', + 'Baker', + 'Altintas', + 'Taipei', + 'Siktiahi', + 'Ramabitsa', + 'Tarmount', + 'Haciqabul', + 'Ellore', + 'Palangkaraya', + 'Mugumu', + 'Ed Damer', + 'Steinfurt', + 'Malthone', + 'Villagarzon', + 'Elizabeth City', + 'La Farlede', + 'Binaqadi', + 'Barquisimeto', + 'Simaria', + 'Castel Volturno', + 'Frickenhausen', + 'Ait Ikkou', + 'Gauravaram', + 'Ubeda', + 'Simcoe', + 'Oakley', + 'Capas', + 'Jixian', + 'Muthabana', + 'Masalli', + 'Leganes', + 'Beidaying', + 'Hyeres', + 'Vinnamala', + 'Chatteris', + 'Parit Buntar', + 'Khelil', + 'Igaci', + 'Raytown', + 'Janakpur', + 'Ilford', + 'Bojnurd', + 'Toyota', + 'Orte', + 'Saint-Brieuc', + 'The Nation / La Nation', + 'Ghulja', + 'Lagoa do Mato', + 'Mohelnice', + 'Lavumisa', + 'Harda Khas', + 'Pendlebury', + 'Sonakhal', + 'Unagatla', + 'Sandhurst', + 'Villa Krause', + 'Sherghati', + 'Supaul', + 'Busovaca', + 'Luathaha', + 'Liversedge', + 'Liberty', + 'Yian', + 'Sion', + 'Itamogi', + 'Berrouaghia', + 'Bharuch', + 'Waunakee', + 'Adel', + 'Crestline', + 'Savissivik', + 'Meiganga', + 'Sannai', + 'Cuizhuangzi', + 'Cold Lake', + 'Agrigento', + 'Rio Branco do Sul', + 'Carneirinho', + 'Viborg', + 'Munsan', + 'Big Lake', + 'Rute', + 'Mela Gudalur', + 'Mbala', + 'Mateus Leme', + 'Kakamega', + 'Al Atarib', + 'Bad Harzburg', + 'Tigbauan', + 'Phak Hai', + 'San Juan de la Costa', + "Cornate d'Adda", + 'Bewdley', + 'Nyimba', + 'Vedelago', + 'Asaba', + 'Bozyazi', + 'Tuxtla', + 'Alvin', + 'Castiglion Fiorentino', + 'Umag', + 'Taquaritinga do Norte', + 'Ghoti Budrukh', + 'Relangi', + 'Woodward', + 'Santiago de Tolu', + 'Marcali', + 'Stanley', + 'Dulay` Rashid', + 'Five Forks', + 'Jandaia do Sul', + 'Culfa', + 'Suzano', + 'Motomachi', + 'Ahermoumou', + 'Jose Cardel', + 'Marsella', + 'Tyngsborough', + 'Eijsden', + 'Checy', + 'Hamidiye', + 'Mocimboa da Praia', + 'Cossato', + 'Adjohon', + 'Jamapa', + 'Dahana', + 'Banfora', + 'Rabo de Peixe', + 'Caloto', + 'West Lampeter', + 'Princeton Meadows', + 'Whitwick', + 'Siversk', + 'Mpraeso', + 'Borovskoy', + 'Cockermouth', + 'Mogadishu', + 'Traipu', + 'Frechen', + 'Cabras', + 'Enkakad', + 'Bocas de Satinga', + 'Tomares', + 'Umbauba', + 'Itiki', + 'Nordhorn', + 'Macetown', + 'Quilengues', + 'Chiclana de la Frontera', + 'Lagoa Seca', + 'Zielonka', + 'Xinqing', + 'Pedro Carbo', + 'Vespasiano', + 'Masis', + 'Prichard', + 'Sandomierz', + 'Fulwood', + 'Bad Liebenzell', + 'Zvenyhorodka', + 'Bellevue', + 'Gudgeri', + 'Srikrishnapur', + 'Madgaon', + 'Piedecuesta', + 'Ibrahimpatnam', + 'Mandrosonoro', + 'Tlumach', + 'Citrus Springs', + 'Cabestany', + 'Metuchen', + 'Baxiangshan', + 'Bithauli', + 'Araguaina', + 'Ban Piang Luang', + 'Kamrej', + 'Chicaman', + 'Kishiwada', + 'Tabua', + 'San Rafael del Sur', + 'Cibolo', + 'Le Relecq-Kerhuon', + 'Boca Raton', + 'Amecameca de Juarez', + 'Radstock', + 'Launceston', + 'Gorantla', + 'Babar', + 'Jammalamadugu', + 'Gordes', + 'Vich', + 'Arroyo Grande', + 'Dvur Kralove nad Labem', + 'Novoukrainka', + 'Gurgunta', + 'Oued Cheham', + 'Salinopolis', + 'Oak Lawn', + 'Fatehgarh Churian', + 'San Alberto', + 'Wetaskiwin', + 'San Jose Pinula', + 'Montataire', + 'Aduku', + 'Planken', + 'Trussville', + 'Sidi Lmokhtar', + 'Machchand', + 'Moletai', + 'Grand-Lahou', + 'Ergoldsbach', + 'Sao Joao dos Patos', + 'Cajapio', + 'Sestao', + 'Bayshore Gardens', + 'Jaragua', + 'Canuelas', + 'Campo do Meio', + 'Chada', + 'Bibala', + 'Suining', + 'Granby', + 'Ostend', + 'Split', + 'Clark', + 'Baikunthapur', + 'Eppingen', + 'Bara Khurd', + 'Las Vigas de Ramirez', + 'Sharm ash Shaykh', + 'Sainthia', + 'Lowestoft', + 'Kaldenkirchen', + 'Acailandia', + 'Rosita', + 'Antardipa', + 'Ihaddadene', + 'Kennett', + 'Houlong', + 'Sriramapuram', + 'Nava Raipur', + 'Zielona Gora', + 'Biella', + 'Niceville', + 'Koscierzyna', + 'Lonkly', + 'Miramas', + 'Zubin Potok', + 'Sao Vicente Ferrer', + 'Fouka', + 'Rasak', + 'Nyzhnia Krynka', + 'Bijni', + 'West Bend', + 'Kurumul', + 'Golbaf', + 'Carencro', + 'Mercer Island', + 'Lai', + 'Rimini', + 'Labutta', + 'Goubellat', + 'Yakymivka', + 'Fianarantsoa', + 'Zhengding', + 'Vittoria', + 'Tactic', + 'Arrecife', + 'Parkes', + 'Ibicoara', + 'Gidi', + 'Afumati', + 'Dongning', + 'Tameslouht', + 'Ashwaubenon', + 'Alto Parana', + 'Mattanur', + 'Chinchali', + 'Monte Siao', + 'Kallanai', + 'Florania', + 'Ghorahi', + 'Uchqurghon Shahri', + 'Tres Isletas', + 'Bambara-Maounde', + 'Bela Vista de Goias', + 'Nova Granada', + 'Kesap', + 'Beldibi', + 'Hyderabad', + 'Otaki', + 'Bhuban', + 'Stansbury Park', + 'Halgur', + 'Arda', + 'Dalupura', + 'Maraveh Tappeh', + 'Kankandighi', + 'Chornomorske', + 'Nikopol', + 'Shinagawa-ku', + 'Zeralda', + 'St. Michael', + 'Fortaleza dos Nogueiras', + 'Nansan', + 'Edattala', + 'Phu Tu Son', + 'Cipo', + 'Bintulu', + 'Amayan', + 'Rainbow City', + 'Dougabougou', + 'Levakant', + 'Allada', + 'Tabursuq', + 'Hoshangabad', + 'Gerli', + 'Bardsir', + 'Sarsawa', + 'Manicaragua', + 'Sint-Pieters-Leeuw', + 'Pescantina', + 'Nimule', + 'Ambovombe', + 'Navsari', + 'Wilbraham', + 'Keene', + 'Novska', + 'Vallabh Vidyanagar', + 'Kifri', + 'Gornji Petrovci', + 'Bareggio', + 'Tosa', + 'Kafr Shukr', + 'Konotop', + 'Frei Miguelinho', + 'Pirapora do Bom Jesus', + 'Nemocon', + 'Ribeirao Bonito', + 'Kundli', + 'Zola Predosa', + 'Bileca', + 'Vargem Grande', + 'Martinopolis', + 'Madna', + 'Colonial Park', + 'Fecamp', + 'Sesto Fiorentino', + 'Bouzina', + 'Bischofswerda', + 'Saulkrasti', + 'Margram', + 'Zapresic', + 'Muriyad', + 'Ayun', + 'Harlingen', + 'San Juan de los Morros', + 'Kozy', + 'Kandern', + 'Taibao', + 'Savonlinna', + 'Fountainebleau', + "L'Ile-Perrot", + 'Zomin Shaharchasi', + 'PortoAlexandre', + 'Nes Ziyyona', + 'Watervliet', + 'Bonito de Santa Fe', + 'Murgod', + 'Aurisina', + 'Kochlowice', + 'Lierre', + 'Wellesley', + 'East Greenwich', + 'Jacarau', + 'Nawa', + 'Upper St. Clair', + 'Qalyub', + 'Darbhanga', + 'Balakliia', + 'Makulubita', + 'Giesen', + 'Sarkeghat', + 'Paiania', + 'Thandla', + 'Gravata', + 'Ad Dis', + 'Nawada Gobindganj', + 'Mosrah', + 'Ballwin', + 'Rehovot', + "Chateau-d'Olonne", + 'Barendrecht', + 'Kihei', + 'Forbes', + 'Van Wert', + 'Angola', + 'Sokuluk', + 'Gadarpur', + 'Beji', + 'Palmela', + 'Bad Lippspringe', + 'Al Badrashayn', + 'Angelim', + 'Higashimurayama', + 'Tangshan', + 'Fritzlar', + 'Hobyo', + 'Erumapalaiyam', + 'Kahului', + 'Varkkallai', + 'Sayula', + 'Waverly', + 'Koranampatti', + 'Peristeri', + 'Weilheim an der Teck', + 'Mustafakemalpasa', + 'Babolsar', + 'Concepcion Chiquirichapa', + 'Yueshanwan', + 'Sangeorgiu de Mures', + 'Edamon', + 'Moston', + 'Gabes', + 'Heesch', + 'Sahjanwa', + 'Auburn', + 'Sorel-Tracy', + 'Sagua de Tanamo', + 'Wassenaar', + 'Sarospatak', + 'Sapahi', + 'Kangayam', + 'Oodweyne', + 'Bac Kan', + 'Hole Narsipur', + 'Canmore', + 'Mulungu do Morro', + 'Fagnano Olona', + 'Sawankhalok', + 'Odanavattam', + 'Tiruvambalapuram', + 'Torotoro', + 'Kil Valur', + 'Koshizuka', + 'Kyonpyaw', + 'Annaberg-Buchholz', + 'Lissone', + 'Maracana', + 'Paysandu', + 'Batu', + 'Ciudad Acuna', + 'Kanavaikuli', + 'Lahaina', + 'Ma`an', + 'Comala', + 'Xiongzhou', + 'Coalcoman de Vazquez Pallares', + 'Benemerito', + 'Kuchinarai', + 'Lugo', + 'Kibuku', + 'Pirajui', + 'Fountain Valley', + 'Zentsujicho', + 'Shahreza', + 'Dragomer', + 'Puyehue', + 'Floridablanca', + 'Bitterfeld', + 'Bozkurt', + 'Sewa', + 'Soreang', + 'Varedo', + 'Narot Mehra', + 'Rogasovci', + 'Nukan', + 'Skipton', + 'Bidur', + 'Walldurn', + 'Ipero', + 'Aritao', + 'Breckerfeld', + 'Manakayi', + 'Narapalli', + 'Waraseoni', + 'Nogent-sur-Oise', + 'Itumbiara', + 'Ban Mae Sam Laep', + 'El Retorno', + 'Dunfermline', + 'Tamaraikkulam', + 'Dingli', + 'Ouadda', + 'Hillview', + 'Eceabat', + 'Yangmei', + 'Mamoudzou', + 'Dietikon', + 'Siauliai', + 'Pierrelatte', + 'Hamada', + 'Hooper', + 'Sakuho', + 'Beijing', + 'Limoeiro do Norte', + 'Ladario', + 'Karuveppampatti', + 'Sheffield', + 'Aguas de Lindoia', + 'Boac', + 'Mende', + 'Gohuma Bairia', + 'Riscani', + 'Koroth', + 'Sibenik', + 'Avalurpet', + 'Roberval', + 'Madinat as Sadis min Uktubar', + 'Rosoman', + 'Porto Alegre', + 'Brugherio', + 'Brody', + 'Keuruu', + 'Lehre', + 'Zawiercie', + 'Dalyoni Bolo', + 'Loay', + 'Kolonia', + 'Bussy-Saint-Georges', + 'Oulad Imloul', + 'Lagoa Formosa', + 'Wurselen', + 'Kestel', + 'Orhei', + 'Lubuagan', + 'Quixere', + 'Scottdale', + 'Caparrapi', + "Bruay-sur-l'Escaut", + 'Nova Ponte', + 'Rostam Kola', + 'Asuncion Nochixtlan', + 'Colton', + 'Little River', + "Jian'ou", + 'Diekirch', + 'Fateha', + 'Cimitarra', + 'Hombal', + 'Lindenhurst', + 'Ouled Djellal', + 'Comanesti', + 'Bousso', + 'Streamwood', + 'Roulia', + 'Duzce', + 'Asuke', + 'Libona', + 'Rellivalasa', + 'Ukwa', + 'Yavatmal', + 'Greasley', + 'Sivas', + 'Hukumati Gizab', + 'Balikpapan', + 'Qincheng', + 'Hongfengcun', + 'Nakai', + 'Oued Zem', + 'Pumalakkundu', + 'Umaria', + 'Sao Lourenco da Serra', + 'Amawom', + 'Barra do Mendes', + 'Aklim', + 'Sambrial', + 'Bunkeflostrand', + 'Taskent', + 'Rakitovo', + 'Nerima', + 'Waxhaw', + 'Hoogezand', + 'San Vicente de Tagua Tagua', + 'Recreio', + 'Lope de Vega', + 'Hejin', + 'Margherita', + 'Bend', + 'Strijen', + 'Manin', + 'Nogi', + 'Tilothu', + 'Manching', + 'Masindi Port', + 'Sumba', + 'Amizmiz', + 'Canada de Gomez', + 'Siddhapur', + 'Ankaramena', + 'Aleppo', + 'Sansanding', + 'Tirumakudal Narsipur', + 'Gumla', + 'Usa', + 'At Tawahi', + 'Koumantou', + 'De Panne', + 'Kukes', + 'Lenoir City', + 'Tiruppalaikudi', + 'Canyon Lake', + 'Nanpingcun', + 'Gubbio', + 'Jasaan', + 'Galaosiyo Shahri', + 'Mount Sinai', + 'Tulshia', + 'Sankt Johann im Pongau', + 'Sandi', + 'Banda', + 'Kilkunda', + 'Solymar', + 'Shenzhou', + 'As Sallum', + 'Baohezhuangcun', + 'Gogui', + 'Aarschot', + 'Kadappuram', + 'Sorkheh', + 'Gawler', + 'La Falda', + 'Masqat', + 'Bazeh Kalagh', + 'Forfar', + 'Kelafo', + 'Conde', + 'Porciuncula', + 'Palanas', + 'Cuyo', + 'Las Cabezas de San Juan', + 'Kranidi', + 'New Orleans', + 'Mettupalaiyam', + 'Kati', + 'Carregado', + 'Hornsby Bend', + 'Salvatierra', + 'Begamganj', + 'Cishan', + 'Draa Ben Khedda', + 'Eshtehard', + 'Sidi Daoud', + 'Las Nieves', + 'Nea Alikarnassos', + 'San Francisco Ixhuatan', + 'Luodong', + 'Waihee-Waiehu', + 'Rosolini', + 'Tundla', + 'Ash Shihaniyah', + 'SeaTac', + 'Daigo', + 'Solana Beach', + 'Kittanapalli', + 'Guspini', + 'Rahimyar Khan', + 'Antanambao', + 'New Silksworth', + 'Bixby', + 'Sahnaya', + 'Kudal', + 'Guaira', + 'Ibaiti', + 'Iksan', + 'Muttamtura', + 'Darnetal', + 'Karma', + 'Leyland', + 'Strzelin', + 'Tam Diep', + 'Tagaytay', + 'Finale Ligure', + 'Cottonwood', + 'Cutlerville', + 'Santa Cruz de Bezana', + 'Frydlant nad Ostravici', + 'Aracuai', + 'Huangyadong', + 'Herzele', + 'Sakarya', + 'Extremoz', + 'Bauta', + 'Kela Khera', + 'Castelvetrano', + 'Kalisz', + 'Qorovul', + 'Sanchez', + 'Tamezmout', + 'Boundji', + 'Bellinzago Novarese', + 'Tungi', + 'Ichenhausen', + 'Sao Sebastiao do Uatuma', + 'Inver Grove Heights', + 'Villefranche-de-Rouergue', + 'Uad Damran', + 'Rishon LeZiyyon', + 'Nellimarla', + 'Bountiful', + 'Sahaswan', + 'Kamonkoli', + 'Santo Augusto', + 'Hammersmith', + 'Breyten', + 'Northwich', + 'Nagambhotlapalem', + 'Montabaur', + 'Ammavarikuppam', + 'Amasya', + 'Bhankarpur', + 'Santa Rosa del Penon', + 'Anshun', + 'Feidh el Botma', + 'Abdurahmoni Jomi', + 'Rionero in Vulture', + 'Bundi', + 'Almargem', + 'McNair', + 'East Palo Alto', + 'Las Flores', + 'Carmo da Mata', + 'Wilkau-Hasslau', + 'Barentin', + 'Outat Oulad Al Haj', + 'Chemax', + 'Ayapango', + 'Ramacca', + 'Oton', + 'Ghoswari', + 'Al Fallujah', + 'Cheviot', + 'Alnavar', + 'Al Qasr', + 'Santo Domingo Suchitepequez', + 'Sulleru', + 'Barahpur', + 'Chapalamadugu', + 'Ar Rutbah', + 'Areia Branca', + 'Avenel', + 'Hoover', + 'Velykodolynske', + 'Beladi', + 'Changchun', + 'Linden', + 'Hillside', + 'Taxco de Alarcon', + 'Kathri', + 'Belemoka', + 'San-Pedro', + 'Ban Huai Hin', + 'Olovo', + 'Sundekuppam', + 'El Alto', + 'Totonicapan', + 'Mujiayu', + 'Makhtal', + 'Ikoma', + 'Sylhet', + 'Passo de Camarajibe', + 'Bad Driburg', + 'Rangapara', + 'Komarom', + 'Bakhmach', + 'Qionghu', + 'Ecatzingo', + 'Pallipram', + 'Taicheng', + 'Datu Piang', + 'Tlaltetela', + 'Preveza', + 'Cambuci', + 'Werkendam', + 'Ipira', + 'Gosport', + 'Grytviken', + 'Rondonopolis', + 'Phra Phutthabat', + 'La Puebla de Cazalla', + 'Leposaviq', + 'San Juan Cotzocon', + 'Sederot', + 'Nidiyanga', + 'Makamba', + 'Resistencia', + 'Rio Blanco', + 'Monte Escobedo', + 'Vasylkiv', + 'Eyl', + 'Fenoarivo', + 'Pokrov', + 'Bang Sao Thong', + 'Granite Bay', + 'Fortul', + 'Lenoir', + 'Trent Hills', + 'Le Petit-Quevilly', + 'Tifra', + 'Jaroslaw', + 'Perrysburg', + 'Ericeira', + 'Maniago', + 'Al Qusiyah', + 'Norridge', + 'Palamos', + 'Pochampalli', + 'Buni', + 'Preetz', + 'Anaconda', + 'Chinde', + 'Bakouma', + 'Jakar', + 'Tavros', + 'Doranala', + 'Mayiladi', + 'Villecresnes', + 'Phulgachhi', + 'Pen', + 'Somersworth', + 'Igrejinha', + 'Umburetama', + 'Kakumiro', + 'Arauquita', + 'Schwieberdingen', + 'Berkeley', + 'Alquizar', + 'Adigappadi', + 'Hardinxveld-Giessendam', + 'Baindur', + 'Beko', + 'Belley', + 'Mousoulou', + 'Ome', + 'Finnsnes', + 'High Point', + 'Budenheim', + 'Temara', + 'Sulejowek', + 'Grecia', + 'Deoghar', + 'Macerata', + 'Monghyr', + 'Hullhorst', + 'Tervuren', + 'On Top of the World Designated Place', + 'Isernhagen-Sud', + 'Parner', + 'Llanera', + 'Tsurugashima', + 'Dhamaun', + 'Porto Valter', + 'Loyalsock', + 'Guntersville', + 'Gandorhun', + 'Zella-Mehlis', + 'Pazin', + 'Bunyan', + 'Toyama', + 'Lamrasla', + 'Huliyar', + 'Springville', + 'Orkelljunga', + 'Pillutla', + 'Paruthur', + 'Zahedan', + 'Baskil', + 'Morteza Gerd', + 'Remchi', + 'Chiaravalle', + 'Grunstadt', + 'Sofifi', + 'Londrina', + 'Mazatlan', + 'Khathjari', + 'Chiang Klang', + 'Wulfrath', + "Qal'at Mgouna", + 'Irigny', + 'Senahu', + 'Perenchies', + 'Vero Beach', + 'Ocho Rios', + 'Castanhal', + 'Soe', + 'My Drarga', + 'Boxtel', + 'Hoyo de Manzanares', + 'Cruz Machado', + 'Morges', + 'Guano', + 'Anantapalle', + 'Tvrdosin', + 'Caojia', + 'Plaza Huincul', + 'Ranillug', + 'Yonezawa', + 'Parnu', + 'Zerizer', + 'Rabta', + 'Suginami-ku', + 'San Jose del Guaviare', + 'Khaira Tola', + 'Ain Oussera', + 'Badvel', + 'Wijnegem', + 'Frome', + 'Theux', + 'Caimbambo', + 'Wang Saphung', + 'Dabhoi', + 'Gebre Guracha', + 'Basatpur', + 'Zambrano', + 'Puttai', + 'Brainerd', + 'San Salvador El Seco', + 'Shostka', + 'Tabocas do Brejo Velho', + 'Tabriz', + 'Bhatpuri', + 'Arpajon', + 'Curti', + 'Hyde', + 'Cayetano Germosen', + 'Cafelandia', + 'Mnagueur', + 'Rygge', + 'Landskrona', + 'Kokubunji', + 'Resende', + 'Aguasay', + 'Painan', + 'Komagane', + 'Malhada de Pedras', + 'Zhutian', + 'Baabda', + 'Jackson', + 'Qoorlugud', + 'Fraserpet', + 'La Crau', + 'Pioltello', + 'Araure', + 'Ilsenburg', + 'Yanhewan', + 'Narayanpur', + 'Holtsville', + 'Vimmerby', + 'Binzhou', + 'Ligang', + 'Quillota', + 'Montmagny', + 'Malaga', + 'Juatuba', + 'Ascope', + 'Effia-Kuma', + 'Bellwood', + 'Encarnacion', + 'Mannur', + 'Puerto Santander', + 'Kendraparha', + 'Wewak', + 'Sendjas', + 'San Pedro Pinula', + 'Kampong Thom', + 'Mpanda', + 'Trebaseleghe', + 'Kerkyra', + 'Rogatec', + 'Katigang', + 'Yushu', + 'Saint-Hyacinthe', + 'Capelle aan den IJssel', + 'Eisenach', + 'Reinfeld', + 'Idri', + 'Kunnattur', + 'Cakung', + 'Rayadrug', + 'Baoshan', + 'Laramie', + 'El Meghaier', + 'Naperville', + 'Macatuba', + 'Katalpur', + 'Carpina', + 'Blyth', + 'Veseli nad Moravou', + 'Burhia Dhanghatta', + 'Svitlodarsk', + 'Lugait', + 'Rancho Cucamonga', + 'Tabara Arriba', + 'Spondon', + 'Marienberg', + 'Middelburg', + 'Huyton', + 'Sroda Slaska', + 'Woomera', + 'Bethalto', + 'Concepcion de Ataco', + 'Fagersta', + 'Halfmoon Bay', + 'Estancia', + 'Makow Mazowiecki', + 'Malangas', + 'Pittsburg', + 'Adukam', + 'West Melbourne', + 'Haverstraw', + 'Lousa', + 'Kulpsville', + 'Plasnica', + 'Charuanwan', + 'Foca', + 'Gombe', + 'Cordeiropolis', + 'Chakicherla', + 'Licey al Medio', + 'Conceicao das Alagoas', + 'Colonia', + 'Yisuhe', + 'Vignola', + 'San Martin Sacatepequez', + 'Mbanza-Ngungu', + 'Oppatavadi', + 'Eitorf', + 'Ubach-Palenberg', + 'Ranibennur', + 'Aklvidu', + 'Capoeiras', + 'Mucambo', + 'Palmares do Sul', + 'Kondakomarla', + 'Niteroi', + 'Sai Ngam', + 'City Bell', + 'Biougra', + 'White', + 'Kumru', + 'Alcala de Henares', + 'Kalawit', + 'Porto Xavier', + 'Balancan', + 'Vodice', + 'Zouping', + 'Nagawaram', + 'Mareth', + 'Kakraul', + 'Horten', + 'Padinjarebagam', + 'Karattuppalaiyam', + 'Redding', + 'Wolgast', + 'Ambrolauri', + 'Dhanot', + 'Nilakkottai', + 'Abaete', + 'Veendam', + 'Oslo', + 'Paiganapalli', + 'White Center', + 'Taminango', + 'Constitucion', + 'Begijnendijk', + 'Baragua', + 'Inverigo', + 'Belsandi Tara', + 'Rumst', + 'Pinner', + 'Reinheim', + 'Liancourt', + 'Villa Canas', + 'Metlika', + 'Balderton', + 'Gauripur', + 'Yamada', + 'Cardona', + 'Sartalillo', + 'Odaiyakulam', + 'Laguna Paiva', + 'Carolina', + 'Bhilavadi', + 'Al Qardahah', + 'Campo Bom', + 'Laichingen', + 'Montero', + 'Nagoya', + 'Portici', + 'Sherpur', + 'Baiheqiao', + 'Saint-Chamas', + 'Bloemfontein', + 'Songyang', + 'Mamfe', + 'Bijiao', + 'Tongobory', + 'Barra do Dande', + 'Revere', + 'Celtik', + 'Carigara', + 'Laindon', + 'Chapa de Mota', + 'Djanet', + 'Vinings', + 'Saltillo', + 'Gueoul', + 'Parnagua', + 'Burton upon Trent', + 'Bocaiuva do Sul', + 'Nova Xavantina', + 'Esteli', + 'Sremska Kamenica', + 'Pennsauken', + 'Bad Wurzach', + 'Banbury', + 'Nkowakowa', + 'Koloti', + 'Chaita', + 'Las Parejas', + 'Guerande', + 'Ski', + 'Suphan Buri', + 'Cheyenne', + 'San Isidro de Lules', + 'Cisternino', + 'Rajbalhai', + 'San Ferdinando di Puglia', + 'Nosivka', + 'Singhwara', + 'Ankarabato', + 'Dod Ballapur', + 'Wattignies', + 'Lebu', + 'Elangunnapuzha', + 'Korneuburg', + 'Saint Andrews', + 'Exmouth', + 'Menaa', + 'Bungotakada', + 'Isagarh', + 'Bilecik', + 'Oulad Bou Rahmoun', + 'Manambondro', + 'Port Morant', + 'Townsend', + 'Ca Mau', + 'Kostolac', + 'Presov', + 'Valle', + 'Sahambala', + 'Chapala', + 'Tepechitlan', + 'Uruacu', + 'Bangued', + 'Renton', + 'Ummannur', + 'Lalmanirhat', + 'Nurkot', + 'Brownfield', + 'San Miguel del Padron', + 'Toualet', + 'San Antonio Aguas Calientes', + 'Los Alamitos', + 'Jilib', + 'San Mateo Ixtatan', + 'Anapolis', + 'Burhanpur', + 'Yokotemachi', + 'Torrinha', + 'Mouzaia', + 'Harshin', + 'Cirencester', + 'Bentley', + 'Man', + 'Yezhi', + 'Kota', + 'Sovicille', + 'Otawara', + 'Zihuatanejo', + 'Charata', + 'Paduma', + 'Otake', + 'Guanta', + 'Aduru', + 'Mannarai', + 'Youghal', + 'Haacht', + 'Stowmarket', + 'Fourou', + 'Barari', + 'Libanggaon', + 'Amari', + 'Lucknow', + 'Babra', + 'Idanre', + 'Bedigoazon', + 'Acari', + 'Burslem', + 'Lohara', + 'Beersel', + 'Polillo', + 'Librazhd-Qender', + 'Kumalarang', + 'Tzitzio', + 'Bisceglie', + 'Karayilan', + "Samarra'", + 'Bafwasende', + 'North Arlington', + 'Souq Sebt Oulad Nemma', + 'Bhalswa Jahangirpur', + 'Wolfsburg', + 'Eningen unter Achalm', + 'Szubin', + 'Varadarajampettai', + 'Zelenodolsk', + 'Karagwe', + 'Vissannapeta', + 'Cheramkod', + 'Sanandaj', + 'Karben', + 'Bueng Khong Long', + 'Jaboatao', + 'Vrilissia', + 'Cumaral', + 'Akil', + 'Ap Da Loi', + 'Normanton', + 'Trois-Rivieres', + 'Almirante', + 'Esperalvillo', + 'Kongupatti', + 'Quirinopolis', + 'Lask', + 'Sainte-Luce-sur-Loire', + 'Castel San Giorgio', + 'Bang Phae', + 'Chinnayagudem', + 'Arsin', + 'Numata', + 'Sab` al Bur', + 'Cumaru', + 'Tecamac', + 'Rodelas', + 'Seyah Cheshmeh', + 'Kashan', + 'Nanzhangcheng', + 'Kendari', + 'Jurado', + 'Monrovia', + 'Peshawar', + 'Guacharachi', + 'Kloten', + 'Bandirma', + 'Quevedo', + 'Adekar Kebouche', + 'Huinca Renanco', + 'Dakpatthar', + 'Chicoutimi', + 'Baiyan', + 'Pimenteiras', + 'Las Brenas', + 'Kusnacht', + 'Burnie', + 'Ercolano', + 'Kroonstad', + 'Modena', + 'Kalna', + 'Bonneville', + 'Bellair-Meadowbrook Terrace', + 'Jiyyammavalasa', + 'Kisai', + 'Vadodara', + 'Nagtala', + 'Kandiyankovil', + 'Lorena', + 'Taphan Hin', + 'Juneau', + 'Iati', + 'Niksar', + 'Roy', + 'Kapoeta', + 'Samdhin', + 'Arafat', + 'Kornepadu', + 'Ait Faska', + 'Olevsk', + 'Rantoul', + 'Glencoe', + 'Stocksbridge', + 'Tela', + 'Chethakal', + 'Canatlan', + 'Guia de Isora', + 'Veglie', + 'Taguig City', + 'Qillin', + 'Harsinghpur', + 'Sitangkai', + 'Michigan City', + 'Nagaizumi', + 'Guariba', + 'El Seibo', + 'Woolton', + 'Tainai', + 'Mukher', + 'Cartama', + 'Suknadanga', + 'Philippsburg', + 'Ladysmith', + 'Atherton', + 'Baeza', + 'Yazikonak', + 'Santa Maria Texmelucan', + 'Ban Mae Kaluang', + 'Khiriawan', + 'Eschen', + 'Tobetsu', + 'Nova Vicosa', + 'Ankarongana', + 'Shahedshahr', + 'Sonoita', + 'Kudligi', + 'Saint-Felicien', + 'Bukama', + 'Nilgiri', + 'Zwevegem', + 'Muang Sing', + 'Pinhal', + 'Saranga', + 'Ebelo', + 'Toli', + 'Cataguases', + 'Miyaki', + 'Caracol', + 'Amol', + 'Basdeopur', + 'Cevicos', + 'Charlotte', + 'Canavieiras', + 'Deva', + 'North Shields', + 'Icaraima', + 'Hoskins', + 'Kamalshahr', + 'Guapiles', + 'Araguari', + 'Bouhlou', + 'Castilla La Nueva', + 'Azemmour', + 'Juvignac', + 'Medjedel', + 'Goksun', + 'San Justo', + 'Iyo', + 'Teploklyuchenka', + 'Yaounde', + 'Ain Kercha', + 'Treviglio', + 'Novi Pazar', + 'San Lazzaro di Savena', + 'Nakhon Ratchasima', + 'Alcantara', + 'Bull Mountain', + 'Oldeani', + 'Chardonnieres', + 'Chini', + 'Piketberg', + 'Baranoa', + 'Buldon', + 'Akora', + 'Santa', + 'Olivet', + 'Kouinine', + 'Vanipenta', + 'Shahr-e Babak', + 'Porto de Moz', + 'The Hills', + 'Kaharlyk', + 'Manpaur', + 'G`azalkent', + 'Hodmezovasarhely', + 'Oak Creek', + 'Kladanj', + 'Merouana', + 'Hiratsuka', + 'Alvares Machado', + 'Lagoa Dourada', + 'Mishan', + 'Puerto Deseado', + 'Befandriana Atsimo', + 'Supia', + 'Avon Lake', + 'Rialto', + 'Renca', + 'Mesra', + 'Rognac', + 'Pongode', + 'Birkhadem', + 'Talacogon', + 'Turhapatti', + 'Szigetszentmiklos', + 'Onoto', + 'Norala', + 'Sorala', + 'Diallassagou', + 'Handa', + 'Wieliczka', + 'Tilougguit', + 'Jaimalpura', + 'Allentown', + 'Crissiumal', + 'Varberg', + 'Abidjan', + 'Cesky Tesin', + 'Burgkirchen an der Alz', + 'Nagina', + 'Shahpur Baghauni', + 'Itarhi', + 'Bad Pyrmont', + 'Raksaha', + 'Pandireddigudem', + 'Sinfra', + 'Bayonne', + 'Bahir Dar', + 'Bobo-Dioulasso', + 'Tubaran', + 'Chihuahua', + 'Hun', + 'Rexburg', + 'Kherson', + 'Harad', + 'Saint-Fons', + 'Iazizatene', + 'Sao Pedro do Sul', + 'Tirmaigiri', + 'Otumba', + 'East Cleveland', + 'Bankoumana', + 'Goiatins', + 'Kushtagi', + 'Patori', + 'Bertrix', + 'Palanan', + 'Achaljamu', + 'Elanad', + 'Mahmud-e Raqi', + 'Bougou', + 'American Fork', + 'East Barnet', + 'Mangrauni', + 'Raghunathpur', + 'Ierapetra', + 'Crvenka', + 'Mentor', + 'Fidirana', + 'Sangareddi', + 'Pflugerville', + 'Kupiskis', + "Vil'nyans'k", + 'Sonipat', + 'Zestaponi', + 'Goffstown', + 'Lengshuijiang', + 'Scottsdale', + 'Rosemont', + 'Garsekurti', + 'Collierville', + 'Bulaevo', + 'Tosagua', + 'Burzaco', + 'Silea', + 'Roosendaal', + 'Tuusula', + 'Turnu Magurele', + 'An Nabk', + 'Sasebo', + 'San Vicente de Castellet', + 'Andranomenatsa', + 'Tbilisi', + 'Postojna', + 'Kessel-Lo', + 'Arnsberg', + 'Itarare', + 'Mazeikiai', + 'Pujehun', + 'Vicovu de Sus', + 'Okayama', + 'Sarauli', + 'Lakeville', + 'Dashtigulho', + 'Itapolis', + 'Al Hazm', + 'Hammelburg', + 'Berastagi', + 'Orlu', + 'Castanet-Tolosan', + 'Lumbayanague', + 'Oeiras do Para', + 'Whitney', + 'Tubara', + 'Santa Maria Madalena', + 'Salinas de Hidalgo', + 'Sangonera la Verde', + 'Noisy-le-Sec', + 'Glenview', + 'Bern', + 'Song Cau', + 'Dyer', + 'Chabal Kalan', + 'Segala Mba', + 'Upper Hutt', + 'Massigui', + 'Nalgonda', + 'Terra Boa', + 'Svrljig', + "'Ain Tellout", + 'Bellefontaine Neighbors', + 'Sahibpur Kamal', + 'Nieuwleusen', + 'Acarlar', + 'Chuo', + 'Hellesdon', + 'Kotor', + 'Montepulciano', + 'Emstek', + 'Daye', + 'Kona', + 'Saint-Genis-Pouilly', + 'Puerto Rico', + 'Sitamau', + 'Anguera', + 'North Lebanon', + 'Pesqueira', + 'Guarei', + 'Pendurti', + 'Falls', + 'Pinillos', + 'Sighetu Marmatiei', + 'Bangui', + 'Tchitado', + 'Tanga', + 'Kesamudram', + 'Narayangarh', + 'Dapitan', + 'Kaithahi', + 'Saint-Cyr-sur-Mer', + 'Jandaira', + 'Bedum', + 'Tomatlan', + 'Pirapemas', + 'Bemahatazana-Belobaka', + 'Poblacion', + 'El Cua', + 'Maldonado', + 'Pando', + 'Madappalli', + 'Cumru', + 'Al Mahwit', + 'Chake Chake', + 'Oria', + 'Zimnicea', + 'Kivisto', + 'Bumahen', + 'Wichian Buri', + 'Carrascal', + 'Pedro Betancourt', + 'Hire Megalageri', + 'Ternate', + 'Saoner', + 'Itaituba', + 'Vohitrafeno', + 'Caluula', + 'Muggia', + 'Aizenay', + 'Khokha', + 'Ipokia', + 'Venmani Padinjara', + 'Siklos', + 'Yangshuwa', + 'Kanekallu', + 'Golbey', + 'Gengzhuangqiaocun', + 'Gustavsberg', + 'Parabiago', + 'Ambohibary', + 'Valladolid', + 'Matamey', + 'Bisee', + 'Tete', + 'Vreden', + 'Toribio', + 'Fucecchio', + 'Zangareddigudem', + 'Ceglie Messapico', + 'Licinio de Almeida', + 'Ad Dabbah', + 'Sapian', + 'Naranjo', + 'Wollaston', + 'Celaya', + 'Dire', + 'Gajiginhalu', + 'Mulug', + 'Upper Makefield', + 'Rodriguez', + 'Bocoio', + 'Thalwil', + 'Carate Brianza', + 'Bondada', + 'Ayagoz', + 'Xiada', + 'Vohburg an der Donau', + 'Mansala', + 'Rafha', + 'Anastacio', + 'Nove Mesto na Morave', + 'Ramechhap', + 'Ambinanindrano', + 'Leibnitz', + 'Alvaraes', + 'Baure', + 'Sohano', + 'Tunja', + 'Goriar', + 'Msambweni', + 'Aibak', + 'Penugolanu', + 'Castagneto Carducci', + 'Ecclesall', + 'Dunmore', + 'North Londonderry', + 'Carnot-Moon', + 'Pau Brasil', + 'Taka', + 'Sihor', + 'Terra Nova', + 'Choachi', + 'Tenri', + 'Port Louis', + 'Laoag', + 'San Martin de la Vega', + 'Nguigmi', + 'Bendapudi', + 'Eminabad', + 'Aldenham', + 'Vianen', + 'Nagasamudram', + 'Zemst', + 'Khalkhal', + 'Mount Clemens', + 'Teotlaltzingo', + 'Siribala', + 'Ligao', + 'Dom Pedrito', + 'Bushey', + 'Namakgale', + 'Waiuku', + 'Juruaia', + 'Sikasso', + 'Laplace', + 'Gao', + 'Casarano', + 'Ratangarh', + 'Brunssum', + 'Castrovillari', + 'Tarkeshwar', + 'Ruisui', + 'Monschau', + 'Alat', + 'Hetauda', + 'Tunglangan', + 'Panipat', + 'El Chal', + 'Bhanumukkala', + 'Don Galo', + 'Saddiqabad', + 'Santo Domingo Petapa', + 'Bingol', + 'Piraju', + 'Camp Verde', + 'Chembagaramanpudur', + 'Behara', + 'Harrison', + 'Nagulapadu', + 'Knemis Dades', + 'Youwangjie', + 'Sarab', + 'Rustavi', + 'Obama', + 'Dymka', + 'Bijie', + 'Tairan Camp', + 'Sahoria Subhai', + 'Southampton', + 'Port Moody', + 'Guaratinga', + 'Xa Muteba', + 'Ahmadnagar', + 'Limanowa', + 'Dembi Dolo', + 'Samrala', + 'Aichach', + 'Codroipo', + 'Nattarampalli', + 'Induno Olona', + 'Gosen', + 'Bevato', + 'Elkridge', + 'Trissino', + 'Siping', + 'Bou Hanifia el Hamamat', + 'Juayua', + 'Tordera', + 'Osnabruck', + 'Zegoua', + 'Ramain', + 'Varanasi', + 'Miedzyrzecz', + 'Bradfordville', + 'Arkansas City', + 'Sidi Mohamed Lahmar', + 'Paravai', + 'Baltara', + 'Paide', + 'Manakara', + 'Edassheri', + 'Usiacuri', + 'Yildirim', + 'Keflavik', + 'Tsetserleg', + 'Paris', + 'Gagnoa', + 'Xintang', + 'Sarni', + 'Xico', + 'Capitan Bermudez', + 'Contenda', + 'Garhwa', + 'Huayin', + 'Basuo', + 'Tenjo', + 'Cachan', + 'Quimperle', + 'Misato', + 'Pittsgrove', + 'Dumont', + 'Salford', + 'Bontoc', + 'Lexington Park', + 'Yame', + 'Narasaraopet', + 'Kesla', + 'Ystad', + 'Laqraqra', + 'Milot', + 'Benoy', + 'Jalandhar', + 'Qahderijan', + 'Wake', + 'Flowing Wells', + 'Santana do Acarau', + 'Kurdi', + 'Sison', + 'Mazamet', + 'Montrouis', + 'Kalaruch', + 'Sarotar', + 'Dabuleni', + 'Thepaha Raja Ram', + 'Glenn Heights', + 'Tuxtepec', + 'Whickham', + 'Targu Frumos', + 'Lauriya Nandangarh', + 'Taineste', + 'Medjez el Bab', + 'Sagon', + 'Cinisello Balsamo', + 'Bartalah', + 'Valabhipur', + 'Tangdukou', + 'Barreirinha', + 'Calintaan', + 'Binan', + 'Rampur', + 'Malatya', + 'Clemson', + 'Wyszkow', + 'Carmiano', + 'Yeovil', + 'Cajobi', + 'Yabuki', + 'Dorud', + 'Marrupa', + 'Maitland', + 'Jahrom', + 'Kurgunta', + 'Tonyrefail', + 'Virreyes', + 'Kalu Khan', + 'Al Qurayya', + 'Libertyville', + 'Kharagpur', + 'Woodley', + 'Mineola', + 'Chalon-sur-Saone', + 'Bouhmama', + 'Mena', + 'San Cataldo', + 'Quipungo', + 'Alakamisy-Ambohimaha', + 'Gloversville', + 'Chuimatan', + 'Nadugadda', + 'Borjomi', + 'Sibonga', + 'Palanpur', + 'Kodinar', + 'Asingan', + 'Chahchaheh', + 'Dobrova', + 'Ruzomberok', + 'Chhoti Sadri', + 'Langzhong', + 'Anekal', + 'Pontal do Parana', + 'Muhammadabad', + 'Moreira', + 'Civita Castellana', + 'Kpalime', + 'Mahespur', + 'Louny', + 'Venkatapuram', + 'Khaw Zar Chaung Wa', + 'Dahutang', + 'Data', + 'Cheddra', + 'Madridejos', + 'DeLand', + 'Koewarasan', + 'Palaiseau', + 'Ropczyce', + 'Urakawa', + 'Tanque Novo', + 'Guider', + 'Kiruhura', + 'Kasese', + 'Murrhardt', + 'Andingcun', + 'Kuhdasht', + 'Freire', + 'Singera', + 'Panglong', + 'Tampa', + 'Mirik', + 'Hatti Mattur', + 'Nakatsugawa', + 'Jitwarpur Nizamat', + 'Carmo', + 'Jocotan', + 'Forquilha', + 'Vargem Grande do Sul', + 'Peniche', + 'Xinying', + 'Nahulingo', + 'Goranboy', + 'San Pedro Sula', + 'Burr Ridge', + 'Leatherhead', + 'Belisce', + 'Villalonga', + 'Santa Anita', + 'Mayluu-Suu', + 'Skovde', + 'Buston', + 'Domodossola', + 'Avelgem', + 'El Malah', + 'Azusa', + 'Neyriz', + 'Coronel Suarez', + 'Saquarema', + 'Meizhou', + 'Apia', + 'Jawor', + 'Saint-Pol-sur-Mer', + 'Heguri', + 'Bobangui', + 'Montalegre', + 'Ben Taieb', + 'Pande', + 'Horsell', + 'Sanjo', + '`Aynkawah', + 'Sfantu-Gheorghe', + 'Escuinapa', + 'Talikota', + 'Guapiara', + 'Point Pleasant', + 'Ambohipandrano', + 'Khangaon', + 'Namlea', + 'Kottapalem', + 'Sebastiao Laranjeiras', + 'Phra Samut Chedi', + 'Madanancheri', + 'Farnworth', + 'Coshocton', + 'Garrison', + 'Peso da Regua', + 'Jaguaruna', + 'Katihar', + 'West Fargo', + 'Melaka', + 'Ambohimanambola', + 'Pabegou', + 'Nossa Senhora das Dores', + 'Burladingen', + 'Byumba', + 'Vallikunnam', + 'Mohan Eghu', + 'San Julian', + 'Miracema do Tocantins', + 'Palma', + 'Goh', + 'DeKalb', + 'Azle', + 'Mont-de-Marsan', + 'Slovenske Konjice', + 'Manazary', + 'Shende', + 'Portland', + 'Lamarao', + 'Reinach', + 'Chrzanow', + 'Hendek', + 'Iwakura', + 'Agua Clara', + 'Capim', + 'Brasilandia', + 'Port Colborne', + 'Ilsfeld', + 'Cienaga de Oro', + 'Faxinal dos Guedes', + 'Earlestown', + 'Andranomeva', + 'Visalia', + 'Tazhava', + 'Linquan', + 'Rampur Jalalpur', + 'Cricova', + 'Cholula de Rivadabia', + 'Apiai', + 'Vilandai', + 'Castellana Grotte', + 'Baturbari', + 'Jandiala', + 'Rive-de-Gier', + 'Feltham', + 'Laguna Niguel', + 'Kannal', + 'Purmerend', + 'Succasunna', + 'Ejea de los Caballeros', + 'Dolisie', + 'Vernag', + 'Tabount', + 'Kirksville', + 'Rahuri', + 'Nanjai Kilabadi', + 'Parigi', + 'Dhamsain', + 'Bainbridge', + 'Zumpango', + 'Le Beausset', + 'Westphalia', + 'Cabinda', + 'Bagahi', + 'Valmontone', + 'Al `Ajami', + 'Maromby', + 'Maaseik', + 'Wuyishan', + 'Ulbroka', + 'Santiago Juxtlahuaca', + 'Ranomena', + 'Buadiposo-Buntong', + 'Khurai', + '`Aqrah', + 'Priverno', + 'Geldrop', + 'Balandougou', + 'Legnago', + 'Don Carlos', + 'Keitumkawn', + 'Levice', + 'Gulam', + 'Ratne', + 'Jiayuguan', + 'Gidha', + 'Juba', + 'Ksar Belezma', + 'Puerto Baquerizo Moreno', + 'Ponta Pora', + 'Sidrolandia', + 'Achim', + 'North Lynnwood', + 'Haslett', + 'Porto Ferreira', + 'Anqing', + 'Takahagi', + 'Fray Luis A. Beltran', + 'Basudebpur', + 'Concon', + 'Mangochi', + 'McKeesport', + 'Matheu', + 'Sumbha', + 'Murajpur', + 'Champerico', + 'Iki', + 'Port-Bouet', + 'Santa Luzia', + 'Chitaldrug', + 'Baliqchi', + 'Uthai Thani', + 'Az Zubaydiyah', + 'Cicero', + 'Rampurwa', + 'El Salto', + 'Duero', + 'Littleover', + 'Ropazi', + 'Andradas', + 'Tirur', + 'Caramoan', + 'San Carlos Alzatate', + 'Zuojiawu', + 'Karacasu', + 'Namioka', + 'Brumunddal', + 'West Milford', + 'Alandatte', + 'Dumingag', + 'Petrolandia', + 'Arachchalur', + 'Urussanga', + 'Cerrillos', + 'Ortega', + 'Kanyobagonga', + 'Atibaia', + 'Eustis', + 'Sefaatli', + 'Lasam', + 'Montalto di Castro', + 'Cihuatlan', + 'Cirebon', + 'Rangewala', + 'Tekkalakote', + 'Cameron Park', + 'Bardaskan', + 'Manambidala', + 'Bukittinggi', + 'Sora', + 'Dashouping', + 'Escarcega', + 'Tezoyuca', + 'Karadichittur', + 'Roskilde', + 'West Caln', + "Pereyaslav-Khmel'nyts'kyy", + 'Senmanat', + 'Formosa', + 'Schaumburg', + 'Tarnobrzeg', + 'Kadaladi', + 'Ouled Haddaj', + 'Vuyyuru', + 'Baruipur', + 'Teniente Primero Manuel Irala Fernandez', + 'Menaceur', + 'Sedeh Lanjan', + 'Ciney', + 'Amboavory', + 'Jale', + 'Kalmar', + 'Koori', + 'Alsager', + 'Gulagac', + 'Zwedru', + 'Kadiyampatti', + 'Wilkes-Barre', + 'Bremerhaven', + 'Motru', + 'Bad Tolz', + 'Nha Trang', + 'Andranomavo', + 'Punta Prieta', + 'Masmouda', + 'Vavatenina', + 'Divandarreh', + 'Atambua', + 'Krosuru', + 'Wrecsam', + 'Apache Junction', + 'Barri', + 'Shingucho-shingu', + 'Kusaha', + 'Sidi Ahmed El Khadir', + 'West Springfield', + 'Al Wajh', + 'Massangena', + 'Doda', + 'Shutayil', + 'Messamena', + 'Navalcarnero', + 'Bellaire', + 'Nichelino', + 'Serra Azul', + 'Rantabe', + 'Padaivedu', + 'Monteroni di Lecce', + 'Drodro', + 'Rees', + 'Bulgan', + 'Fraijanes', + 'Montbrison', + 'Kangaroo Flat', + 'Urbach', + 'Lindon', + 'Olen', + 'Langgons', + 'Velim', + 'Marhamat', + 'Anamur', + 'Milattur', + 'Forecariah', + 'Quartier Morin', + 'Worb', + 'Playas', + 'Kingsville', + 'North Strabane', + 'Kolumalapalle', + 'Narippatta', + 'Yotsukaido', + 'Takahashi', + 'Dulce Nombre de Maria', + 'Reguiba', + 'Dapi', + 'Elk River', + 'Karaagac', + 'Duba', + 'Rasnov', + 'Cabrobo', + 'Easthampton', + 'Parvatipuram', + 'Husainpur', + 'Nipomo', + 'Chettimangurichchi', + 'Bekitro', + 'Itapagipe', + 'Palestina', + "Erval d'Oeste", + 'Vasilika', + 'Kolappalur', + 'Batken', + 'Bank', + 'Qorako`l Shahri', + 'Manor', + 'Majanji', + 'Al Balyana', + 'Niederkassel', + 'Dulce Nombre de Jesus', + 'Mason City', + 'Lavello', + 'Zaruma', + 'Mignoure', + 'Namyslow', + 'Hagaranahalli', + 'Filippoi', + 'Ita', + 'Wuzhong', + 'Bedfordview', + 'Mollet', + 'Malipakar', + 'Innisfil', + 'Cehegin', + 'Stone', + 'Lake St. Louis', + 'Panskura', + 'Injibara', + 'Festus', + 'Cicevac', + 'Busra ash Sham', + 'Krompachy', + 'Mumford', + 'Phulmalik', + 'Jundiai', + 'Annaka', + 'Senhora dos Remedios', + 'Anthem', + 'Sadarak', + 'Naga City', + 'Xiping', + 'Planeta Rica', + 'Kilgore', + 'Ciudad Sabinas Hidalgo', + 'Tsaratanana', + 'Puduppalli Kunnam', + 'Manilva', + 'Colorno', + 'Majsperk', + 'Pibrac', + 'Nova Gorica', + 'Sorbolo', + 'Halberstadt', + 'Magalang', + 'Cozumel', + 'Chanco', + 'Gympie', + 'Colwood', + 'Vire', + 'Troina', + 'Oudenbosch', + 'Bertinoro', + 'El Bazouriye', + 'Cabra', + 'Alakamisy-Ambohimahazo', + 'Milan', + 'Sanare', + 'Trencin', + 'Minas', + 'Santa Vitoria do Palmar', + 'Saintes', + 'San Jose de las Lajas', + 'Governador Dix-Sept Rosado', + 'Ville-la-Grand', + 'Stjordal', + 'Kirkwall', + 'Aigaleo', + 'Uppukkottai', + 'Lakeway', + 'Cuchi', + 'Kamina', + 'Rawa Mazowiecka', + 'Xinyuan', + 'Scarsdale', + 'Tosashimizu', + 'Akot', + 'Yaritagua', + 'East Rancho Dominguez', + 'Pongoz', + 'Ambazoa', + 'Liuchuan', + 'Higashiura', + 'Blackfoot', + 'Gniezno', + 'Blanchard', + 'Zadar', + 'Nakaechi', + 'Mairena del Aljarafe', + 'Massama', + 'Mobo', + 'Kezmarok', + 'La Porte', + 'Berching', + 'Siegsdorf', + 'New Albany', + 'Half Way Tree', + 'Jharka', + 'Zinacantepec', + 'Draveil', + 'Bek-Abad', + 'Fayetteville', + 'Oyam', + 'Pasil', + 'Clamart', + 'Mbuzini', + 'Ouenza', + 'Perdur', + 'Soamanandrariny', + 'Hanawa', + 'Yekambarakuppam', + 'Mashiki', + 'Thibodaux', + 'Nijkerk', + 'Hayang', + 'Malabon', + 'Aparri', + 'Bwana Mkubwa', + 'Charqueada', + 'Kuju', + 'Kwamhlanga', + 'Alimos', + 'Ypane', + 'Sahave', + 'Marib', + 'Shirley', + 'Bunbury', + 'Talayan', + 'Ljungby', + 'San Juan de Betulia', + 'Laixi', + 'La Ferte-Bernard', + 'Lagos de Moreno', + 'Tangbian', + 'Adalar', + 'Nyamira', + 'Desert Hot Springs', + 'Datia', + 'Cognac', + 'Jaltipan de Morelos', + 'Ponsacco', + 'Dindori', + 'Hlaingbwe', + 'Delijan', + 'Cirie', + 'Une', + 'Boldesti-Scaeni', + 'Sapeacu', + 'Nagdah', + 'Douar Ezzerarda', + 'Bali', + 'Stevenage', + 'Saryshaghan', + 'Melchor Romero', + 'Barbana', + 'Zanjan', + 'Sennan', + 'Ponta Delgada', + 'Royse City', + 'Wonthaggi', + 'Putaendo', + 'Kokawa', + 'Wloszczowa', + 'Warner Robins', + 'Kranenburg', + 'Tome', + 'Usilampatti', + 'Puerto Pinasco', + 'El Maiten', + 'Gurgaon', + 'Immenstadt im Allgau', + 'Wisla', + 'Gannavaram', + 'Yingzhong', + 'Masho Khel', + 'Reitz', + 'Belen de Umbria', + 'Ripollet', + 'Lauaan', + 'Beira', + 'Ratu', + 'Mure', + 'Medicine Hat', + 'Ixcatepec', + 'Bois-des-Filion', + 'Nogent-le-Rotrou', + 'Djibouti', + 'University of California-Santa Barbara', + 'Byahatti', + 'Bentonville', + 'Danau Kandimarg', + 'Tevragh Zeina', + 'Sattar', + 'Tranoroa', + 'Mengdong', + 'Luz', + 'Wuyang', + 'Batemans Bay', + 'Dinas', + 'Itauna', + 'Artena', + 'Carouge', + 'Xylokastro', + 'Bahia de Caraquez', + 'Marabella', + 'Bremerton', + 'Ixtlahuacan del Rio', + 'Garchitorena', + 'Huittinen', + 'Fonsorbes', + 'Bafia', + 'Da Lat', + 'Pasian di Prato', + 'Sarayonu', + 'Chiman', + 'Colombo', + 'Kirikera', + 'Pemmperena', + 'Grunwald', + 'Savalgi', + 'Chambly', + 'Puduppatti', + 'Himamaylan', + 'Lansdowne', + 'Douar Mzoura', + 'Powell', + 'Erfurt', + 'Kizhake Chalakudi', + "L'Arbaa Nait Irathen", + 'Galmi', + 'Junagarh', + 'Murgap', + 'Sallanches', + 'Loutete', + 'Rosa Zarate', + 'Caninde', + 'Santa Tecla', + 'Skoghall', + 'Banswada', + 'Cresskill', + 'Dabaozi', + 'Cortland', + 'Ennery', + 'Ifanirea', + 'Antsaidoha-Bebao', + 'Tepic', + 'Qiryat Gat', + 'Cabeceiras de Basto', + 'Pirot', + 'Waxahachie', + 'De Pere', + 'Salsomaggiore Terme', + 'Potenza Picena', + 'Wright', + 'Resadiye', + 'Melito di Napoli', + 'Toui', + 'Guercif', + 'Daga', + 'Porto Feliz', + 'Sinende', + 'Goasi', + 'Kikube', + 'North Tidworth', + 'Narona', + 'Capitao Eneas', + 'Makakilo', + 'Billere', + 'Tabas', + 'Klang', + 'Agogo', + 'Turnov', + 'Fairhaven', + 'Muriae', + 'Chakai', + 'Maniamkulam', + 'Seberang Jaya', + 'Jaboticatubas', + 'Burley', + 'Four Square Mile', + 'Kirchlinteln', + 'El Quetzal', + 'Mar del Plata', + 'Elattur', + 'Mezica', + 'Brunn am Gebirge', + 'Quetta', + 'Morokweng', + 'Kitanagoya', + 'Manacapuru', + 'Towamencin', + 'Aragona', + 'Regla', + 'Caldeirao Grande', + 'Musina', + 'Jacksonville', + 'Azul', + 'Winter Park', + 'Englefield Green', + 'Ambon', + 'Lucena', + 'Huntington Station', + 'Itarema', + 'San Francisco Chimalpa', + 'Ondokuzmayis', + 'Basford', + 'Antsaravibe', + 'Meilen', + 'Tangermunde', + 'Wijchen', + 'Castilleja de la Cuesta', + 'Lanark', + 'Usak', + 'Wall', + 'Mijas', + 'Sitionuevo', + 'East Lake', + 'Mahabo-Mananivo', + 'Dhoraji', + 'Jalacingo', + 'Willstatt', + 'Nakhl-e Taqi', + 'Opelousas', + 'Golo-Djigbe', + 'Santiago Nonualco', + 'Pulsano', + 'Simrol', + 'Ihnasya al Madinah', + 'Pozzallo', + 'Ramayipatti', + 'Sihaul', + 'Wanze', + 'Senduria', + 'Ergue-Gaberic', + 'Mauldin', + 'Sempeter pri Gorici', + 'Ammanabrolu', + 'Mitrapur', + 'Massarosa', + 'Binnish', + 'La Tour-de-Peilz', + 'Lavaltrie', + 'Barsbuttel', + 'Seberi', + "Murang'a", + 'Macherla', + 'Acharipallam', + 'Setana', + 'Yichun', + 'Lugus', + 'Parnamirim', + 'Kayes', + 'Chinu', + 'Tubmanburg', + 'Cekme', + 'Mehdauli', + 'Vaulx-en-Velin', + 'Waikabubak', + 'Fontibon', + 'Omaha', + 'Pambujan', + 'Kassorola', + 'Khonj', + 'La Maddalena', + 'Fandrandava', + 'Chinna Mupparam', + 'Nea Smyrni', + 'Burgdorf', + 'Ituporanga', + 'Rayamangalam', + 'Albury', + 'Paranapanema', + 'Smiltene', + 'Lohmar', + 'Puliyankunnu', + 'Tiruvalla', + 'Hochberg', + 'Minto', + 'Antelope', + 'Mohanur', + 'Wiang Sa', + 'Dobrich', + 'Ingelheim', + 'Shetou', + 'Gavle', + 'Svedala', + 'Borogani', + 'Kisoro', + 'Ortenberg', + 'Uttarpara', + 'Rajaldesar', + 'Al Badari', + 'Masinigudi', + 'Gold Coast', + 'Indiana', + 'Swissvale', + "Al Jazirah al Hamra'", + 'Santa Maria Capua Vetere', + 'Mangala', + 'Upper Bicutan', + 'Hacilar', + 'Woltersdorf', + 'Khwazakhela', + 'Santa Fe de Antioquia', + 'Gammasa', + 'Iwashita', + 'Bohmte', + 'Tondabayashicho', + 'Salobrena', + 'Unebicho', + 'Isaszeg', + 'Bloomsburg', + 'Vidor', + 'Niquero', + 'Niepolomice', + 'Halacho', + 'Khanpur Khairanti', + 'Sao Roque', + 'Shively', + 'Fox Crossing', + 'Postmasburg', + 'Vicentinopolis', + 'Rafael Delgado', + 'Fuglafjordhur', + 'Bekapaika', + 'Rastede', + 'Devnya', + 'Derecik', + 'Nida', + 'Odayarpatti', + 'Bala', + 'Harra', + 'Ain Youcef', + 'Kenge', + 'Sapone', + 'Dongtai', + 'Ratnapur', + 'Giron', + 'Kartuzy', + 'Lake Villa', + 'Kolaccheri', + 'Kiridh', + 'Hattersheim', + 'Daheba', + 'Et Taiyiba', + 'Ciudad del Plata', + 'Ohringen', + 'Tegina', + 'Ishii', + 'Gahmar', + 'Lancaster', + 'Carmel', + 'Firminy', + 'Akishima', + "Milla'ab", + 'Beparasy', + 'Esperanza', + 'Bignay', + 'Spruce Grove', + 'Krommenie', + 'Hollviken', + 'Westminster', + 'Shymkent', + 'Majhaulia', + 'Biscarrosse', + 'Kadur Sahib', + 'Luce', + "Al Ha'ir", + 'Teluk Intan', + 'Dubove', + 'Qiaotouba', + 'Qasbat Tadla', + 'North Walsham', + 'Aspe', + 'Dokkum', + 'Plaridel', + 'Sonsbeck', + "Cava de' Tirreni", + 'Santa Elena', + 'Barka Gaon', + 'Asagicinik', + 'Dagua', + 'Campina Verde', + 'Kaitaia', + 'Leverkusen', + 'Merchtem', + 'Kiwoko', + 'Samborondon', + 'Palm Valley', + 'Fengjia', + 'Muhos', + "'Ain Kerma", + 'Sihma', + 'Tuminkatti', + 'Letychiv', + 'Notse', + 'Pearsall', + 'Jeypore', + 'San Antonio Palopo', + 'Kingstown', + 'Lislique', + 'Sannat', + 'Monnickendam', + 'Kurabalakota', + 'Miranorte', + 'Rudersberg', + 'Kasama', + 'Granja', + 'Amarwa Khurd', + 'Ad Diwaniyah', + 'Evington', + 'Warman', + 'Kuttampuzha', + 'Mons', + 'Korsholm', + 'Gourma Rharous', + 'General Alvear', + 'Matelica', + 'Bongabon', + 'Irituia', + 'Son en Breugel', + 'Chauny', + 'Bayeux', + 'Unicov', + 'Shaler', + 'Aesch', + 'Rahata', + 'Porangaba', + 'Hattingen', + 'Ocampo', + 'Pirna', + 'Perunad', + 'Umm Badr', + 'Ramganj Mandi', + 'Saidia', + 'Mutata', + 'Manatuto', + 'Lambayeque', + 'Grasse', + 'Centreville', + 'Bandrele', + 'Glogow', + 'Canagatan', + 'Trisshileri', + 'Pavona', + 'Diamniadio', + 'Harsola', + 'Gulariya', + 'Dubendorf', + 'San Prisco', + 'Maissade', + 'Agios Nikolaos', + 'Dubak', + 'Nalut', + 'Bhadrachalam', + 'Upper Moreland', + 'Geyve', + 'El Tejar', + 'Castilla', + 'Amsin', + 'Meulebeke', + 'San Francisco El Alto', + 'Presidente Getulio', + 'Pakwach', + 'Mayfield', + 'Kalawana', + 'Evosmos', + 'Killarney', + 'Zhubei', + 'El Ksiba', + 'Sierpc', + 'Lambari', + 'Fakfak', + 'Dawlish', + 'Kambaliyampatti', + 'Dianke', + 'Gaoya', + 'Almenara', + 'Molina de Segura', + 'Zighout Youcef', + 'San Jose Poaquil', + 'Louvres', + 'Los Altos', + 'Newton Aycliffe', + 'Chesterfield', + 'Korhogo', + 'Srbac', + 'Warstein', + 'Talladega', + 'Tigaon', + 'Pozos', + 'Raja Pakar', + 'Kuttiyeri', + 'Tres Cachoeiras', + 'Luena', + 'Betatao', + 'Sredisce ob Dravi', + 'West Monroe', + 'Sonabedha', + 'Rhenen', + 'Talainayar Agraharam', + 'Serido', + 'Campobasso', + 'Zabkowice Slaskie', + 'Harji', + 'Ubata', + '`Ajab Shir', + 'Tatoufet', + 'Hailun', + 'Kiratot', + 'El Idrissia', + 'Liannong', + 'Nedelisce', + 'Pirangi', + 'Beringen', + 'Leopoldo de Bulhoes', + 'Northolt', + 'Quesada', + 'Peumo', + 'Zitiste', + 'Kerman', + 'Cigli', + 'Gaibandha', + 'Tostado', + 'Ilog', + 'Mohana', + 'Erbach', + 'Mahasoabe', + 'Latifpur', + 'Rajim', + 'Tirhassaline', + 'Rathenow', + 'Strendur', + 'Grigny', + 'Skoczow', + 'Belwa', + 'Kasavanampatti', + 'Kalinagar', + 'Navappatti', + 'Megara', + 'Lolotique', + 'Saint-Junien', + 'Pijino del Carmen', + 'Guelph/Eramosa', + 'Solok', + 'Ciudad del Este', + 'Khagaria', + 'Kinogitan', + 'La Garenne-Colombes', + 'Manaquiri', + 'Telgte', + 'Igaracu do Tiete', + 'Mantsala', + 'Bogatic', + 'St. Cloud', + 'Huntertown', + 'Smithville', + 'Nueva Valencia', + 'Piru', + 'Yukarikaraman', + 'Merzifon', + 'Cosham', + 'Kharian', + 'Mantes-la-Ville', + 'Duanzhuang', + 'Kashipur', + 'Seaside', + 'Orchha', + 'Djemmorah', + 'Kocarli', + 'Mojkovac', + 'Nigel', + 'Kursunlu', + 'Soyagaon', + 'Pamuru', + 'Bassila', + 'Di An', + 'Vaureal', + 'El Dificil', + 'Troyan', + 'Toca', + 'Bet Shemesh', + 'Kayalpattanam', + 'Bernards', + 'Zomba', + 'Okabecho-okabe', + 'Nedroma', + 'Kostel', + 'Mosbach', + 'Miyauchi', + 'Jhitkahiya', + 'Rhymney', + 'Banisilan', + 'Medina', + 'Sayarpuram', + 'Landsberg', + 'Sevastopol', + 'Ceara-Mirim', + 'Assisi', + 'Goudomp', + "Debark'", + 'Melrose Park', + 'Le Rheu', + 'Port Douglas', + 'San Andres Tuxtla', + 'Princetown', + 'Seybaplaya', + 'Riegelsberg', + 'Cagayan de Oro', + 'Lommel', + 'Wapi', + 'Feira de Santana', + 'Obertshausen', + 'Imarui', + 'Soavinandriana', + 'Strathmore', + 'Kanke', + 'Arraial do Cabo', + 'Matsudo', + 'Pandhana', + 'Sao Miguel do Guapore', + 'Medea', + 'Rheinfelden (Baden)', + 'Taglio', + 'Korgan', + 'Pantabangan', + 'Bhattiprolu', + 'Lingampet', + 'Puerto Pimentel', + 'Velampatti', + 'Vilsbiburg', + 'Mianpur Dubauli', + 'Panguipulli', + 'Hafr al Batin', + 'Brasnorte', + 'Vignate', + 'Frederikshavn', + 'Alpena', + 'Pagbilao', + 'Villers-les-Nancy', + 'Chagalamarri', + 'Dispur', + 'Menemen', + 'Baqershahr', + 'Jandola', + 'Oborniki Slaskie', + 'Bandlaguda', + 'Goulburn', + 'Safotulafai', + 'Dreieich', + 'Galatsi', + 'Meixedo', + 'Banovici', + 'San Martino Buon Albergo', + 'Recani', + 'Prudnik', + 'Sao Miguel do Araguaia', + 'Sunkarevu', + 'New Castle', + 'Ban Thung Tam Sao', + 'Nariman', + 'Pata Ellamilli', + 'Capitolio', + 'Dobele', + 'Leicester', + 'Giovinazzo', + 'Ribeiro do Amparo', + 'Pailitas', + 'San Pedro Ayampuc', + 'Stalowa Wola', + 'Khargone', + 'Lustenau', + 'Sale', + 'Narhan', + 'Rubiataba', + 'Arceburgo', + 'Hokitika', + 'Vila Frescainha', + 'Lomme', + 'Lazarevac', + 'Tikrit', + 'Sudak', + 'Phrae', + 'Ramapo', + 'Ninomiya', + 'Hrazdan', + 'Taguasco', + 'Serravalle', + 'Bushtyno', + 'Gobo', + 'Kuttalam', + 'Awans', + 'Chancay', + 'Jocotenango', + 'Woodhouse', + 'KwaDukuza', + 'Wildau', + 'Batobato', + 'Crest Hill', + 'Balvadi', + 'Karkudalpatti', + 'Santa Ines', + 'Guacari', + 'Mantena', + 'Ladera Ranch', + 'Sulechow', + 'Matruh', + 'Isla de Maipo', + 'Gothva', + 'Frutillar', + 'Raxruha', + 'Machesney Park', + 'Wingene', + 'Lejanias', + 'Le Chesnay', + 'Bissendorf', + 'Abadiania', + 'Ometepec', + 'Enna', + 'Petarukan', + 'Boone', + 'Awbari', + 'Annaram', + 'Boki-Were', + 'Al Mijlad', + 'Raiparthi', + 'Aougrout', + 'Ranchi', + 'Onojo', + 'Saumalkol', + 'Atri', + 'Kara-Kol', + 'Lauria Inferiore', + 'San Felice Circeo', + 'Wooburn', + 'Agualva', + 'Fotadrevo', + 'Herenfa', + 'Berlin', + 'Astolfo Dutra', + 'Gahini', + 'Ambanja', + 'Hargeysa', + 'Santa Cruz do Capibaribe', + 'Ertis', + 'Esperantina', + 'Biloxi', + 'Hang Tram', + 'Diinsoor', + 'Asfour', + 'Pula', + 'Cacaopera', + 'Noida', + 'Pittsfield', + 'Le Muy', + 'Barletta', + 'Buraydah', + 'Ingolstadt', + 'Chautham', + 'Northwest Harborcreek', + 'Oldsmar', + 'Neder-Over-Heembeek', + 'Kamisu', + 'Puerto Piritu', + 'Ambatolahy', + 'Daru', + 'New Brighton', + 'Queluz', + 'Epernay', + 'Bacalar', + 'Ban Nikhom Phatthana', + 'Valbonne', + 'Yondo', + 'Khowrmuj', + 'Jingdezhen', + 'Campana', + 'Kuli', + 'Ogimachi', + 'Biskupiec', + 'Framingham', + 'Lomma', + 'Papraur', + 'Mukumbura', + 'Kawayan', + 'Ncora', + 'Zhaoling', + 'Boscombe', + 'Abbeville', + 'Reisterstown', + 'Tongeren', + 'Joal-Fadiout', + 'Rosario de Lerma', + 'Velpuru', + 'Douarnenez', + 'Sofia', + 'Itayanagi', + 'Ekinozu', + 'Bonanza', + 'Mohlanapeng', + 'Cudworth', + 'Barga', + 'Okinawa', + 'Owase', + 'Ainan', + 'Nyunzu', + 'Yokohama', + 'Aiud', + 'Tachov', + 'Khagam', + 'Anosiarivo', + 'Khust', + 'Apolda', + 'Bogra', + 'Djinet', + 'Holiday City-Berkeley', + 'Carmo do Rio Verde', + 'Kirchseeon', + 'Victorica', + 'Lahar', + 'Veranopolis', + 'Bechar', + 'Noceto', + 'Nossa Senhora da Gloria', + 'Bhaluhar', + 'Hofu', + 'Sozopol', + 'Kyabe', + 'Sosua', + 'Benedikt', + 'Bad Neuenahr-Ahrweiler', + 'Narendrapatnam', + 'Gurramkonda', + 'Pinhel', + 'Shiling', + 'Nadol', + 'Paredes', + 'Adjido', + 'Trebnje', + 'Yelpur', + 'Sirpur', + 'Barvala', + 'Angol', + 'Birpur Barapatti Pindraun', + 'Hendijan', + 'Parakou', + 'Mangaratiba', + 'Schwabisch Hall', + 'Sarqan', + 'Aleshtar', + 'Warin Chamrap', + 'West Des Moines', + 'Cutervo', + 'Valiyakumaramangalam', + 'Kottoppadam', + 'Sukth', + 'Sambhal', + 'Velliyod', + 'Waldshut-Tiengen', + 'Lakhnaur', + 'Barrocas', + 'Djamaa', + 'Matale', + 'Ban Phai', + 'Sao Paulo de Olivenca', + 'Harar', + 'Barbasa', + 'Kukatpalli', + 'Aisho', + 'Raynham', + 'Harborcreek', + 'Bafra', + 'Gibsons', + 'Cluj-Napoca', + 'Hoeselt', + 'Acopiara', + 'Filipstad', + 'Camardi', + 'Bunol', + 'Rania', + "Su'ao", + 'Konen Agrahar', + 'Sidi Qacem', + 'Chapada dos Guimaraes', + 'Bad Berleburg', + 'Thabazimbi', + 'Bradley', + 'Masif Sarsink', + 'Anse Rouge', + 'Galten', + 'Oshoba', + "Alta Floresta D'Oeste", + 'Ranaghat', + 'Patchur', + 'Rameswaram', + 'Fonadhoo', + 'Kuraymah', + 'Bosanska Krupa', + 'Cajica', + 'Sabou', + 'Fundacion', + 'Koba', + 'Rudra Nagar', + 'Tskhinvali', + 'Itarantim', + 'Mirzapur', + 'Mocajuba', + 'Berazategui', + 'Penjamo', + 'Doctor Mora', + 'Boleslawiec', + 'Angat', + 'Siraha', + 'Rock Ferry', + 'Salt', + 'Az Zintan', + 'Livno', + 'Winterville', + 'Daryabad', + 'Newton Mearns', + 'Milovice', + 'Nanqiao', + 'Nord', + 'Doba', + 'Guayacanes', + 'Didcot', + 'Skelmersdale', + 'Ivoamba', + 'Roessleville', + 'Sadovoye', + 'Magugpo Poblacion', + 'Toritama', + 'Mulakaledu', + 'Baia da Traicao', + 'Pissila', + 'Echuca', + 'Les Cayes', + 'Gajwel', + 'San Jose de Jachal', + 'Swedru', + 'Ribeirao', + 'Zhetibay', + 'Monatele', + 'Gibraleon', + 'Kufstein', + 'Chak Five Hundred Seventy-five', + 'Sterling', + 'Khattan', + 'Kuse', + 'Euless', + 'Baggao', + 'Al Juwayyidah', + 'Ilobasco', + 'Assi Bou Nif', + 'Ikniwn', + 'Gudja', + 'Monteiro', + 'Bryn Mawr-Skyway', + 'Corona de Tucson', + 'Sonson', + 'Clevedon', + 'Palestrina', + 'Uberherrn', + 'Civitella in Val di Chiana', + 'Balya', + 'Vinanivao', + 'Alexandria', + 'Merida', + 'Kursumlija', + 'Delgado', + 'Menton', + 'Dharampur Bande', + 'Goiana', + 'Matican', + 'Carballo', + 'Itapebi', + 'Minsk Mazowiecki', + 'Bhadaur', + 'Saidu Sharif', + 'Lalla Mimouna', + 'Mian Sahib', + 'Borzna', + 'Pakenham', + 'Al Kut', + 'Guemoukouraba', + 'Dhandhuka', + 'Crawfordsville', + 'Arak', + 'Acahay', + '`Abasan al Kabirah', + 'Farashband', + 'San Tomas', + 'Calasparra', + 'Ambahoabe', + 'Bayindir', + 'Merauke', + 'Ayagawa', + 'Ndjamba', + 'Lakewood', + 'Krishnanagar', + 'Kyustendil', + 'Porto Franco', + 'Anoka', + 'Tulsa', + 'De Haan', + 'Caluco', + 'Dumraon', + 'Fauske', + 'Jogiara', + 'Rambha', + 'Campina Grande do Sul', + 'Pontevedra', + 'Brejolandia', + 'Sundarpur', + 'Waltikon', + 'Putten', + 'Jardim', + 'Siemianowice Slaskie', + 'Buchen in Odenwald', + 'Ningde', + 'Tonsberg', + 'Shaliuhe', + 'Siracusa', + 'Galleh Dar', + 'Ponneri', + 'Gifhorn', + 'Avarua', + 'Longzhou', + 'Wenxicun', + 'Pontardulais', + 'Alipur', + 'Doetinchem', + 'Maravatio de Ocampo', + 'Elma', + 'Paravur Tekkumbhagam', + 'Oak Hills Place', + 'Dorou', + 'Galapagar', + 'Prata', + 'Bytca', + 'Kanmaki', + 'Ban Pong Tao', + 'Antsohihy', + 'Ban Ang Sila', + 'Gorisnica', + 'Abrera', + 'Visaginas', + 'Suoluntun', + 'Le Perreux-Sur-Marne', + 'Durbat', + 'Ourinhos', + 'Homa Bay', + 'Kolkwitz', + 'Gornji Grad', + 'Soroca', + 'Licata', + 'Zhangshanying', + 'Shuangcheng', + 'Shariff Aguak', + 'Redcliff', + 'Selargius', + 'Zapotlan del Rey', + 'Uetersen', + 'Rochedale', + 'Mongeri', + 'Tonala', + 'Lloydminster', + 'Ribeira Brava', + 'Xingcheng', + 'Baku', + 'Ban Dung', + 'Klatovy', + 'Empangeni', + 'Chundale', + 'Sorontona', + 'Dieppe', + 'Grandville', + 'Udawantnagar', + 'Qaqortoq', + 'Bischheim', + 'Urucui', + 'Uychi', + 'Kalispell', + 'Amman', + 'Esplugas de Llobregat', + 'Lohfelden', + 'Nacimiento', + 'Phongsali', + 'Ngerulmud', + 'Mulsen', + 'Yby Yau', + 'Balassagyarmat', + 'Malingaon', + 'Xionglin', + 'Glenvar Heights', + 'Miho', + 'North Liberty', + 'Dongshan', + 'Bauang', + 'Beguedo', + 'Cruz Grande', + 'Douar Trougout', + 'Primrose', + 'Merrimack', + 'Raigarh', + 'Potomac', + 'Voorhees', + 'Dalippur', + 'Castle Pines', + 'La Montanita', + 'Sabang', + 'Vintar', + 'Mikumi', + 'Sydney', + 'Tarhjicht', + 'Nizao', + 'Cahors', + 'Elland', + 'Diplahan', + 'Kayankulam', + 'Custodia', + 'Felipe Carrillo Puerto', + 'Paraguacu', + 'Lohafary', + 'Austintown', + 'Huixquilucan', + 'Pulpi', + 'Oakleaf Plantation', + 'Katsina', + 'Florennes', + 'Bad Honnef am Rhein', + 'Brenes', + 'West Linn', + 'Xanthi', + 'Saint-Saulve', + 'Sierra Bullones', + 'Dravograd', + 'Bizen', + 'Kurman', + 'Kuzma', + 'Kolagallu', + 'Bordj Ghdir', + 'Jogeva', + "Sofiyivs'ka Borshchahivka", + 'Pipra', + 'Lubeck', + 'Oppegard', + 'Kuopio', + 'Tuscumbia', + 'Kasaji', + 'Fort Atkinson', + 'Tamar', + 'Middelfart', + 'Grumo Nevano', + 'Downey', + 'Singhanwala', + 'Kawasaki', + 'Opglabbeek', + 'Maun', + 'Bosel', + 'Benaulim', + 'Smethwick', + 'Maromandia', + 'Hawaiian Gardens', + 'Jantho', + 'Malangam', + 'Rock Hill', + 'Livingstone', + 'Jaranwala', + 'Krong Kep', + 'West Covina', + 'Belzig', + 'Kitagata', + 'Fountain', + 'San Lorenzo de Descardazar', + 'Bati', + 'Quzanli', + 'Hosuru', + 'Poco Verde', + 'Simeulu', + 'Yangquan', + 'Shahzadpur', + 'Lopik', + 'Tarogong', + 'Iraiyur', + 'Freiberg', + 'Corinto', + 'Ukiha', + 'Alindao', + 'Ambohimahavelona', + 'Sosan', + 'Bhagabanpur', + 'Madamba', + 'Liubymivka', + 'Hassan', + 'Sham Shui Po', + 'Barbalha', + 'Wonju', + 'Taftanaz', + 'Waipio', + 'Guidan Roumdji', + 'Vohringen', + 'Crusinallo', + 'Nagareyama', + 'Geel', + 'Takahama', + 'Czarna Bialostocka', + 'Matthews', + 'Fleury-Merogis', + 'Tall `Aran', + 'Kamikawa', + 'Rose Belle', + 'Anorombato', + 'Wolfsberg', + 'Montreal', + 'Macrohon', + 'Miribel', + 'Cobanlar', + 'Gelsenkirchen', + 'Noblesville', + 'Sabaur', + 'Aizkraukle', + 'El Bayadh', + 'Bir Jdid', + 'Gunjur', + 'Nauta', + 'Browns Mills', + 'Shawnee', + 'Bredasdorp', + 'Camas', + 'Malebennur', + 'Ninh Binh', + 'Alto do Rodrigues', + 'Wilton Manors', + 'Fort Hunt', + 'Usia', + 'Lorgues', + 'Sanrha', + 'Bluffton', + 'Kendallville', + 'Preddvor', + 'Xovos', + 'Staoueli', + 'James Island', + 'Pampa del Infierno', + 'Lucaya', + 'Chino Valley', + 'Fort Leonard Wood', + 'Mirpur Bhtoro', + 'Ixtapa Zihuatanejo', + 'Albemarle', + 'Bekkaria', + 'Payatas', + 'Vitanje', + 'Koila Dewa', + 'Zhaoqing', + 'Sabac', + 'Kakonko', + 'Hojambaz', + 'Voinjama', + 'Xiaba', + 'Ramasingavaram', + 'Of', + 'Champoton', + 'Burntwood', + 'Viernheim', + 'Estoril', + 'Aflou', + 'Ampanefena', + 'Kamiamakusa', + 'Juan L. Lacaze', + 'Motkur', + 'My Luong', + 'Vernouillet', + 'Balatan', + 'Samaipata', + 'Alwa Tirunagari', + 'Saku', + 'Kuilsrivier', + 'Lynn Haven', + 'Maisach', + 'Bingerville', + 'Alicia', + 'Riversdale', + 'Bishunpur', + 'Tepoztlan', + 'Vishakhapatnam', + 'Gameleira', + 'Nyanza', + 'Bad Frankenhausen', + 'Huasca de Ocampo', + 'Kasongan', + 'Tortoreto', + 'Moschato', + 'Requena', + 'Kelowna', + 'Nyahanga', + 'Pohadi', + 'Kalibo', + 'Round Rock', + 'Pequannock', + 'Haifa', + 'Ezpeleta', + 'Boosaaso', + 'Oberschleissheim', + 'Seesen', + 'Alexandreia', + 'Salihli', + 'Mandali', + 'Xoxocotla', + 'Jakkampalaiyam', + 'Bradford West Gwillimbury', + 'West Perth', + 'Jaszbereny', + 'Vukovar', + 'Wittstock', + 'Dartmouth', + 'Zacatecas', + 'Oswego', + "Debre Werk'", + 'Annemasse', + 'Juripiranga', + 'Tultitlan de Mariano Escobedo', + 'Coachella', + 'Zagubica', + 'Altepexi', + 'Balao', + 'Velappadi', + 'Pamukkale', + 'West Vancouver', + 'Encamp', + 'Gudibanda', + 'Bensenville', + 'Augusta', + 'Natitingou', + 'Ganyesa', + 'Jiujiang', + 'Bad Schwartau', + 'Kings Norton', + 'Dharampuri', + 'Huanta', + 'Iganga', + 'Buckeburg', + 'Perryville', + 'Bingham', + 'Severn', + 'As Sulayyil', + 'Les Coteaux', + 'Jinmingsi', + 'Mel Nariyappanur', + 'Timimoun', + 'Nato', + 'Unisan', + 'Danao', + 'Mount Hagen', + 'Fiesole', + 'Concepcion Las Minas', + 'Zagorje', + 'Eilenburg', + 'Vardenik', + 'Ribeirao Branco', + 'Bismil', + 'Lichinga', + 'Reinosa', + 'Paray-le-Monial', + 'Villamediana de Iregua', + 'Madisonville', + 'Cheam', + 'Qabatiyah', + 'Bagnacavallo', + 'Shankou', + 'San Giuseppe Vesuviano', + 'Penaflor', + 'Amatenango del Valle', + 'Vanderbijlpark', + 'Coelemu', + 'Labiod Medjadja', + 'Olkusz', + 'Shefar`am', + 'Ulan Hua', + 'Sirka', + 'Caceres', + 'Gurh', + 'Oildale', + 'Colonial Heights', + 'eMuziwezinto', + 'Uster', + 'Pampur', + 'Martinsville', + 'Suttamalli', + 'Go Cong', + 'San Miguel de Papasquiaro', + 'Aliabad', + 'Huaura', + 'Denderleeuw', + 'Kremiss', + 'Kattukkottai', + 'Sakaka', + 'Khuran Milik', + "Al Ma`alla'", + 'Belluno', + 'Liptovsky Mikulas', + 'Baghra', + 'Touboro', + 'Science City of Munoz', + 'Trzebinia', + 'Filandia', + 'Myjava', + 'Ramachandrapuran', + 'Agdangan', + 'Bourzanga', + 'Barstow', + 'Annigeri', + 'Taio', + 'Deokali', + 'Lake Country', + 'Taizhou', + 'Adwick le Street', + 'Quilon', + 'Partesh', + 'Cwmbran', + 'Vaerlose', + 'San Calixto', + 'Oued Sly', + 'Lomazzo', + 'Guzelbahce', + 'Fishkill', + 'Annapolis', + 'Washington Terrace', + 'Keansburg', + 'Xintangcun', + 'Eastwood', + 'Sinmperekou', + 'Long Hill', + 'Roswell', + 'Budakeszi', + 'Lincolnia', + 'Flagstaff', + 'Eynesil', + 'Ban Wiang Ka Long', + 'Groveton', + 'Des Peres', + 'Namacunde', + 'Zlotow', + 'Chittoor', + 'Torre del Greco', + 'Morrinhos', + 'Upper Chichester', + 'Marousi', + 'Calimete', + 'Oytal', + 'Sao Joao dos Angolares', + 'Anenii Noi', + 'Perry', + 'Ait Melloul', + 'Kitchener', + 'Amboronabo', + 'Burtonwood', + 'Croydon', + 'San Celoni', + 'Suhaj', + 'Hod HaSharon', + 'Kitenkela', + 'Alagappapuram', + 'Ramat HaSharon', + 'Tortona', + 'Rota', + 'Innisfail', + 'Cuyahoga Falls', + 'Guadix', + 'Wil', + 'Vemuladivi', + 'Taungup', + 'Itaocara', + 'San Pedro Mixtepec', + 'Konce', + 'Puerto Guzman', + 'Mahuwa Singhrai', + 'North Merritt Island', + 'Most', + 'Lahstedt', + 'Westland', + 'Liuma', + 'Charagua', + 'Krasnogorskiy', + 'Pakarya Harsidhi', + 'Beni Oulid', + 'Daqing', + 'Ecorse', + 'Pineto', + 'Olbernhau', + 'Dorfen', + 'Madeira', + 'Sassnitz', + 'Susegana', + 'Suamico', + 'Spring', + 'Milazzo', + 'Erada', + 'Obernburg am Main', + 'Oak Harbor', + 'Marokarima', + 'Malangawa', + 'McAllen', + 'Merrill', + 'Pe de Serra', + 'Bilohorodka', + 'Dhakaich', + 'Malhador', + 'Wenatchee', + 'Almere', + 'Benguela', + 'Pisek', + 'Matam', + 'Sidi Bouzid', + 'Chhota Udepur', + 'Tehran', + 'Panjakent', + 'Itapage', + 'Quarteira', + "Mi'eso", + 'Saverne', + 'Panchimalco', + 'Parambil', + 'San Miguel de Salcedo', + 'Sanwas', + 'Sidhap Kalan', + 'Umraniye', + 'Falavarjan', + 'Gaalkacyo', + 'La Ferte-sous-Jouarre', + 'Schwalbach', + 'La Junta', + 'Timbuktu', + 'Jessore', + 'Tomisato', + 'Parachinar', + 'Myslowice', + 'Beelitz', + 'Minehead', + 'Samadiala', + 'Mankera', + 'San Pedro Sacatepequez', + 'Gaithersburg', + 'Pongnam', + 'Chambery', + 'Wareham', + 'Catu', + 'Gangania', + 'Shingu', + 'Sawahlunto', + 'Aydincik', + 'Somvarpet', + 'Beshariq', + 'Hongjiang', + 'Nallamadu', + 'Hengelo', + 'Sidi Tabet', + 'Pucioasa', + 'Salvaterra', + 'Zhongbai', + 'Pital', + 'Acu', + 'La Ligua', + 'Kalugumalai', + 'Raharpur', + 'La Grande', + 'Sint-Lievens-Houtem', + 'Ban Tha Pho', + 'Shangluhu', + 'Ciudad Madero', + 'Matias Barbosa', + 'Yara', + 'Lamitan', + 'Port Laoise', + 'Adiyaman', + 'Jaguey Grande', + 'Douai', + 'Tecuci', + 'Tifni', + 'Changsha', + 'Acatzingo', + 'Eilat', + 'South Ogden', + 'Talsi', + 'Dzuunmod', + 'Kafr al Kurdi', + 'Cochrane', + 'Xigaze', + 'Terra Santa', + 'Na Klang', + 'Parsa', + 'Side', + 'Noshiromachi', + 'Tepehuacan de Guerrero', + 'Makri', + 'Bernex', + 'Magway', + 'Modugno', + 'East Gwillimbury', + 'Dornakal', + 'The Crossings', + 'Gazi', + 'Dengjiazhuang', + 'Betroka', + 'Manno', + 'Hausjarvi', + 'Menifee', + 'Qobustan', + 'Ban Bang Pu Mai', + 'Macau', + 'Lithgow', + 'Kissa', + 'Kenadsa', + 'Mosul', + 'Letterkenny', + 'Conda', + 'Poonamallee', + 'Huruta', + 'Chennevieres-sur-Marne', + 'Camiri', + 'Cazanga', + 'East Bakersfield', + 'Baiji', + 'Garsfontein', + 'Bad Iburg', + 'Glen Carbon', + 'Arvin', + 'Sobhapur', + 'Talas', + 'Aalborg', + 'Bagnara Calabra', + 'Pokhram', + 'Aragoiania', + 'Aravankara', + 'East Nottingham', + 'Ramjibanpur', + 'Kilmangalam', + 'Talisay', + 'Krusevac', + 'Baranain', + 'Gauli Palasiya', + 'Sieverne', + 'Chakradharpur', + 'Ipil', + 'Xiamen', + 'Henichesk', + 'Wajir', + 'Polonuevo', + 'Las Piedras', + 'Vysokyi', + 'Barra do Pirai', + 'Malancha', + 'Vendome', + 'Kisarazu', + 'Ambohidanerana', + 'Villareal', + 'San', + 'Ipele', + 'Bologna', + 'Berwyn', + 'Kadambanad', + 'Diest', + 'Tuy Hoa', + 'Neuilly-sur-Seine', + 'Kiryas Joel', + 'Takoma Park', + 'Suwa', + 'Heiloo', + 'Chorbogh', + 'Teror', + 'Passo do Sertao', + 'Akim Swedru', + 'Bucheon', + 'Upper', + 'Kobuleti', + 'Adesar', + 'Hugli', + 'El Khroub', + 'Mwaya', + 'Atome-Avegame', + 'Dhahran', + 'Ratnapura', + 'Roncq', + 'Ambatomena', + 'Ichihara', + 'Waiblingen', + 'Anapoima', + 'Tizi Nisly', + 'Schiffdorf', + 'Merrick', + 'Jesenik', + 'Jnane Bouih', + 'Victoria Falls', + 'Genhe', + 'Charne', + 'Bicas', + 'Yandrapalle', + 'Onklou', + 'Arezzo', + 'Taung', + 'Paphos', + 'Otacilio Costa', + 'Ichora', + 'Urlaha', + 'Espelkamp', + 'Birao', + 'Lora del Rio', + 'Altavilla Milicia', + 'Anchorage', + 'Pendik', + 'Bugojno', + 'Kadavur', + 'Zahirabad', + 'Oschersleben', + 'Guerrero Negro', + 'Posadas', + 'Tabligbo', + 'Bermeo', + 'Coquimatlan', + 'Farg`ona', + 'Resita', + 'Syurte', + 'Viacha', + 'Tsing Yi Town', + 'Pultusk', + 'Patratu', + 'Satbayev', + 'Bonga', + 'San Salvador', + 'Szentendre', + 'Yvetot', + 'Bilpura', + 'Kabugao', + 'Haskovo', + 'Ingleside', + 'Wapakoneta', + 'Alhendin', + 'Mandialaza', + 'Kharsawan', + 'Chanda', + 'Maulavi Bazar', + 'Rautara', + 'Ljig', + 'Iona', + 'Sakaiminato', + 'North Myrtle Beach', + 'Peralillo', + 'Santa Flavia', + 'Ban Thung Khao Phuang', + 'Mandalavadi', + 'Bulanik', + 'Chong-Aryk', + 'Jayamkondacholapuram', + 'Havran', + 'Belobaka', + 'Fujisawacho-niinuma', + 'Puyallup', + 'Anderanboukan', + 'San Lazaro', + 'Metpalli', + 'Cerkezkoy', + 'Attleborough', + 'Chunakara Vadakku', + 'Eqlid', + 'Rockhampton', + 'Onalaska', + 'Hani i Elezit', + 'Ituzaingo', + 'Teculutan', + 'Bajwara', + 'Loran', + 'Guinayangan', + 'Wunstorf', + 'Sheopur', + 'Gol Tappeh', + 'Casselberry', + 'Jurema', + 'Chimay', + 'Horasan', + 'Sankt Wendel', + 'Eksambe', + 'Francisco Morato', + 'Panfilovka', + 'Ensley', + 'Kudavasal', + 'Palpala', + 'Itapecerica', + 'Drancy', + 'Monaghan', + 'Beamsville', + 'Crato', + 'Keila', + 'Riedlingen', + 'Bucine', + 'Vadippatti', + 'Segou', + 'Bag`dod', + 'Vellanad', + 'Conceicao do Castelo', + 'Alenquer', + 'Kalgi', + 'Somers Point', + 'McMinnville', + 'Erlangen', + 'Dabeiba', + 'Selkirk', + 'Toffo', + 'Aba', + 'Landi Kotal', + 'Duyen Hai', + 'Ostermundigen', + 'Doutou', + 'Emmeloord', + 'Ainsdale', + 'Isla Mujeres', + 'Bosobolo', + 'Forst (Lausitz)', + 'Swarzedz', + 'Guttenberg', + 'Gibsonton', + 'Moa', + 'Nattarasankottai', + 'Itaberai', + 'Teghra English', + 'Bellingham', + 'Lafayette', + 'Fort Wayne', + 'Odranci', + 'Hannut', + 'Ashburn', + 'Malate', + 'Jardin', + 'Clarington', + 'Mandiakui', + 'Merrydale', + 'Uirauna', + 'Panvel', + 'Chaguanas', + 'Basaha', + 'Susanville', + 'Kharik', + 'Grandview', + 'Mallapalli', + 'Ban Krang', + 'Camaiore', + 'Gross-Zimmern', + 'Vehkalahti', + 'Merchweiler', + 'Mohnesee', + 'Beach Park', + 'Yeghvard', + 'Beckingen', + 'Monte Dourado', + 'Gori', + 'Striano', + 'Barueri', + 'Tinogasta', + 'Santa Isabel do Rio Negro', + 'Kriftel', + 'Shiroishi', + 'Uaua', + 'Pinamalayan', + 'Sitalpur', + 'Las Matas de Santa Cruz', + 'Longchang', + 'Causeni', + 'Lajinha', + 'Sabbashahr', + 'Itacoatiara', + 'Mashan', + 'Trondheim', + 'Walsall Wood', + 'Lompoc', + 'Ibicuitinga', + 'Machang', + 'Babai', + 'Kobe', + 'Imsida', + 'Florida Ridge', + 'Antanambao Mahatsara', + 'Gudivada', + 'Apopka', + 'Bissau', + 'Itaete', + 'Shuiding', + 'Bratunac', + 'Beaconsfield', + 'Attnang-Puchheim', + 'Mennzel Bou Zelfa', + 'Dourados', + 'Obukhiv', + 'Lengede', + 'Riberalta', + 'Cafayate', + 'Bourem Inali', + 'Carapicuiba', + 'Orangevale', + 'La Homa', + 'Jaladurgam', + 'Gilarchat', + 'Reyhanli', + 'Durango', + 'Carros', + 'Hima', + 'Panthersville', + 'Arab', + 'Parasia', + 'Graca', + 'Metekora', + 'Harunabad', + 'Thonon-les-Bains', + 'Kodikulam', + 'Sarai Jattan', + 'Anderson Creek', + 'Gazulapalle', + 'Warrenton', + 'Qiman al `Arus', + 'Vaux-le-Penil', + 'Wuyi', + 'Calafell', + 'General Carneiro', + 'Sylvan Lake', + 'Onslow', + 'Sidi Amrane', + 'Heusden', + 'Yushan', + 'Kodakkal', + 'Dodvad', + 'Chaoshan', + 'San Jose de Urquico', + 'Rosmalen', + 'Sumilao', + 'Bharno', + 'Caversham', + 'Union Choco', + 'Annappes', + 'Angostura', + 'Nandasmo', + 'Ielmo Marinho', + 'Overland Park', + 'Umrapur', + 'Pirai do Sul', + 'Kumlu', + 'Kiyosu', + 'Paragaticherla', + 'Maayon', + 'Bardejov', + 'Masaka', + 'Maoming', + 'Santa Josefa', + 'Itarsi', + 'Prilep', + 'Pangil', + 'Kottapadi', + 'Central Falls', + 'Navarre', + 'Jatai', + 'Antsoantany', + 'Tiszavasvari', + 'Philadelphia', + 'Sundsvall', + 'Teapa', + 'Sansanne-Mango', + 'Itanhomi', + 'Upanema', + 'Longbridge', + 'Bryan', + 'Nueva Granada', + 'Malial', + 'Basud', + 'Kurichchi', + 'Pichidegua', + 'Amarante do Maranhao', + 'Kankol', + 'Tinajdad', + 'Amwa Majhar', + 'Pinetown', + 'Cherry Hill Mall', + 'Nangloi Jat', + 'Teopisca', + 'Bintuni', + 'Vengavasal', + 'Westmount', + 'Garmsar', + 'Sao Lourenco do Sul', + 'Duncan', + 'Kongarapalli', + 'Taounate', + 'Itapipoca', + 'Yungay', + 'Matalom', + 'Cartavio', + 'Bind', + 'Marck', + 'Galvarino', + 'Midvale', + 'Bangkok', + 'Tausa', + 'Gaoyou', + 'Balingen', + 'Wambrechies', + 'Pomerode', + 'Dolbeau', + 'Miyar', + 'Mahalapye', + 'Mandritsara', + 'Petrovske', + 'Mansa Konko', + 'Achern', + 'Malvern', + 'Veurne', + 'Tarauaca', + 'Sahna', + 'Patiram', + 'Nandurbar', + 'Nalwar', + 'Bahawalnagar', + 'Douar Ouled Ayad', + 'Guaitarilla', + 'Campo Mourao', + 'Sartana', + 'Yaojiazhuangcun', + 'Ma`raba', + 'Santo Tome', + 'Sapulpa', + 'Iasi', + 'Starkville', + 'Monte Cristi', + 'Youfangcun', + 'Vestal', + 'Shashemene', + 'Cambre', + 'Catonsville', + 'Honow', + 'Efkarpia', + 'West Caldwell', + 'Lianga', + 'Losser', + 'La Peche', + 'Kolea', + 'Avrig', + 'Mahatsinjony', + 'Sukhodilsk', + 'Le Bourget', + 'Sao Geraldo do Araguaia', + 'Palos Verdes Estates', + 'Millville', + 'Sao Miguel das Matas', + 'Saarbrucken', + 'Tres de Maio', + 'Sortobe', + 'Dhulia', + 'Pilate', + 'Itai', + 'Groot-Brakrivier', + 'Lemsid', + 'Cimarron Hills', + 'Boppard', + 'Shouguang', + 'Ares', + 'Aioun', + 'Catmon', + 'Kizilpinar', + "Ta' Xbiex", + 'Zuzemberk', + 'Vicksburg', + 'Koppies', + 'Karaj', + 'Cherrapunji', + 'Qasr-e Shirin', + 'El Penol', + 'Bacuri', + 'Rozenburg', + 'Phusro', + 'Howard', + 'Masquefa', + 'Taisar', + 'Tirebolu', + 'Wallenhorst', + 'Pa', + 'Baghpat', + 'Woodburn', + 'Tonawanda', + 'Mparo', + 'Douar Lamrabih', + 'Avondale', + 'Arenys de Mar', + 'Anguillara Sabazia', + 'Alblasserdam', + 'Otukpo', + 'Chandera', + 'Rubino', + 'Boskoop', + 'Boujad', + 'Bijai', + 'Teotihuacan', + 'Choba', + 'Swellendam', + 'Wood Dale', + 'Motta di Livenza', + 'Modakkurichchi', + 'Rajahmundry', + 'Rio Formoso', + 'Shantipur', + 'Wolvega', + 'Ennigerloh', + 'Seydi', + 'Melvindale', + 'Shangcaiyuan', + 'Gooty', + 'Ituiutaba', + 'Dakota Ridge', + 'Langen', + 'Klaukkala', + 'Lessines', + 'Saraykoy', + 'Oshu', + 'Kac', + 'Nanan', + 'Mandvi', + 'Larbert', + 'Garalo', + 'Binningen', + 'Templeton', + 'Yejituo', + 'San Estanislao', + 'Lake Stevens', + 'Kothanuru', + 'Dhanaura', + 'Goba', + 'Kasba', + 'Hot Springs', + 'Tiruvalur', + 'Birzebbuga', + 'Morafeno', + 'Cruzilia', + 'Sao Bras de Alportel', + 'Balingoan', + 'Purisima de la Concepcion', + 'Augustinopolis', + 'Caloocan City', + 'Itubera', + 'Lublin', + 'Temoaya', + 'Sebdou', + 'Beclean', + 'Itamarandiba', + 'Bayramic', + 'Ban Lam Narai', + 'Sirsa', + 'Talaja', + 'Amberieu-en-Bugey', + 'Arilje', + 'Gorbea', + 'Zakopane', + 'Izium', + 'Masvingo', + 'Shulan', + 'Chester', + 'Shinozaki', + 'Rupahi', + 'Douar Ait Taleb', + "'Ain Boucif", + 'San Gil', + 'Charters Towers', + 'Monovar', + 'Tierra Amarilla', + 'Toprakkale', + 'Pehcevo', + 'Cinfaes', + 'Lawang Bato', + 'Olinda', + 'Jiaoxi', + 'Bogatynia', + 'Bhawanipur Rajdham', + 'Jardin America', + 'Castelginest', + 'Herborn', + 'Cordeiro', + 'Dharmsala', + 'Gitega', + 'Vransko', + 'Pananaw', + 'Wettingen', + 'Deressia', + 'Secunderabad', + 'Queimada Nova', + 'Kollankulam', + 'Dharmaragar', + 'Beylaqan', + 'Lutsk', + 'Madinat as Sadat', + 'Shengping', + 'Yasu', + 'Hanmayingcun', + 'Ukiah', + 'Svilajnac', + 'Itoman', + 'Kathu', + 'Dugny', + 'Guanajuato', + 'Dreux', + 'Brovary', + 'Sicuani', + 'Hsenwi', + 'Manama', + 'Schleiden', + 'Kovancilar', + 'Tekkali', + 'Schlangen', + 'Zama', + 'Hostivice', + 'Calpulalpan', + 'Chalchihuitan', + 'Fondettes', + 'Messadine', + 'Duas Barras', + 'Alencon', + 'Cankiri', + 'Lake Zurich', + 'Bararam', + 'Nim ka Thana', + 'Woodlawn', + 'Contramaestre', + 'Monroe', + 'Kannod', + 'Ciechanow', + 'Kishunpur', + 'Haikou', + 'Fene', + 'Moree', + 'Rasulpur Dhuria', + 'Bowie', + 'Besiri', + 'Texistepec', + 'Langenau', + 'Nowra', + 'Metema', + 'Mokeri', + 'Kelibia', + 'Contamana', + 'Albergaria-a-Velha', + 'Paracuaro', + 'Kokshetau', + 'Campo Magro', + 'General Mariano Alvarez', + 'Stevenston', + 'Nagda', + 'Sliedrecht', + 'Kilmarnock', + 'Guantiankan', + 'Barira', + 'Dunn', + 'Portugalete', + 'Nizampatam', + 'Anse a Galets', + 'Louisville', + 'Policka', + 'Moita', + 'Sitanagaram', + 'Vrchlabi', + 'Greystones', + 'Kriel', + 'Guachochi', + 'Tuzla', + 'Juigalpa', + 'Paramonga', + 'Perali', + 'Idanha-a-Nova', + 'Ambilobe', + 'Kaga Bandoro', + 'Gilroy', + 'Nilavarappatti', + 'Tokat', + 'Miaojiaping', + 'Cascais', + 'Bilzen', + 'Malmo', + 'Tutong', + 'Tergnier', + 'Kalicherla', + 'Bishop Auckland', + 'Kyotamba', + 'Sonsonate', + 'Edmundston', + 'Ban Nong Tong', + 'Carnarvon', + 'Sloviansk', + 'Molave', + 'Kurobeshin', + 'Le Raincy', + 'Chinobod', + 'Alamnagar', + 'Kot Bhai', + 'Altenholz', + 'Esanai', + 'Kita-ku', + 'Lambidou', + 'Corumba', + 'North Providence', + 'Aulnoye-Aymeries', + 'Suifenhe', + 'Shar', + 'Vakfikebir', + 'Mudgee', + 'Vailoa', + 'Georgina', + 'Pachalum', + 'Al Fqih Ben Calah', + 'Lahti', + 'Eura', + 'Wolsztyn', + 'Santa Maria da Boa Vista', + 'Udipi', + 'Olgiate Olona', + 'Okinawa Numero Uno', + 'Albany', + 'Saint Sampson', + 'Bakel', + 'Governador Lindenberg', + 'Sannieshof', + 'Charlottetown', + 'Bidkhun', + 'Tenafly', + 'Dandarah', + 'Yunnanyi', + 'Mary', + 'Ouankoro', + 'Zhaicun', + 'Loboc', + 'Naduvannur', + 'Biedenkopf', + 'Jarash', + 'Yuanyangzhen', + 'Ashington', + 'Rufino', + 'Chodziez', + 'Heidenheim', + 'Aanekoski', + 'Puerto Tirol', + 'Attock Khurd', + 'Sihung', + 'Pennsville', + 'Vejen', + 'Sanford', + 'Loano', + 'West Deer', + 'Safsaf', + 'Collingswood', + 'East Setauket', + 'Sever do Vouga', + 'Savona', + 'Mayilur', + 'Bouchabel', + 'Mbarara', + 'Bersenbruck', + 'Bevonotra', + 'Linghai', + 'Aksay', + 'Santiago Tangamandapio', + 'Changshu', + 'Buckhurst Hill', + 'Yangchun', + 'Tumbao', + 'Mazagran', + 'Bussy', + 'Meerzorg', + 'Veys', + 'Peddapuram', + 'Wuwei', + 'Kongnolli', + 'Uar Esgudud', + 'Munxar', + 'Targu Secuiesc', + 'Zuvvaladinne', + 'Tombos', + 'Sanjianxiang', + 'Colonie', + 'Velakkuttai', + 'Haveri', + 'Machilipatnam', + 'Witbank', + 'Tierralta', + 'Blytheville', + 'Gorgan', + 'Pallapatti', + 'Sariq', + 'Fort Worth', + 'Hatonuevo', + 'Steinbach', + 'Anloga', + 'East Stroudsburg', + 'Rioverde', + 'Villa Castelli', + 'Hopa', + 'Knysna', + 'Koumassi', + 'Pampierstad', + 'Agawam', + 'Utrera', + 'Oggaz', + 'Wadgira', + 'Pagqen', + 'Mizdah', + 'Bni Tajjit', + 'Douar Snada', + 'Sandy Springs', + 'Louviers', + 'Mamak', + 'Rimouski', + 'Giddarbaha', + 'Ittiva', + 'Pedro Luro', + 'Kerepestarcsa', + 'Shohimardon', + 'Santa Ana', + 'La Sierra', + 'Diguapo', + 'Soio', + 'Saint-Jean-de-Luz', + 'Illkirch-Graffenstaden', + 'Saint-Pierre-des-Corps', + 'Aivanallur', + 'Rio Piracicaba', + 'Kompalle', + 'Chiayi', + 'Rostraver', + 'Flossmoor', + 'Frunze', + 'General Roca', + 'Aglipay', + 'Nussloch', + 'Yakinca', + 'Kattupputtur', + 'Kolkata', + 'Bo', + 'San Antonio del Tachira', + 'Patrocinio', + 'Cuvelai', + 'Duarte', + 'Panetha', + 'Cave', + 'Lahore', + 'Maijdi', + 'Janhapara', + 'Tondi', + 'Al Hamalah', + 'Ludhiana', + 'El Hammadia', + 'Ejura', + 'Antsalova', + 'Fiadanana', + 'Nedumana', + 'Novo Aripuana', + 'Qufadah', + 'Igreja', + 'Punganuru', + 'Ranigaon', + 'Presidente Kennedy', + 'Pedda Kotayalanka', + 'Loudoun Valley Estates', + 'Vezirkopru', + 'Dronfield', + 'Ugo', + 'Obburdon', + 'Camiling', + 'Cermik', + 'Ampatuan', + 'Alangulam', + 'Candler-McAfee', + 'Behenjy', + 'Talanga', + 'Garissa', + 'Mbandaka', + 'Senboku', + 'Banganapalle', + 'Pedreiras', + 'Loreto', + 'Elias Fausto', + 'Columbia', + 'Sao Ludgero', + 'Revelganj', + 'Uhersky Brod', + 'Feira Grande', + 'Norten-Hardenberg', + 'Omura', + 'Wilcannia', + 'Bishopbriggs', + 'Dandoli', + 'Satna', + 'Palni', + 'Elda', + 'Bakhtiyarpur', + 'Kara-Tash', + 'Holden', + 'Kodumba', + 'Udayendram', + 'Wangjiaxian', + 'Ragusa', + 'Pijijiapan', + 'Muret', + 'Feldkirchen-Westerham', + 'Ineu', + 'Smithtown', + 'Incesu', + 'Svay Rieng', + 'Bhucho Mandi', + 'Mukaiengaru', + 'Maiquetia', + 'Manglaur', + 'Centennial', + 'Bonfinopolis', + 'Cova Figueira', + 'Ianca', + 'Mahesana', + 'San Francisco de Mostazal', + 'Langenfeld', + 'Buchholz in der Nordheide', + 'Coutras', + 'Grigiskes', + 'Biswan', + 'Perumbalai', + 'Shujalpur', + 'Ciudad Manuel Doblado', + 'Tunzi', + 'Great Harwood', + 'Andhra Tharhi', + 'Toudja', + 'Ramsar', + 'Gaesti', + 'Champigny-sur-Marne', + 'Ash Shaddadah', + 'Santaquin', + 'Quilpie', + 'Tubay', + 'Babylon', + 'Pualas', + 'Johi', + 'Sahaspur', + 'Padiham', + 'Busto Garolfo', + 'Kamatero', + 'Miary', + 'Ialysos', + 'Seydisehir', + 'Morgan Hill', + 'Malard', + 'Kafr ad Dawwar', + 'Sancoale', + 'Sakata', + 'Jinoba-an', + 'Sorgues', + 'Sumedang', + 'Sanrh Majhgawan', + 'Dalanzadgad', + 'Chundal', + 'Piombino', + 'Port Harcourt', + 'Goyang', + 'Puntarenas', + 'Gaszowice', + 'Santa Rosa de Copan', + 'Carmo da Cachoeira', + 'Bargersville', + 'Beius', + 'Stony Brook University', + 'Razlog', + 'Spenge', + 'Ban Mae Kha Tai', + 'Chilpur', + 'Pacific Grove', + 'Kalyan', + 'Balmazujvaros', + 'Hortolandia', + 'Pataili', + 'San Agustin de las Juntas', + 'Sao Felix do Xingu', + 'Prior Lake', + 'Puzol', + 'Udumanthala', + 'Novopskov', + 'Auerbach', + 'Zarate', + 'Makuyu', + 'Ertvelde', + 'Kommuru', + 'Manari', + 'Belonia', + 'Immokalee', + 'Kuiyibagecun', + 'Ascoli Piceno', + "Cassina de' Pecchi", + 'Sousa', + 'Alhaurin de la Torre', + 'Djidian Kenieba', + 'Los Rios', + 'Castel Goffredo', + 'Resana', + 'Rehli', + 'Puerto Boyaca', + 'Bandar-e Anzali', + 'Martha Lake', + 'Chopinzinho', + 'Friedrichsdorf', + 'La Granja', + 'San Agustin Loxicha', + 'Platteville', + 'Dighaun', + 'Mebane', + 'Pavumba', + 'Epinay-sur-Seine', + 'Alfredo Wagner', + 'Singen', + 'El Asintal', + 'Sankrail', + 'Nghi Son', + 'Pithoragarh', + 'Pudunagaram', + 'Tsuchiura', + 'Jhundpura', + 'Itapecuru Mirim', + 'Naranjal', + 'Pinneberg', + 'Schnaittach', + 'Penco', + 'Oissel', + 'Dungra Chhota', + 'Amudat', + "Qo'shko'pir", + 'Pachahi', + 'Sufian', + 'Haguenau', + 'Mansapur', + 'Discovery Bay', + "'Ain Leuh", + 'Tabernes de Valldigna', + 'Medvode', + 'Sillamae', + 'Baker City', + 'Gllogovc', + 'Dhrangadhra', + 'Homosassa Springs', + 'Outreau', + 'Beni', + 'Meltonakkal', + 'Mango', + 'San Pedro Jocopilas', + 'Roodepoort', + 'Unterhaching', + 'Ibiruba', + 'San Bernardo', + 'Marhanets', + 'Itapitanga', + 'Sivalarkulam', + 'Maguing', + 'Lianran', + 'Smila', + 'Forrest City', + 'Lumino', + 'Qingzhou', + 'Bajina Basta', + 'Bruggen', + 'Tapiramuta', + 'Ganga Sagar', + 'Hvardiiske', + 'Capitao Poco', + 'Carranglan', + 'Ekpe', + 'Camana', + 'Choyr', + 'Tullahoma', + 'Bothell', + 'Livadeia', + 'Samarate', + 'Brixham', + 'Anororo', + 'Ypejhu', + 'Bradford-on-Avon', + 'Wigston Magna', + 'Acigol', + 'Puerto Salgar', + 'Benicia', + 'Fundeni', + 'Dioumanzana', + 'Chellalat el Adhaouara', + 'Zonhoven', + 'Vellar', + 'Sceaux', + 'Luuka Town', + 'Kristiansand', + 'Ripley', + 'Lutry', + 'Gurabo al Medio', + 'Sepidan', + 'Chubbuck', + 'Harding', + 'Rogatica', + 'Maple Valley', + 'Qal`at Bishah', + 'Boladi', + 'Terlizzi', + 'Morriston', + 'Jaslo', + 'Mendrisio', + 'Valangiman', + 'Medveda', + 'Barton upon Humber', + 'Sauk Village', + 'Dison', + 'Little Bookham', + 'Marlton', + 'Harhorin', + 'Rampur Kalan', + 'Trofa', + 'Gatesville', + 'Etchojoa', + 'Baichigan', + 'New Hartford', + 'Charleville-Mezieres', + 'Anna', + 'Unchahra', + 'Porto de Mos', + 'Ambhua', + 'Al Minya', + 'Morro do Chapeu', + 'Saintard', + 'Bukwo', + 'Kawartha Lakes', + 'Badr Hunayn', + 'Valley Cottage', + 'Duxbury', + 'Baipingshan', + 'Fuente de Oro', + 'Nayudupet', + 'Zhexiang', + 'Khagra', + 'Sarakhs', + 'Saint-Pierre-du-Mont', + 'Urumqi', + 'Vannivedu', + 'Cesano Maderno', + 'Juarez', + 'Neuruppin', + 'Dig', + 'Momil', + 'Gondal', + 'Torres Novas', + 'Akkattettar', + 'Dourbali', + 'Ondo', + 'Monjas', + 'Mechernich', + 'Arlit', + 'Mandelieu-la-Napoule', + 'Murska Sobota', + 'Daean', + 'Kalaleh', + 'Bamei', + 'Myingyan', + 'Lingquan', + 'Pescia', + 'Malloussa', + 'El Attaf', + 'Panmana', + 'Caqueza', + 'Moulay Bousselham', + 'Pathein', + 'Reha Mota', + 'Florencio Varela', + 'Al Lataminah', + 'Azogues', + 'Jhandapur', + 'Verucchio', + 'Subachoque', + 'Solila', + "Rava-Rus'ka", + 'Witu', + 'Gander', + 'General Pico', + 'Tongluo', + 'Serinhisar', + "Fiorenzuola d'Arda", + 'Vadamadurai', + 'Bayou Cane', + 'Plunge', + 'Quakertown', + "Limeira d'Oeste", + 'Qulan', + 'Agramonte', + 'Jamunamukh', + 'Gondomar', + 'Lajas', + 'Quinapundan', + 'Kurten', + 'Coremas', + 'Mafra', + 'Pinewood', + 'Linguere', + 'Miradouro', + 'Baganga', + 'Pauini', + 'Trebur', + 'Bollnas', + 'Romulus', + 'Banire Kore', + 'Legnaro', + 'Manatanna', + 'Deruta', + 'Finestrat', + 'Chico', + 'Mankono', + 'Pimentel', + 'Umirim', + 'Velanganni', + 'Natchitoches', + 'Kanungu', + 'West Palm Beach', + 'Caraballeda', + 'Tha Bo', + 'Nishio', + 'Bragado', + 'Palopo', + 'Concorezzo', + 'Nagla', + 'Nobres', + 'Sagada', + 'Bayona', + 'Cheat Lake', + 'Zion', + 'Kochi', + 'Cho Lach', + 'Sannicandro di Bari', + 'Pelotas', + 'Selb', + 'Sugarmill Woods', + 'Oatlands', + 'Basoda', + 'Lakewood Ranch', + 'Tenango del Aire', + 'Tulkarm', + 'Bella Union', + 'Heath', + 'Massaranduba', + 'Higashikagawa', + 'Barra do Choca', + 'Bolivar', + 'Aguacatan', + 'Yakacik', + 'Alexander Bay', + 'Dehri', + 'Mehnatobod', + 'Thol', + 'Burkburnett', + 'Sanarate', + 'Pollokshaws', + 'Sencur', + 'Zhanjiang', + 'San Pedro Ixcatlan', + 'Le Pre-Saint-Gervais', + 'Jamunia', + 'Opol', + 'Ambalajia', + 'Penola', + 'Mangindrano', + 'Oued el Alleug', + 'Marai Kalan', + 'Zatec', + 'Southern Cross', + 'San Luis de la Paz', + 'Omihachiman', + 'Illapel', + 'Tsianisiha', + 'Siteia', + 'McFarland', + 'Nova Resende', + 'Rheinfelden', + 'Mubi', + 'Ourika Wawrmas', + 'Quinte West', + 'Korce', + 'Kalanaur', + 'Dhulian', + 'Balen', + 'Auterive', + 'Wronki', + 'Budha Theh', + 'Quy Nhon', + 'Cedro', + 'Garlasco', + 'Biharamulo', + 'Chateaurenard', + 'General Cabrera', + 'Gostynin', + 'Ad Diraz', + 'Asau', + 'Oceanside', + 'Longmont', + 'Nanzhuang', + 'Satadougou', + "Saint-Barthelemy-d'Anjou", + 'Owariasahi', + 'Zombo', + 'Dudley', + 'San Agustin Acasaguastlan', + 'Lochem', + 'Nidderau', + 'Devarkonda', + 'Leven', + 'Munchberg', + 'Mariani', + 'Cornillon', + 'Svidnik', + 'Heek', + 'Chak Thirty-six North Branch', + 'Tholey', + 'Charsadda', + 'Restinga Seca', + 'Villeneuve-Tolosane', + 'Profondeville', + 'Amla', + 'Ludwigslust', + 'Caterham', + 'Al Ghardaqah', + 'Mineral de la Reforma', + 'Kosice', + 'Sehnde', + 'Imam Qasim', + 'Ain El Aouda', + 'Candido Mendes', + 'Hacari', + 'Massafra', + 'Vittal', + 'Koh Kong', + 'Stockach', + "Saint Paul's Bay", + 'Tieshansi', + 'Xinyi', + 'Rawanduz', + 'Nanyuki', + 'Sarpamari', + 'Tres Coracoes', + 'Narutocho-mitsuishi', + 'Cukai', + 'Douchy-les-Mines', + 'Mandirituba', + 'Imouzzer Kandar', + 'Oulad Teima', + 'Lere', + 'Kanavaypudur', + 'Naugaon Sadat', + 'Bonnyrigg', + 'Charthawal', + 'Znojmo', + 'Cankova', + 'Tondo', + 'Badhan', + "Wu'an", + 'North Bellport', + 'Ambatosoratra', + 'Naduvattam', + 'Columbia Heights', + 'Neustadt am Rubenberge', + 'Maturin', + 'Ehringshausen', + 'Dandkhora', + 'Xinzhai', + 'Winter Gardens', + 'Bayur', + 'Dame-Marie', + 'Saco', + 'Metsemotlhaba', + 'Apen', + 'Nuevo Laredo', + 'Cofimvaba', + 'Miryal', + 'Shuangluan', + 'Thorold', + 'Zagarolo', + 'Wenling', + 'Dengtalu', + 'Oharu', + 'Sumoto', + 'Osmaniye', + 'Aquiraz', + 'Bury Saint Edmunds', + 'Nacajuca', + 'Baao', + 'Velbert', + 'Pyskowice', + 'North Platte', + 'Prostejov', + 'Paradise', + 'Newington Forest', + 'Gonghaur', + 'Rawicz', + 'Lamsabih', + 'Zaoqiao', + 'Chaoke', + 'Renala Khurd', + 'Saint-Sulpice-la-Pointe', + 'Kerkrade', + 'Glen Cove', + 'Cottbus', + 'Laurens', + 'Mont-Laurier', + 'Omalur', + 'Loum', + 'An Nu`maniyah', + 'Allende', + 'Jagdispur', + 'Bourgoin-Jallieu', + 'Icapui', + 'Niiza', + 'Disa', + 'Rhynern', + 'Mata', + 'Straubing', + 'Kingsnorth', + 'Esch-sur-Alzette', + 'Moyobamba', + 'Flores Costa Cuca', + 'Vetlanda', + 'Hengbei', + 'Hatillo de Loba', + 'Conway', + 'Ashta', + 'Diphu', + 'Sutculer', + 'Bad Worishofen', + 'Mahta', + 'Umm Qurun', + 'Strongsville', + 'Kamidani', + 'Playa del Carmen', + 'Moralzarzal', + 'Lauffen am Neckar', + 'Kelishad va Sudarjan', + 'Cocentaina', + 'Harrislee', + 'Sojat', + 'Mahallat Damanah', + 'South Frontenac', + 'Ban Phan Chali', + 'San Sebastian Coatan', + 'Diego Martin', + 'Thai Nguyen', + 'Pewaukee', + 'Ifanadiana', + 'Asjen', + 'Mankachar', + 'Apparaopeta', + 'DeRidder', + 'Orleans', + 'Nagojanahalli', + 'Peddavadlapudi', + 'Dadrewa', + 'Sagure', + 'Natal', + 'Shirakawa', + 'Calera de Tango', + 'Sahbuz', + 'Rodeio', + 'Shaying', + 'Sakoabe', + 'Ramabhadrapuram', + 'Kararan', + 'Zolote', + 'El Espinal', + 'Laverton', + 'Ramgarha', + 'Sant Julia de Loria', + "Sant'Agnello", + 'Lidzbark Warminski', + 'Shu', + 'Pweto', + 'Burscough', + "Foum Jam'a", + 'Hua Hin', + 'Marovato', + 'Gangapatnam', + 'Rijeka', + 'Jihong', + 'Seacombe', + 'Friendswood', + 'Estreito de Camara de Lobos', + 'Loei', + 'Dan', + 'Jerez de Garcia Salinas', + 'Pukhrayan', + 'Choma', + 'Tlapa de Comonfort', + 'Catbalogan', + 'Boudenib', + 'Paraparaumu Beach', + 'East Lansing', + 'Stradella', + 'Tenali', + 'Irungalur', + 'Hollymead', + 'Farap', + 'Pitesti', + 'Muzambinho', + 'La Mesa', + 'Mulki', + 'Bruck an der Mur', + 'Gongguan', + 'Vayalar', + 'Freiberg am Neckar', + 'Passa e Fica', + 'Myrhorod', + 'Mutoko', + 'Warwick', + 'Shanmukhasundarapuram', + 'Koscian', + 'Nanyangcun', + 'Patton', + 'Grimes', + 'Vempatti', + 'Smizany', + 'Chipinge', + 'Miyota', + 'Villajoyosa', + 'Almoloya del Rio', + "'Ali Ben Sliman", + 'Brodosqui', + 'Vikrutamala', + 'Valatt', + 'Aparecida', + 'Guskhara', + 'Oke-Mesi', + 'Roche-la-Moliere', + 'Sentrupert', + 'Buraan', + 'Kisii', + 'Lohardaga', + 'Angleton', + 'Quakenbruck', + 'Bystrzyca Klodzka', + 'Ippagudem', + 'Lunner', + 'Almora', + 'Hasilpur', + 'Agiripalle', + 'Parys', + 'Loningen', + 'King City', + 'Suitland', + 'Bathnaha', + 'Gachancipa', + 'Bad Bentheim', + 'Fuyang', + 'Federal Heights', + 'Vallirana', + 'Manandroy', + 'Varidhanam', + 'Valreas', + 'Konstanz', + 'Hissaramuruvani', + 'Uripa', + 'Kimbe', + 'Medina Sidonia', + 'Tarragona', + 'Antindra', + 'Montana', + 'Cervignano del Friuli', + 'Hezuo', + 'Puerto Escondido', + 'Salto de Pirapora', + 'Gaobeidian', + 'Chak Husaini', + 'Grajales', + 'Lykovrysi', + 'Ankatafa', + 'Sbiba', + 'Matanog', + 'Sikka', + 'Avsallar', + 'Sarykemer', + 'Lighthouse Point', + 'Fatehpur Sikri', + 'Lakhminia', + 'Nagari', + 'Ansongo', + 'Bad Nauheim', + 'Trostberg an der Alz', + 'Mogila', + 'Mahrail', + 'Mouans-Sartoux', + 'Hude', + 'Lazi', + 'Berkhampstead', + 'Nehram', + 'Fussa', + 'Waipahu', + 'Dawei', + 'Haldia', + 'Olten', + 'Apastepeque', + 'Krzeszowice', + 'Koini', + 'Douar Messassa', + 'Lima', + 'Honcho', + 'Mulchen', + 'Acheres', + 'Harper Woods', + 'Tlacoachistlahuaca', + 'Baikonur', + 'De Bilt', + 'Vianden', + 'Arnold', + 'Bhabhua', + 'Al Ghizlaniyah', + 'Chakur', + 'Jixi', + 'Kibiti', + 'Viechtach', + 'Comalcalco', + 'Westfield', + 'Uacu Cungo', + 'Camargo', + 'San Juan del Rio del Centauro del Norte', + 'Bebington', + 'Largs', + 'Sompeta', + 'Ipsala', + 'Pola de Laviana', + 'Jose Maria Morelos', + 'Wasserburg am Inn', + 'Al Hawiyah', + 'Kilrajakularaman', + 'Chak Sixty-one Gugera Branch', + 'Jafaro', + 'Yattir', + 'Piura', + 'Dachau', + 'Bom Jesus dos Perdoes', + 'Itacare', + 'Delcevo', + 'Negombo', + 'Tenedla', + 'Amadora', + 'At-Bashy', + 'Fort Collins', + 'Amuria', + 'Vicuna', + 'Dahu', + 'Eloy Alfaro', + 'Beaumont', + 'Lloro', + 'Vinh Long', + 'Padiyanallur', + 'Tobruk', + 'Pazar', + 'Bideipur', + 'Magdalena de Kino', + 'Parla', + 'Kanuma', + 'Quatro Barras', + 'Chikusei', + 'Panambi', + 'Arecibo', + 'Bekatra Maromiandra', + 'Ahus', + 'Tilingzhai', + 'Hanahan', + 'Lunbei', + 'Majhua', + 'Bad Segeberg', + 'Holywell', + 'Tivim', + 'El Charco', + 'Mailiao', + 'Chenlu', + 'Beylul', + 'Camarajibe', + 'Le Puy-en-Velay', + 'Santander de Quilichao', + 'Nakasongola', + 'Shabestar', + 'Lindau', + 'Hathaura', + "Ayt 'Attou ou L'Arbi", + 'Fiuggi', + 'Dhekiajuli', + 'Shuyangzha', + 'Tokoroa', + 'Chaozhou', + 'Durres', + 'Old Jamestown', + 'Chimbote', + 'Ivanovka', + 'Silver Firs', + 'Santa Ana Nextlalpan', + 'Ourtzagh', + 'Lysa nad Labem', + 'Hirakata', + 'Udine', + 'Andrelandia', + 'Seysses', + 'Atami', + 'Woudrichem', + 'Brzeszcze', + 'Zhangping', + 'Rolante', + 'Mabai', + 'Varatanapalli', + 'Catak', + 'Mahabaleshwar', + 'Galgamuwa', + 'Kelso', + 'Esquipulas', + 'Seoni Malwa', + 'Laboulaye', + 'Winter Haven', + 'Jimaguayu', + 'Rury', + 'Lubbenau/Spreewald', + 'Dijiasuoxiang', + 'Wielsbeke', + 'Pagani', + 'Kizhur', + 'San Carlos City', + 'Chaudfontaine', + 'Camacupa', + 'Celina', + 'Roudnice nad Labem', + 'Rajni', + 'Umm Ruwaba', + 'Harir', + 'Suwasra', + 'Rio Casca', + 'Bry-sur-Marne', + 'Fleron', + 'Minamikyushu', + 'Tsallagundla', + 'Bashtanka', + 'Soresina', + 'Sogut', + 'Parkersburg', + 'Artesia', + 'Barka Parbatta', + 'San Carlos de Guaroa', + 'Abreus', + 'Sarsai Nawar', + 'Saint-Marc', + 'George', + 'Kamamaung', + 'Bayburt', + 'Narsampet', + 'As Sidrah', + 'Menasha', + 'Aberaman', + 'Irikkur', + 'Herning', + 'Apeldoorn', + 'El Campo', + 'Thakhek', + 'Karanjia', + 'Maullin', + 'Druten', + 'Mundra', + 'Paveh', + 'Carovigno', + 'Jodar', + 'Montevarchi', + 'LaGrange', + 'Masanasa', + 'Maddela', + 'Chicago', + 'Kamayakkavundanpatti', + 'Xiangyang', + 'Leizhou', + 'Atner', + 'Siderno Marina', + 'Agra', + 'Shin-Kamigoto', + 'Anenecuilco', + 'Sejenane', + 'Paletwa', + 'Pantanaw', + 'Senec', + 'Messaad', + 'Brielle', + 'Cangxi', + 'Dagarua', + 'Kingston South East', + 'Allestree', + 'Oslob', + 'Bolsover', + 'Grand Turk', + 'Bohodukhiv', + 'Boulogne-sur-Mer', + 'Kyotanabe', + 'Coimbatore', + 'Jinshi', + 'Ornskoldsvik', + 'Woonsocket', + 'Buhusi', + 'Villanueva de la Canada', + 'Karugamad', + 'Jumla', + 'Pueblo Nuevo Vinas', + 'Tres de Mayo', + 'Sal Rei', + 'Valkeakoski', + 'Kungsangen', + 'Zulte', + 'Kerewan', + 'Tabango', + 'Naha', + 'Akkus', + 'Tulcea', + 'Moratuwa', + 'Utsunomiya', + 'Noeux-les-Mines', + 'Los Barrios', + 'Agadi', + 'Hlohovec', + 'Gages Lake', + 'Tiverton', + 'Golmarmara', + 'Puszczykowo', + 'Mangabe', + 'Hohr-Grenzhausen', + 'Zango', + 'Cua Lo', + 'Mansalay', + 'Leverano', + 'Yanahuanca', + 'Sinaia', + 'Gudluru', + 'Vadugapatti', + 'Ban Tha Phra', + 'Kikwit', + 'Cantagalo', + "Yan'an Beilu", + 'Tampakan', + 'Glanerbrug', + 'Copalchi', + 'Zacualtipan', + 'Fidenza', + 'Tsararano', + 'Ferguson', + 'Renukut', + 'Sao Sebastiao do Cai', + 'Mainburg', + 'Ceres', + 'Uluberiya', + 'Mauji', + 'Telfs', + 'Cachoeira dos Indios', + 'Triunfo', + 'Mittegrossefehn', + 'Murehwa', + 'Velika Polana', + 'Borgosesia', + 'Shichuanxiang', + 'Befandefa', + 'Pilar do Sul', + 'Quimper', + 'Saeby', + 'Netanya', + 'Peri-Mirim', + 'Tidjelabine', + 'Tecolutla', + 'Anakaputtur', + 'Quipapa', + 'Dikili', + 'Yuma', + 'Parakadavu', + 'Oxapampa', + 'Odense', + 'Tanambao-Daoud', + 'Bussum', + 'Dartford', + 'Bilaua', + 'Stoughton', + 'Alcanena', + 'Cloverleaf', + 'Akambadam', + 'Anomabu', + 'Lo Barnechea', + "Wen'anyi", + 'Port Orchard', + 'Ahuachapan', + 'Horta', + 'Bourg-de-Peage', + 'Neuville-les-Dieppe', + 'Walton upon Thames', + 'Jaguaquara', + 'Koshigaya', + 'Wendeburg', + 'Tall Shihab', + 'Tounfafi', + 'Al Hamah', + 'Mostar', + 'Illizi', + 'Khaspur', + 'Sirvan', + 'Lapu-Lapu City', + 'Sint-Gillis-Waas', + 'Cassongue', + 'Beahitse', + 'Virapalle', + 'Geislingen an der Steige', + 'Bogalusa', + 'Stavanger', + 'Jiaji', + 'Kannapuram', + 'Bamukumbit', + 'Maroua', + 'Correggio', + 'Bikkavolu', + 'Kumaranallur', + 'Guinagourou', + 'Salonta', + 'Zhutang', + 'Tamarankottai', + 'Tzucacab', + 'Gurais', + 'Taichung', + 'Oas', + 'Anjukulippatti', + 'Melchor Ocampo', + 'Nochistlan de Mejia', + 'Victor', + 'Limeira', + 'Chalfont Saint Peter', + 'La Marsa', + 'Cullinan', + 'Shimeo', + 'Neuhof', + 'Kannulu', + 'Daganbhuiya', + 'Antigua Guatemala', + 'Bowmanville', + 'Zemoura', + 'Tyele', + 'Cedartown', + 'Kingsland', + 'Toyomamachi-teraike', + 'Bni Khloug', + 'Montargis', + 'Sarasambo', + 'Aurora', + 'Nilka', + 'Siena', + 'Santa Vitoria', + 'Goz-Beida', + 'Murray', + 'Hennaya', + 'Santa Isabel Cholula', + 'Bellamkonda', + 'Andasibe', + 'Quvasoy', + 'Villa Park', + 'Thaon-les-Vosges', + 'Acri', + 'Limbdi', + 'Kaisariani', + 'Coconut Creek', + 'Kitakoriyamacho', + 'Gaggenau', + 'Newmarket', + 'Grombalia', + 'Dogansehir', + 'Chinsali', + 'Moisei', + 'La Vallee de Jacmel', + 'Kadamalaikkundu', + 'Wagga Wagga', + 'Sonthofen', + 'Sukkur', + 'Zhaozhou', + 'Boshruyeh', + 'Macara', + 'Cheongju', + 'Nisko', + 'Meftah', + 'Amilly', + 'Ageoshimo', + 'Butzbach', + 'Giria', + 'Sultonobod', + 'Sucua', + 'Keregodu', + 'Dudhgaon', + 'Chalma', + 'Pierre-Benite', + 'Oberriet', + 'Tramandai', + 'Shiggaon', + 'Whitestown', + 'Zaqatala', + 'Zaboli', + 'Houston', + 'Sorab', + 'Enkhuizen', + 'Kaniwara', + 'Sare-Yamou', + 'Kriens', + 'Petersburg', + 'Rawah', + 'Montenegro', + 'Boorama', + 'Gangawati', + 'Manjuyod', + 'Argostoli', + 'Clarksburg', + 'Fall River', + 'Boffa', + 'Sodag', + 'Hingyon', + 'Kanding', + 'Gresham', + 'Peruru', + 'Kemigawa', + 'Manbazar', + 'Capitol Hill', + 'Bo`z', + 'Murshidabad', + 'Resplendor', + 'Baishi Airikecun', + 'Bulancak', + 'Aibonito', + 'East Islip', + 'Mengibar', + 'Kotwapatti Rampur', + 'Qaryat al Qi`an', + 'Estiva Gerbi', + 'Sipalay', + 'Mandrosohasina', + 'Hadera', + 'Partick', + 'Pulawy', + 'Lobamba', + 'Vitoria de Santo Antao', + 'Fada Ngourma', + 'Al Hoceima', + 'Agcogon', + 'Konina', + 'Mahbubnagar', + 'Temamatla', + 'Assab', + 'Gautier', + 'Mangalkot', + 'San Benito', + 'Ben Ahmed', + 'Natori-shi', + 'Wymondham', + 'Hassi el Ghella', + 'Pesca', + 'Kuttappatti', + 'Campo de Criptana', + 'Pelham', + 'Mont-Saint-Hilaire', + 'Sao Joaquim', + 'Lusca', + "Topol'cany", + 'Itagui', + 'Artur Nogueira', + 'Cheadle Hulme', + 'Timmiarmiut', + 'Carmopolis', + 'Dolhasca', + 'Mangghystau', + 'Much', + 'Vadamugam Vellodu', + 'Vamanapuram', + 'Bordj Mokhtar', + 'Tougouni', + 'Beldaur', + 'Limbuhan', + 'Paraibano', + 'Realeza', + 'Puchheim', + 'Cardedeu', + 'Cacocum', + 'Luacano', + 'Villavicencio', + 'Fray Bartolome de Las Casas', + 'Burke', + 'Dabiya', + 'Pizarra', + 'Kothapet', + 'Ile-Ife', + 'Chittattukara', + 'Chuadanga', + 'Wausau', + 'Ban Tha Ton', + 'Feyzabad', + 'Frankenberg', + 'Gostivar', + 'Magelang', + 'Sabaya', + 'Maleme Hodar', + 'Virakeralam', + 'Bhisho', + 'Rakhiv', + 'Manjimup', + 'Catalpinar', + 'Liaoyuan', + 'Akureyri', + 'Ris-Orangis', + 'Larena', + 'Ampelokipoi', + 'Waterloo', + 'Siguiri', + 'Sujina', + 'Monte Alegre do Piaui', + 'Maicao', + 'Ambohimiadana', + "Pan'an", + 'Vallauris', + 'Lingampalli', + 'Ewell', + 'Tambau', + 'South Union', + 'Moga', + 'Talghar', + 'Salinas', + 'Gonghe', + 'Temple City', + 'Ternat', + 'San Ramon de la Nueva Oran', + 'Donaueschingen', + 'Adilcevaz', + 'Plainedge', + 'Salotgi', + 'Barotac Nuevo', + 'Chevigny-Saint-Sauveur', + 'Jaisalmer', + 'Cameri', + 'Lowshan', + 'Pleasant View', + 'Gazipura', + 'Indwe', + 'Yen Bai', + 'Hospet', + 'Jamai', + 'Palmetto Estates', + 'Ponnani', + 'Hwlffordd', + 'Tiny', + 'Bhattu Kolan', + 'Yangsan', + 'Allanridge', + 'Quyang', + 'Agoo', + 'Sabbavaram', + 'Chamical', + 'Buritama', + 'Minna', + 'Millbury', + 'San Jose Villa de Allende', + 'Ercis', + 'Mogok', + 'Dona Remedios Trinidad', + 'Rio Novo do Sul', + 'Kormend', + 'Ambohimitombo', + 'New Carrollton', + 'Mount Airy', + 'Qinhuangdao', + 'Madingou', + 'Robbinsdale', + 'Huejucar', + 'Erraballa', + 'Andover', + 'Kefar Yona', + 'Sweet Home', + 'Deming', + 'Asakuchi', + 'Al Mu`abbadah', + 'Varna', + 'Pine Bluff', + 'Rovira', + 'Brackley', + 'Kodala', + 'Patu', + 'Satoraljaujhely', + 'Ambohimanga', + 'Cherukolattur', + 'Marechal Deodoro', + 'Pelaya', + 'Cape Town', + 'Sarakkayhalli', + 'Manado Light', + 'Ihsaniye', + 'Yangiobod', + 'Natagaima', + 'Mattathur', + 'Strathroy-Caradoc', + 'Talagante', + 'Montclair', + 'Kiryandongo', + 'Nzeto', + 'Pativilca', + 'Quiapo', + 'Pulilan', + 'Serra Preta', + 'Alagoa Grande', + 'Jerez', + 'Edam', + 'Merces', + 'Galaz', + 'Pahuatlan de Valle', + 'Nathpur', + 'Orem', + 'Milford Mill', + 'Vimercate', + 'Baie de Henne', + 'Fanandrana', + 'Stenungsund', + 'Fondi', + 'Vipparla', + 'Arroyomolinos', + 'Kaniyur', + 'Cristuru Secuiesc', + 'Marawi City', + 'Dong Hoa', + 'Moura', + 'Selsey', + 'Dinalupihan', + 'Busko-Zdroj', + 'Itatim', + 'Foumban', + 'Purwa', + 'Woippy', + 'Muzhakkunnu', + 'Backa Topola', + 'Colmenarejo', + 'Burg', + 'Ashoknagar', + 'Manerbio', + 'Montmorency', + 'Carteret', + 'Kotekara', + 'El Socorro', + 'Mayahaura', + 'Besigheim', + 'Barsinghausen', + 'Raita', + 'Zwenkau', + 'Fords', + 'Palamedu', + 'McHenry', + 'Rahovec', + 'Lajosmizse', + 'Sakhmohan', + 'Lyons', + 'Ulatu', + 'Tuskegee', + 'Humacao', + 'Launaguet', + 'Paranaiba', + 'Shitab Diara', + 'Asansol', + 'Signal Mountain', + 'Hammam Bou Hadjar', + 'Novi Sad', + 'Karghar', + 'Arzano', + 'Thatri', + 'Vossevangen', + 'Cuya', + 'Lymanka', + 'Bucak', + 'Cathedral City', + 'Sidi Slimane', + 'Tapejara', + 'Marco', + 'Tingloy', + 'Shelby', + 'Sijua', + 'Moss Point', + 'Satwas', + 'Parkal', + 'Sitapur', + 'Maniyamturuttu', + 'Nakhla', + 'Ispica', + 'Nayagaon', + 'Telimele', + 'Mono', + 'Kolga', + 'Mirjaveh', + 'Warrensburg', + 'Furstenfeld', + 'Oswiecim', + 'Pariyapuram', + 'Cuito Cuanavale', + 'Yoqne`am `Illit', + 'Komatsu', + 'Farmersville', + 'Mazar-e Sharif', + 'Tasil', + 'Surla', + '`Ain el Hadjel', + 'Potukonda', + 'Alcochete', + 'McKinleyville', + 'Lage', + 'Dugda', + 'Candijay', + "Intich'o", + 'Mercier', + 'Kamnik', + 'Tleta Taghramt', + 'Dungannon', + 'Montespertoli', + 'Port Jervis', + 'Matiguas', + 'Tago', + 'Oulad Daoud', + 'Haymana', + 'Tshela', + 'Aso', + 'South Hadley', + 'Doranahalli', + 'La Palma', + 'Laitila', + 'Stara Zagora', + 'Az Zuwaydah', + 'Storozhynets', + 'Muppalla', + 'Urganch', + 'Bialystok', + 'Libertad', + 'Bilara', + 'Zemio', + 'Mbabane', + 'Tsukawaki', + 'Middle Valley', + 'Sierra Vista Southeast', + 'Balboa', + 'Ostrowiec Swietokrzyski', + 'Lucapa', + 'Chiang Rai', + 'Khujand', + 'Great Baddow', + 'Buthidaung', + 'El Chol', + 'Albuera', + 'Carmignano', + 'Sommacampagna', + 'Kutum', + 'Cambambe', + 'Cunha Pora', + 'Longtaixiang', + 'Lawas', + 'Meru', + 'Ibiuna', + 'Aalen', + 'Kuah', + 'Bertem', + 'Consuegra', + 'Gamarra', + 'Chunakhali', + 'Tavsanli', + 'Kolwezi', + 'Dori', + 'Melsele', + 'Murugampalaiyam', + 'Yirga `Alem', + 'Vrnjacka Banja', + 'Bentota', + 'Bron', + 'Uncia', + 'Woerden', + 'San Juan Nonualco', + 'National City', + 'Rajshahi', + 'Mount Kisco', + 'Ludza', + 'Pattambi', + 'Korsimoro', + 'Keota', + 'Marion', + 'Kyaukmyaung', + 'Algun', + 'Montrose', + 'Loganville', + 'Gracemere', + 'Anthony', + 'Lapeer', + 'Blachownia', + 'Dimiao', + 'Dellys', + 'Haramachida', + 'Porto da Folha', + 'Ullo', + 'Iju', + 'Caister-on-Sea', + 'Dunaharaszti', + 'Ghanpur', + 'Ban Mae Ka Hua Thung', + 'Ajodhya', + 'La Garriga', + 'Canela', + 'Mallappadi', + 'Rossington', + 'Suriapet', + 'La Caleta', + 'Kumbhraj', + 'Chengdu', + 'Tarnowskie Gory', + 'Wattwil', + 'Hoogeveen', + 'Jagalur', + 'Veymandoo', + 'Sahatona-Tamboharivo', + 'Jaqueira', + 'Marshfield', + 'Jonkoping', + 'Navabad', + 'Phuc Yen', + 'Pinyahan', + 'Palanga', + 'Fujiyoshida', + 'Quela', + 'Ulcinj', + 'Sao Raimundo das Mangabeiras', + "L'Oulja", + 'Polas', + 'Vattalkundu', + 'Lod', + 'Suwanee', + 'Asse', + 'Bensekrane', + 'Siki', + 'Neira', + 'Middelharnis', + 'Managua', + 'Ambatomborona', + 'Touama', + 'Karatepe', + 'Willoughby Hills', + 'Boonton', + 'Mwatate', + 'Sanchahe', + 'Bommayapalaiyam', + 'Fort Bliss', + 'Bronx', + 'Nahuizalco', + 'Ankazomiriotra', + 'East Kelowna', + 'Bacioi', + 'Hassloch', + 'Hengyang', + 'Xishan', + 'Tamuin', + 'Vestmanna', + 'Bochnia', + 'Znin', + 'Purattur', + 'Arapongas', + 'Montividiu', + 'Yagoua', + 'Chinna Annaluru', + 'Chiatura', + 'Dhalai', + 'El Tablon', + 'Jyllinge', + 'Ives Estates', + 'Arenoso', + 'Eppstein', + 'Stony Plain', + 'Charleroi', + 'Bressanone', + 'Graulhet', + 'Diksmuide', + 'Heverlee', + 'Nova Russas', + 'Sholaqqorghan', + 'Viskovo', + 'Palenque', + 'Gatada', + 'Kannanendal', + 'Wadenswil', + 'Nepomuceno', + 'Dalli Rajhara', + 'Okpo', + 'Canelinha', + 'Saharbani', + 'Somma Lombardo', + 'Bojnik', + 'Beylikduzu', + 'Ban Pha Bong', + 'Trumbull Center', + 'Mandera', + 'Rio do Pires', + 'Bagnolo in Piano', + 'Tianchang', + 'Lillerod', + 'Ipswich', + 'Olaippatti', + 'Duzici', + 'Pernamitta', + 'Villa de Zaachila', + 'Wakefield', + 'Mandello del Lario', + 'Heywood', + 'Adigrat', + 'Juchitepec', + 'Nizhyn', + 'San Miguel de Allende', + 'Salemata', + 'Fenton', + 'As Sulaymaniyah', + 'Olhanpur', + 'Moon', + 'Churchdown', + 'Berezne', + 'Avenal', + 'Mettuppalaiyam', + 'Fort St. John', + 'Ghogardiha', + 'Palia Kalan', + 'Dardenne Prairie', + 'Bezerros', + 'Iowa City', + 'Mondragone', + 'Baojishi', + 'Cuiaba', + 'Kopparam', + 'Gokavaram', + 'Karamursel', + 'Bariariya Tola Rajpur', + 'Guambog', + "Mirassol d'Oeste", + 'Manbengtang', + 'Cali', + 'Yaguachi Nuevo', + 'Tiruppalai', + 'Kodivalasa', + 'Elakadu', + 'Hadyach', + 'Kumaralingam', + 'La Victoria de Acentejo', + 'Busembatia', + 'Arbroath', + 'Mahanoro', + 'El Qaa', + 'Ixhuatlan de Madero', + 'Golub-Dobrzyn', + 'Bundala', + 'Lurate Caccivio', + 'Palapag', + 'Biba', + 'Gokceada', + 'Kuzhittura', + 'Assomada', + 'Imias', + 'Obihiro', + 'Makinohara', + 'Chettiyapatti', + 'Wuyuan', + 'Tukrah', + 'Gorinchem', + 'Chettikulam', + 'Knic', + 'Piratini', + 'Takamatsu', + 'Ichhapur', + 'Ross', + 'Jamay', + 'Coyotepec', + 'Hani', + 'Malilipot', + 'Kozloduy', + 'Antwerp', + 'Vikramasingapuram', + 'Houma', + 'Osawa', + 'Colonia del Sacramento', + 'Daulatnagar', + 'Nannamukku', + 'Wanderley', + 'Dunhuang', + 'Campolongo Maggiore', + 'Chiche', + 'Merksem', + 'Kaboila', + 'Boscoreale', + 'Lordegan', + 'Douar Laouamra', + 'General Juan Madariaga', + 'Kottangara', + 'Badurpalle', + 'Rumoi', + 'Blackstone', + 'Marinha Grande', + 'Honmachi', + 'Oostkamp', + 'Xingangli', + 'Castelfidardo', + 'Zemun', + 'Krishnagiri', + 'Haomen', + 'Korbach', + 'Governador Celso Ramos', + 'East Pennsboro', + 'Aurich', + 'Ajjipuram', + 'Bury', + 'Nazare da Mata', + 'Humberto de Campos', + 'Pechea', + 'Cinarcik', + 'Jidd Hafs', + 'Noyon', + 'Zhaodianzi', + 'Sacavem', + 'Bilopillya', + 'Pader', + 'Miami Lakes', + 'Aplao', + 'Camabatela', + 'Domingos Martins', + 'Mahon', + 'Takaba', + 'Konskie', + 'Basso', + 'San Juan Despi', + 'Picui', + 'Ranipet', + 'Shidongcun', + 'Santa Rita do Sapucai', + 'Park Hills', + 'Caldera', + 'Gorenja Vas', + 'Holborn', + 'Welland', + 'Radzyn Podlaski', + 'Riacho dos Machados', + 'Dukli', + 'Motherwell', + 'Santa Rosa de Viterbo', + 'El Ateuf', + 'Villa Corona', + 'Rajaram', + 'Payipira', + 'Molnlycke', + 'Kautalam', + 'Duisburg', + 'Ghoradongri', + 'Sangasso', + 'Tacna', + 'Santa Helena', + 'Casablanca', + 'Kalliyassheri', + 'Xiancun', + 'Dabaga', + 'Chilcuautla', + 'Sihora', + 'New Britain', + 'Kingsburg', + 'Saint-Raymond', + 'Garoua', + 'Tulunan', + 'Torello', + 'Punal', + 'Beutelsbach', + 'Telenesti', + 'Abim', + 'Ed Damour', + 'Tecamachalco', + 'Nootdorp', + 'Centar Zupa', + 'Bernay', + 'Jatauba', + 'Azrou', + 'Hashtrud', + 'Chimichagua', + 'Radzymin', + 'Feldkirchen', + 'Houzhuang', + 'Brant', + 'Saint-Mande', + 'Marauatpur', + 'Radovljica', + 'La Entrada', + 'Kidbrooke', + 'Dnestrovsc', + 'Melksham', + 'Keshan', + 'Stockholm', + 'Fort Mitchell', + 'Haigerloch', + 'Altinozu', + 'Odzaci', + 'La Dorada', + 'Al Buraymi', + 'New Glasgow', + 'Vermilion', + 'Chongshan', + 'Kavarna', + 'Ban San Phak Wan Luang', + 'Puliyampatti', + 'Benslimane', + 'Chaumont-Gistoux', + 'Mae Hong Son', + 'Agia Paraskevi', + 'Kattipparutti', + 'Afyonkarahisar', + 'Berdychiv', + 'Alewah', + 'Pachrukhi', + 'Warzat', + 'Tilaran', + "Gonfreville-l'Orcher", + 'Muradiye', + 'Qara', + 'Defiance', + 'Bagu Na Mohra', + 'Milenaka', + 'Ruda Slaska', + 'Suchiapa', + 'Pachuca', + 'Kingston upon Thames', + 'Kumiyama', + 'Enriquillo', + 'El Astillero', + 'Bergheim', + 'Pindoretama', + 'Ardabil', + 'Lucban', + 'Cacuso', + 'Merimandroso', + "Sa'in Qal`eh", + 'Paula Candido', + 'Varapatti', + 'Erikolam', + 'Clarksdale', + 'Carhue', + 'Lisle', + 'Teotitlan', + 'Chebrolu', + 'Gabrovo', + 'Shambhunath', + 'Creve Coeur', + 'Silver City', + 'Matehuala', + 'Tefe', + 'Nohfelden', + 'Yangambi', + 'Camotan', + 'Las Mercedes', + 'Chevella', + 'Sokolov', + 'Porto Calvo', + 'Kayattar', + 'Batu Gajah', + 'Uppada', + 'Majalgaon', + 'Saint-Ghislain', + 'Staveley', + 'Jaffar Khanpet', + 'Chaiyaphum', + 'Parole', + 'Arara', + 'Hatfield', + 'Santiago Chimaltenango', + 'Cao Bang', + 'Gungoren', + 'Marechal Floriano', + 'Rio Cuarto', + 'Tazishan', + 'Binjai', + 'Pattensen', + 'Zhangjiazhuang', + 'Maasin', + 'Kizhakkanela', + 'Taquarituba', + 'Quedlinburg', + 'Schorndorf', + 'Melilli', + 'Griffin', + 'Ambondromisotra', + 'Cetinje', + 'Channelview', + 'Hanur', + 'Kenmore', + 'Jayrud', + 'Dollis Hill', + 'Taldyqorghan', + 'Al `Uqaylah', + 'Mutia', + 'Dabra', + 'Mae Ai', + 'Bamban', + 'Mayantoc', + 'Mechta Ouled Oulha', + 'Sacapulas', + 'Jaynagar-Majilpur', + 'Gladeview', + 'Dayong', + 'Monte Rico', + 'Nova Soure', + 'San Juan Zitlaltepec', + 'Almel', + 'Sakhua', + 'Kromeriz', + 'Vitorino', + 'Hayward', + 'Kahnuj', + 'Manalurpettai', + 'Altstatten', + 'Sydney Mines', + 'Liuhu', + 'Kasaali', + 'Acton', + 'Jiquipilas', + 'Talegaon Dhamdhere', + 'San Antonio Enchisi', + 'Kaduna', + 'Saint-Ave', + 'Renapur', + 'Carlos Spegazzini', + 'Riverdale', + 'Sint-Gillis-bij-Dendermonde', + 'El Aioun', + 'Ichinoseki', + 'Phulera', + 'Donwari', + 'Motipur', + 'Capitao de Campos', + 'York', + 'Rancho Mirage', + 'Ekma', + 'Thogapalle', + 'Glodeni', + 'Sedan', + 'Cupar', + 'Charbagh', + 'Sahasmal', + "Bazal'tove", + 'Gorkha', + 'Banha', + 'Oguzeli', + 'Nauagarhi', + 'Zaio', + 'Pirojgarh', + 'Aoshang', + 'Faruka', + 'Boaco', + 'Rani Sagar', + 'Cao Lanh', + 'Boyabat', + 'Waslala', + 'Vohitsaoka', + 'Dayton', + 'Borujen', + 'Konya', + 'Naj` Hammadi', + 'Yatomi', + 'Laferriere', + 'Deo', + 'Pampan', + 'Citluk', + 'Naranjito', + 'Haiwei', + 'Bohinjska Bistrica', + 'Kochkor-Ata', + 'Chiromo', + 'Kimitsu', + 'Linkoping', + 'Valinhos', + 'Lozova', + 'Siyazan', + 'Blenheim', + "Olho d'Agua das Cunhas", + 'Oak Park', + 'La Macarena', + 'Tasso Fragoso', + 'Ladkhed', + 'Taber', + 'Atchoupa', + 'Quilpue', + 'Cimislia', + 'Cimerak', + 'Ar Ruhaybah', + 'Xo`jaobod', + 'Antombana', + 'Tarnos', + 'Blackhawk', + 'Hengchun', + 'Petrzalka', + 'Ambatolampy', + 'Renai', + 'Massalubrense', + 'Marcianise', + 'Mako', + 'Palmito', + 'Marshalltown', + 'Taywarah', + 'Guiyang', + 'Patan', + 'Montichiari', + 'Vittoriosa', + 'Chili', + 'Manasa', + 'Khushab', + 'Kujukuri', + 'Zhongzhai', + 'Cagua', + 'Kryzhopil', + 'Phuntsholing', + 'Chhapra Bahas', + 'Banak', + 'Wolfhagen', + 'Biyala', + "An'gang", + 'Ban Phe', + 'Andoain', + 'Martina Franca', + 'Tadikonda', + 'Itapetininga', + 'Wusong', + 'La Carolina', + 'Greeneville', + 'Datori', + 'Gulbahor', + 'Weifen', + 'Farnley', + 'Piedade', + 'Reiskirchen', + 'Mahitsy', + 'Akouda', + 'Hucclecote', + 'Antsenavolo', + 'Jever', + 'Sablayan', + 'Holzkirchen', + 'Ramara', + 'Phulhara', + 'Ridgeland', + 'Youdiou', + 'Talwandi Chaudhrian', + 'Tadian', + 'Messias', + 'Suar', + 'Minamisuita', + 'Nipani', + 'Jugial', + 'Bisignano', + 'Opole', + 'El Jicaro', + 'Joensuu', + 'Todupulai', + 'Kamituga', + 'Guararapes', + 'Pike Creek Valley', + 'Taimali', + 'Chiconquiaco', + 'Brookfield', + 'Belo Jardim', + 'Uttaramerur', + 'Tokorozawa', + 'Mulungu', + 'Spencer', + 'Sopron', + 'Catape', + 'Mata-Utu', + 'Cha Grande', + 'Jandira', + 'Kunnatnad', + 'Kolhapur', + 'Manchenahalli', + "Cox's Bazar", + 'Mahisi', + 'Nunspeet', + 'Xocali', + 'Hitachiomiya', + 'Beaucaire', + 'Soissons', + 'Hjorring', + 'Antonio Cardoso', + 'Blaydon', + 'Jhalida', + 'Minden', + 'Wardenburg', + 'Ain Bessem', + 'Milwaukee', + 'Coventry', + 'Puli', + 'Arqalyq', + 'Januaria', + 'Los Almacigos', + 'Moreton', + 'Jennings', + 'Laguna Woods', + 'Biloela', + 'Chaddesden', + 'Monmouth Junction', + 'Sullivan', + 'Wassenberg', + 'Canyon', + 'Mograne', + 'Mardakan', + 'Areia', + 'El Tigre', + 'Wallingford', + 'Goshogawara', + 'Chandannagar', + 'San Marcos', + 'Pedra Branca', + 'Dhani Sukhan', + 'Barni', + 'Maumere', + 'Strathroy', + 'Guozhen', + 'Hlobyne', + 'Blace', + 'Pentecoste', + 'Malden', + 'Bad Orb', + 'Hallstahammar', + 'Delano', + 'Bhadsara', + 'Thal', + 'Dongnanyanfa', + 'Rio Bueno', + 'Vendas Novas', + 'Chhara', + 'Katarmala', + 'Ngorongoro', + 'Takaoka', + 'Kalianpur', + 'Smarje', + 'Mangaldan', + 'Aulla', + 'Koila Belwa', + 'Shenjiaba', + 'Emmen', + 'Ambalavayal', + 'Crepy-en-Valois', + 'Ladwa', + 'Oxchuc', + 'Khagaur', + 'Esmeraldas', + 'Cirkulane', + 'Kaviti', + 'Wadsworth', + 'Lukovit', + 'Venafro', + 'Saint-Gely-du-Fesc', + 'Paramount', + 'Dounan', + 'Fort Beaufort', + 'Ban Mueang Na Tai', + 'Hornsey', + 'Collingwood', + 'Lower Allen', + 'Rangwasa', + 'Merelbeke', + 'Jamao al Norte', + 'Caudebec-les-Elbeuf', + 'Cakovec', + 'Matatiele', + 'Carmen de Patagones', + 'Kivsharivka', + 'Faberg', + 'Doornkop', + 'Istres', + 'Ancud', + 'Ovidiu', + 'Strangnas', + 'Shazhou', + 'Gazantarak', + 'Daule', + 'El Ksar', + 'Riobamba', + 'Paxtakor Shahri', + 'Vontimitta', + 'Pyinmana', + 'Sunset Hills', + 'Raha Tiga', + 'Ujfeherto', + 'Kiskoros', + 'Benin City', + 'Delhi Cantonment', + 'Khowy', + 'Ban Rangsit', + 'Cabudare', + 'Valderrama', + 'Turki', + 'Lohne', + 'Pangkalpinang', + 'Bazhajiemicun', + 'Ribat Al Khayr', + 'Estaimpuis', + 'Mineros', + 'Huanghuajie', + 'Espungabera', + 'Bawku', + 'San Bartolo', + 'Raghogarh', + 'Jaito', + 'Kakiri', + 'Radzionkow Nowy', + 'Chilakalurupet', + 'Maragheh', + 'Alacati', + 'Gata', + 'Rohera', + 'Lakshmicharipara', + 'Myronivka', + 'Matriz de Camarajibe', + 'Tamarana', + "Ambinanin' Andravory", + 'Kirakira', + 'Bramhall', + 'Roccastrada', + 'Hasbergen', + 'Ranjal', + 'Sawakin', + 'Romblon', + 'Jogaili', + 'Kaga', + 'Jinka', + 'Limerick', + 'Chatou', + 'Narman', + 'Ponce', + 'Baskale', + 'Wilnsdorf', + "Saint-Michel de l'Atalaye", + 'Pakala', + 'Sao Sebastiao da Boa Vista', + 'Spring Hill', + 'Orkney', + 'Mahasolo', + 'Ranranagudipeta', + 'Oeiras', + 'Carlos Manuel de Cespedes', + 'Stavroupoli', + 'Conner', + 'Koscielisko', + 'San Donato Milanese', + 'Udon Thani', + 'Jinchang', + 'Dzuunharaa', + 'Castries', + 'Inegol', + 'Novohrodivka', + 'Chyhyryn', + 'Cabarete', + 'Ganderkesee', + 'Bielawa', + 'Roura', + 'Namegawa', + 'Shabqadar', + 'Geylegphug', + 'Guapi', + 'Urgnano', + 'Zhitiqara', + 'Wildberg', + 'Begusarai', + 'Heihe', + 'Zegzel', + 'Or Yehuda', + 'Uenohara', + 'Atherstone', + 'Taku', + 'Central Islip', + 'Dessel', + 'Coal', + 'Gaoua', + 'Narwana', + 'Shuzenji', + 'West Lafayette', + 'Hazar', + 'Kongsvinger', + 'Povoa de Varzim', + 'Zunheboto', + 'Kalmiuske', + 'Zacatepec', + 'Rethen', + 'Benjamin Aceval', + 'Bandeirantes', + 'Yakou', + 'Passi', + 'Malanje', + 'Catacaos', + 'Capelinha', + 'Qingyang', + 'Alcira', + 'La Gloria', + 'Tucson', + 'Ban Chorakhe Samphan', + 'Cienaga', + 'Pebane', + 'Lontra', + 'Haikoucun', + 'Dhanwar', + 'Weifang', + 'Thari Mir Wah', + 'Nakano', + 'Coron', + 'Maysville', + 'Jeonghae', + 'Trgoviste', + 'Salaga', + "Nan'ao", + 'Locri', + 'Kenndie', + 'Florsheim', + 'Ouroeste', + 'Pizzo', + 'Woodmere', + 'Muzaffarpur', + 'Vicencia', + 'Yuriria', + 'Andoas', + 'Chauhanpatti', + 'Debila', + 'Xiwanzi', + 'Mexico City', + 'Perico', + 'Nova Ipixuna', + 'Pullambadi', + 'Osaka', + 'Kazo', + 'Garut', + 'Tonya', + 'Zawiyat Razin', + 'Hajdunanas', + 'Mollerusa', + 'Nuevo San Carlos', + 'Inverurie', + 'Cuddapah', + 'Leiria', + 'Magdeburg', + 'As Saqlawiyah', + 'Sidhi', + 'Jitaicun', + 'Alegrete', + 'Arwal', + 'Pueblo Nuevo', + 'Kovilur', + 'Valaiyampattu', + 'Kollengode', + 'Southside', + 'Tiruvattar', + 'Ahlat', + 'Chandpura', + 'Singleton', + 'Valu lui Traian', + "K'olito", + 'Norzagaray', + 'Onet Village', + 'Maserada sul Piave', + 'Gaziantep', + 'Borgentreich', + 'Cheb', + 'Santa Maria Atzompa', + 'Caspe', + 'Dalton', + 'Ilebo', + 'Harpenden', + 'Tiznit', + 'Fredrikstad', + 'Itaparica', + 'Zabol', + 'Majali', + 'Ubombo', + 'Karaiyampudur', + 'Port Orange', + 'Atitalaquia', + 'Caldas', + 'Shyroke', + 'Songadh', + 'Hai Duong', + 'Ayamonte', + 'Heroica Guaymas', + 'Haraiya', + 'Iarintsena', + 'Cardoso', + 'Yoloten', + 'Tandrano', + 'Tingo Maria', + 'Quimbaya', + 'Jambalo', + 'Samaxi', + 'Leme', + 'Bougtob', + 'Sudipen', + 'Artigas', + 'Ambatotsipihina', + 'Charlottesville', + 'Creutzwald', + 'Mangbwalu', + 'Tempe', + 'Agareb', + 'Jurh', + 'Tambo', + 'Kungalv', + 'Navipet', + 'Ramapattanam', + 'Baykan', + 'Suhr', + 'Taurisano', + 'Maydolong', + 'Oulad Said', + 'Pianiga', + 'Jefferson Hills', + 'Paine', + 'Pocheon', + 'Vennesla', + 'Porto Recanati', + 'Mirpeta', + 'Sendenhorst', + 'Carmichael', + 'Borgo San Lorenzo', + 'Torton', + 'Banta', + 'Urbino', + 'Kedzierzyn-Kozle', + 'Yamunanagar', + 'Ishidoriyacho-eso', + 'Tatui', + 'Amarapura', + 'Kodoli', + 'Monterey', + 'Basavakalyan', + 'Beroun', + 'East Donegal', + 'To`raqo`rg`on', + 'Usharal', + 'Harihar', + 'Amborompotsy', + 'Barkly East', + 'Charaut', + 'Kuttattuppatti', + 'Aracagi', + 'Hongzhai', + 'Sonqor', + 'Krishnamsettipalle', + 'Balugan', + 'Agua Preta', + 'Laayoune', + 'Huyuk', + 'Muban Saeng Bua Thong', + 'Saviano', + 'Florina', + 'Naestved', + 'Itabaianinha', + 'Tekkumbagam', + 'Valls', + 'Ozuluama de Mascarenas', + 'Wanluan', + 'Toledo', + 'Beawar', + 'Parimpudi', + 'Ramachandrapuram', + 'Jalpatagua', + 'Chur', + 'Jajireddigudem', + 'Salgotarjan', + 'Thames Ditton', + 'Sao Joao dos Poleiros', + 'Japoata', + 'Backi Petrovac', + 'Mugalivakkam', + 'Bachra', + 'Ahmadpur East', + 'Ganserndorf', + 'Attibele', + 'Briniamaro', + 'Bazidpur', + 'Santos', + 'Aimores', + 'Ciudad de la Costa', + 'Westchester', + 'Brand', + 'Codlea', + 'Nallagunta', + 'Rovinari', + 'Schiedam', + 'Highland Village', + 'Heikendorf', + 'Ifs', + 'Chuzhou', + '`Anadan', + 'Acornhoek', + 'Fonds Verrettes', + 'Hikone', + 'Cartagena del Chaira', + 'Moyogalpa', + 'Cariari', + 'Moe', + 'Sahatavy', + 'Otley', + 'Nambiyur', + 'Camden', + 'Pomona', + 'Vizianagaram', + 'Lalo', + 'Kafr Zayta', + 'Bruchsal', + 'Gua', + 'Ali Sabieh', + 'Ouargaye', + 'Huzurnagar', + 'Pharaha', + 'Yayas de Viajama', + 'Novi di Modena', + 'Chofugaoka', + 'Besagarahalli', + 'Paulinia', + 'Davis', + 'Landhaura', + 'Apace', + 'San Felice sul Panaro', + 'Silvino Lobos', + 'Liugoucun', + 'Cislago', + 'Bububu', + 'Gohpur', + 'Bhatkal', + 'Kotal', + 'Lons-le-Saunier', + 'Sisai', + 'Palangarai', + 'Harohalli', + 'Tawargeri', + 'Newton', + 'Muscoy', + 'Kade', + 'Sene', + 'Morarano Chrome', + 'Nibria', + 'Rosbach vor der Hohe', + 'Hilpoltstein', + 'Seddouk Oufella', + 'Arbaa Sahel', + 'Olivar', + 'Sevan', + 'Forquilhinha', + 'Lake Mary', + 'Paramus', + 'Naula', + 'Hukeri', + 'Pinheiro', + 'Sivaganga', + 'Baleraja', + 'Koiri Bigha', + 'Le Pecq', + 'Koflach', + 'Namerikawa', + 'Yavuzeli', + 'Songzi', + 'Etawa', + 'Shek Tong Tsui', + 'Long Binh', + 'Tecuala', + 'Vyronas', + 'Potirendaba', + 'Luino', + 'Hazle', + 'Capitan Mauricio Jose Troche', + 'Attiecoube', + 'Queensburgh', + 'Santiponce', + 'Fleurus', + 'Ammapettai', + 'Montoro', + 'Al Waqf', + 'Bam', + 'Dehaqan', + 'Santa Lucia Cotzumalguapa', + 'Tucuran', + 'Ambesisika', + 'Bholsar', + 'Chino', + 'Androndrona Anava', + 'Country Club Estates', + 'Laren', + 'Asolo', + 'Aarsal', + 'Sumner', + 'Majalengka', + 'Barwaaqo', + 'Bon Air', + 'Ampitatafika', + 'Lizzano', + 'Majdel Aanjar', + 'Dahmani', + 'Bhauradah', + 'Qaha', + 'Khorramabad', + 'Shyamnagar', + 'Suisun City', + 'Frecheirinha', + 'Shaw', + 'Chepen', + 'Nulvi', + 'Sao Miguel Arcanjo', + 'Kollipara', + 'Pajara', + 'Kortemark', + 'Cha da Alegria', + 'Crawford', + 'Mistelbach', + 'Erkner', + 'Ituni', + 'Odate', + 'Nanded', + 'Piekary Slaskie', + 'Ainapur', + 'Kapaa', + 'Nemuro', + 'Huolu', + 'Banane', + 'Tredegar', + 'Farkhor', + 'Tlayacapan', + 'Szprotawa', + 'Panchari Bazar', + 'Hirson', + 'Kukrahill', + 'Zierikzee', + 'Talata-Volonondry', + 'Medjana', + 'Santa Maria La Pila', + 'Puligunta', + 'Kataha', + 'Weston', + 'Kaikalur', + 'Glen Ellyn', + 'East St. Paul', + 'Crown Point', + 'Lagoa', + 'Aybasti', + 'Awaji', + 'Sulibele', + 'Puerto Caicedo', + 'La Jigua', + 'Vera', + 'Friedrichsthal', + 'El Dorado', + 'Bezaha', + 'Pagegiai', + 'Ban Sai Ma Tai', + 'Trzic', + 'Sao Benedito do Rio Preto', + 'Arcata', + 'Brunete', + 'Cousse', + 'Itapeva', + 'Shaoshanzhan', + 'Ban Samo Khae', + 'Fort Smith', + 'Giannitsa', + 'Gerona', + 'Degeh Bur', + 'Awsim', + 'Blitar', + 'Basavilbaso', + 'Palsud', + 'Sibolga', + "Dias d'Avila", + 'Cascades', + 'Tecpan Guatemala', + 'Overland', + 'Levin', + 'Tolongoina', + 'Asten', + 'Tamandare', + 'Kannamangalam Tekku', + 'Kandakkadava', + 'Kilattingal', + 'Bayyanagudem', + 'Bremervorde', + 'Wyandanch', + 'Lutz', + 'Kasanda', + 'Tungavi', + 'Palompon', + 'Vinjamur', + 'Ponteland', + 'Kamienna Gora', + 'Springboro', + 'Morsbach', + 'Depalpur', + 'Londuimbali', + 'Garbagnate Milanese', + 'Yenimahalle', + 'Masagua', + 'Machiques', + 'Carbonia', + 'Pisco', + 'Lynwood', + 'Choisy-le-Roi', + 'Ambalaromba', + 'Seremban', + 'Kolar', + 'Telkapalli', + 'Deoria', + 'Glenn Dale', + 'Notre-Dame-des-Prairies', + 'Majayjay', + 'Luzhang', + 'Steha', + 'Sao Jose do Cerrito', + 'Zwettl', + 'Lubango', + 'Moka', + 'Sarpang', + 'Farrukhnagar', + 'Acanceh', + 'Dumas', + 'Minignan', + 'Mazatan', + 'Kadod', + 'Capotille', + 'Wertheim', + 'Gloucester Point', + 'Jibou', + 'Champasak', + 'Katuete', + 'Atuntaqui', + 'Tupi', + 'Nort-sur-Erdre', + 'Leland', + 'Zongolica', + 'North St. Paul', + 'O`nhayot', + 'South River', + 'Euclid', + 'Miedzyrzec Podlaski', + 'Madinat ash Shamal', + 'Sandanski', + 'Parora', + 'Gela', + 'Beronono', + 'Nederland', + 'Lower Macungie', + 'Acambay', + 'Cape Girardeau', + 'Mafamude', + 'Siddhirganj', + 'Gonbad-e Kavus', + 'San Antonio del Sur', + 'Tsuruga', + 'Sarcelles', + 'Pont-Saint-Esprit', + 'Serekali', + 'Iguape', + 'Rosemount', + 'Hulikal', + 'Nordenham', + 'Bannur', + 'Westerkappeln', + 'Bozdogan', + 'Eslohe', + 'Koth', + 'Kattari', + 'Caltanissetta', + 'Francavilla al Mare', + 'Iguai', + 'Moundou', + 'Kozakai-cho', + 'Alfaro', + 'Sonsoro', + 'Ljubljana', + 'Charakunda', + 'Ambohidranandriana', + 'Caraguatay', + 'Moriyama', + 'Voula', + 'Penamiller', + 'Ponnai', + 'Juan Aldama', + 'Douglasville', + 'Pati', + 'Pfarrkirchen', + 'Roman', + 'Busan', + 'Kottapeta', + 'Nagua', + 'Salaya', + 'Ciudad Dario', + 'Buttelborn', + 'Victorville', + 'San Pelayo', + 'Mauren', + 'Khairtal', + 'Bombarral', + 'Cadillac', + 'Isiro', + 'Cabral', + 'Pelagor', + 'Guastalla', + 'Cruces', + 'Gungu', + 'Milford', + 'Tigrana', + 'Mundargi', + 'Daram', + 'Saudharkrokur', + 'Canonsburg', + 'Aourir', + 'Blue Springs', + 'Commerce', + 'Saint-Die-des-Vosges', + 'Lubuklinggau', + 'Lake City', + 'Bougouni', + 'Netrakona', + 'Meral', + 'Csongrad', + 'Tokai', + 'Linthicum', + 'Taylorville', + 'Sano', + 'Araguatins', + 'Prokuplje', + 'Dhansaria', + 'Turtkul', + 'Gyzylgaya', + 'Bollullos de la Mitacion', + 'Mallagunta', + 'San Luis Potosi', + 'Ukmerge', + 'Chenove', + 'Huejuquilla el Alto', + 'South Gate', + 'Llantwit Major', + 'Thomazeau', + 'Memunda', + 'Stockton-on-Tees', + 'Himatnagar', + 'Rapid City', + 'Banes', + 'Rheydt', + 'Bobenheim-Roxheim', + 'Lauterach', + 'Kalmthout', + 'Yashan', + 'Baures', + 'Malanday', + 'Treviso', + 'Ico', + 'Vladimirci', + 'Dora', + 'Abergavenny', + 'Parow', + 'Tineghir', + 'Iapu', + 'Cremona', + 'Tazmalt', + 'Vernon Hills', + 'Palermo', + 'Ambiula', + 'Alaverdi', + 'Altamira', + 'Sao Goncalo', + 'Sakado', + 'Pingree Grove', + 'Newton Abbot', + 'Puerto Morelos', + 'Buesaco', + 'Santa Maria da Vitoria', + 'Zeuthen', + 'Hebri', + 'Petua', + 'Sertania', + 'Yangiyer', + 'Mahmudabad', + 'Dinhata', + 'Bariarpur', + 'Mondeville', + 'Efatsy-Anandroza', + 'Rasht', + 'Port Royal', + 'Marotta', + 'Suffern', + 'Pearland', + 'Paduvari', + 'Ain Lechiakh', + 'Ruvo di Puglia', + 'Wittingen', + 'Rochester Hills', + 'Matsavaram', + 'Dunakeszi', + 'Sowa Town', + 'Barneveld', + 'Koycegiz', + 'Shanhu', + 'Zaidpur', + 'Valenca', + 'Rockaway', + 'Champua', + 'Caojiachuan', + 'Saraykent', + 'Gheorgheni', + 'Shonai', + 'Itaueira', + 'Itanagar', + 'Bandar-e Mahshahr', + 'Yelandur', + 'Kempston', + 'Elazig', + 'San Juan Guichicovi', + 'Tom Price', + 'Zhegaozhen', + 'Igdir', + 'Chang', + 'Nyakrom', + 'Shamunpet', + 'Sultanabad', + 'Tazarka', + 'Rathdrum', + 'Souahlia', + 'Konnur', + 'Rinconada', + 'Lamballe', + 'Planaltina', + 'Sunadkuppi', + 'Mangpa', + 'Marsciano', + 'Eonyang', + 'Bani Murr', + 'Baba Hassen', + 'Colatina', + 'Tianningcun', + 'Morazan', + 'Guantingzhan', + 'Misserghin', + 'Carolina Forest', + 'Tanakallu', + 'Castellarano', + 'Kumla', + 'Yorktown', + 'Santo Domingo Xenacoj', + 'Louga', + 'Fort Lee', + 'Tepperumalnallur', + 'Ampanety', + 'Ajuy', + 'Idstein', + 'Pristina', + 'Snina', + 'Habaswein', + 'Joghtay', + 'Shimizucho', + 'Kotancheri', + 'Woodstock', + 'Falagueira', + 'Moorestown-Lenola', + 'San Juan Sacatepequez', + 'Rio do Sul', + 'Mirador', + 'Moba', + 'Hassi Khelifa', + 'Nossa Senhora Aparecida', + 'Ibiza', + 'Watauga', + 'Zemrane', + 'Santa Cruz Xoxocotlan', + 'Pilis', + 'Evian-les-Bains', + 'Beraketa', + 'Goldsboro', + 'Isulan', + 'Sundern', + 'Aikaranad', + 'Yola', + 'Sunderland', + 'Ouled Ben Abd el Kader', + 'Pakaur', + 'Antsirabe Avaratra', + 'Schofield Barracks', + 'Serang', + 'Lede', + 'Guernica', + 'Mostoles', + 'Villa Mercedes', + 'Carcar', + 'Beruniy', + 'Mariano Acosta', + 'Toin', + 'Kashikishi', + 'Dar Chabanne', + 'uMhlanga Rocks', + 'Mieres', + 'Chipurupalle', + 'Aranda', + 'Nacogdoches', + 'Benner', + 'Ahvaz', + 'Sint-Michiels', + 'Yamamoto', + 'Retie', + 'Pitman', + 'Bagabag', + 'Phalaborwa', + 'Couva', + 'Kheri Naru', + 'Bazidpur Madhaul', + 'Carlton Colville', + 'Al Mafraq', + 'Amasra', + 'Peruibe', + 'Cuarte de Huerva', + 'Labrador', + 'Bawshar', + 'Pirojpur', + 'Taraza', + 'Teminabuan', + 'Shimotsuke', + 'Garca', + 'Rifadpur', + 'Los Chiles', + 'Bhandarso', + 'Capurso', + 'Caetes', + 'Pajapita', + 'Puerto Real', + 'East York', + 'Teteven', + 'Coruche', + 'Wurzen', + 'Pescaria Brava', + 'Guzhou', + 'Stanwell', + 'Khanabad', + 'Xisa', + 'Guruzala', + 'Basla', + 'Sabana Larga', + 'Dom Feliciano', + 'Kuytun', + "L'Asile", + 'Bokhtar', + 'Nazarabad', + 'Leticia', + 'Malang', + 'Ambohimiera', + 'Amjhar', + 'Veitshochheim', + 'Besana in Brianza', + 'Lynn', + 'Barura', + 'Statte', + 'Balchik', + 'Ottappidaram', + 'Afir', + 'Barleben', + 'Ashland', + 'Jirja', + 'Woodbridge', + 'Smithton', + 'Neustadt bei Coburg', + 'Hewitt', + 'Ulstein', + 'Nong Bua', + 'Piracanjuba', + 'New Mirpur', + 'Darabani', + 'Szolnok', + 'Nqutu', + 'Ii', + 'Glassmanor', + 'Eruvatti', + 'Magarao', + 'Sunam', + 'Sonaimukh', + 'Chapelle-lez-Herlaimont', + "Hai'an", + 'Blaricum', + 'El Marsa', + 'Jutiapa', + 'Cihanbeyli', + 'Bershad', + 'La Mujer', + 'Sompting', + 'Lomas de Sargentillo', + 'Guacui', + 'Mpwapwa', + 'Sanhe', + 'Kampong Trach', + 'Govindgarh', + 'Weilmunster', + 'Hopkinsville', + 'Xicotencatl', + 'Crikvenica', + 'Faizabad', + 'Dehgolan', + 'Changji', + 'Obita', + 'Ottobrunn', + 'Pak Phanang', + 'Dongta', + 'Guaduas', + 'Clarksville', + 'Luxembourg', + 'Kut-e `Abdollah', + 'Puerto Penasco', + 'Hebli', + 'Locust Grove', + 'Lappeenranta', + 'Kizilcasar', + 'Terenos', + 'Kamen', + 'Barra de Santo Antonio', + 'Saint-Servan-sur-Mer', + 'Pinto', + 'Beverly Hills', + 'Rouyn-Noranda', + 'Joaquim Pires', + 'Wichelen', + 'Saint-Medard-en-Jalles', + 'Dibrugarh', + 'Gouvieux', + 'Schriesheim', + 'Taliwang', + 'Prachuap Khiri Khan', + 'Salida', + 'Dahivel', + 'Tanjung Selor', + 'Tabio', + 'Ilampillai', + 'Acaxochitlan', + 'West Donegal', + 'Marosakoa', + 'Coahuitlan', + 'Valasske Mezirici', + 'Mengmeng', + 'Umm ar Rizam', + 'Altindag', + 'Lewisburg', + 'Kamifurano', + 'Pune', + 'Miami Springs', + 'San Sebastian de Yali', + 'Berwick', + 'Quixada', + 'Wujindian', + 'Jiadong', + 'Bagnan', + 'Puke', + 'Bogazliyan', + 'Sertaozinho', + 'Beiya', + 'Pragadavaram', + 'Alarobia Bemaha', + 'Lara', + 'Pyryatyn', + 'Kantabanji', + 'Kodusseri', + 'Meiningen', + 'Fontainebleau', + 'Panapur', + 'Ulaangom', + 'Bihpur', + 'Bodinayakkanur', + 'Lins', + 'Helsingor', + 'Maryanaj', + 'New Kru Town', + 'Mococa', + 'Bab Ezzouar', + 'Ghazaouet', + 'Pulaski', + 'Grenchen', + 'East Riverdale', + 'Bourne', + 'Maqat', + 'Oliveirinha', + 'Deltona', + 'Vermillion', + 'Chotala', + "Hong'an", + 'Houbu', + 'Furth', + 'Tchibanga', + 'Ishaka', + 'Namala Guimbala', + 'Aire-sur-la-Lys', + 'Shentang', + 'Guilford', + 'Oswaldtwistle', + 'Binga', + 'Birstall', + 'Kandukur', + 'Dunleary', + 'Majia', + 'Pamban', + 'Hata', + 'Mitchells Plain', + 'Blankenfelde', + 'Itoigawa', + 'Andriba', + 'Maghra', + 'Bugarama', + 'Urk', + 'Hoima', + 'Binbrook', + 'Manali', + 'Manevychi', + 'Gotzis', + 'Byala Slatina', + 'Capoocan', + 'Pepperell', + 'Ayr', + 'Saint-Jean-de-Braye', + 'Chungju', + 'Barobo', + 'Laamarna', + 'Mudichchur', + 'Madhyamgram', + 'Sefrou', + 'Haora', + 'Harlakhi', + 'Bremgarten', + 'Conselheiro Lafaiete', + 'Nanbei', + "'Ain Mabed", + 'Koper', + 'Knowle', + 'Cerkno', + 'Perai', + 'Baghin', + 'Colne', + 'Kingsborough', + 'Mahagaon', + 'Chengguan', + 'Santa Eugenia', + 'Yuli', + 'Bad Rothenfelde', + 'Chongqing', + 'Aviles', + 'Gokhulapur', + 'Lokapur', + 'Menfi', + 'Kisvarda', + 'Sungai Guntung', + 'Ambolidibe Atsinanana', + 'Oberkirch', + 'Poteau', + 'Raleigh', + 'Ulongue', + 'Floresta Azul', + 'Migori', + 'Veracruz', + 'Roubaix', + 'Mariscal Jose Felix Estigarribia', + 'Sint-Michielsgestel', + 'Gering', + 'Yanchep', + 'Hof', + 'Villa Regina', + 'South Amboy', + 'Songkhla', + 'Barwah', + 'Stourbridge', + 'Bodegraven', + 'New Windsor', + 'Kubadupuram', + 'Lagoa dos Gatos', + "Pa'in Chaf", + 'Allagadda', + 'Casilda', + 'Tarancon', + 'Adilabad', + 'Chakla Waini', + 'Sabadell', + 'Bekipay', + 'Tajimi', + 'Ciputat', + 'Oulad Khallouf', + 'Rajpur Kalan', + 'Ghadamis', + 'Nyaungdon', + 'Bimo', + 'Tena', + 'Kendall', + 'Muzaffarabad', + 'Tongren', + 'Vrbas', + 'Resende Costa', + 'Cherchell', + 'Namayingo', + 'Port Angeles', + 'Egersund', + 'Bhasaula Danapur', + 'Wavre', + 'Itariri', + 'Timbiras', + 'Mahajanga', + 'Lochearn', + 'Vestby', + 'Kulgo', + 'Mola di Bari', + 'Istrana', + 'Shangtianba', + 'Aue', + 'Kirchzarten', + 'Lanling', + 'Tuneri', + 'Montrouge', + 'Taslicay', + 'Choro', + 'Schio', + 'Diadema', + 'Rosstal', + 'Taohongpozhen', + 'Nueva Guadalupe', + 'Tacoma', + 'Mayyanad', + 'Botou', + 'Alfonso', + 'Bexley', + 'Marginea', + 'Alliance', + 'Davuluru', + 'Candiac', + "Hammam M'Bails", + 'Corridonia', + 'Jaguarari', + 'Bouati Mahmoud', + 'Topeka', + 'Honiton', + 'Sambalpur', + 'Kalamner', + 'Tulua', + 'Baesweiler', + 'Chatra Gobraura', + 'Swanage', + 'Santa Magdalena', + 'Marrero', + 'Mizunami', + 'Bickley', + 'Ocala', + 'Dera Baba Nanak', + 'Reda', + 'Bourem Guindou', + 'Tagum', + 'Talata Ampano', + 'Poninguinim', + 'Bad Urach', + 'Alterosa', + 'Lake Hiawatha', + 'Rhosllanerchrugog', + 'Zirapur', + 'Bakixanov', + 'Scottburgh', + 'Laubach', + 'Bhakkar', + 'Bath', + 'Iztapa', + 'Kaabong', + 'Haiku-Pauwela', + 'Walthamstow', + 'Leiyang', + 'Silverton', + 'Hangzhou', + 'Namminikara', + 'Badr', + 'Ban Bang Rin', + 'Skydra', + 'Changting', + 'Bou Nouh', + 'Villorba', + 'Conceicao de Macabu', + 'Haldipur', + 'Chavakkad', + 'Artondale', + 'Isla Raton', + 'Taiyur', + 'Mel Bhuvanagiri', + 'Faratsiho', + 'Piso Firme', + 'Venlo', + 'Nules', + 'San Gaspar Ixchil', + 'London Colney', + 'Hesarghatta', + 'Gingee', + 'Shahpura', + 'Bhikhi', + 'Kamenice', + 'Amesbury', + 'Hanoi', + 'Davenport', + 'Bishunpur Sundar', + 'Oggiono', + 'Beinan', + 'Tsimasham', + 'Campos', + 'Chaponost', + 'Amondara', + 'Pariyari', + 'Brusque', + 'Coquimbo', + 'Promissao', + 'Malakoff', + 'Yunxian Chengguanzhen', + 'Baiao', + 'Tremelo', + 'Sesto Calende', + 'Pozega', + 'Liepaja', + 'Lonigo', + 'San Leonardo', + 'Gonabad', + 'Valdemoro', + 'Sikandra', + 'Vise', + 'Rybnik', + 'Hit', + 'Rosu', + 'Fengrenxu', + 'Barrie', + 'Adjud', + 'Manta', + 'Susuz', + 'Junin', + 'Lauda-Konigshofen', + 'Rocas de Santo Domingo', + 'Barjhar', + 'Kurdamir', + 'Prabumulih', + 'Miyazaki', + 'Menzel Kamel', + 'Micco', + 'Sankt Leon-Rot', + 'De Pinte', + 'Chanaral', + 'Abiko', + 'Carlos A. Carrillo', + 'Sarande', + 'Matoes', + 'Cholchol', + 'Gangarampur', + 'Aihua', + 'Yanggao', + 'Portalegre', + 'Falea', + 'Qapqal', + 'Gotenba', + 'Villanueva del Pardillo', + 'Boysun', + 'Bundu', + 'Jucas', + 'Abony', + 'Bahabad', + 'Paradarami', + 'Eerbeek', + 'Nyon', + 'Shannon', + 'Barreirinhas', + 'Manompana', + 'Fron', + 'Nathdwara', + 'Ganassi', + 'Korntal-Munchingen', + 'Tessenderlo', + 'Prescott', + 'Chivilcoy', + 'Juina', + 'Primero de Enero', + 'Campiglia Marittima', + 'Bharra', + 'Naju', + 'Halls Creek', + 'Sakawa', + 'Ain Temouchent', + 'Caimanera', + 'Mahao', + 'Zacatelco', + 'Perumuchchi', + 'Aberdeen', + 'Dentsville', + 'Lincoln Village', + 'Bou Hadjar', + 'Retiro', + 'Ouaouzgane', + 'Balrampur', + 'Iznik', + 'Coatepeque', + 'Dicle', + 'Phon', + 'Sanpaicun', + 'Zgornja Kungota', + 'Coulommiers', + 'Woodhaven', + 'Pirai', + 'Marana', + 'Benjamin Constant', + 'Kimpese', + 'Kashiba', + 'Vandithavalam', + 'Ar Ruseris', + 'Fusui', + 'Aalten', + 'Hiranai', + 'Kigumba', + 'Ocosingo', + 'Kotabommali', + 'Sugar Hill', + 'Covington', + 'Mielec', + 'Totness', + 'Ribeirao Preto', + 'Caotun', + 'Salaiya', + 'Karapurcek', + 'Hornchurch', + 'Hlyboka', + 'Azizpur Chande', + 'Sokhodewara', + 'Brus', + 'Dilovasi', + 'Manjathala', + 'Sabbah', + 'Sadiola', + 'Lomita', + 'Aveiro', + 'Kittur', + 'Rajpur', + 'Betsukai', + 'Shintomi', + 'Tahara', + 'Vuliyattara', + 'El Kelaa des Srarhna', + 'Gammarth', + "Sal'a", + 'Kolin', + 'Ceel Dheere', + 'Magpet', + 'Tarhzirt', + 'Chateauguay', + 'Qovlar', + 'Gryfice', + 'Bokakhat', + 'Terrace', + 'Acajutiba', + 'Tefam', + 'Wolow', + 'Kuaidamao', + 'Punnayur', + 'Maliana', + 'Ouando', + 'Howell', + 'Kodarma', + 'Gines', + 'Douglas', + 'Werve', + 'Corfe Mullen', + 'Rayon', + 'Tequila', + 'Kozani', + 'Rafina', + 'Placido de Castro', + 'Mascara', + 'Pszczyna', + 'Hochheim am Main', + 'Babahoyo', + 'Tanglou', + 'Villagarcia de Arosa', + 'Sao Romao', + 'Izmir', + 'Kabwe', + 'Wulan', + 'Lake Charles', + 'Babhantoli', + 'Villaviciosa', + 'Doljevac', + 'Achchippatti', + 'Parasbani', + 'Agadez', + 'Yalluru', + 'Saumur', + 'Ambodiriana', + 'Panda', + 'Jadupatti', + 'Santo Domingo Este', + 'Manakambahiny', + 'Hajduhadhaz', + 'Kitamilo', + 'Shenwan', + 'Madattukkulam', + 'La Romana', + 'Glasgow', + 'Aars', + 'Niquelandia', + 'South Farmingdale', + 'Mangalia', + 'Kualaserba', + 'Flandes', + 'Veroli', + 'Ixtapan de la Sal', + 'Makarska', + 'Lakho', + 'Dhanur Kalyanwadi', + 'Castillo', + 'Jamui', + 'Mutyalapalle', + 'Karaikkudi', + 'Gogounou', + 'Kurumbapalaiyam', + 'Myrne', + 'Maroamalona', + 'Collado-Villalba', + 'Sikandarpur', + 'Olonne-sur-Mer', + 'Cambundi Catembo', + 'Vohimasina', + 'Chenango', + 'Muara Teweh', + 'Las Matas de Farfan', + 'Livermore', + 'West Columbia', + 'Anqiu', + 'Mumbai', + 'Ogijares', + 'Bethesda', + 'Wyndham', + 'Lake Shore', + 'Yuchi', + 'Jalandhar Cantonment', + 'Manevy', + 'Sao Jose do Rio Pardo', + 'Chimoio', + 'Dagami', + 'Sagarejo', + "Lin'an", + 'Gobardhanpur Kanap', + 'Apple Valley', + 'Allauch', + 'Villafranca di Verona', + 'Yulin', + 'Herbolzheim', + 'Dolton', + 'Terebovlya', + 'Sri Madhopur', + 'Benifayo', + 'Valambur', + 'Sendrisoa', + 'Ranipur', + 'Media Luna', + 'Ocna Mures', + 'Kulundu', + 'Damdama', + 'Milagros', + 'Smithfield', + 'Lake Butler', + 'Ciudad Barrios', + 'Escuintla', + 'Cherry Hinton', + 'Solonytsivka', + 'Ribeirao Claro', + 'Balussheri', + 'Nanjikkottai', + 'Asela', + 'Martos', + 'Canto do Buriti', + 'Samalut', + 'Dhuburi', + 'Maltahohe', + 'Horst', + 'Bafang', + 'La Blanca', + 'Seraing', + 'Hedensted', + 'Chala', + 'Bou Salem', + 'Boralday', + 'Marktredwitz', + 'Neuenrade', + 'Muddada', + 'Serrinha', + 'Horizon West', + 'Machalpur', + 'Pathra', + 'Nelspruit', + 'Rankhandi', + 'Gaoniang', + 'Ohrid', + 'Conguaco', + 'Taylors', + 'Aqadyr', + 'Kirkop', + 'El Golea', + 'Slupsk', + 'Sawran', + 'Ansan', + 'Villa Dominico', + 'Kambia', + 'Sainte-Genevieve-des-Bois', + 'An Nuhud', + 'Samarkand', + 'Ringsaker', + 'Blangmangat', + 'Nuenen', + 'North Aurora', + 'Hoek van Holland', + 'Santa Cruz Cabralia', + 'Suvalan', + 'Phra Pradaeng', + 'Arnedo', + 'Pagsanjan', + 'Pietermaritzburg', + 'Weissenthurm', + 'Pebberu', + 'Kangazha', + 'Ancona', + 'Bou Noura', + 'Owensboro', + 'Catalina Foothills', + 'Alegre', + 'Douar Imoukkane', + 'Tirkadavur', + 'Washington Court House', + 'Colmenar Viejo', + 'Baikunthpur', + 'Sitrah', + 'Presidente Janio Quadros', + 'St. Louis Park', + 'Kempten', + 'Dergaon', + 'Pannaipuram', + 'Gifu', + 'Toul', + 'Sirakorola', + 'Vaals', + 'Barranca', + 'Elgoibar', + 'Kasamatsucho', + 'Ampitahana', + 'Totowa', + 'Mocoa', + 'Beni Slimane', + 'Bethany', + 'Ramdiri', + 'Bariadi', + 'Cofradia', + 'Tandil', + 'Bolton', + 'Sirat', + 'Uspenka', + 'Capim Branco', + 'Ozumba', + 'Dighawani', + 'Keonjhargarh', + 'Makurazaki', + 'Sada', + 'Sarmin', + 'Yamkanmardi', + 'Zhujiezhen', + 'Brampton', + 'Nynashamn', + 'Dawson Creek', + 'Analalava', + 'Dobrovnik', + 'Puxi', + 'Beni Tamou', + 'Damargidda', + 'San Casciano in Val di Pesa', + 'Springdale', + 'Luque', + 'Zara', + 'Dakit', + 'Urena', + 'Naduhatti', + 'General Jose de San Martin', + 'Raia', + 'Bhuj', + 'Tisnov', + 'Tekkekoy', + 'Faridpur', + 'Sao Vicente', + 'Bad Salzuflen', + 'Itororo', + 'Petersberg', + 'Castelsarrasin', + 'Madiama', + 'Voluntari', + 'Butaleja', + 'Carandai', + 'Arusha', + 'Strumica', + 'Quijingue', + 'Banovce nad Bebravou', + 'Huaibei', + 'Siswa', + 'Ansiao', + 'Baisa', + 'Kozuchow', + 'Mions', + 'Kenema', + 'Rajaudha', + 'Onga', + 'City of Calamba', + 'Buli', + 'Lokhvytsya', + 'Chak Thathi', + 'Qasr-e Qomsheh', + 'Wilson', + 'Tarawa', + 'Libjo', + 'Batocina', + 'Tajpur', + 'Kanyakulam', + 'Manzhouli', + 'Bougaa', + 'Kitzingen', + 'Fenoughil', + 'Dhanera', + 'Dhorgaon', + 'Abohar', + 'Zug', + 'Ghaura', + 'Aparecida do Taboado', + 'Zapotlanejo', + 'Natanz', + 'Guarda', + 'Chengara', + 'Tup', + 'Livani', + 'Sirsia Hanumanganj', + 'Qal`at al Madiq', + 'Tamarac', + 'South Miami', + 'Thung Sai', + 'Grodzisk Wielkopolski', + 'Jiutepec', + 'Nautanwa', + 'Grenada', + 'Nandod', + 'Helotes', + 'Nang Rong', + 'Makiivka', + 'Paxtaobod', + 'Susaki', + 'Fuso', + 'Tsawwassen', + 'Villeurbanne', + 'Ternopil', + 'Pandaul', + 'Nardo', + 'Puerto Aysen', + 'Marvdasht', + 'Funafuti', + 'Siraway', + 'El Milagro', + 'Dinapore', + 'Conisbrough', + 'Ban Duea', + 'Kawm Umbu', + 'Derhachi', + 'Setubinha', + 'Assis', + 'Shikarpur', + 'Hukumati Baghran', + 'Manuel Urbano', + 'Penacova', + 'Mishawaka', + 'Kremenchuk', + 'Salzkotten', + 'Sakai', + 'Ostercappeln', + 'Sebikhotane', + 'Mahatalaky', + 'Recanati', + 'Sainte-Foy-les-Lyon', + 'Hengshan', + 'Antonio Enes', + 'Adivala', + 'Odaipatti', + 'Aginiparru', + 'Sao Goncalo dos Campos', + 'Sagaing', + 'Anjad', + 'Jevargi', + 'Bastia', + 'General Tinio', + 'Kailua', + 'Fukutsu', + 'Nogata', + 'Park Forest', + 'Lolokhur', + 'Moengo', + 'Hermosillo', + 'Greencastle', + 'Pittsburgh', + 'Luuq', + 'Toluca', + 'Satgachia', + 'Nova Varos', + 'Tetovo', + 'Empalme', + 'Cherryland', + 'Ka-Bungeni', + 'Fernandopolis', + 'Oita', + 'Walnut', + 'Dalian', + 'Viersen', + 'Jyvaskylan Maalaiskunta', + 'Warragul', + 'Malaikkal', + 'Loule', + 'Honolulu', + 'Azalea Park', + 'Samba Cango', + 'Boblingen', + 'Istmina', + 'Southend', + 'Kallithea', + 'Khiria', + 'Huaral', + 'Mendota', + 'Upper Buchanan', + 'Damietta', + 'Porto Empedocle', + 'Santo Antonio do Ica', + 'Nunungan', + 'Naysar', + 'Pico Rivera', + 'Riano', + 'Snodland', + 'Sangre Grande', + 'Sun City', + 'Naunhof', + 'Fisciano', + 'Vedene', + 'Khawad', + 'Vernon', + 'Ahiro', + 'Chidambaram', + 'Chacabuco', + 'Elsdorf', + 'Chuhal', + 'Djelfa', + 'Qiryat Shemona', + 'Altamirano', + 'Cubellas', + 'Allonnes', + 'Aracaju', + 'Novi Knezevac', + 'Ikalamavony', + 'Staunton', + 'Mpophomeni', + 'Manombo Atsimo', + 'Madinat Hamad', + 'Guntramsdorf', + 'Elchuru', + 'Dongyang', + 'Gardony', + 'Taro', + 'Tablat', + 'Garulia', + 'Gopichettipalaiyam', + 'Raibari Mahuawa', + 'Tibba', + 'Taoyuan District', + 'Manavadar', + 'San Pedro Garza Garcia', + 'Amuntai', + 'Damua', + 'Desio', + 'Tomboutou', + 'Parlier', + 'Modasa', + 'Ubud', + 'Makan', + 'Vieux-Conde', + 'Maranga', + 'Acobamba', + 'Rugby', + 'Paramirim', + 'Nirgua', + 'Alto Paraiso de Goias', + 'Bandar Lampung', + 'Koumaira', + 'Sebt Ait Ikkou', + 'Vavuniya', + 'Grand Terrace', + 'Surajgarha', + 'Pedro Velho', + 'Lanzhou', + 'Tieli', + 'Jadia', + 'Mapanas', + 'Nangan', + 'Glazoue', + 'Villa Literno', + 'Qianshanhong Nongchang', + 'Paso de Carrasco', + 'Calliaqua', + 'Watertown Town', + 'Tasgaon', + 'China', + 'Yoshinogari', + 'Warora', + 'Castlebar', + 'Hezhou', + 'Tijuana', + 'Vilachcheri', + 'Tama', + 'Stony Brook', + 'Puurs', + 'Dam Dam', + 'Muktsar', + 'Wishaw', + 'Clay', + 'Hattula', + 'Marojala', + 'Aguas Belas', + 'Ogano', + 'Kattagaram', + 'Sulakyurt', + 'Mariveles', + 'Kavala', + 'Leavenworth', + 'Ambohitsimanova', + 'Lingsugur', + 'Delicias', + 'Xihuachi', + 'Kukarmunda', + 'Saruu', + 'Lwengo', + 'Bokod', + 'Andraitx', + 'Polanco', + 'Willowbrook', + 'Peddapalle', + 'Doddipatla', + 'Basco', + 'Bhimavaram', + 'Timoktene', + 'Tizi', + 'Mettet', + 'Matadi', + 'Xiaotangzhuang', + 'Guna', + 'Villaricca', + 'Pfedelbach', + 'Barud', + 'San Antonio del Monte', + 'Advi Devalpalli', + 'Nowy Dwor Gdanski', + 'Hsinchu', + 'Bad Kreuznach', + 'Pasaje', + 'Miluo Chengguanzhen', + 'Puyang Chengguanzhen', + 'Vasto', + 'Abarkuh', + 'West Plains', + 'Carazinho', + 'Vedi', + 'Port Maria', + 'Karratha', + 'Fes', + 'Ghulakandoz', + 'Porto Acre', + 'Jinzhong', + 'Thyolo', + 'Dien Bien Phu', + 'Panniyannur', + 'Banbhag', + 'Saint-Lambert', + 'Bhoj', + 'Salug', + 'Sobraon', + 'Reigate', + 'Colts Neck', + 'Salcaja', + 'Liesti', + 'Tianyingcun', + 'Maia', + 'Befeta', + 'Ina', + 'Sutherlin', + 'Saint-Brice-sous-Foret', + 'St. Louis', + 'Bistaria', + 'Nixa', + 'Boqueirao', + 'Khimlasa', + 'Munai', + 'Encinitas', + 'Citlaltepec', + 'Tokigawa', + 'Kalloni', + 'Sachse', + 'Huckeswagen', + 'Meric', + 'Ilkal', + 'Bawana', + 'Greetland', + 'Alcazar de San Juan', + 'Soltau', + 'Wahiawa', + 'Mogogelo', + 'Villerupt', + 'Carqueiranne', + 'Morohongo', + 'Hervey Bay', + 'Lakhna', + 'Mardin', + 'Lidingo', + 'Dar Chioukh', + 'Juncos', + 'Alliston', + 'Ilorin', + 'Arita', + 'Skara', + 'Progress Village', + 'Savelugu', + 'Khirpai', + 'Kamalnagar', + 'Mount Holly', + 'Vreed-en-Hoop', + 'Akcakale', + 'Vence', + 'Kurawar', + 'Seoul', + 'Novi Grad', + 'Kudamatsu', + 'Colina', + 'Kottagudem', + 'Tankal', + 'Kazarman', + 'East Orange', + 'Mulanur', + 'Sfizef', + 'Larvik', + 'Pawni', + 'San Juan de Vilasar', + 'Venmani', + 'Gentilly', + 'Picnic Point', + 'Beiwusidui', + 'Pakdasht', + 'Tatvan', + 'Ardmore', + 'Capela do Alto Alegre', + 'Rakai', + 'Yakouren', + 'Puerto Asis', + 'Shirali', + 'Adjumani', + 'Huite', + 'Indaiatuba', + 'Ouled Moussa', + 'Tamboril', + 'Kasimkota', + 'Iligan', + 'Stratton Saint Margaret', + 'Jiancheng', + 'Mungod', + 'Sremcica', + 'Sara', + 'Garrel', + 'Narela', + 'Meilan', + 'Martinsicuro', + 'Culaba', + 'Suez', + 'Villamaria', + 'Baytunya', + 'Oleshky', + 'Mogaung', + 'Twinsburg', + 'Casatenovo', + 'Sadda', + 'Shiyali', + 'Independencia', + 'Jinotepe', + 'Guilin', + 'Samal', + 'Gouda', + 'Tarui', + 'Sao Felipe', + 'Srivilliputtur', + 'Malmal', + 'Pembroke Dock', + 'Schwaikheim', + 'Beizhou', + 'Horw', + 'Gohi Bishunpur', + 'Taby', + 'Ramallo', + 'Woodcrest', + 'San Jacinto Amilpas', + 'Sundarsi', + 'Basildon', + 'Harasta', + 'Brislington', + 'Collingdale', + 'Brezice', + 'Xam Nua', + 'Ans', + 'Hardiya', + 'Linnich', + 'Varamin', + 'Asuncion Mita', + 'Dalgan', + 'Sahawar', + 'Jiutai', + 'Bruckmuhl', + 'Bingley', + 'Dundigal', + 'Somers', + 'Villas', + 'Ouro Branco', + 'Apora', + 'Miduturu', + 'Manambolosy', + 'Wuchuan', + 'Kasumbalesa', + 'Brent', + 'Balma', + 'Beidou', + 'Farim', + 'Dagmara', + 'Bijaipur', + 'Karera', + 'Amersfoort', + 'Gold', + 'Patilar', + 'Tweed Heads', + 'San Quintin', + 'Saloa', + 'Ruiming', + 'Sarayan', + 'Silver Springs Shores', + 'Vakon', + 'Ingenbohl', + 'Chetma', + 'Blacksburg', + 'Pondokaren', + 'Queimadas', + 'Encantado', + 'Tsubata', + 'Mers el Hadjad', + 'Northglenn', + 'Genc', + 'Lohiyar Ujain', + 'Ruoqiang', + 'Madera', + 'Santana do Cariri', + 'Ramamangalam', + 'Bangawan', + 'Emirdag', + 'Santa Fe Springs', + 'Cabucgayan', + 'Beaver Falls', + 'Cypress', + 'Glanmire', + 'Ipua', + 'Jaca', + 'Pueblo Viejo', + 'Kashiwa', + 'Dos Hermanas', + 'Ras Tanura', + 'Sidi Abdellah Ben Taazizt', + 'Badger', + 'Velingrad', + 'Velsen-Zuid', + 'Iscuande', + 'Ziro', + 'Charneca', + 'Tourlaville', + 'Loma Linda', + 'Sevran', + 'Eraniel', + 'Trece Martires City', + 'Franceville', + 'Coari', + 'Khairabad', + 'Bunhe', + 'Taguatinga', + 'Gilan-e Gharb', + 'Nyborg', + 'Kawagoe', + 'Pinheiral', + 'Bavaro', + 'Lisse', + 'Shuilou', + 'Paraty', + 'San Pedro Huamelula', + 'Bamber Bridge', + 'Port Huron', + 'Quba', + 'Ajacuba', + 'Lambarene', + 'Sikandarabad', + 'Chornobaivka', + 'Dores de Campos', + 'Wakoro', + 'Palomares del Rio', + 'Ankola', + 'Chibia', + 'Birmingham', + 'Antrim', + 'Kingsport', + 'Rockledge', + 'Carrillos', + 'Irig', + 'Denekamp', + 'Oberhaching', + 'Tisma', + 'Uriangato', + 'Tori-Cada', + 'Wolomin', + 'Montignies-le-Tilleul', + 'Des Moines', + 'Angeles City', + 'Morinville', + 'Zunyi', + 'Port Blair', + 'Daparkha', + 'Petrosani', + 'Lewes', + 'Douar Souk L`qolla', + 'Siaton', + 'Beyne-Heusay', + 'Padang', + 'Nova Kakhovka', + 'Assare', + 'Garh Sisai', + 'Ulster', + 'Magny-les-Hameaux', + 'Vallieres', + 'Garagoa', + 'Sungurlu', + 'Gadaul', + 'Taraclia', + 'Aden', + 'Grand Bourg', + 'Sao Francisco do Guapore', + 'Meruoca', + 'Oud-Beijerland', + 'Nitra', + 'Capoterra', + 'Tasikmalaya', + 'Cuevo', + 'Fruit Cove', + 'Le Teil', + 'Beur', + 'Bouskene', + 'Saint-Pierre', + 'Palitana', + 'Bourkika', + 'Jiquilisco', + 'Hassa', + 'Ixtapaluca', + 'Velyka Dymerka', + 'Kampli', + 'Caetano', + 'Luis Antonio', + 'Horgen', + 'Nalatvad', + 'Quime', + 'Betsiboka', + 'Rancheria Payau', + 'Dongxing', + 'Panagyurishte', + 'Soure', + 'Sinalunga', + 'Bhopatpur', + 'Tottington', + 'Zogno', + 'Alpinopolis', + 'Ibirite', + 'Talusan', + 'Mae Wang', + 'Prato', + 'Barvynkove', + 'Altavilla Vicentina', + 'Mussomeli', + 'Hilchenbach', + 'Agueda', + 'Botupora', + 'San Fernando', + 'Gallarate', + 'Balombo', + 'Philipsburg', + 'Oulad Aissa', + 'Brazzaville', + 'Tissaf', + 'Sanniquellie', + 'Zeist', + 'Turgutlu', + 'Ashikaga', + 'Seclin', + 'Actopan', + 'Accra', + 'Destelbergen', + 'Paiker', + 'Iowa Colony', + 'Muvattupula', + 'Springe', + 'Coqueiral', + 'Gmunden', + 'Perry Barr', + 'Cuneo', + 'Masur', + 'Calimera', + 'Diang', + 'Nacozari Viejo', + 'Jurmala', + 'Tsarasaotra', + 'Chon Thanh', + 'Dandenong', + 'Zutphen', + 'Bunawan', + 'Kottaram', + 'Majhgawan', + 'Macedonia', + 'Totogalpa', + 'Grand Junction', + 'Medfield', + 'Tolcayuca', + 'Santiago del Estero', + 'Ibague', + 'Caucaia', + 'Quartucciu', + 'Shangshan', + 'Roldan', + 'Tiddim', + 'El Bosque', + 'Mangapet', + 'Enfield Lock', + 'Paulo Afonso', + 'Golden Gate', + 'Del City', + 'Brandys nad Labem-Stara Boleslav', + 'Ambatomasina', + 'San Pascual', + 'Gothurutha', + 'Budapest', + 'El Hamel', + 'Vadakadu', + 'Casalpusterlengo', + 'Vardannapet', + 'Glucholazy', + 'Talkha', + 'Victoriaville', + 'Fengdeng', + 'Bhalki', + "Sao Jorge d'Oeste", + 'Calaca', + 'Al `Awwamiyah', + 'Listowel', + 'Komenda', + 'Janiuay', + 'Copperas Cove', + 'Kovvali', + 'Cucer-Sandevo', + 'Birkat as Sab`', + 'Silaiyampatti', + 'Lianzhuangcun', + 'Arataca', + 'Natchez', + 'Guangyuan', + 'Ko Samui', + 'Hajduboszormeny', + 'Ambatondrazaka', + 'Akonolinga', + 'Barpeta', + 'Torrelodones', + 'Norrtalje', + 'Saurh', + 'Wohlen', + 'Felling', + 'Honjo', + 'Petare', + 'Proper Bansud', + 'Dendermonde', + 'Parauna', + 'Bibhutpur', + 'Sultepec', + 'Chiquinquira', + 'Lint', + 'Zhedao', + 'Santo Antonio de Padua', + 'Alburquerque', + 'Fuzuli', + 'Ocean Pines', + 'Atacames', + 'Francheville', + "Wadi Halfa'", + 'Ninh Hoa', + 'Baiquan', + 'Antigua', + 'Bevata', + 'Xinpo', + 'Warkan', + 'Qibray', + 'Gisors', + 'Pa Mok', + 'Tabuleiro do Norte', + 'Melres', + 'Sangam', + 'Ostrhauderfehn', + 'Buritirama', + 'Oleksandriia', + 'Dongzhang', + 'Rio Preto da Eva', + 'Idupugallu', + 'Florida City', + 'Tobias Fornier', + 'Newquay', + 'Ivins', + 'Kotah-ye `Ashro', + 'Klaksvik', + 'Klaeng', + 'Vatutine', + 'Rignano Flaminio', + 'Soamahamanina', + 'Essex', + 'Arenzano', + 'Arbaoun', + 'Ain Kansara', + 'Stein', + 'Gleisdorf', + 'Ottaviano', + 'Lloret de Mar', + 'Sindos', + 'Lausanne', + 'Swadlincote', + 'Buffalo Grove', + 'Coelho Neto', + 'Msata', + 'Ciudad Cuauhtemoc', + 'Home Gardens', + 'Resen', + 'Khergam', + 'Neu Isenburg', + 'Kiato', + 'Mahina', + 'Keelung', + 'Mahiari', + 'Itambacuri', + 'Tsiatajavona-Ankaratra', + 'Sake', + 'Narrabri', + 'Popovaca', + 'Uruma', + 'Doha', + 'Ilaiyankudi', + 'Cabo Frio', + 'Mustang', + 'Kolbermoor', + 'Anahidrano', + 'Skikda', + 'Omurtag', + 'Maqu', + 'Mouscron', + 'Anchieta', + 'Pendembu', + 'Mokokchung', + 'Cestos City', + 'Oulad Zemam', + 'Beerse', + 'Solin', + 'Orani', + 'Itapira', + 'Elburg', + 'Pretoria', + 'Nishihara', + 'Siparia', + 'Fort Riley', + 'Dar Chaifat', + 'Khiria Jhansi', + 'Rezvanshahr', + 'Gacheta', + 'Rio Mayo', + 'Huainan', + 'Sarkisla', + 'Frias', + 'Msila', + 'Neumarkt', + 'Ajjanahalli', + 'Santa Maria Xadani', + 'Loikaw', + 'Gebze', + "Shin'onsen", + 'Bhind', + 'Nakhon Si Thammarat', + 'Wangzhuang', + 'Ban Kao', + 'Barki Saria', + 'Wylie', + 'Birdsville', + 'Atoyac de Alvarez', + 'Bamberg', + 'Agourai', + 'Saavedra', + 'Gundelfingen', + 'Hunucma', + 'Ambohitrimanjaka', + 'San Pedro Necta', + 'Eeklo', + 'Gutalac', + 'Chankou', + 'Cerro Cora', + 'Gulfport', + 'Southaven', + 'Vemulapudi', + 'Pinal de Amoles', + 'Montgomery', + 'Conchali', + 'Cercola', + 'Baependi', + 'Urgup', + 'Qurayyat', + 'Ban Si Don Chai', + 'Duyun', + 'Gentio do Ouro', + 'Detroit Lakes', + 'Muna', + 'Pine Lake Park', + 'Omerli', + 'Ambatondrakalavao', + 'Las Cabras', + 'Dueville', + 'Darjeeling', + 'Los Gatos', + 'Poa', + 'Keren', + 'Jiajin', + 'Vogosca', + 'Radece', + 'Damonojodi', + 'Khurda', + 'Surallah', + 'Johor Bahru', + 'Cuervos', + 'Brixton', + 'Kasap', + 'San Juan La Laguna', + 'Dambai', + 'Marantao', + 'Aguazul', + 'Teoloyucan', + 'Munnar', + 'Madhopur Hazari', + 'Yaotsu', + 'Le Ray', + 'Segbwema', + 'La Apartada', + 'Vitrolles', + 'Amtali', + 'Neston', + 'Portage La Prairie', + 'Taytay', + 'Barahbatta', + 'Tiorpara', + 'Hajira', + 'As Sabburah', + 'Krynica', + 'East Rutherford', + 'Stovring', + 'Andernach', + 'Levski', + 'Vihiga', + 'Kadikkad', + 'Husepur', + 'Nokha', + 'Santa Isabel do Ivai', + 'Bithar', + 'Gaziemir', + 'Manapparai', + 'Sinait', + 'Brandfort', + 'Ibaraki', + 'General San Martin', + 'Parma Heights', + 'Yoshiwara', + 'Mudanya', + 'Bafq', + 'Mikolow', + "Chang'an", + 'Chota', + 'Kesbewa', + 'Sinacaban', + 'Ambarawa', + 'Molodohvardiisk', + 'Herat', + 'Sakaidecho', + 'Honefoss', + 'Handan', + 'Clydebank', + 'Glenpool', + 'Uman', + 'San Biagio di Callalta', + 'Ocean City', + 'Qiryat Yam', + 'Baldwin', + 'Sweden', + 'Soavina', + 'Hugo', + 'Tra Vinh', + 'Taibet', + 'Turi', + 'Agua Blanca Iturbide', + 'Cherasco', + 'Parun', + 'Cedar Hills', + 'Arugollu', + 'Puduppattanam', + 'Monki', + 'Dorog', + 'Tissamaharama', + 'Seffner', + 'Tongchuan', + 'Nethirimangalam', + 'Ambalanirana', + 'Torbat-e Jam', + 'Balangkayan', + 'Malazgirt', + "Uchtepa Qishlog'i", + 'Les Anglais', + 'Sing Buri', + 'Zhaoxiang', + 'Porta Westfalica', + 'Kajur', + 'Casa Grande', + 'Nkoteng', + 'Pasca', + 'Merefa', + 'Perols', + 'Ellensburg', + 'Mannukara', + 'Rum', + 'Moglingen', + 'Tendrara', + 'Traiskirchen', + 'Dialoube', + 'Poona-Piagapo', + 'Orthez', + 'Hollinwood', + 'Ferryhill', + 'Heerhugowaard', + 'Bad Soden-Salmunster', + 'Twin Falls', + 'Imamoglu', + 'Gartringen', + 'Vaudreuil-Dorion', + 'Chonthrhu', + 'Aberbargoed', + 'Langar', + 'Cabarroguis', + 'Anakapalle', + 'Geraldton', + 'Seara', + 'Guasca', + 'Ban Mae Sun Luang', + 'Sankt Johann in Tirol', + 'Musselburgh', + 'Nave', + 'Bhola', + 'Mansehra', + 'Villa Sarmiento', + 'Sao Bento do Una', + 'Balasan', + 'Bocana de Paiwas', + 'Costesti', + 'Podu Iloaiei', + 'Jaitpura', + 'Rio Colorado', + 'Villa Nueva', + 'Tlaquepaque', + 'Sutton on Hull', + 'Radomyshl', + 'Muttanampalaiyam', + 'Elon', + 'Great Falls', + 'Al `Amirat', + 'Ganzhu', + 'Machachi', + 'Lehututu', + 'Ambohimierambe-Andranofito', + 'Singur', + 'Lagindingan', + 'Bocaina', + 'Siguatepeque', + 'Jalalkhera', + 'Santa Lucija', + 'Colmeia', + 'Aneho', + 'Beterou', + 'Mont-Organise', + 'Cabanas', + 'Leduc', + 'Euxton', + 'Trebinje', + 'Patri', + 'Ostrava', + 'Bay Shore', + 'Ghatkesar', + 'Gia Nghia', + 'University Heights', + 'Montigny-les-Metz', + 'Pipraun', + 'Villa de Cura', + 'Polokwane', + 'Daloa', + 'Souma', + 'Cerea', + 'Gracanice', + 'Anteza', + 'Dedza', + 'Hilsea', + 'Koduvalli', + 'Katravulapalle', + 'Nembro', + 'Wanlaweyn', + 'Sardulgarh', + 'Hatibanda', + 'Khizrpur', + 'Sailana', + 'Holbrook', + 'Horwich', + 'Motibennur', + 'Kenner', + 'Bugallon', + 'Angermunde', + 'Boguszow-Gorce', + 'Motomiya', + 'Nanzhuangzhen', + 'Tangxing', + 'Tamanrasset', + 'Brasileia', + 'Canandaigua', + 'Gardez', + 'Qorasuv', + 'Halemba', + 'Biandanshan', + 'Mauleon', + 'Komarno', + 'Husi', + 'Ngaoundal', + 'Ferreiros', + 'Gatunda', + 'Aioi', + 'Jainagar', + 'Longyan', + 'Mocimboa', + 'Central Point', + 'Machadodorp', + 'Niesky', + 'Trets', + 'Kajang', + 'Tuxpan', + 'Anosy Avaratra', + 'Novhorod-Siverskyi', + 'Zhenjiang', + 'Srungavarapukota', + 'San Sebastian de la Gomera', + 'Frankston', + 'Ninga', + 'Morauna', + 'Saginaw', + 'Kussnacht', + 'Lerida', + 'Silchar', + 'Thakurainia', + 'Wote', + 'Riom', + 'Khachrod', + 'Takizawa', + 'Psychiko', + 'Niasso', + 'Rio das Pedras', + 'La Maquina', + 'Kaguchi', + 'San Fabian', + 'Devarapalle', + 'Nahiyat Ghammas', + 'Tlapacoyan', + 'Debe', + 'Santa Maria de Itabira', + 'Ifigha', + 'Neduvattur', + 'Monteprandone', + 'San Pedro Perulapan', + "Bou'nane", + 'Ponnagyun', + 'Guaicara', + 'Krivogastani', + 'Ino', + 'Stupava', + 'New Mills', + 'Ankadimanga', + 'Liaoyang', + 'Chittaurgarh', + 'Bim Son', + 'Cullera', + 'Blackburn', + 'Great Driffield', + 'Mukacheve', + 'Warka', + 'Villers-la-Ville', + 'Gavarr', + 'Khrustalnyi', + 'Easley', + 'Caykara', + 'Woudenberg', + 'Kismaayo', + 'Chojnice', + 'Holladay', + 'Leiderdorp', + 'Reading', + 'Evansville', + 'Bamaiya Harlal', + 'Johns Creek', + 'Qo`qon', + 'Tambaga', + 'Kouande', + 'Sapang Dalaga', + 'Rocafuerte', + 'Curepipe', + 'Jabuticabal', + 'Hasroun', + 'Nishinoomote', + 'Salemi', + 'Bailongqiaocun', + 'Niefern-Oschelbronn', + 'Jablah', + 'Susa', + 'Ortuella', + 'Bitonto', + 'Karadge', + 'Quelimane', + 'Pirthipur', + 'Crisopolis', + 'Venosa', + 'Anantapur', + 'Grajau', + 'Idah', + 'Sidlice', + 'Middle Smithfield', + 'Kanie', + 'Khari', + 'Patnagarh', + 'Tamentit', + 'Ciudad Valles', + 'Kampen', + 'Madangir', + 'Swidwin', + 'Analaroa', + 'Cayirli', + 'Cernay', + 'Houghton Regis', + 'Redon', + 'Belchatow', + 'Maktar', + 'Belagal', + 'Chikkarampalaiyam', + 'Novato', + 'Notre-Dame-de-Gravenchon', + 'Wuustwezel', + 'Palaiyampatti', + 'Kandanur', + 'Nahavand', + 'Tokushima', + 'Sandakan', + 'Avidha', + 'Chaital', + 'Shri Mahavirji', + 'Tashir', + 'Taghbalt', + 'Cottage Grove', + 'Paso de los Toros', + 'Ilicinia', + "Monteforte d'Alpone", + 'Helleland', + 'Corcuera', + 'Goa', + 'Oqqo`rg`on', + 'Chalco', + 'Chorwad', + 'Jhenida', + 'Triesen', + 'Ban Don Kaeo', + 'Nova Prata', + 'Pyrzyce', + 'Sogod', + 'Kilindoni', + 'Condega', + 'Qatana', + 'Nuth', + 'Ban Phru', + 'Kiiminki', + 'Antanandehibe', + 'La Concordia', + 'Guilherand', + 'Antsiranana', + 'Mersch', + 'Al Awjam', + 'Selouane', + 'Coronda', + 'Oregon City', + 'Bordj Zemoura', + 'Alwarkurichchi', + 'Kangasala', + 'Sonada', + 'Burdur', + 'Lollar', + 'Bethel Park', + 'Higashiagatsuma', + 'Gandujie', + 'Talugtug', + 'Cocotitlan', + 'Cassano al Ionio', + 'New Hamburg', + 'Jacobina', + 'Tourougoumbe', + 'Ribeiropolis', + 'Midsomer Norton', + 'Gretz-Armainvilliers', + 'Nagqu', + 'Sunnyvale', + 'Canberra', + 'Rupana', + 'Owosso', + 'Sao Jose do Norte', + 'Mantaly', + 'Socota', + 'Cape St. Claire', + 'Lepakshi', + 'Rounia', + 'Tavira', + 'Qazyan', + 'Chettipulam', + 'Grantsville', + 'Nagarote', + 'Baras', + 'Ashaiman', + 'Birendranagar', + 'West Allis', + 'Ouricuri', + 'Sarzana', + 'Vallegrande', + 'El Kseur', + 'Assenede', + 'Chitauria', + 'Steinheim', + 'Amsterdam', + 'Goth Tando Sumro', + 'Santa Cruz Itundujia', + 'Cartago', + 'Nandaime', + 'Andes', + 'Dialakorodji', + 'Schulzendorf', + 'Bantacan', + 'Calarasi', + "Ra's Gharib", + 'East Lake-Orient Park', + 'Baitoa', + 'Ormesson-sur-Marne', + 'El`ad', + 'Campechuela', + 'Shiotacho-matsusaki', + 'Kankaanpaa', + 'Ronda', + 'Urucuia', + 'Morlaix', + 'Satwar', + 'Bethel', + 'Corvallis', + 'Yinchuan', + 'Talavera', + 'Shahriston', + 'Royston', + 'Bexbach', + 'Vilangudi', + 'Brinkmann', + 'Hellendoorn', + 'Imi-n-Tanout', + 'South Hayling', + 'Schroeder', + 'Petrovka', + 'Zouerate', + 'Bingen am Rhein', + 'Bovenden', + 'Biri', + 'Teaneck', + 'Yvoir', + 'Santa Eulalia del Rio', + 'Rolleston', + 'Badantola', + 'Waimea', + 'Muzo', + 'Staro Nagoricane', + 'Paranhos', + 'Neustrelitz', + 'Paouignan', + 'Oakashicho', + 'Iygli', + 'Rangra', + 'Sassenage', + 'Lommedalen', + 'Giyon', + 'Conroe', + 'Upperu', + 'Qorao`zak', + 'Lingal', + 'Jasien', + 'Nonoichi', + 'Al Bardiyah', + 'Arrentela', + 'Saharsa', + 'Kalwakurti', + 'Lakeland North', + 'Bad Ems', + 'Quibaxi', + 'Huskvarna', + 'Bihac', + "'Ain Naga", + 'Euclides da Cunha', + 'East Grinstead', + 'Koneurgench', + 'Calauag', + 'Kiangan', + 'Mill Valley', + 'Turpan', + 'Cacimbinhas', + 'Urucuca', + 'Anouvong', + 'Kunimedu', + 'Islamabad', + 'Chahar Borj-e Qadim', + 'Maribor', + 'Ilgin', + 'Mount Lavinia', + 'Tsukumiura', + 'Aix-en-Provence', + 'Ipaporanga', + 'Pokotylivka', + 'Nazipur', + 'Tebingtinggi', + 'Wenzenbach', + 'Solingen', + 'Beveren', + 'Koszalin', + 'Ticul', + 'Kuroishi', + 'Marialva', + 'Auria', + 'Raffadali', + 'Kumagaya', + 'Ruppichteroth', + 'Dougoufe', + 'Dompu', + 'Petrus Steyn', + 'Lattes', + 'Raalte', + 'Taungdwingyi', + 'Guixi', + 'Ganta', + 'Schonwalde-Siedlung', + 'Ifatsy', + 'Baroda', + 'Los Andes', + 'Zhaitangcun', + 'Simraungadh', + 'Fada', + 'Moldova Noua', + 'Sankt Andra', + 'Ica', + 'Ngora', + 'Robbah', + 'Rahiar Kunchi', + 'Ouatagouna', + 'Benavente', + 'Suluru', + 'Burstadt', + 'Hindoli', + 'Santa Maria Petapa', + 'Sanana', + 'Mangidy', + 'Buldan', + 'Ustrzyki Dolne', + 'Bouznika', + 'Florstadt', + 'Santiago Papasquiaro', + 'Sirur Tajband', + 'Ottumwa', + 'Terralba', + 'Henrietta', + 'Adrar', + 'Koni', + 'Cardonal', + 'Haskah Menah', + 'Dawmat al Jandal', + 'Salzgitter', + 'Shikharpur', + 'So-Awa', + 'Baghmari', + "'Ain Arnat", + 'Gojra', + 'Majidpur', + 'Cayeli', + 'Bouansa', + 'Barhi', + 'New Amsterdam', + 'Ibigawa', + 'Schwarzenberg', + 'Darling', + 'Borgaon', + 'Daudnagar', + 'Kitsuki', + 'Fairfax', + 'Mannarakkat', + 'Oberasbach', + 'Murphy', + 'Laurentides', + 'Manjha', + 'Kobo', + 'Kameoka', + 'Bafut', + 'Krasyliv', + 'Saiha', + 'Edinburgh', + 'Kishkenekol', + 'Leyton', + 'Isiolo', + 'Maibog', + 'Chiranellur', + 'Athens', + 'Sampit', + 'Ak-Suu', + 'Toulal', + 'Rangia', + 'Morton Grove', + 'Kaithinia', + 'Antipolo', + 'Tabant', + 'Starachowice', + 'Al Yadudah', + 'Zonguldak', + 'Piaseczno', + 'Nirna', + 'Mitrovice', + 'Plainfield', + 'Wheat Ridge', + 'Oakbrook', + 'Yahualica de Gonzalez Gallo', + 'Drimmelen', + 'Campo de la Cruz', + 'El Roble', + 'Umarga', + 'Mqam at Tolba', + 'Consett', + 'Ponders End', + 'Mount Lebanon', + 'Harima', + 'Marquette-les-Lille', + 'Spata', + 'Panaon', + 'Viravada', + 'Juazeiro do Norte', + 'Tieling', + 'Montemorelos', + 'Manggar', + 'Scicli', + 'Gorlice', + 'Rong Kwang', + 'Mill Creek', + 'Rivne', + 'Linamon', + 'Creazzo', + 'Moody', + 'Patnanungan', + 'Qasr al Qarabulli', + 'Leingarten', + 'Saqultah', + 'Farob', + 'Chinju', + 'Bartolome Maso', + 'Pedreguer', + 'Kuldiga', + 'San Bernardo del Viento', + 'Kushiro', + 'Maha Sarakham', + 'Campbell River', + 'Balaxani', + 'Kilakkurichchi', + 'Uitenhage', + 'Tudela de Duero', + 'Brianka', + 'Garkha', + 'Kassel', + 'Avigliana', + 'Herk-de-Stad', + 'Kassama', + 'Al Jubayl', + 'Longavi', + 'Ugamedi', + 'Leutkirch im Allgau', + 'Lambeth', + 'Koduman', + 'Bais', + "Petite Riviere de l'Artibonite", + 'Presidencia Roque Saenz Pena', + 'Bad Munder am Deister', + 'Kottadindulu', + 'Thorigny-sur-Marne', + 'Tinkoni', + 'Bilacari', + 'Madruga', + 'Chula Vista', + 'Strzelce Opolskie', + 'Chalgeri', + 'Teningen', + 'Cumanda', + 'Ankilivalo', + 'Arica', + 'Mabacun', + 'Agios Dimitrios', + 'Pihuamo', + 'Bithlo', + 'Lititz', + 'Barth', + 'Nkhotakota', + 'Castellammare del Golfo', + 'Tearce', + 'Jinzhou', + 'Mahaditra', + 'Nelkattumseval', + 'Marinilla', + 'Taguai', + 'Ambohidronono', + 'Ansfelden', + 'Angus', + 'Chintalapalli', + 'Piedra Blanca', + 'Prenzlau', + 'San Manuel Chaparron', + 'Grantham', + 'Atyra', + 'Cornwall', + 'Conselice', + 'Brasilia', + 'Igarape Grande', + 'Toplita', + 'Malaimbandy', + 'Kasane', + 'Villa Ocampo', + 'Simarwara Durgapur', + 'Piripa', + 'Giza', + 'Xianyang', + 'Rodas', + 'Canal Winchester', + 'Vashon', + 'Pully', + 'Venice', + 'Miantsoarivo', + 'Rio Cauto', + 'Shahjanpur', + 'Motala', + "'Ain Azel", + 'Taebaek', + 'Mankato', + 'Sevres', + 'Sarangani', + 'Berndorf', + 'Paratinga', + 'Floral Park', + 'Santa Catalina', + 'Greenacres', + 'Verviers', + 'Deoni Buzurg', + 'Lantapan', + 'Brakpan', + 'Santa Ana Chiautempan', + 'Chamtha', + 'Umarkhed', + 'Chai Nat', + 'Willingboro', + 'Licab', + 'Esquel', + 'Santiago Tuxtla', + 'Suran', + 'Mazenod', + 'Holmdel', + 'Saguiaran', + 'Nidadavole', + 'Santa Rosa de Calamuchita', + 'Malkanur', + 'Avaniyapuram', + 'Saktipur', + 'Lynnfield', + 'Orange Park', + 'Lanuvio', + 'Magna', + 'Hanumangarh', + 'Ashmun', + 'Ayirapuram', + 'Viale', + 'Andarai', + 'Falesti', + 'Soverato Marina', + 'Loeches', + 'Ibirataia', + 'Chester-le-Street', + 'Gouna', + 'Azeffoun', + 'Burlingame', + 'Crest', + 'Flowood', + 'Lupane', + 'Iferhounene', + 'Eravattur', + 'Mayaguez', + 'Butte', + 'Azangaro', + 'Viladecans', + 'Argenta', + 'Rauma', + 'Khombole', + 'Trstenik', + 'Kiryu', + 'Mambusao', + 'Tan Uyen', + 'Yazoo City', + 'Turnisce', + 'Navolato', + 'San Giustino', + 'Lice', + 'Antsambahara', + 'Nagahama', + 'Herndon', + 'Phibun Mangsahan', + 'Lushnje', + 'Wanaque', + 'Chuncheon', + 'Thomassique', + 'Havza', + 'Wellington', + 'Nagato', + 'Aucamville', + 'Corupa', + "Ra's al Khafji", + 'Novi Ligure', + 'Isnos', + 'Taiyong', + 'Beigangwa', + 'Greymouth', + 'Sevilla', + 'Beech Grove', + 'Viry-Chatillon', + 'Bogdanci', + 'Hillsdale', + 'Pao de Acucar', + 'Fuqing', + 'Oderzo', + 'Osterholz-Scharmbeck', + 'Saint-Maurice', + 'Puluvappatti', + 'Kabala', + 'Marchtrenk', + 'Lorch', + 'Kamtaul', + 'San Rafael Pie de la Cuesta', + 'La Puente', + 'San Andres', + 'Robore', + 'Baladharmaram', + 'Breza', + 'Rio del Mar', + 'Bocas del Toro', + 'Gorleston-on-Sea', + 'Fair Oaks', + 'Rapallo', + 'Villahermosa', + 'Kalappatti', + 'Wilmington Island', + 'San Francisco Menendez', + 'Champahati', + 'Baden', + 'Maxaranguape', + 'Macas', + 'Rio Tinto', + 'Ingre', + 'Teroual', + 'Nikki', + 'Bir Tam Tam', + 'Myrtle Grove', + 'Wuxi', + 'Si Mustapha', + 'Rundu', + 'Bar Bigha', + 'Iriba', + 'Jebba', + 'Mohanpur Gaughata', + 'Fetromby', + 'Halwara', + 'Carignano', + 'Eagan', + 'Bambari', + 'Lachhmipur', + 'Khemis Miliana', + 'Candeias', + 'Nioro du Rip', + 'San Andres de la Barca', + 'San Juan Nepomuceno', + 'Knightdale', + 'Marano Vicentino', + 'Cergy', + "Ping'anbao", + 'Zhovti Vody', + 'Saalfelden am Steinernen Meer', + 'Lubumbashi', + 'Oxkutzkab', + 'Conakry', + 'Puenteareas', + 'Gotsucho', + 'Sohna', + 'Morehead City', + 'Lingshou', + 'Antotohazo', + 'Ceadir-Lunga', + 'Apucarana', + 'Ihosy', + 'Oulad Barhil', + 'Turkoglu', + 'Progreso', + 'Oued el Aneb', + 'Kendall West', + 'Kignan', + 'Camborne', + 'Bagha Purana', + 'Meskiana', + 'Do Gonbadan', + 'Geesthacht', + 'Lodja', + 'Winterthur', + 'Taketa', + 'Maple Heights', + 'Kelangah', + 'Stary Sacz', + 'Chaplynka', + 'Carlos Chagas', + 'Metlaoui', + 'Ciudad de Huitzuco', + 'Ranod', + 'Clevelandia', + 'Alabat', + 'Belovodskoe', + 'Arriaga', + 'Sandy Hook', + 'Sainte-Agathe-des-Monts', + 'Guacara', + "Quan'ancun", + 'Arques', + 'Nenmem', + 'Gravatal', + 'Saidabad', + 'Kolasin', + 'Merritt Island', + 'Jieshou', + 'Hvalba', + 'Maglaj', + 'Favara', + 'Elk Grove Village', + 'Oppeano', + 'Budai', + 'Novi', + 'Tambe', + 'Balungao', + 'Bocaranga', + 'Pickering', + 'Turuttiyad', + 'Fihaonana', + 'Kidira', + 'Hopkins', + 'Vitoria-Gasteiz', + 'Harpur', + 'Sibundoy', + 'El Kouif', + 'Furth im Wald', + 'Vazhani', + 'Mississippi Mills', + 'Sayville', + 'Bhawanipur', + 'Bel Air', + 'Lishui', + 'Katuria', + 'Sepatan', + 'Uwchlan', + 'Vieste', + 'Pfullendorf', + 'Poggio Renatico', + 'Balha', + 'Brandywine', + 'Manville', + 'Sokcho', + 'Gaigirgordub', + 'Kadattur', + 'Munnelli', + 'San Pancrazio Salentino', + 'Kourani', + 'As Suwayrah', + 'Lubliniec', + 'Poytya', + 'Stezzano', + 'Novomoskovsk', + 'Conchal', + 'Sandona', + 'Nishitokyo', + 'Gardnerville Ranchos', + 'Nyiregyhaza', + 'Encheng', + 'Al Maraghah', + 'Willmar', + 'Sharg`un', + 'Kaiken', + 'Peravur', + 'Ikot Abasi', + 'Swift Current', + 'Oizumi', + 'Lieksa', + 'Tarare', + 'Marton', + 'Berilo', + 'Oldham', + 'Kuppadi', + 'Horodnia', + 'Ataco', + 'Nassjo', + 'Wednesfield', + 'Los Bellosos', + 'Mombaca', + 'Recife', + 'Dalupo', + 'Capitan Pablo Lagerenza', + 'Tsarabaria', + 'La Guaira', + 'Ain Fakroun', + 'Tsiatosika', + 'Melikgazi', + 'San Gabriel', + 'Shek Wai Kok', + 'Pati do Alferes', + 'Nueva Santa Rosa', + 'Gerasdorf bei Wien', + 'Sesheke', + 'Nueva Rosita', + 'Diamante', + 'Fyzabad', + 'Masinloc', + 'Bejofo', + 'Ayolas', + 'Eski Arab', + 'Kaihua', + 'Narlica', + 'Amaga', + 'Haaltert', + 'Inhapim', + 'Itacurubi de la Cordillera', + 'Rawatsar', + 'Kakhovka', + 'Virapandi', + 'Dasai', + 'Laukaa', + 'Uummannaq', + 'Florin', + 'Perampuzha', + 'Mercato San Severino', + 'Zografos', + 'Bukit Gambir', + 'Mericourt', + 'Tchibota', + 'Mantova', + 'Santa Cruz Amilpas', + 'Atarra', + 'Saint-Jean', + 'Mansong', + 'Barbacha', + 'Perondi', + 'Lavasan', + 'Poljcane', + 'Haedo', + 'Sumber', + 'Riedisheim', + 'Manvel', + 'Willenhall', + 'Green Cove Springs', + 'Covasna', + 'Zantiebougou', + 'Mityana', + 'Schiffweiler', + 'San Antonio Sacatepequez', + 'Kalas', + 'Recreo', + 'Bugiri', + 'Rentachintala', + 'Babhani Bholwa', + 'Sedgley', + 'Dimapur', + 'Elkhart', + 'Musoma', + 'Luneburg', + 'New Iberia', + 'Espera Feliz', + 'Catumbela', + 'Leyte', + 'Palkot', + 'San Cugat del Valles', + 'Briancon', + 'Ermita', + 'Kunnumel', + 'Rain', + 'Barretos', + 'Jbail', + 'Hadibu', + 'Nagalapuram', + 'Sibagat', + 'Chamalieres', + 'Minamishibetsucho', + 'Gedaref', + 'Mrirt', + 'Rasra', + 'Holbeach', + 'San Angelo', + 'Olawa', + 'Sroda Wielkopolska', + 'Sabalpur', + 'Ban Plai Bua Phatthana', + 'Homer Glen', + 'Esparza', + 'Media', + 'Fasintsara', + 'Atarfe', + 'Tyamagondal', + 'Panganiban', + 'Arao', + 'Campogalliano', + 'Port Dickson', + 'Iracemapolis', + 'Ahmadabad', + 'Kruje', + 'Gourcy', + 'Penn', + 'Lubartow', + 'Yigilca', + 'Tiwi', + 'Buffelshoek', + 'Camacari', + 'Kunitomi', + 'Druento', + 'Qarabalyq', + 'Reghaia', + 'Edavanakad', + 'Baden-Baden', + 'Seevetal', + 'Puerto Viejo', + 'Segarai', + 'Khadbari', + 'Zag', + 'Amta', + 'Biritinga', + "Hayma'", + 'Nebbi', + 'Malacky', + 'Bacolod', + 'Nkouraba', + 'Cuatro Cienegas de Carranza', + 'Barishal', + 'Lower', + 'Lam Luk Ka', + 'Petershagen', + 'Balham', + 'Titusville', + 'Vijes', + 'Harpur Bochaha', + 'Sido', + 'Sogam', + 'Victorias', + 'Khaira', + 'Capanema', + 'Slatina', + 'Hajduszoboszlo', + 'Kentau', + 'Ba', + 'Amagasaki', + 'Changzhi', + 'Orange Lake', + 'Montigny-en-Gohelle', + 'Gangaikondan', + 'Shibin al Kawm', + 'Kai', + 'Bayanhongor', + 'Erer Sata', + 'Hamamatsu', + 'Cedar Rapids', + 'Lubbeek', + 'Pingyi', + 'Reo', + 'Ban Chomphu Nuea', + 'Mundi', + 'Kantai', + 'Genoa', + 'Agadir', + 'Wawarsing', + 'Best', + 'Tanjore', + 'Tiete', + 'Pacajus', + 'Varzea Grande', + 'Inawashiro', + 'Sigmaringen', + 'Banjarmasin', + 'Punata', + 'Fairview Heights', + 'Embu', + 'Brea', + 'Mohacs', + 'Rosedale', + 'Hammam al `Alil', + 'Papireddippatti', + 'Beaumont-sur-Oise', + 'Makokou', + 'Eastchester', + 'Huntington', + 'Zarumilla', + 'San Sebastian de Buenavista', + 'Ouezzane', + 'Ambalarondra', + 'Waukegan', + 'San Pablo Huixtepec', + 'Kasangati', + 'Boanamary', + 'Nancy', + 'El Pueblito', + 'South Miami Heights', + 'Sayyid Sadiq', + 'Xghajra', + 'Bulacan', + 'Tanichchiyam', + 'Heilbron', + 'Sao Caetano de Odivelas', + 'Ayos', + 'Rawasari', + 'Herve', + 'Fairview Shores', + 'Naklo nad Notecia', + 'San Carlos', + 'Varazze', + 'Konak', + 'Kattakampala', + 'New Berlin', + 'Tuam', + 'Talaigua Nuevo', + 'Suchanino', + 'Alcobendas', + 'Mangalapuram', + 'Lombard', + 'Temacine', + 'Fancheng', + 'Norristown', + 'Kasrawad', + 'Memuro-minami', + 'Newman', + 'Kumamoto', + 'Shimizu', + 'Adrogue', + 'Belo Horizonte', + 'Kurtalan', + 'Gulf Shores', + 'Kenitra', + 'Esquipulas Palo Gordo', + 'Dursunbey', + 'Pazhayannur', + 'Little Elm', + 'Bassersdorf', + 'Sant Just Desvern', + 'Surir', + 'Goodlettsville', + 'Nanjo', + 'Al Jammaliyah', + 'Binyin', + 'Osterwieck', + 'Novoishimskiy', + 'Ananas', + 'Lower Moreland', + 'Gradsko', + 'Qiaotou', + 'Gole', + 'Marale', + 'Kantang', + 'Rajapur', + 'Boha', + 'Orbetello', + 'Suances', + 'Cheshunt', + 'Nagykallo', + "Bu'aale", + 'Chikitigarh', + 'Djenne', + 'Le Luc', + 'Sankeshwar', + 'Hrebinka', + 'Ronchin', + 'Pugo', + 'Putnam Valley', + 'Valka', + 'Sig', + 'Andernos-les-Bains', + 'Neuchatel', + 'Varginha', + 'Noventa Vicentina', + 'Athy', + 'Aradeo', + 'Cururupu', + 'Funza', + 'Alghero', + 'Termiz', + 'Rockwall', + 'Joliet', + 'Alatri', + 'Damarcherla', + 'Nako', + 'Muzaffarnagar', + 'Kazlu Ruda', + 'Urun-Islampur', + 'Thorpe Saint Andrew', + 'Baubau', + 'Felixstowe', + 'Mimata', + 'Babenhausen', + 'Valley Center', + 'Trabzon', + 'Ban Pak Phun', + 'Koudougou', + 'Wisbech', + 'Danvers', + 'Dhamnod', + 'Rudrur', + 'Nueva Italia de Ruiz', + 'Beacon', + 'Deerfield Beach', + 'Tinkhang', + 'Aiyomojok', + 'Gevas', + 'Salou', + 'Pontarlier', + 'Valluvandad', + 'Pingtouchuanxiang', + 'Takasagocho-takasemachi', + 'Marsaskala', + 'Zequ', + 'Sarapui', + 'Bhangha', + 'Saint Albans', + 'Fountain Hills', + 'Bultfontein', + 'Plant City', + 'Bidestan', + 'Alvorada', + 'Faenza', + 'Benyahia Abderrahmane', + 'Al Hindiyah', + 'Vohitromby', + 'Guadalupe Victoria', + 'Croissy-sur-Seine', + 'Agudo', + 'Carire', + 'Rho', + 'Gassino Torinese', + 'Uzice', + 'Magny-le-Hongre', + 'Bazimini', + 'New York', + 'Croata', + 'Shahgarh', + 'Nyzhnohirskyi', + 'St. Simons', + 'Fleury-les-Aubrais', + 'Paulino Neves', + 'Buderim', + 'Kapchagay', + 'Ahogbeya', + 'Warabi', + 'Sasagawa', + 'Edappalli', + 'Bobleshwar', + 'Sardinata', + 'Niederhasli', + 'Grez-Doiceau', + 'Crimmitschau', + 'Bound Brook', + 'Magog', + 'Pottstown', + 'Loveland', + 'Fort Mill', + 'Kpandu', + 'Ngoc Son', + 'San Agustin', + 'Turkeli', + 'Keynsham', + 'Padre Las Casas', + 'Huangzhai', + 'Acul du Nord', + 'Sha Tin', + 'Reddipalle', + 'Dalby', + 'Chigwell', + 'Chaigoubu', + 'Marolinta', + 'Tarma', + 'Schwabisch Gmund', + 'La Teste-de-Buch', + 'Bouna', + 'Creil', + 'Balatonalmadi', + 'Srivardhan', + 'Ettenheim', + 'Spanish Town', + 'Castellabate', + 'Salimpur', + 'Bhusaval', + 'Ayomi', + 'Babhniyawan', + 'Dundankop', + 'Khulna', + 'Incheon', + 'Prizren', + 'Yuvileine', + 'Taastrup', + 'Maibara', + 'Liberec', + 'Bordj Bou Arreridj', + 'Bueu', + 'Baba I', + 'Uttarkashi', + 'Karuizawa', + 'Ikast', + 'Tapolca', + 'Matrah', + 'Chakia', + 'Sosa', + 'Kabalo', + 'High River', + 'Chemnitz', + 'Mirganj', + "Mohale's Hoek", + 'Capena', + "Fanja'", + 'Idumbavanam', + 'Travagliato', + 'Noicattaro', + 'Lasht-e Nesha', + 'Bel Air North', + 'Missour', + 'Itaipe', + 'Sandpoint', + 'Villepinte', + 'Tendukheda', + 'Ljubovija', + 'Mount Pleasant', + 'Yucca Valley', + 'Lai Chau', + 'Litija', + 'Liberty Lake', + 'Kidamangalam', + 'Cambara', + 'Funchal', + 'Nansana', + 'Ongwediva', + 'Jamhra', + 'Gulbene', + 'Hot Springs Village', + 'Gothini', + "Douar 'Ayn Dfali", + 'Belsh', + 'Shimokodanaka', + 'Gerash', + "Welench'iti", + 'Pozorrubio', + 'Placer', + 'Streatham', + 'Muttalakanpatti', + 'Bougival', + 'Porteiras', + 'Liuguoju', + 'Sultanhani', + 'Dhutauli', + 'Jilin', + 'Dongsu', + 'Saho', + 'Santa Cecilia', + 'Jalor', + 'Kuruvambalam', + 'Maraba', + 'Yairipok', + 'Shafter', + 'Zabari', + 'Sauzal', + 'Barharwa', + 'Sisia', + 'Fabriano', + 'Buttar', + 'Corlu', + 'Kailashahar', + 'Mosina', + 'Novo Oriente', + 'Ploemeur', + 'Goshaingaon', + 'Amba Icharua', + 'Lehara', + 'eXobho', + 'Waldenbuch', + 'Marimba', + 'Kavundappadi', + 'Al Hudaydah', + "Castelnovo ne' Monti", + 'Ipuiuna', + 'Az Zawiyah', + 'Barros Blancos', + 'Royan', + 'Zaojiao', + 'Tiztoutine', + 'Tanashicho', + 'Ebbw Vale', + 'Swidnik', + 'Snohomish', + 'Melendugno', + 'Pavia', + 'Whitman', + 'Cuilo', + 'Russas', + 'Callao', + '`Izbat al Burj', + 'Lushoto', + 'Dayr al Barsha', + 'Pepinster', + 'Bustos', + 'Ureshinomachi-shimojuku', + 'Sabugal', + 'Forest City', + 'Afonso Bezerra', + 'Kaliganj', + 'Hato Mayor', + 'Kete Krachi', + 'Rasipuram', + 'Ennepetal', + "Cassano d'Adda", + 'Ronne', + 'Olpe', + 'Karlsruhe', + 'Janpur', + 'Wepener', + 'Happy Valley', + 'Bhadrakh', + 'Gaz', + 'Kayanza', + 'St. Ann', + 'Chitcani', + 'Tafrant', + 'Itabuna', + 'Iracoubo', + 'Mirzanagar', + 'Livry-Gargan', + 'Bequimao', + 'Dazhangzi', + 'Zhugang', + 'Waalre', + 'La Roda', + 'Kamthi', + 'Piggs Peak', + 'Mahadeopur', + 'Annakattumula', + 'Patterson', + 'Koriukivka', + 'Altenberge', + 'Rajula', + 'Hayden', + 'Yambio', + 'Saram', + 'Vereeniging', + 'Beauharnois', + 'La Ravoire', + 'Kulhudhuffushi', + 'Alatsinainy-Bakaro', + 'Nedugula', + 'Goaso', + 'Araucaria', + 'Fort Washington', + 'Sargur', + "Boula'wane", + 'Hussepur', + 'Frederick', + 'An Nimas', + 'Joao Pessoa', + 'La Eliana', + 'Samsun', + 'Bang Phlat', + 'Ketugram', + 'Dhobauli', + 'Bad Wildungen', + 'Estancia Pozo Colorado', + 'Roseller Lim', + 'Skarzysko-Kamienna', + 'Alba', + 'Sengurichchi', + 'Togitsu', + 'Chas', + 'Petlad', + 'El Rama', + 'Parkstone', + 'Lorca', + 'Western Bicutan', + 'Harare', + 'Fort Valley', + 'Staryy Beyneu', + 'Mainit', + 'Rosales', + 'Isafjordhur', + 'Moreau', + 'Kapra', + 'Tarascon', + 'Tubbergen', + 'Santa Comba Dao', + 'Fatikchari', + 'Sumy', + 'Ignacio de la Llave', + "Sant'Elpidio a Mare", + 'Pocono', + 'Kakogawacho-honmachi', + 'Guajara-Mirim', + 'Beekman', + 'Meymand', + 'University of Virginia', + 'Haines City', + 'Simao Dias', + 'Signal Hill', + 'Makedonski Brod', + 'Kamakurayama', + 'Blackpool', + 'Poxoreo', + 'Kohir', + 'Talata-Angavo', + 'Bielefeld', + 'Tomesti', + 'Universal City', + 'Kotka', + 'Uttukkottai', + 'Baruta', + 'Solana', + 'Ain Mediouna', + 'Garoowe', + 'Ap Tan Ngai', + 'Sarigol', + 'Muong Lay', + 'Guryongpo', + 'Moorestown', + 'Nottampatti', + 'Marienheide', + 'Tiruppur', + 'Bikkatti', + 'Fereydunshahr', + 'Ezhamkulam', + 'Ajax', + 'Buguda', + 'Shijiazhuang', + 'Pantanal', + 'Meylan', + 'Dobanovci', + 'Bizerte', + 'Gingin', + 'Baroni Khurd', + 'Cusseta', + 'Alhambra', + 'Chaoyang', + 'Wakema', + 'Holland', + 'Prunedale', + 'Akure', + 'Huautla', + 'Ghora Gali', + 'Ayase', + 'Ciudad de Loreto', + 'Bittou', + 'Andong', + 'Halluin', + 'Yuncos', + 'Eurajoki', + 'Morden', + 'Gueret', + 'Mutukula', + 'Abovyan', + 'Al Hufuf', + 'Tapaua', + 'Tegalbuleud', + 'Brackenheim', + 'Tanaina', + 'Siloam Springs', + 'Kuusankoski', + 'Concordia', + 'Parsippany', + 'Lapanga', + 'Manhuacu', + 'Extrema', + 'El Manteco', + 'Setti Fatma', + 'Guneysinir', + 'Sagara', + 'Kohima', + 'Guacheta', + 'Hidaj', + 'Gollalagunta', + 'Sawla', + 'Tamazouzt', + 'Qoryooley', + 'Constantine', + 'Boulsa', + 'Bhataulia', + 'Oued Athmenia', + 'Villa Ballester', + 'Leipzig', + 'Khewra', + 'Wondelgem', + 'Somanya', + 'Melissa', + 'Wangdue Phodrang', + 'Rietavas', + 'Kirkby in Ashfield', + 'Khadra', + 'Whitemarsh', + 'Palauig', + 'Aleg', + 'El Barrio de la Soledad', + 'Rosario Oeste', + 'Pappakudi', + 'Zorneding', + 'Grodzisk Mazowiecki', + 'Osowa', + 'Samayanallur', + 'Manampizha', + 'Bago', + 'Country Club', + "Lu'an", + 'Santa Teresinha (2)', + 'East Rockaway', + 'Minja', + 'Ilkhechi', + 'Moreno', + 'Novoazovsk', + 'Seondha', + 'Castleton', + 'Hannover', + 'Bhopal', + 'Barnoi', + 'Casaluce', + 'Hola', + "Trezzo sull'Adda", + 'Minxiong', + 'Eboli', + 'Bairiya', + 'Bad Waldsee', + 'Montagu', + 'Torres', + 'Pelitli', + 'Ararenda', + 'Coronel Fabriciano', + 'Monteria', + 'Kavlinge', + 'Grand Gosier', + 'Tidjikja', + 'Palayad', + 'Kattipudi', + 'Kibichuo', + 'Guoxing', + 'Viera West', + 'Aw Dheegle', + 'Ipanguacu', + 'Oued Fodda', + 'Yoju', + 'Leesburg', + 'Sathiala', + 'Senkaya', + 'Kucevo', + 'Mandal', + 'Dauis', + 'Oyon', + 'Kulmbach', + 'Macul', + 'Aysha', + 'Ash Shajarah', + 'Huishi', + 'Chiyoda-ku', + 'Ceccano', + 'Massenya', + 'Impasugong', + 'Champlin', + 'Saint Ives', + 'Sycamore', + 'Mahalgaon', + 'Yanqi', + 'Puren', + 'Koronadal', + 'Santa Catarina Ixtahuacan', + 'Dulmial', + 'Bourg-les-Valence', + 'Du Yar', + 'Marsa', + 'Bootle', + 'Swidnica', + 'Saint-Avold', + 'Goleta', + 'Peshkopi', + 'Ahenkro', + 'Fort Lewis', + 'Barra do Garcas', + 'Greytown', + 'Thouare-sur-Loire', + 'Abdul Hakim', + 'Pont-Sainte-Maxence', + 'Malindi', + 'Meridian', + 'Katsuyama', + 'Pigue', + 'Iradan', + 'Mukerian', + 'Yuzhang', + 'Quang Ngai', + 'Vila Nova de Gaia', + 'Tembagapura', + 'California City', + 'Dolores Hidalgo Cuna de la Independencia Nacional', + 'Guazacapan', + 'Tympaki', + 'Zhuozhou', + 'Tangub', + 'Gelemso', + 'Paal', + 'Hassi Berkane', + 'Plandiste', + 'Eduttavaynattam', + 'Alice', + 'Hannibal', + 'Novi Travnik', + 'Kairana', + 'Ilinden', + 'Mmabatho', + 'Hoyacho', + 'Menzel Bourguiba', + 'Tinsukia', + 'Medina del Campo', + 'Ramon', + 'Pierrefitte-sur-Seine', + 'Monroeville', + 'Randaberg', + 'Maple Shade', + 'Grottaglie', + 'Hisua', + 'Endicott', + 'Palagiano', + 'Martigues', + 'Huacho', + 'Wangtang', + 'Daiyue', + 'Chiguayante', + 'Einbeck', + 'Tezonapa', + 'Sousse', + 'Vikravandi', + 'Hellemmes-Lille', + 'Manampaneva', + 'Spresiano', + 'San Jose La Arada', + 'Salina Cruz', + 'Pindamonhangaba', + 'Bad Aibling', + 'Zhangjiazhuangcun', + 'Selestat', + 'Ascencion de Guarayos', + 'Hastinapur', + 'Xangda', + 'Trujillo Alto', + "Ji'an", + 'Dapaong', + 'Kernersville', + 'Conceicao do Coite', + 'Aguas Santas', + 'A Coruna', + 'Yanshanbu', + 'Boizenburg', + 'Zachary', + 'Lake Forest', + 'La Paz', + 'Malta', + 'Alfreton', + 'Halfeti', + 'Paracuru', + 'Valdivia', + 'Belen', + 'Cawayan', + 'Oda', + 'Ankililoaka', + 'Pondaluru', + 'Larkana', + 'Polaia Kalan', + 'Curua', + 'Hayashima', + 'Brigham City', + 'Beverungen', + 'Towcester', + 'Nove Zamky', + 'Palpa', + 'Gollapudi', + 'Gescher', + 'Camboriu', + 'El Plan', + 'Sokolo', + 'Titel', + 'Vallentuna', + 'Montreux', + 'Delmas', + 'Edirne', + 'Ngolonianasso', + 'Nsiika', + 'Rowlett', + 'Kuchlagh', + 'La Palma del Condado', + 'Senago', + 'Antanandava', + 'Steger', + 'Thatta', + 'Al Hamzah', + 'Krolevets', + 'Cabrera', + 'Baia-Sprie', + 'Santiago Suchilquitongo', + 'Aabenraa', + 'San Marco in Lamis', + 'Kota Bharu', + 'Guayabal', + 'Dasaut', + 'Kauhava', + 'Sabie', + 'Garrucha', + 'Maungdaw', + 'Kaduturutti', + 'Castenedolo', + 'Rio Rico', + 'Varese', + 'Chadchan', + 'Seoni Chhapara', + 'Rodeo', + 'Saint-Esteve', + 'Kierspe', + 'Velke Mezirici', + 'Khajuraho', + 'Thuin', + 'Douz', + 'Bac Lieu', + 'Differdange', + 'Imqabba', + 'Barao de Cocais', + 'Bijar', + 'Payerne', + 'Janakammapeta', + 'Takaishi', + 'Los Alamos', + 'Xianshuigu', + 'San Giuliano Terme', + 'Leigh', + 'Sinjar', + 'Chalastra', + 'Souakene', + 'Carmo do Cajuru', + 'Tirumangalam', + 'Nagardevla Budrukh', + 'Aqchah', + 'As Sars', + "'Ain el Assel", + 'Cusco', + 'Bacarra', + 'Maracacume', + 'Tanhuato de Guerrero', + 'Winston-Salem', + 'Fukuchiyama', + 'Grangemouth', + 'Olive Branch', + 'Zumpango del Rio', + 'Mardan', + 'Greenwood', + 'Torre de Moncorvo', + 'Nanthankulam', + 'Gadzin Han', + 'Manappakkam', + 'San Vicente Pacaya', + 'Suresnes', + 'Piraziz', + 'Anadia', + 'Valle Nacional', + 'Zhongdong Shequ', + 'Howick', + 'San Pedro de Uraba', + 'Konaje', + 'Rayappanpatti', + 'Nanuque', + 'Zhutailing', + 'Bovec', + 'Villa Riva', + 'Sanza Pombo', + 'Brossard', + 'Olbia', + 'Bredene', + 'Kaliro', + 'Joetsu', + 'Kruszwica', + 'Surajpura', + 'Primeira Cruz', + 'Hanumana', + 'Ghattupal', + 'Changamkari', + 'Bistrita', + 'Les Herbiers', + 'As Suqaylibiyah', + 'Quebec City', + 'Wiehl', + 'Waltrop', + 'Kadiana', + 'Tadapurambakkam', + 'Delportshoop', + 'Pinghu', + 'Bad Endorf', + 'Singarayakonda', + 'Prestwich', + 'Weesp', + 'Itamukkala', + 'Ad Dab`ah', + 'Salvador', + 'Weipa', + 'Agdam', + 'Tolu Viejo', + 'Erzincan', + 'Arfoud', + 'Giulianova', + 'El Paso de Robles', + 'Pedda Nindrakolanu', + 'Shawangunk', + 'Flamanzi', + 'Icod de los Vinos', + 'Vatananto', + 'Frattaminore', + 'Sterling Heights', + 'Zhongliao', + 'Tirmalgiri', + 'Deutsch-Wagram', + 'Peddaboddepalle', + 'Kilosa', + 'Gulbaar', + 'Malema', + 'Novo Horizonte', + 'Blairgowrie', + 'Nieder-Olm', + 'Pertuis', + 'Xacmaz', + 'Tenerife', + 'Krishnapuram', + 'Molndal', + 'Luba', + 'Puttalam', + 'Jegunovce', + 'Kauniainen', + 'Zapatoca', + 'Abong Mbang', + 'Tiana', + 'Ryki', + 'Mitai', + 'Mullingar', + 'Dom Pedro', + 'Simiti', + 'Naguilian', + 'Roccapiemonte', + 'Lanaken', + 'Svatove', + 'Salto del Guaira', + 'Liman', + 'Paisley', + 'Skwierzyna', + 'Balagtas', + 'Belma', + 'Olathe', + 'Kumil', + 'Hamworthy', + 'Solwezi', + 'Fucheng', + 'Lanivo', + 'Dao', + 'Putao', + 'Rafaela', + 'Yozgat', + 'Sao Joao da Boa Vista', + 'Sapanca', + 'Prebold', + 'Gwangyang', + 'Imerintsiatosika', + 'Munnalam', + 'Tizayuca', + 'Sorso', + 'Anah', + 'Locarno', + 'Wageningen', + 'Muhammadganj', + 'Binangonan', + 'San Gregorio de Nigua', + 'Oneonta', + 'Bole', + 'Mannara', + 'Darnah', + 'Habo', + 'Aubagne', + 'Pinili', + 'Ostuni', + 'Jitwarpur Chauth', + 'Mainaschaff', + 'Inderbor', + 'Westlake', + 'Esher', + 'Gaimersheim', + 'Fort Knox', + 'Tiruvambadi', + 'Towada', + 'Kasangulu', + 'Chernihiv', + 'Pardubice', + 'Ekibastuz', + 'Tramore', + 'Luchenza', + 'Asarganj', + 'Mascalucia', + 'Bartica', + 'Ipixuna', + 'Gainza', + 'Buea', + 'Puerto Concordia', + 'Mohammadabad', + 'Kindia', + 'Kadingilan', + 'Amboasary-Gara', + 'Kanchanadit', + 'Sabaneta', + 'Amingaon', + 'Los Polvorines', + 'Catano', + 'Alto Longa', + 'Zharkent', + 'Samba', + 'Hempstead', + 'Arandelovac', + 'Ghogha', + 'Cluses', + 'Kumarapalaiyam', + 'Warendorf', + 'Ath', + 'Thanh Xuan', + 'Esik', + 'Luneville', + 'Burgstadt', + 'Deggendorf', + 'Kozlu', + 'Abdullahnagar', + 'Penarroya-Pueblonuevo', + 'Curug', + 'Cosmopolis', + 'Schoningen', + 'Desri', + 'Tandubas', + 'Masiu', + 'Petawawa', + 'Buxerolles', + 'Wazirabad', + 'Wath upon Dearne', + 'Agios Athanasios', + 'Novi Marof', + 'Chotebor', + 'Ciudad General Belgrano', + 'Kitaibaraki', + "'s-Gravenzande", + 'Wellington North', + 'Jiuduhe', + 'Villingen-Schwenningen', + 'Stockbridge', + 'Wauconda', + 'Kayyngdy', + 'Sultandagi', + 'Torshavn', + 'Wadala Sandhuan', + 'Nordkirchen', + 'Nandikotkur', + 'Geisenheim', + 'Shirvan', + 'Port Victoria', + 'Santo Antonio de Jesus', + 'Matulji', + 'Bagaces', + 'Thisted', + 'Perur', + 'Coreau', + 'Chitose', + 'Marka', + 'Weener', + 'Barrinha', + 'Limburg', + 'Qazax', + 'Itapa-Ekiti', + 'Cajetina', + 'Bendougouba', + 'Buriti do Tocantins', + 'Uppur', + 'Hikari', + 'Parnarama', + 'Hammerfest', + 'Alcala la Real', + 'Deolali', + 'Arukutti', + 'Dar Si Aissa', + 'Ratia', + 'Gangaura Behra', + 'Mehdya', + 'Vagos', + 'Ramdeora', + 'Saint Helena Bay', + "'Ain Fekan", + 'Saint-Germain-les-Arpajon', + 'Farrukhabad', + 'Palmeiras', + 'Saint-Lo', + 'Marwa', + "Khmis Sidi al 'Aydi", + 'San Joaquin', + 'Koteshwar', + 'Pandalkudi', + 'Kukawa', + 'Lydney', + 'Kanchanpalli', + 'Chlef', + 'Moquegua', + 'Yanguancun', + 'Dalavaypattanam', + 'Gundlupet', + 'Sabinov', + 'Praia Grande', + 'Tamba-Sasayama', + 'Dachengzicun', + 'Biloziria', + 'Sanaur', + 'Rudehen', + 'Palmares', + 'Fairfax Station', + 'Bangor', + 'Mula', + 'North Chicago', + 'Koencho', + 'Mahibadhoo', + 'Vallejuelo', + 'Cajola', + 'Mvurwi', + 'Jinku', + 'Zgorzelec', + 'Barnstable', + 'San Ignacio de Velasco', + 'Fuquan', + 'Bayan Lepas', + 'Clovis', + 'Kappeln', + 'Ivaipora', + 'Piraquara', + 'Capul', + 'Sieradz', + 'Mombasa', + 'Bayawan', + 'Kahrizak', + 'Rohnert Park', + 'Ouzera', + 'Mampong', + 'Mezokovesd', + 'Briceni', + 'Pedara', + 'Koch Bihar', + 'Thaba Nchu', + 'Dehti', + 'Sebnitz', + 'Metz', + 'Mansa', + 'Sanwer', + 'Bingawan', + 'Vechur', + 'Kirkja', + 'Tororo', + 'Miandasht', + 'Peringom', + 'Nambutalai', + 'Zarafshon Shahri', + 'Deer Park', + 'Atalaia', + 'Congleton', + 'Srebrenik', + 'Rauch', + 'Slavuta', + 'Vengattur', + 'Bokoro', + 'Pureparo de Echaiz', + 'Osijek', + 'Divrigi', + 'East San Gabriel', + 'Gapan', + 'Comilla', + 'Ropar', + 'Oak Bay', + 'San Felipe', + 'Dinmanpur', + 'Konigslutter am Elm', + 'Nittenau', + 'Ardahan', + 'Kelaa Kebira', + 'Gulcho', + 'Sanzhou', + 'Albinea', + 'Acireale', + 'Wadegaon', + 'Australind', + 'Yaopu', + 'Ramnagar', + 'Tummalacheruvu', + 'Morretes', + 'Askoy', + 'Teziutlan', + 'Tupiza', + 'Monastir', + 'Dumri', + "Estrela d'Oeste", + 'Ribeirao Pires', + 'Holesov', + 'Ghatal', + 'Santa Maria Huatulco', + 'Vleuten', + 'Ambatomiady', + 'Isale', + 'Chalmette', + 'Tirukkoyilur', + 'Ishikari', + 'Lunglei', + 'Desuri', + 'Kaikoura', + 'Vaghodia', + 'Al Fayd', + 'Kahoku', + 'Bettendorf', + 'Palafolls', + 'Hadzici', + 'Jocoro', + 'Nanto', + 'Botelhos', + 'St. Clair Shores', + 'Rahon', + 'Monte Santo', + 'Watsonville', + 'Shankar Saraiya', + 'Zlin', + 'Maner', + 'Sauce', + 'Pudupattanam', + 'Midalt', + 'Jarinu', + 'Goma', + 'Laqtah', + 'Ghorbanki', + 'Nehoiu', + 'Comayaguela', + 'Sardhana', + 'Butiama', + 'Novo Hamburgo', + 'Isahaya', + 'Rouiba', + 'Minatitlan', + 'Devikolam', + 'Putatan', + 'Ala', + 'Sirsi', + 'Lawrence', + 'Sarapaka', + 'Turmalina', + 'Yaguara', + 'Tongjin', + 'Monte Alegre de Minas', + 'Nesher', + 'Morsand', + 'Rafsanjan', + 'Brisbane', + 'Rheinberg', + 'Assamannur', + 'Unterageri', + 'Sanquelim', + 'Steinhaus', + 'Banja Luka', + 'Guaimaro', + 'Luquembo', + 'Ambatomarina', + 'Kucove', + 'Thilogne', + 'Sangereng', + 'Beni Saf', + 'Itabela', + 'Woodfield', + 'Sandavagur', + 'Drobak', + 'Fuldabruck', + 'San Luis Obispo', + 'Chandralapadu', + 'Kanp', + 'Sainte-Sophie', + 'Karakopru', + 'Maple Grove', + 'Yachiyo', + 'Wuling', + 'Foammulah', + 'Mouvaux', + 'Gorizia', + 'Umarkot', + 'Forestdale', + 'Rawatbhata', + 'Bongaree', + 'Jalingo', + 'Koog aan de Zaan', + 'Lapy', + 'Rayleigh', + 'Khandsa', + "Fontaine-l'Eveque", + 'Den Helder', + 'Nanmucun', + 'Sakaraha', + 'Flixton', + 'Magenta', + 'Muroto-misakicho', + 'Panhar', + 'Ait Ourir', + 'Mawkanin', + 'Cheadle', + 'Kuusamo', + 'Velivennu', + 'Espoo', + 'Kakhandiki', + 'Kortenberg', + 'Krasnodon', + 'Hennigsdorf', + 'Olmos', + 'Maropaika', + 'Mahadipur', + 'Itatuba', + 'Uckfield', + 'Belle Glade', + 'Acatlan de Osorio', + 'Elfers', + 'Al Jumayl', + 'Hatsukaichi', + 'At Tall', + 'Uzynaghash', + 'Beatrice', + 'Frogn', + 'Yao', + 'Monfort Heights', + 'El Mansouria', + 'Sileby', + 'Emerald', + 'Alubijid', + 'Llanquihue', + 'Yerrapalem', + 'Tuguegarao', + 'Tiel', + 'Mahallat', + 'Varena', + 'Cherukunnu', + 'Lahat', + 'Nevsehir', + 'Suonan', + 'Cloncurry', + 'Chikhli Kalan', + 'Vedasandur', + 'Coyaima', + 'Yinajia', + 'Rewtith', + 'Villa Adelina', + 'Siur', + 'Urucania', + 'Petrinja', + 'Bruntal', + 'Wietmarschen', + 'Tumkur', + 'Qana', + 'Darlowo', + 'Qamdo', + 'Nova Gradiska', + 'Barhiya', + 'Balighattam', + 'St. Petersburg', + 'Paglat', + 'Nauhata', + 'Derdara', + 'Barai', + "King's Lynn", + 'San Pablo Tacachico', + 'Presidente Bernardes', + 'Elizabethton', + 'Ezhou', + 'Kallamalai', + 'Estrela', + 'Tuburan', + 'Solapuram', + 'Dohta', + 'Yazihan', + 'Monett', + 'Mayang Imphal', + 'Starse', + 'Chilon', + 'Libano', + 'Raneswar', + 'Raydah', + 'Tanjungpandan', + 'Puning', + 'Cabreuva', + 'Shoufeng', + 'Ipecaeta', + 'Lujan', + 'Sultanhisar', + 'Kaynarca', + 'Mwanza', + 'Newport', + 'Beypazari', + 'Ramshir', + 'Trakai', + 'Mohiuddinnagar', + 'Geddes', + 'Rinteln', + 'Nagarur', + 'Bagra', + 'Polkowice', + 'Ban Sathan', + 'Cotonou', + 'Trier', + 'Odenthal', + 'Oyim', + 'Tajarhi', + 'Birigui', + 'Moravske-Toplice', + 'Tatebayashi', + 'Serravalle Pistoiese', + 'Targuist', + 'Datu Paglas', + 'Aral', + 'Mazarron', + 'Dank', + 'Elk City', + 'Chandankiari', + 'Shatiancun', + 'Kakamas', + 'Sikat', + 'Bhawanandpur', + 'Stone Ridge', + 'Kushimoto', + 'Great Wyrley', + 'Tall Banat', + 'Yunfu', + 'Westville', + 'Karkamis', + 'Ingabu', + 'Rensselaer', + 'Tonbridge', + 'Phai Sali', + 'Vaiano', + 'Bikramganj', + 'Nawsari', + 'Akcaabat', + "Al Qa'im", + 'Carambei', + 'Boloso', + 'Shijiazhuangnan', + 'Sidi Lahsene', + 'River Edge', + 'Shuozhou', + 'Walnut Park', + 'Ben Chicao', + 'Eggenfelden', + 'Floriano', + 'Sevilimedu', + 'Vesoul', + 'Chinna Kalaiyamputtur', + 'Los Cordobas', + 'Malimono', + 'Eslamabad-e Gharb', + 'Akersberga', + 'Bhaktapur', + 'Tokha', + 'Egelsbach', + 'Windham', + 'Shantou', + 'Joyo', + 'Asni', + 'Camacan', + 'Lomas de Zamora', + 'Pedernales', + 'Presidente Medici', + 'Itabirito', + 'Heris', + 'Zarraga', + 'Korgas', + 'Horti', + 'Aksaray', + 'Ghal Kalan', + 'Apex', + 'Tummapala', + 'Vavveru', + 'Maumee', + 'Chicomba', + 'La Madeleine', + 'Mekra', + 'Mataquescuintla', + 'Sitio do Quinto', + 'Khanah Sur', + 'World Golf Village', + 'Mambore', + 'Limoux', + 'Hinode', + 'Spodnji Duplek', + 'Yancheng', + 'Patia', + 'Caiguantun', + 'Gualdo Tadino', + 'Fulshear', + 'Dacun', + 'Nawabshah', + 'Sannicolau Mare', + 'Zontecomatlan de Lopez y Fuentes', + 'Devanangurichchi', + 'Kovur', + 'Manikganj', + 'Qal`ah-ye Zal', + 'Schuttorf', + 'Fushun', + 'Tsuruno', + 'Sikhio', + 'Tomelloso', + 'Israin Kalan', + 'Dinajpur', + 'Douar Oulad Mbarek', + 'Afanyangan', + 'North Branch', + 'Pattukkottai', + 'Malo Crnice', + 'Taboao da Serra', + 'Kathurah', + 'Tottori', + 'Gundlapelle', + 'Dusseldorf', + 'Guisser', + 'Abuyog', + 'Sabalgarh', + 'Cabugao', + 'Pata Kalidindi', + 'Mountain Top', + 'Limonar', + 'Urupes', + 'Mudhol', + 'Peranampattu', + 'Deyr', + 'San Enrique', + 'Santa Lucia', + 'Ghoriyan', + 'Pervomaiskyi', + 'Obikiik', + 'Jauli', + 'Grevenbroich', + 'Chupaca', + 'Ban Bueng Kok', + 'Rada`', + 'Chapeltique', + 'Ghazipur', + 'Barcarena Nova', + 'Kayaralam', + 'Saint-Gilles', + 'Crosby', + 'Tatahuicapan', + 'Nedumangad', + 'Meitingen', + 'Matomou', + 'Balud', + 'Yatou', + 'Stephanskirchen', + 'Same', + 'Rome', + 'Oud-Heverlee', + 'Mau Dhaneshpur', + 'Sparks', + 'Kyotango', + 'Hirokawa', + 'Kahama', + 'General Emilio Aguinaldo', + 'Bozhou', + 'Areiopolis', + 'Izumisano', + 'Karmauli', + 'Itele', + 'Ghedi', + 'Champdani', + 'Ringas', + 'Istog', + 'Madre de Deus', + 'San Jose', + 'Duraiswamipuram', + 'Kele', + 'Andujar', + 'Erdemli', + 'Iwakuni', + 'Clarines', + 'Saint-Martin-de-Crau', + 'Kankipadu', + 'Bantayan', + 'Kilibo', + 'Kerpen', + 'Moultrie', + 'Belaur', + 'Juan Rodriguez Clara', + "P'yongch'ang", + 'Verukulambu', + 'Esenyurt', + 'Ban Muang Ngam', + 'Wako', + 'Yankou', + 'Radeberg', + 'Haddada', + 'Mill Creek East', + 'Szekesfehervar', + 'Lower Saucon', + 'Lijiacha', + 'Xushan', + 'Tumauini', + 'Molakalumuru', + 'Mayureswar', + 'Gnarrenburg', + 'Kefamenanu', + 'Lockhart', + 'Pulgaon', + 'El Palmar', + 'Champlain', + 'Adalpur', + 'Omboue', + 'Yueyaquan', + 'Melila', + 'Potomac Park', + 'Barranqueras', + 'Santa Cruz de la Sierra', + "Yan'an", + 'Talwara', + 'Kod', + 'Hechi', + 'Green Bay', + 'Murzuq', + 'Farnham', + 'Roznava', + 'Merate', + 'Nathana', + 'Malakwal', + 'Buhi', + 'Tadepallegudem', + 'Monastyryshche', + 'Sneek', + 'Gaoshu', + 'Pergine Valsugana', + 'Fond des Blancs', + 'College', + 'Liuliang', + 'Huilongcun', + 'Ballymena', + 'Fitampito', + 'Herzogenaurach', + 'Weilheim', + 'Melegnano', + 'Coribe', + 'Thondiamannu', + 'Bermejo', + 'Granville', + 'Retalhuleu', + 'Mayari', + 'Tall Qasab', + 'Bisaria', + 'Bellinzona', + 'Klodzko', + 'Dessalines', + 'Zamosc', + 'Village St. George', + 'Sturgis', + 'Sanyi', + 'Vennandur', + 'El Paso', + 'Andurkonam', + 'Zitsa', + 'Dainyor', + 'Kaysville', + 'Tijucas do Sul', + 'Cap-Haitien', + 'Linslade', + 'Banki', + 'Santo Antonio do Amparo', + 'Diapaga', + 'Buqda Caqable', + 'Cochabamba', + 'Irthlingborough', + 'Ena', + 'Donostia', + 'Dippoldiswalde', + 'Gisenyi', + 'As Sanamayn', + "Capo d'Orlando", + 'Bahcesaray', + 'Yamanouchi', + 'Balangkas', + 'Mihona', + 'Ambodimandresy', + 'El Mirage', + 'Gazojak', + 'Marvast', + 'Etzatlan', + 'Sicklerville', + 'Pocao', + 'Nicoadala', + 'Belo Campo', + 'Maina', + 'Piripiri', + 'Bani Hasan ash Shuruq', + 'McComb', + 'Southeast', + 'Cavriglia', + 'Kole', + 'Granger', + 'Rivera', + 'Ponferrada', + 'St. Anthony', + 'Queanbeyan', + 'Rangsdorf', + 'Rio Grande da Serra', + 'Carrieres-sur-Seine', + 'Brookhaven', + 'Lauderhill', + 'Puerto Jimenez', + 'Casas Adobes', + 'Aligudarz', + 'Buchireddipalem', + 'Mae Sai', + 'Guarai', + 'Sprimont', + 'Huizucar', + 'Teano', + 'Liria', + 'Zhemgang', + 'Funing', + 'Amorebieta', + 'Irece', + 'Bansang', + 'Shizukuishi', + 'Kummarapurugupalem', + 'Buzovna', + 'Vadakkumbagam', + 'Nago', + 'Toma', + 'Barra do Ribeiro', + 'Mentone', + 'Chorhat', + 'Norrkoping', + 'Hohoe', + 'Montefiascone', + 'Dario Meira', + 'Sopetran', + 'Dras', + 'Sadpur', + 'Miryang', + 'Jagoniguda', + 'Naklo', + 'Agcabadi', + 'Kermanshah', + 'Kauswagan', + 'Barah', + 'Araujos', + 'Herculandia', + 'Sironj', + 'Varazdin', + 'Macomb', + 'Corcoran', + 'Molfetta', + 'River Vale', + 'Ban Bang Krang', + 'Cutrofiano', + 'Muh Hasan', + 'Warnes', + 'Ottobeuren', + 'Pendencias', + 'Wood-Ridge', + 'Guttikonda', + "Saint John's", + 'White Marsh', + 'Vatomandry', + 'Shrirampur', + 'Ambahita', + 'Olean', + 'Hanno', + 'Jask', + 'Navelim', + 'Camalaniugan', + 'Boumahra Ahmed', + 'Porto Real do Colegio', + 'Geneseo', + 'Risod', + 'Rio Negro', + 'Kendu Bay', + 'Soro', + 'Etawah', + 'Khed Brahma', + 'Fukuyoshi', + 'Ikot Okoro', + 'Marahra', + 'Coimbra', + 'Svitavy', + 'Urdaneta', + 'Takhatgarh', + 'Khanjahanpur', + 'Chatelet', + 'Babile', + 'Budrio', + 'Samma', + 'Konigsbach-Stein', + 'Dobris', + 'Rheine', + 'Porkeri', + 'Menzel Abderhaman', + 'Jhonkar', + 'Torrance', + 'Ladan Kara', + 'Lealman', + 'Witzenhausen', + 'Ribeira do Pombal', + 'Madhopur', + 'Villarrica', + 'Makurdi', + 'Pleasanton', + 'Gullapuram', + 'Aboisso', + 'Gravatai', + 'Chinnamanur', + 'Longjia', + 'Chinna Orampadu', + 'Bangar', + 'Summerlin South', + 'Obiliq', + 'Kurate', + 'Wentzville', + 'Vallet', + 'Moalboal', + 'Ambohidrapeto', + 'Tecozautla', + 'Passagem Franca', + 'Armthorpe', + 'Kuchai Kot', + 'Lata', + 'Kashasha', + 'Auburndale', + 'Ban Tom Klang', + 'Shendurjana', + 'Chikushino', + 'Varandarapilli', + 'Highlands', + 'Harua', + 'Namaacha', + 'Presidente Dutra', + 'Ipu', + 'Bockhorn', + 'Davangere', + 'Saint-Michel-sur-Orge', + 'Crowley', + 'Tamganj', + 'Aklera', + 'Tiruppachur', + 'Candelaria de La Frontera', + 'Sidi Aoun', + 'Verrettes', + 'El Bolson', + 'Itamaraju', + 'Corbetta', + 'Borger', + 'Kodavatipudi', + 'Poisy', + 'Kakamigahara', + 'Petite Riviere de Nippes', + 'Hammanskraal', + 'Tonami', + 'Santa Maria Jalapa del Marques', + 'Los Palacios', + 'Rolim de Moura', + 'Ambahatrazo', + 'North Little Rock', + 'Cheyyar', + 'Masakkavundanchettipalaiyam', + 'Moorhead', + 'Khamir', + "L'Assomption", + 'Galliate', + 'Bad Zwischenahn', + 'Tiruvadi', + 'Ferreira do Zezere', + 'Brecht', + 'Mampikony', + 'La Pointe', + 'Ulipuram', + 'Rubizhne', + "Sant'Angelo in Lizzola", + 'Charkhari', + 'Atascocita', + 'Ternitz', + 'Olfen', + 'Hughenden', + 'Getulina', + 'Kewatgawan', + 'Konza', + 'Ocean', + 'Koziatyn', + 'Epanomi', + 'Mattul', + 'Meise', + 'Hazrat Shiura', + 'Plzen', + 'Tokunoshima', + 'Guelmim', + 'Targu Neamt', + 'Fugu', + 'Petroupoli', + 'Cueramaro', + 'Karaman', + 'Palmira', + 'Rajasur', + 'Calenzano', + 'Puerto Cabello', + 'Kaizu', + 'Pavullo nel Frignano', + 'Embalse', + 'Pocatello', + 'Milngavie', + 'Cachoeira de Minas', + 'Barra', + 'Savanette', + 'Surbo', + 'Ronda Alta', + 'Baghlia', + 'Chanasma', + 'Waterlooville', + 'Stord', + 'Kapiri Mposhi', + 'Plombieres', + 'Khutubi', + 'Pobe', + 'Trashi Yangtse', + 'Cranston', + 'Capinota', + 'Itambe', + 'Undi', + 'South Lyon', + 'St. George', + 'Anlu', + 'Anantavur', + 'Lake Arrowhead', + 'Kankuria', + 'Mellacheruvu', + 'Novy Bor', + 'Palacaguina', + 'La Corredoria', + 'Somerset', + 'Encrucijada', + 'Rampur Tilak', + 'Bay Point', + 'Changhua', + 'Ousseltia', + "Finch'a'a", + 'Ap Binh Thanh', + 'Coonoor', + 'Nachikatsuura', + 'Shanhur', + 'Proserpine', + 'Melavayi', + 'Celebration', + 'Chalkari', + 'Pedda Pendyala', + 'Aravakkurichchi', + 'Oranjemund', + 'Langfang', + 'Jinjiang', + 'Ziracuaretiro', + 'Misseni', + 'El Ayote', + 'Penamaluru', + 'Kadinamkulam', + 'Villa Hidalgo', + 'Hallandale Beach', + 'Periya Semur', + 'Ifakara', + 'Baucau', + 'Cesa', + 'Antiguo Cuscatlan', + 'Barnstaple', + 'Grottammare', + 'Rottenburg an der Laaber', + 'Befasy', + 'Nusaybin', + 'White Bear Lake', + 'Joaquin V. Gonzalez', + 'Dorval', + 'Raghopur', + 'La Garde', + 'Laukaha', + 'Chawalhati', + 'Burnham', + 'Padinska Skela', + 'Bilehra', + 'Rosemead', + 'Grafton', + 'Diemen', + 'Centre de Flacq', + 'Yomra', + 'Attanur', + 'Haspra', + 'Liquica', + 'Mala Vyska', + 'Sanger', + 'Ban Kat', + 'Miami', + 'Karmegh', + 'Montmelo', + 'Santa Cruz Verapaz', + 'Sao Gabriel', + 'Jose Marmol', + 'Wallisellen', + 'Izumiotsu', + 'Mashhad Rizeh', + 'Tiszafoldvar', + 'Limoges', + 'Kuzhippilli', + 'Vergara', + 'Bukavu', + 'Awasa', + 'Rusera', + 'Baragoi', + 'Coolidge', + 'Karjan', + 'Jagannathpur', + 'Somireddipalle', + 'Hamura', + 'Domchanch', + 'Salama', + 'Charmahin', + 'Ocotlan', + 'Cajidiocan', + 'Dembecha', + 'Great Sankey', + 'Katsepy', + 'Casteel', + 'Netapur Tanda', + 'Vangviang', + 'Lala Musa', + 'Treia', + 'Coon Rapids', + 'Jaromer', + 'Sociedad', + 'Ganda', + 'Belao', + 'Una', + 'Feijo', + 'Halen', + 'Courrieres', + 'Rendsburg', + 'Djangoa', + 'Gotse Delchev', + 'Pematangsiantar', + 'City of Isabela', + 'Traversetolo', + 'Sahuria', + 'Hardia', + 'Bhagwanpur Desua', + 'Karamay', + 'Dammennu', + 'Pedra Preta', + 'Zakiyah', + 'Wagner', + 'Naka', + 'Searcy', + 'Pak Chong', + 'Kreuzau', + 'Ammur', + 'Curimata', + 'Oued Lill', + 'Esplanada', + 'Dagana', + 'Villalba', + 'Qorovulbozor', + 'Mengdan', + 'Kenosha', + 'Lumbatan', + 'Beyla', + 'Billdal', + 'Barao do Grajau', + 'Antsatramidola', + 'Moirang', + 'Jalakati', + 'Sakhipur', + 'Tyler', + 'Gornji Milanovac', + 'Ootacamund', + 'Luruaco', + 'Cowra', + 'Jenison', + 'Dar`a', + 'Guamare', + 'Palatka', + 'Brooklyn Center', + 'Bondo', + 'Paracuellos de Jarama', + 'Troon', + 'Passy', + 'Laredo', + 'Rubim', + 'Brand-Erbisdorf', + 'Pofadder', + 'Itzehoe', + 'Yuyao', + 'Sao Carlos', + 'Jiangdi', + 'Avon', + 'Mission Viejo', + 'Mbaiki', + 'Kakan', + 'Aland', + 'Marlborough', + 'Mesetas', + 'Shahrinav', + 'Kourou', + 'Kobilje', + 'Kragujevac', + 'Corigliano Calabro', + 'Jalpan', + 'Kongoussi', + 'Sursand', + 'Jiangjiadong', + 'Ja`fariyeh', + 'Fulham', + 'Jhabrera', + 'Tumberi', + 'Masunga', + 'Schellenberg', + 'Binfield', + 'Jose C. Paz', + 'Padre Garcia', + 'Tillor Khurd', + 'Alta Gracia', + 'Subotica', + 'Zaandijk', + 'Ochsenfurt', + 'Cholpon-Ata', + 'Veinticinco de Diciembre', + 'Shuangtian', + 'Madalum', + 'Dianguirde', + 'Pitangui', + 'Patacamaya', + 'Kosvik', + 'Xiba', + 'Debagram', + 'Ban Na Kham', + 'Sao Francisco do Conde', + 'Stegen', + 'Bulalacao', + 'Pala Oua', + 'Pak Kret', + 'Mundahal Khurd', + 'Befotaka', + 'Gobindpura', + 'Al Aaroui', + 'Mwenga', + 'Caturama', + 'Pelengana', + 'Kandel', + 'Vila Real', + 'Bechloul', + 'Nykobing Falster', + 'Mazabuka', + 'Binka', + 'Konongo', + 'Wrzesnia', + 'Vasiana', + 'Khan Shaykhun', + 'Tolna', + 'Piedrahita', + 'Wilsele', + 'Ye', + 'Acucena', + 'Ames', + 'Ventnor City', + 'Elhovo', + 'Bajiao', + 'Torreon', + 'Lana', + 'Herrenberg', + 'Pira', + 'Pascagoula', + 'Takeo', + 'Nuevitas', + 'Foothill Farms', + 'Mogotes', + 'Los Palacios y Villafranca', + 'Tanuku', + 'Oroszlany', + 'Sanyo-Onoda', + 'Eichenau', + 'Sundapalaiyam', + 'Kanoni', + 'Dioungani', + 'Evora', + 'Funtua', + 'Kawkareik', + 'Coffs Harbour', + 'Villiers', + 'Caldiran', + 'Palakkuzhi', + 'Sassenburg', + 'Oravita', + 'Liaojiayuan', + 'Nisshin', + 'Sirkhandi Bhitha', + 'Union de Tula', + 'Chitila', + 'Montecito', + 'Bozuyuk', + 'Sahiwal', + 'Nidamangalam', + 'Beixingzhuang', + 'Alaminos', + 'Kobiri', + 'Drawsko Pomorskie', + 'Manassas Park', + 'Nu`ayjah', + 'Ndjili', + 'Hamidiyeh', + 'Pleiku', + 'Candon', + 'Samokov', + 'La Belleza', + 'Kargil', + 'Barugo', + 'Leichlingen', + 'Rizal', + 'Chhabila', + 'Sinanpasa', + 'Kabudarahang', + 'Yonghetun', + 'Selden', + 'Longchuan', + 'Guanduqiao', + 'Tinnanur', + 'Mandsaur', + 'Joso', + 'Besarh', + 'Decin', + 'Kut Chap', + 'Botolan', + 'Grad', + 'Dumaguete City', + 'Mimoso do Sul', + 'Tello', + 'Iwate', + 'Pordenone', + 'Naihati', + 'Painesville', + 'Alatsinainy Ialamarina', + 'Rinopolis', + 'Mut', + 'Shirahama', + 'Port Alfred', + 'Nantan', + 'Catuipe', + 'Isola Vicentina', + 'Barahkurwa', + 'Kladno', + 'Biritiba-Mirim', + 'Southwater', + 'Kariya', + 'Kearney', + 'Botosani', + 'Queen Creek', + 'Tamanique', + 'Putla Villa de Guerrero', + 'Visbek', + 'Apac', + 'Grossbeeren', + 'Chislehurst', + 'Esposende', + 'Winnipeg', + 'Thermi', + 'Mahires', + 'Neshannock', + 'Clearwater', + 'Tallinn', + 'Manegaon', + 'Nabua', + 'Baxter', + 'Arlington Heights', + 'Manafwa', + 'Groenlo', + 'Ngerengere', + 'Lakato', + 'Corner Brook', + 'Palakodu', + 'San Pietro Vernotico', + 'Arouca', + 'Bad Nenndorf', + 'Ban Nong Hoi', + 'Grand Island', + 'Perris', + 'Long My', + 'Contai', + 'Berlare', + 'Hlinsko', + 'Oum Hadjer', + 'Kusatsu', + 'Kamudi', + "N'Zerekore", + 'Minoo', + 'Moulares', + 'Taybad', + 'Nayanagar', + 'Barberena', + 'Oromocto', + 'Phirangipuram', + 'Neosho', + 'Leno', + 'Fakirtaki', + 'Noto', + 'Phulpur', + 'Karahal', + 'Alur', + 'Garcia Hernandez', + 'Zawodzie', + 'Gararu', + 'Wallsend', + 'Debre Birhan', + 'Bargaon', + 'Washougal', + 'Wutong', + 'Kajha', + 'Tamilisan', + 'Ambohimasina', + 'Abha', + 'Holguin', + 'Epe', + 'Owen Sound', + 'Essen', + 'Sermadevi', + 'Naga', + 'Kanjikkovil', + 'Maracai', + 'Kyongju', + 'Oulad Friha', + 'Camp Pendleton South', + 'Govindapuram', + 'Sligo', + 'Cubal', + 'Mamuju', + 'Talca', + 'Carcagente', + 'Bagepalli', + 'Liuba', + 'Nao-Me-Toque', + 'Punitaqui', + 'Saint-Pierre-du-Perray', + 'Manila', + 'Alkmaar', + 'Providence', + 'Xinguara', + 'Jangaon', + 'Ibimirim', + 'McAlester', + 'Saalfeld', + 'Palladam', + 'Yacuanquer', + 'Port Denison', + 'Mama Khel', + 'Stabroek', + 'Lake Arbor', + 'Killingworth', + 'Ouled Chebel', + 'Utnur', + 'Kandangan', + 'Soest', + 'Tbeng Meanchey', + 'Haoping', + 'Sater', + 'West Bromwich', + 'Mandiraja Kulon', + 'Karczew', + 'Krems an der Donau', + 'Osako', + 'Budwan', + 'Scalea', + 'Gersthofen', + 'Shiloh', + 'Kopargo', + 'Trotwood', + 'Teltow', + 'Annonay', + 'Makwassie', + 'Ouled Rached', + 'Rankweil', + 'Farmington Hills', + 'Mbini', + 'Horice', + 'Jacqueville', + 'Banbishancun', + 'Tamanar', + 'Konidena', + 'Hengzhou', + 'Liverpool', + 'Marofototra', + 'Cheruvannur', + 'Iormughanlo', + 'Tomar', + 'Si Satchanalai', + 'Guiseley', + 'Bicske', + 'Richland', + 'Elizabeth', + 'Linares', + 'Narbonne', + 'Agua Azul do Norte', + 'Crateus', + 'Luton', + 'Bardstown', + 'Amiawar', + 'Dulhanganj', + 'Senanga', + 'Kobylka', + 'Imlil', + 'St. Catharines', + 'Laziska Gorne', + 'An Cabhan', + 'Tarbes', + 'Leutenbach', + 'Frederiksvaerk', + 'Sechura', + 'Geldermalsen', + 'Krishnarajasagara', + 'Catarroja', + 'Dugo Selo', + 'Brieselang', + 'Kosum Phisai', + 'East Fishkill', + 'Sendhwa', + 'Ampasinambo', + 'Urbano Santos', + 'Myaydo', + 'Premnagar', + 'Andovoranto', + 'Yaraka', + 'Fuller Heights', + 'Tarqui', + 'Struer', + 'Chintalapalle', + 'Boali', + 'Keswick', + 'Deal', + 'Tayasan', + 'Sakha', + 'Vandalur', + 'Somma Vesuviana', + 'Almaguer', + 'Jacksonville Beach', + 'Yuxi', + 'Torhout', + 'Buqkoosaar', + "O'Hara", + 'Dortmund', + 'Freeport City', + 'Poptun', + 'Upper Grand Lagoon', + 'Shendi', + 'Cuautitlan Izcalli', + 'Blagnac', + 'Letpandan', + 'Bhachhi', + 'Dunavarsany', + 'Lessogou', + 'Elze', + 'Clare', + 'Harvey', + 'Ain Feka', + 'Wayne', + 'Pohang', + 'Pothia', + 'Pokhraira', + 'Pontalina', + 'Amatura', + 'Sao Joao das Lampas', + 'Pinan', + 'Sidi Lakhdar', + 'Badami', + 'Matadepera', + "Sant'Agata di Militello", + 'Laiyang', + 'Selmana', + 'Rothwell', + 'Kamareddipet', + 'Hobe Sound', + 'Mong Tun', + 'Armur', + 'Islampur', + 'Wadgassen', + 'Selma', + 'Momanpet', + 'Aktepe', + 'Mjolby', + 'Patchogue', + 'Nueva Loja', + 'Kronjo', + 'Turuvekere', + 'Tejupilco', + 'Vetraz-Monthoux', + 'Rickmansworth', + 'Cicuco', + 'Chandigarh', + 'Winslow', + 'Amtala', + 'Waingapu', + 'Mayuge', + 'Ruse', + 'Sindangan', + 'Xankandi', + 'Al Qutayfah', + 'Llorente', + 'Naliya', + "Az Zarqa'", + 'Bomporto', + 'Wadern', + 'Cegled', + 'Jalapa', + 'Shizhaobi', + 'Union de Reyes', + 'Nahargarh', + 'Massapequa Park', + 'Kalvarija', + 'Portes-les-Valence', + 'Sidi ech Chahmi', + 'Hope', + 'Cumbal', + 'Soalkuchi', + 'Modling', + 'Khujner', + 'Biederitz', + 'Laval', + 'Mikkeli', + 'Otopeni', + 'Qashyr', + 'Noisy-le-Grand', + 'Titao', + 'Koga', + 'Tipasa', + 'Bukoba', + 'Pruszcz Gdanski', + 'Tachiarai', + 'Na Yung', + 'Oignies', + 'Fillmore', + 'Tachilek', + 'Lathrop', + 'Binalbagan', + 'Kallidaikurichi', + 'Leova', + 'Adazi', + 'Caibarien', + 'El Obeid', + 'Pareo', + 'Whitstable', + 'Raymond', + 'Centenario', + 'Zhangziying', + 'Satyavedu', + 'Uspantan', + 'Castelli', + 'Zuitou', + 'Chiradzulu', + 'Tadif', + 'Stains', + 'Mathba', + 'St. Clair', + 'Beni Ounif', + 'Aripuana', + 'Cumana', + 'Bhui', + 'Jeddah', + 'Settara', + 'San Andres de Llevaneras', + 'Fermo', + 'Kinross', + 'Sanjiaocheng', + 'Serrita', + 'Analapatsy', + 'La Bruyere', + 'Bac Giang', + 'Manticao', + 'Jaen', + 'Uzumlu', + 'Pauri', + 'Fernandina Beach', + 'Mullaittivu', + 'Fairfield Glade', + 'Dougouni', + 'Shuangyashan', + 'Ashford', + 'Dalan', + 'Longchamps', + 'Berat', + 'Mirangaba', + 'Bhagta', + 'Bodmin', + 'Wittenbach', + 'Capriolo', + 'Plock', + 'Petropolis', + 'Nimmekal', + 'Eisenberg', + 'Minakshipuram', + 'Fox Lake', + 'Simunul', + 'Zhangatas', + 'Besikduzu', + 'Diepenbeek', + 'Castalla', + 'Nakaseke', + 'Daimiel', + 'Aguascalientes', + 'Bluefields', + 'Weinheim', + 'Khirhar', + 'Vilkaviskis', + 'Gorha', + 'Condeixa-a-Nova', + 'Shikokuchuo', + 'Maghar', + 'Khutha Baijnath', + 'Kathmandu', + 'Tornesch', + 'Isernia', + 'Koriapatti', + 'Tharike', + 'Hastings', + 'Pindra', + 'Eislingen', + 'Bridgetown', + 'Salt Lake City', + 'Kashti', + 'Hulyaypole', + 'Sexmoan', + 'Shrewsbury', + 'Tha Yang', + 'Waghai', + 'Scone', + 'Ritto', + 'Sukrah', + 'Strausberg', + 'Kurhani', + 'Nesarg', + 'Ciudad Constitucion', + 'Francavilla Fontana', + 'Langenselbold', + 'Soklogbo', + 'Deinze', + 'Zaanstad', + 'Lafia', + 'Prosperidad', + 'Rumuruti', + 'Savigliano', + 'Hundested', + 'Neuhausen auf den Fildern', + 'Chitre', + 'Sarrebourg', + 'Ayanikkad', + 'Dapoli', + 'Centerton', + 'Plettenberg', + 'Skocjan', + 'Oamishirasato', + 'Penticton', + 'Aurillac', + 'Red Hook', + 'Kiel', + 'Mannadipattu', + 'Ronchi dei Legionari', + 'Az Zuwaytinah', + 'Ulverston', + 'Tholikuzhi', + 'Phrai Bueng', + 'St. Paul', + 'Shenley Church End', + 'Gourrama', + 'Itiuba', + 'Bena', + 'Partinico', + 'Fort Thomas', + 'Marasesti', + "Monte Sant'Angelo", + 'Lubbecke', + 'Grande-Synthe', + 'Jincheng', + 'Monteros', + 'Randburg', + 'Shakopee', + 'Escuque', + 'Bauko', + 'Muscatine', + 'Ralla', + 'Usuppur', + 'Iwaki', + 'Compton', + 'Isna', + 'Suhbaatar', + 'Igboho', + 'San Rafael Cedros', + 'Lapua', + 'Zdzieszowice', + 'Arcadia', + 'Orotina', + 'Bogande', + 'Alpignano', + 'Highland Park', + 'Newberg', + 'Veles', + 'Scituate', + 'Bellatti', + 'Tempio Pausania', + 'Buri Ram', + 'Marquette', + 'Niquinohomo', + 'Cuddalore', + 'Waimalu', + 'Potiskum', + 'Mohammedia', + 'Bamiantong', + 'La Trinitaria', + 'Bekes', + 'Cajabamba', + 'Conyers', + 'Malungun', + 'Takehara', + 'Aspen Hill', + 'Suzuka', + 'Ait Youssef Ou Ali', + 'Klaipeda', + 'Churriana de la Vega', + 'Malpura', + 'Romano di Lombardia', + 'Jose de Freitas', + 'Soc Trang', + 'Brahmadesam', + 'Chalons-en-Champagne', + 'Guria', + 'Talwandi Sabo', + 'Makhmur', + 'Podvelka', + 'Satghara', + 'Chimteppa', + 'Yaypan', + 'Kearsley', + 'Qianwangcun', + 'Suratgarh', + 'Santa Helena de Goias', + 'San Andres Itzapa', + 'Zulakallu', + 'Pinos Puente', + 'El Carmen de Bolivar', + 'Nahiyat al Iskandariyah', + 'Kasterlee', + 'Cabries', + 'Dakhan', + 'Senguio', + 'Lachute', + 'Bni Quolla', + 'Indanan', + 'Spennymoor', + 'Pottireddippatti', + 'Chaville', + 'Goto', + 'Somoto', + 'Antaritarika', + 'Chikkala', + 'Maheswa', + 'Schmallenberg', + 'Itajuba', + 'Udarband', + 'Itanhaem', + 'Sandiacre', + 'Castlegar', + 'Maler Kotla', + 'Kuressaare', + 'Huangxicun', + 'Sand', + 'Farrokh Shahr', + 'Valandovo', + 'Madakkathara', + 'Kaurihar', + 'Santa Lucia La Reforma', + 'Gjirokaster', + 'Indore', + 'Thorigne-Fouillard', + 'Fiorano Modenese', + 'Drexel Hill', + 'Ash Shamiyah', + 'Baia Farta', + 'Chakdaha', + 'Dambulla', + 'Muttukuru', + 'Chiesanuova', + 'Mehran', + 'Metkovic', + 'Portomaggiore', + 'Spokane', + 'Oldbury', + 'Vilattikulam', + 'Nasushiobara', + 'San Martin de Loba', + 'Campo Largo', + 'Yenakiieve', + 'Uropa', + 'Kaleybar', + 'Mit Salsil', + 'Hedehusene', + 'Gross-Enzersdorf', + 'Germiston', + 'Qal`eh Chan`an', + 'Rocky Point', + 'Cingoli', + 'Garden Grove', + 'Ouro Preto', + 'Mead Valley', + 'Liwonde', + 'Kokologo', + 'Monona', + 'Funato', + 'College Park', + 'Tighedouine', + 'Peringottukurusshi', + 'Ankilimivory', + 'Tabina', + 'Bahagalpur', + 'Huelva', + 'Musashino', + 'Igapora', + 'San Antonio Huista', + 'Cristais', + 'Rajkot', + 'Hasseh', + 'Wyckoff', + 'Rukungiri', + 'Rio Pomba', + 'Morteros', + 'Camillus', + 'Cholai', + 'Alto Rio Doce', + 'Imperatriz', + 'Ross on Wye', + 'Gairtganj', + 'Sakubva', + 'Porsgrunn', + 'Oss', + 'Dickson', + 'Famailla', + 'Napak', + 'La Tebaida', + 'Dhaka', + 'Pulicat', + 'Dayr Mawas', + 'Rampur Kudarkatti', + 'La Rochelle', + 'Cowansville', + 'Van Buren', + 'Montevideo', + "Sant'Antimo", + 'IJmuiden', + 'Urtaowul', + 'Chtiba', + 'Bavanat', + 'Mooresville', + 'Longbangcun', + 'Marawi', + 'Meppel', + 'Ha Long', + 'Matsubushi', + 'Massakory', + 'Jamsaut', + 'Uzungoz', + 'Phatthaya', + 'Glew', + 'Gavimane', + 'Ionia', + 'Hackney', + 'Shanshan', + 'Chilanga', + 'Puerto El Triunfo', + 'Egg Buckland', + 'Ladner', + 'Chapadinha', + 'Mangualde', + 'Aisai', + 'Itamati', + 'Kunkalagunta', + 'Yucheng', + 'Ozhur', + 'Volochysk', + 'Garliava', + 'Ananindeua', + 'Naugatuck', + 'Toronto', + 'Daoukro', + 'Isperih', + 'Sebaco', + 'Apizaco', + 'Curepto', + 'Phimai', + 'Melzo', + 'Edd', + 'Santo Agostinho', + 'Chystiakove', + 'Kobilo', + 'Rafai', + 'Potchefstroom', + 'Ban Bang Sai', + 'Makato', + 'Mairinque', + 'Suwalki', + 'Vinh Yen', + 'Odobesti', + 'Rogerstone', + 'Campos Belos', + 'Nanguneri', + 'Ban Mon Pin', + 'Bududa', + 'Devikapuram', + 'Pietrasanta', + 'Lienen', + 'Ma`arrat an Nu`man', + 'Jicome', + 'Bockenem', + 'Bossembele', + 'Dongchuan', + 'Tepetlan', + 'Piddig', + 'Cheektowaga', + 'Vohipeno', + 'Soubala', + 'Xalapa', + 'Peru', + 'Middlesborough', + 'Oral', + 'Villa Celina', + 'Bacacay', + 'Gangapur', + 'Loyola Heights', + 'South Riding', + 'Gurupi', + 'Galapa', + 'Bagor', + 'Chitemo', + 'Birnin Kebbi', + 'Tagajo', + 'Maychew', + 'Worms', + 'Tesanj', + 'South El Monte', + 'Kitcharao', + 'Shorewood', + 'Agen', + 'Kargi', + 'Oppicherla', + 'Van', + 'Port Augusta', + 'Ahlen', + 'Moudjbara', + 'Ghaxaq', + 'El Arrouch', + 'Carson', + 'Kitatajima', + 'Kamp-Lintfort', + 'Kachhari', + 'Fort Myers', + 'Imbituva', + 'North Charleston', + 'Unterfohring', + 'Triangle', + 'Tuntum', + 'Dollard-des-Ormeaux', + 'Eschweiler', + 'Pomorie', + 'Xewkija', + 'Buenavista', + 'Arsanjan', + 'Demnat', + 'Saharefo', + 'Maragogi', + 'Odivelas', + 'Villaviciosa de Odon', + 'Keszthely', + 'Alcantarilla', + 'Loutraki', + 'Falou', + 'Linfen', + 'Azamnagar', + 'Karvina', + 'Shady Hills', + 'Chopda', + 'Vredefort', + 'Westmont', + 'Ulaanbaatar', + 'Southbridge', + 'North Druid Hills', + 'Viseu', + 'Le Thor', + 'Tacaratu', + 'Elur', + 'Motupe', + 'Trim', + 'Chorzow', + 'Kelle', + 'Ljutomer', + 'Kanbara', + 'Maroviro', + 'Zapotitlan', + 'Forlimpopoli', + 'Dhuusamarreeb', + 'Bakwa-Kalonji', + 'Bakeshiyingcun', + 'Ngoulemakong', + 'Nor Hachn', + 'Ejmiatsin', + 'Handlova', + 'Pillaiyarkuppam', + 'Miramar', + 'Nanyo', + 'Northampton', + 'Heerenveen', + 'Doiwala', + 'Engenheiro Caldas', + 'Undrajavaram', + 'Mahudha', + 'Ashtead', + 'Kotagiri', + 'Nieuw Amsterdam', + 'Hamakita', + 'Ipueiras', + 'Mannargudi', + 'Ramsey', + 'La Mata', + 'Strabane', + 'Meaford', + 'Truseni', + 'Kanhangad', + 'Matanga', + 'Seohara', + 'Hijuelas', + 'Narvik', + 'Alamada', + 'Moslem Ebn-e `Aqil', + 'Kabasalan', + 'Mataram', + 'Jagodina', + 'Zifta', + 'Jambughoda', + 'Ledeberg', + 'Melonguane', + 'Okuizumo', + 'Petion-Ville', + 'Coweta', + 'Chiniot', + 'Ntara', + 'Munuf', + 'West Falls Church', + 'Kanchanaburi', + 'Nahiyat al Kifl', + 'Aqsu', + 'Ghaziabad', + 'Katpadi', + 'Midalam', + 'Omegna', + 'Noci', + 'Taniyama-chuo', + 'Patrocinio Paulista', + 'Merseburg', + 'Nashua', + 'Lamut', + 'Ankang', + 'Ahor', + 'Edakkunnam', + 'South Shields', + 'Liangwu', + 'Sonbari', + 'Shinjo', + 'Patuvilayi', + 'Ponte de Sor', + 'Kawamata', + 'Ulvila', + 'Polomolok', + 'Saila', + 'Mahaiza', + 'El Trebol', + 'Cisterniga', + 'Fossombrone', + 'Nyala', + 'Villa Gesell', + 'Lapa', + 'Nidamaluru', + 'Peraiyur', + 'Dodarasinakere', + 'Pachino', + 'Saint-Etienne-du-Rouvray', + 'Gilching', + 'Laurentian Valley', + 'Garhshankar', + 'Lambesc', + 'Hanamaki Onsen', + 'Seagoville', + 'Rheda-Wiedenbruck', + 'Sutton Coldfield', + 'Safaja', + 'Enns', + 'Mutki', + 'Villa de Leyva', + 'Chauk', + 'Aadorf', + 'Lukow', + 'Yuanquan', + 'Muniz', + 'Maxixe', + 'Soquel', + 'Hillcrest Heights', + 'Mancheral', + 'Featherstone', + 'Chelak', + 'Tondela', + 'Hostomel', + 'Alfonsine', + 'Iskilip', + 'Jaciara', + 'Gidri', + 'Tulare', + 'Mushabani', + 'Patharia', + 'Coudekerque-Branche', + 'Bastos', + 'San Antonio Abad', + 'Eagle Point', + 'Fernando de la Mora', + 'Mhajar', + 'Longhua', + 'Sevnica', + 'Karian', + 'Narsipatnam', + 'Nuuk', + 'Mount Juliet', + 'Tome-Acu', + 'Ferozepore', + 'Sirakoro', + 'Barra Velha', + 'Zabok', + 'Azandarian', + 'Conewago', + 'El Gara', + 'Mamungan', + 'Livorno', + 'Ain Zaouia', + 'Ilamatlan', + "Anse d'Hainault", + 'Voloina', + 'Itaitinga', + 'Odatturai', + 'Vedaranniyam', + 'Bretzfeld', + 'Lom Sak', + 'Yingyangcun', + 'Manamodu', + 'Cerqueira Cesar', + 'Sorgun', + 'Ban', + 'Oyrarbakki', + 'Mandaguacu', + "Sered'", + 'Ringkobing', + 'Kalihati', + 'Modi`in Makkabbim Re`ut', + 'Malverne', + 'Copenhagen', + "L'Ancienne-Lorette", + 'Dagenham', + 'Marovandrika', + 'Nossa Senhora dos Milagres', + 'Sacueni', + 'Santa Maria a Monte', + 'Souq Jamaa Fdalate', + 'Chinguar', + 'Swiedbodzin', + 'Mangalme', + 'San Anselmo', + 'Ban Patong', + 'Bazar-e Yakawlang', + 'Teresopolis', + 'Baliangao', + 'Hunsur', + 'Idigny', + 'Kose', + 'Pedra Azul', + 'Mount Vernon', + 'Uzun', + 'Hamadanak', + 'As', + 'Tounfit', + 'Blumberg', + 'Konstancin-Jeziorna', + 'Ebino', + 'Hull', + 'Tajumulco', + 'Castellaneta', + 'Sidhapa', + 'Slagelse', + 'Dunbar', + 'Nahariyya', + 'Tsuyama', + 'Selston', + 'Brummen', + 'Gigante', + 'Caluquembe', + 'Nirkunnam', + 'Peabody', + 'Braunstone', + 'San Fernando de Henares', + 'Sebt Bni Smith', + 'Lakkampatti', + 'Miami Gardens', + 'Gluckstadt', + 'Santa Maria de Palautordera', + 'Bas Limbe', + 'El Puerto de Santa Maria', + 'Bundaberg', + 'Giszowiec', + 'Gweru', + 'Belle-Anse', + 'Bodupal', + 'Inopacan', + 'Pencoed', + 'Zittau', + 'Sialkot City', + 'Obera', + 'Naikankudi', + "L'Aquila", + 'Plan-les-Ouates', + "Ch'ungmu", + 'Corte Madera', + 'Kaukauna', + 'Tsararafa', + 'Lembok', + 'Altena', + 'Gliwice', + 'Bora', + 'Hagen im Bremischen', + 'Tsaramasoandro', + 'Alto Rio Senguer', + 'Inungur', + 'Alor Setar', + 'Al Hajar al Aswad', + 'Kurunegala', + 'Efringen-Kirchen', + 'Little Egg Harbor', + 'Itagi', + 'Koppunur', + 'Bemidji', + 'Tantoyuca', + 'Guiratinga', + 'Barton', + 'An Khe', + 'Yamagata', + "Lee's Summit", + 'Nea Filadelfeia', + 'Bagneux', + 'Samtredia', + 'Yingcheng', + 'Shibata', + 'Mount Pearl Park', + 'Arendonk', + 'Dhenkanal', + 'Kapenguria', + 'Saida', + 'Gloucester', + 'Bibai', + 'Lebbeke', + 'Dhusar Tikapatti', + 'Rondon', + 'Tamayo', + 'Daksinkali', + 'Plymouth', + 'Scordia', + 'Haywards Heath', + 'Battle Ground', + 'Fishers', + 'Gonen', + 'Xingren', + 'Queenstown', + 'Port Coquitlam', + 'Raritan', + 'Nagykovacsi', + 'North Bend', + 'Wilmot', + 'Taal', + 'Ruhengeri', + 'Zhovkva', + 'Sangtuda', + 'Pont-Rouge', + 'Urbandale', + 'Reeuwijksebrug', + 'Hamburg', + 'Midsayap', + 'Janai', + 'Harrow on the Hill', + 'Nakao', + 'Kaoma', + 'Verbania', + 'Dancagan', + 'Tecumseh', + 'Luhansk', + 'Elanjivaliseri', + 'Dhariwal', + 'Mahuva', + 'Dabhaura', + 'Pilisvorosvar', + 'Ravne na Koroskem', + 'As Suwayq', + 'Semri', + 'Siliana', + 'Noria', + 'Chabahar', + 'Gimpo', + 'Parempuyre', + 'Tam Hiep', + 'Bir', + 'Miranda', + 'Dhemaji', + 'Menongue', + 'Argao', + 'Fuldatal', + 'Gualeguaychu', + 'Hoboken', + 'Melgar', + 'Bishunpura', + 'Karabuk', + 'Tirthahalli', + 'Binmaley', + 'Trajano de Morais', + 'Villeneuve-Saint-Georges', + 'Moribabougou', + 'Al Mansurah', + 'Palangavangudi', + 'Nasugbu', + 'Giugliano in Campania', + 'Emsdetten', + 'Satte', + 'Prescot', + 'Carles', + 'Razkrizje', + 'Djado', + 'Wurzburg', + 'Langhnaj', + 'Kysucke Nove Mesto', + 'Ar Rastan', + 'Huehuetoca', + 'Cerca la Source', + 'Goris', + 'Plon', + 'Bad Lauterberg', + 'Gasparillo', + 'Sao Amaro das Brotas', + 'Rangvasa', + 'Kolokonde', + 'Osmancik', + 'Trofaiach', + 'Seaford', + 'Appley Bridge', + 'Majadahonda', + 'Sudley', + 'Raman Mandi', + 'Diyarbakir', + 'Xiaoxita', + 'Azacualpa', + 'Mullassheri', + 'Aruppukkottai', + 'El Rosario', + 'Puyo', + 'Pomichna', + 'Plaisance-du-Touch', + 'Ghosrawan', + 'Yuba City', + 'Homs', + 'Baunatal', + 'Cape Coral', + 'Umburanas', + 'Kakegawa', + 'Shahr-e Pir', + 'Neiba', + 'Thiers', + 'Chorbog', + 'Melton Mowbray', + 'Ariyalur', + 'Suarez', + 'Manoel Vitorino', + 'Sint-Katelijne-Waver', + 'Nongzhangjie', + 'Bayan', + 'Ceduna', + 'Grand Falls', + 'Masallatah', + 'Meppen', + 'Bromley', + 'Vanves', + 'Novelda', + 'Kaspi', + 'Horseheads', + 'Rath', + 'Vadakarai Kil Pidagai', + 'El Kansera', + 'Tanmen', + 'Henin-Beaumont', + 'Fort Payne', + 'Southlake', + 'Kaeng Khoi', + 'Broughton Astley', + 'Oyamazaki', + 'Rodolfo Sanchez Taboada', + 'Devrukh', + 'Kombai', + 'Juvisy-sur-Orge', + 'Aigua', + 'Sandridge', + 'Oum el Bouaghi', + 'Marilandia', + 'Pirbahora', + 'Bauria', + 'Matsushige', + 'Ipatinga', + 'Venkatagirikota', + 'Hazel Crest', + 'Codegua', + 'North Castle', + 'Talhar', + 'Kirchberg', + 'Achocalla', + 'Apam', + 'Cloverly', + 'La Trinite', + 'Zivinice', + 'Thanh Hoa', + 'Pocao de Pedras', + 'Tiruvadanai', + 'Koshu', + 'Cuencame de Ceniceros', + 'Tellar', + 'Praia do Carvoeiro', + 'Pursat', + 'Cunupia', + 'North Lakhimpur', + 'Carlsbad', + 'Breinigsville', + 'Saint-Bruno-de-Montarville', + 'Oro Valley', + 'Msemrir', + 'Papendrecht', + 'Pedersore', + 'Balasore', + 'Cadale', + 'Viera East', + 'Kambila', + 'Snaresbrook', + 'Rockville', + 'Culcheth', + 'San Valentino Torio', + 'Sharbaqty', + 'Chainpura', + 'Laligam', + 'Huntsville', + 'Pidigan', + 'Hohenems', + 'Rabinal', + 'Campo do Brito', + 'Les Lilas', + 'Kudali', + 'Binnaguri', + "Land O' Lakes", + 'Hatch End', + 'Abiy Adi', + 'Prijedor', + 'Affton', + 'Pilon', + 'Boditi', + 'Ferdows', + 'Villa Dolores', + 'Desenzano del Garda', + 'Venaria Reale', + 'Cuite', + 'Nonthaburi', + 'Lawrenceburg', + 'Guasipati', + 'Tottenham', + 'Utica', + 'Al Basrah', + 'Gorukle', + 'Caranavi', + 'Douglass', + 'Havirov', + 'Sukheke Mandi', + 'Audubon', + 'Puerto del Rosario', + 'Chilkuru', + 'Burco', + 'La Mornaghia', + 'Amatitan', + 'Songhuajiangcun', + 'San Antonio de las Vueltas', + 'Azadshahr', + 'Bislig', + 'Rampura', + 'Ban Klang', + 'Granadero Baigorria', + 'Usuda', + 'Montego Bay', + 'Chornomorsk', + 'Saint-Lys', + 'Sabana Grande de Palenque', + 'Xiluodu', + 'Gigaquit', + 'Carrollwood', + 'Kigali', + 'Lyman', + 'Sao Luis', + 'Patakakani', + 'Mar de Ajo', + 'Inverell', + 'Neuville-en-Ferrain', + 'Molde', + 'Petrel', + 'Indian Harbour Beach', + 'Gahi Mammar', + 'Alanya', + 'Shazand', + 'Leso', + 'Auray', + 'Panauti', + 'Ovada', + 'Tokoname', + 'Junnar', + 'Chahar Dangeh', + 'Monte Aprazivel', + 'Maida Babhangawan', + 'Matias Romero', + 'Nasu', + 'Quata', + 'Barracao', + 'Dila', + 'Piatykhatky', + 'Sikeston', + 'Luzzi', + 'Diabougou', + 'Wiesbaden', + 'Chumphon', + 'Cervia', + 'South Dundas', + 'Halfmoon', + 'Jardim de Piranhas', + 'Carol Stream', + 'Coronel Du Graty', + "L'Isle-sur-la-Sorgue", + 'Kapolei', + 'Bad Windsheim', + 'Hitchin', + 'Almeirim', + 'Kurakhove', + 'Mataro', + 'Khao Yoi', + 'Shihezi', + 'Itaiopolis', + 'Iskandar', + 'Pinamungahan', + 'Highland City', + "Ping'an", + 'Khe Sanh', + 'Konigs Wusterhausen', + 'Kottampatti', + 'Tshabong', + 'Gastonia', + 'Fuensalida', + 'Cabot', + 'Celldomolk', + 'Sakiai', + 'Pirmasens', + 'Bostonia', + 'Ngathainggyaung', + 'Nattappettai', + 'Oulad Hamdane', + 'Campo Belo', + 'Portsmouth', + 'Birmitrapur', + 'La Fare-les-Oliviers', + 'Biberach', + 'Kadoli', + 'Sremski Karlovci', + 'Leon Valley', + 'La Huacana', + 'Cambados', + 'Ban Bang Non', + 'Ranai', + 'Orumiyeh', + 'Moyuta', + 'Hansot', + 'Pasewalk', + 'Ukunda', + 'Guarne', + 'Bethal', + 'Schwabach', + 'Laventille', + 'North Reading', + 'Hithadhoo', + 'Shubrakhit', + 'Overath', + 'Karikad', + 'Nakatsu', + 'Pancevo', + 'Hamirpur', + 'Pilar de la Horadada', + 'Akdepe', + 'Phillaur', + 'Itaquaquecetuba', + 'Bridport', + 'Marotaolana', + 'Kowary', + 'Ibaan', + 'Derazhnia', + 'Baishan', + 'Strzelce Krajenskie', + 'Ampere', + 'West Nipissing / Nipissing Ouest', + 'Dashiqiao', + 'Iguaraci', + 'Minglanilla', + 'Andalgala', + 'Dili', + 'Galena Park', + 'Cinar', + 'Akropong', + 'Aston', + 'Ire', + 'Vrhnika', + 'Randfontein', + 'Rorvik', + 'Sugbongkogon', + 'Trancas', + 'Dusti', + 'Pernes-les-Fontaines', + 'Gamharia', + 'Rozdilna', + 'Harchoune', + 'Cherakara', + 'Valantaravai', + 'Todmorden', + 'Rehau', + 'Draguignan', + 'Clifton', + 'Murud', + 'Jalna', + 'Chhatarpur', + 'Palavur', + 'Balangiga', + 'Canete', + 'Stoneham-et-Tewkesbury', + 'Puttanattam', + 'Lisen', + 'Carrefour', + 'Qostanay', + 'Triggiano', + 'Chingleput', + 'Sonupur', + 'Spodnje Hoce', + 'Oftersheim', + 'Chom Thong', + 'Nagod', + 'Ecoporanga', + 'Cowley', + 'Schiltigheim', + 'San Andres del Rabanedo', + 'Velilla de San Antonio', + 'Bad Neustadt', + 'Timbo', + 'Muskogee', + 'Enid', + 'Batouri', + 'Kragero', + 'Mirano', + 'Alangayam', + 'Cianorte', + 'Crystal Lake', + 'Sebt Gzoula', + 'Kyazanga', + 'Lendava', + 'Bad Durrheim', + 'Jiangguanchi', + 'Gonzalez Catan', + 'La Roche-sur-Foron', + 'Itapiranga', + 'Aktuluk', + 'Uslar', + 'Luis Correia', + 'Anjahamana', + 'Dracevo', + 'Pavlikeni', + 'Salvatierra de Mino', + 'El Karimia', + 'Heilsbronn', + 'Honaz', + 'Multai', + 'Zarghun Shahr', + 'Jesenice', + 'Catanauan', + 'Haaksbergen', + 'Tiruvasaladi', + 'Karvarakundu', + 'Malay', + 'Tamura', + 'Loma Plata', + 'Hamilton Square', + 'Dewal Thal', + 'Partur', + 'Koilakh', + 'Great Dunmow', + 'Sinj', + 'Bekily', + 'Hercules', + 'Dunstable', + 'Kosgi', + 'Xinsi', + 'Tomboco', + 'Southwick', + 'Sangamner', + 'Mossoro', + 'Khon Kaen', + 'Dhamnagar', + 'Denain', + 'Berezivka', + 'Humaita', + 'Malhipur', + 'San Luis de Since', + 'Martin', + 'Rajapudi', + 'Yucaipa', + 'Ortigueira', + 'City of Paranaque', + 'Kabira', + 'Kalandy', + 'Uwajima', + 'Fantino', + 'San Juan Atitan', + 'Kennesaw', + 'Prattville', + 'Showa', + 'Hakmana', + 'Maastricht', + 'Pesqueria', + 'Ban Ngao', + 'Amparafaravola', + 'Bhagwatpur', + 'Landshut', + 'Justo Daract', + 'San Vicente', + 'Hall in Tirol', + 'Avare', + 'Binxian', + 'Llaillay', + 'Hoskote', + 'La Massana', + 'Tucker', + 'Pacasmayo', + 'Ambato', + 'Apiuna', + 'Shaxi', + 'Mpigi', + 'Evere', + 'Sakaki', + 'Sampona', + 'Puerto Leguizamo', + 'Santiago del Teide', + 'Wudalianchi', + 'Blieskastel', + 'Rutesheim', + 'Phan Ri Cua', + 'Itahri', + 'Chilibre', + 'Schalksmuhle', + 'Askale', + 'Vite', + 'Ibipora', + 'Haugesund', + 'Valdemorillo', + 'Iringal', + 'Tariba', + 'Hays', + 'Annur', + 'Mariental', + 'Cieza', + 'Kirkland', + 'Nohar', + 'Shiso', + 'Yoshinogawa', + 'La Motte-Servolex', + 'Sippola', + 'Bonthe', + 'Rijswijk', + 'Pontinia', + 'Aarhus', + 'Kitaotao', + 'Rreshen', + 'Mahinawan', + 'Manrar', + 'Pattanapuram', + 'Baidoa', + 'Zhongxiang', + 'La Colonia Tovar', + 'Amelia', + 'Fuveau', + 'Aguas Formosas', + 'Trikonavattam', + 'Chewara', + 'Bhado Khara', + 'Novyi Rozdil', + 'Palepalli', + 'Ellamanda', + 'San Martin Totolan', + 'Edinet', + 'Weihai', + 'Granadilla de Abona', + 'Sulphur Springs', + 'Astoria', + 'Seyitgazi', + 'Kingsteignton', + 'Weybridge', + 'Plumtree', + 'Brugnera', + 'Stony Point', + 'Shikrapur', + 'Canguaretama', + 'Pedreira', + 'Kot Kapura', + 'Banyuwangi', + 'Sueca', + 'Afzala', + 'Kenilworth', + 'Stuhr', + 'Riverbank', + 'Jdour', + 'Al Minshah', + 'Warren', + 'Kalundborg', + 'Farroupilha', + 'Walsrode', + 'Ilheus', + 'Heroica Ciudad de Tlaxiaco', + 'Smoline', + 'Cueto', + 'Pingtang', + 'Khorugh', + 'Engandiyur', + 'Canlaon', + 'El Reno', + 'Gadwal', + 'Villa Verde', + 'Pulluru', + 'Talatona', + 'Badepalli', + 'Oltiariq', + 'Aizumisato', + 'Antsoso', + 'Jianganj', + 'Mullurkara', + 'Taouloukoult', + 'Chambray-les-Tours', + 'Tabarre', + 'Bamumkumbit', + 'Maquela do Zombo', + 'Widnau', + 'Kurihara', + 'Shumen', + 'Kosatarosh', + 'Villa Vasquez', + 'Nampicuan', + 'Waasmunster', + 'Wilmington', + 'Silao', + 'Geseke', + 'Jamsa', + 'Forres', + 'Tukh', + 'Aversa', + 'Sanpetru', + 'Terra de Areia', + 'Samahuta', + 'North Kingstown', + 'Chiplun', + 'Sztum', + 'Neustadt an der Donau', + 'Alajuelita', + 'Peyziwat', + 'San Carlos Sija', + "Ji'an Shi", + 'Ciudad de Allende', + 'Carmo do Paranaiba', + 'Dakar', + 'Paulista', + "Rignano sull'Arno", + 'Papenburg', + 'Imanombo', + 'Cabangan', + 'Miedzychod', + 'Guanabacoa', + 'Djemmal', + 'Alpedrete', + 'Alcacer do Sal', + 'Barhagarh', + 'Nong Ki', + 'Kot Shamir', + 'Yugawara', + 'Polangui', + 'Villa Ygatimi', + 'Mathura', + 'Morley', + 'Gokak', + 'Red Hill', + 'Bad Durrenberg', + 'Yeghegnadzor', + 'Muroran', + 'Fair Lawn', + 'Urai', + 'Caiaponia', + 'Chimbarongo', + 'Buabidi', + 'Erba', + 'Elandsdoorn', + 'Mongar', + 'Basankusu', + 'Longji', + 'Sucun', + 'Kakata', + 'Altus', + 'Elizabethtown', + 'Kamlapur', + 'Birkenau', + 'San Mateo Atenco', + 'Viqueque', + 'Dibaya', + 'Sao Benedito', + 'Daphne', + 'Irving', + 'Randazzo', + 'Cameron', + 'Gasa', + 'Salyan', + 'Magadi', + 'Pingzhen', + 'Kibiito', + 'Katherine', + 'Sebekoro', + 'Itamaraca', + 'Changling', + 'Tunduru', + 'River Ridge', + 'El Adjiba', + 'Pardiguda', + 'Guaranesia', + 'Subaykhan', + 'Dar Naim', + 'Fulda', + 'Meiti', + 'Choybalsan', + 'Granollers', + 'Al Qays', + 'Moldava nad Bodvou', + 'El Molar', + 'Bayt Saham', + 'Coruripe', + 'Leawood', + "Kunp'o", + 'Aracati', + 'Soaserana', + 'Ambatofotsy', + 'Linganore', + 'Rotterdam', + 'Kumano', + 'Shoeburyness', + 'Palayan City', + 'Oak Island', + 'Saylac', + 'Darwa', + 'Heidelberg', + 'Puerto Maldonado', + 'Szazhalombatta', + 'Fatwa', + 'Kolattupuzha', + 'Mahinog', + 'Villa Rumipal', + 'Somnaha', + 'Veauche', + 'Baheri', + 'Sangzishi', + 'Moser', + 'Cologne', + 'Rypin', + 'Salinas da Margarida', + 'Carini', + 'Gloria do Goita', + 'Tekes', + 'Northport', + 'Seven Oaks', + 'Nemmara', + 'Consolacion del Sur', + 'El Calafate', + 'Foumbot', + 'Magnolia', + 'Carangola', + 'Caracas', + 'Vallahbhapuram', + 'Al `Aqiq', + 'Sincan', + 'Bebra', + 'Malkapur', + 'Valongo', + 'Flawil', + 'Piprahi', + 'Cheney', + 'Tarar', + 'Gode', + 'Tangkak', + 'La Canada Flintridge', + 'Schwanewede', + 'Thomasville', + 'Wolossebougou', + 'Zunilito', + 'Aristobulo del Valle', + 'Staden', + 'Bakhri', + 'Chatillon', + 'Puerto Varas', + 'El Tarf', + 'Goianinha', + 'Bimgal', + 'Abu Qurqas', + "Aghbalou n'Kerdous", + 'Tayakou', + 'Johnson City', + 'Stjordalshalsen', + 'Kot Mumin', + 'Orbassano', + 'Carius', + 'Dikwella South', + 'Gallipoli', + 'Wschowa', + 'Sundarnagar', + 'Gustrow', + 'Pindai', + 'Mit Ghamr', + 'Xinyaoshang', + 'Torbeck', + 'Marrakech', + 'Xiushuicun', + 'Valasa', + 'Tuttlingen', + 'Avesta', + 'Highgate', + 'Osian', + 'Kanniyambram', + 'Malinao', + 'Xingsha', + 'Oltenita', + 'Capiata', + 'Malanville', + 'Hunters Creek', + 'Balwa', + 'Muli', + 'Lotte', + 'Hikkaduwa', + 'Pelahiivka', + 'Chilapa de Alvarez', + 'Farafenni', + 'Tanbaichicho', + 'Saks', + 'Calilabad', + 'Salvaterra de Magos', + 'Chittayankottai', + 'Popondetta', + 'Synelnykove', + 'Flying Fish Cove', + 'Gikongoro', + 'Baraki', + 'Karebilachi', + 'El Hachimia', + 'Highland Heights', + 'Chilecito', + 'Kawashiri', + 'Scotch Plains', + 'Adi Keyh', + 'Cheung Chau', + 'Therwil', + 'Tiruvalanjuli', + 'Bagrami', + 'San Juan Lalana', + 'Nodinge-Nol', + 'Gonzalez', + 'Moshi', + 'Zhangjiajie', + 'Bafilo', + 'Presidente Prudente', + 'Sassenberg', + 'Kot Addu', + 'Nicholasville', + 'Magitang', + 'Glauchau', + 'Bendrahalli', + 'Mislinja', + 'Nysa', + 'Tomaz pri Ormozu', + 'Tassin-la-Demi-Lune', + 'Schubelbach', + 'Juban', + 'Epinay-sur-Orge', + 'West Bradford', + 'Genk', + 'Miyakonojo', + 'Eastpointe', + 'Quispamsis', + 'Gadda Madiral', + 'Nouadhibou', + 'Neuss', + 'Yanagawa', + 'Kirano', + 'Sheldon', + 'Florange', + 'Albert', + 'Vilcun', + 'Mannheim', + 'Qahramon', + 'Joaquim Tavora', + 'Amravati', + 'Small Heath', + 'Ancon', + 'San Juan Chamelco', + 'Neunkirchen', + 'Timargara', + 'Poole', + 'Tenkodogo', + 'Jidigunta', + 'Agoncillo', + 'Diosd', + 'Kondaparti', + 'Darauli', + 'San Giuliano Milanese', + 'Farkhana', + 'Upernavik', + 'Isehara', + 'Ginsheim-Gustavsburg', + "Port Saint John's", + 'Shekhupura', + 'Malvinas Argentinas', + 'Mirbat', + 'Kishangarh', + 'Ibicui', + 'Tangalla', + 'Napa', + 'Tavares', + 'Koro', + 'Edasseri', + 'Negotin', + 'Barcs', + 'Keshwari', + 'Depew', + 'Kottaya', + 'Khair Khan', + 'Toviklin', + 'New Port Richey East', + 'Tarrega', + 'Mariel', + 'Krabi', + 'Rinconada de Malloa', + 'Felida', + 'Sidi Ifni', + 'Tobre', + 'Yorba Linda', + 'Casper', + 'Hartswater', + 'Guanambi', + 'Winthrop', + 'Marcacao', + 'Bridlington', + 'Feicheng', + 'Pinhalzinho', + 'Dubai', + 'Periyakulam', + 'Tranent', + 'Hammam Sousse', + 'Ampasimbe', + 'Santa Mariana', + 'San Antonio de Ibarra', + 'Charlton Kings', + 'Tecoanapa', + 'Kamwenge', + 'Arai', + 'Jocotepec', + 'Kiziltepe', + 'Hadali', + 'Bahadurpur', + 'Thoen', + 'Nakhon Phanom', + 'Nayoro', + 'Baramati', + 'Lamas', + 'Krupanj', + "Ning'an", + 'Monfalcone', + 'Hitachi-Naka', + 'Lower Burrell', + 'Formoso do Araguaia', + 'Vadigenhalli', + 'Bansbaria', + 'Tupanatinga', + 'Dome', + 'Niles', + 'Siquirres', + 'Boddikurapadu', + 'Bryant', + 'Serebryansk', + "'Ain el Hammam", + 'Aldo Bonzi', + 'Gajhara', + 'Tokyo', + 'Karakthal', + 'Cabildo', + 'Buon Trap', + 'Liushui', + 'Echternach', + 'Vagur', + 'Bicester', + 'Rheden', + 'Asaka', + 'Ulladulla', + 'Titara', + 'Haya', + 'Francisville', + 'Robat Karim', + 'Shidong', + 'Bhadohi', + 'Otsuki', + 'Barnaon', + 'Valavanur', + 'Kyenjojo', + 'Mara Rosa', + 'Markgroningen', + 'Planegg', + 'Cajvana', + 'Tatabanya', + 'Andemaka', + 'Nazilli', + 'Leusden', + 'Kanada', + 'Dasol', + 'Al Wakrah', + 'Lucan', + 'Jelilyuzi', + 'Puerto Quito', + 'Cran-Gevrier', + 'Damous', + 'Gunungsitoli', + 'Minamikarasuyama', + 'Sandton', + 'San Antonio del Tequendama', + 'Paiporta', + 'Guying', + 'Fontenay-le-Fleury', + 'Denov', + 'Bulambuli', + 'Yulu', + 'Cajamar', + 'Brvenica', + 'Ocozocoautla de Espinosa', + 'Panapur Langa', + 'Memmingen', + 'Ban Non Sombun', + 'Oegstgeest', + 'Aigio', + 'Bhanuvalli', + 'Rankovce', + 'Bagchini', + 'Voitsberg', + 'Ban Nong Kathao', + 'Canby', + 'Cosne sur Loire', + 'Nilothi', + 'Ma`arratmisrin', + 'Lyndon', + 'Gurgentepe', + 'Adjahome', + 'Andippatti', + 'West Manheim', + 'Brunsbuttel', + 'Grave', + 'Zapotlan de Juarez', + 'Yakkabog`', + 'Tan Phuoc Khanh', + 'Tinley Park', + 'Baligaon', + 'Bell Ville', + 'Gwagwalada', + 'Pie de Pato', + 'Mount Fletcher', + 'Waris Aliganj', + 'Queens', + 'Tamana', + 'Craibas', + 'Caivano', + 'Grants Pass', + 'Citrus Hills', + 'Elko', + 'El Abiodh Sidi Cheikh', + 'Pathardi', + 'Acatlan', + 'Westwood', + 'Cibinong', + 'Dayr `Atiyah', + 'Ja`ar', + 'Archdale', + 'Seguin', + 'Vif', + 'San Vicente de Canete', + 'Limbe', + 'Calumet City', + 'Ain el Hadid', + 'Sint-Andries', + 'Skhirate', + 'Assi-Ben Okba', + 'San Ildefonso', + 'Saint-Raphael', + 'Dire Dawa', + 'Koropi', + 'Murata', + 'Mongu', + 'Villa Maria', + 'Taqah', + 'I-n-Amguel', + 'Moloacan', + 'Milford Haven', + 'Campo Alegre', + 'Atlantic Beach', + 'Kalikapur', + 'Fuefuki', + 'Centralina', + 'Sarafand', + 'Chuangjian', + 'Kheralu', + 'Sulecin', + 'Kirchhain', + 'Coffeyville', + 'Uji', + 'Westport', + 'Compostela', + 'Stratford-upon-Avon', + 'Tougue', + 'Neves', + 'Satiro Dias', + 'Numbrecht', + 'Galle', + 'Ilion', + 'Dasarhalli', + 'Flat Rock', + 'Old Bridge', + 'Barbacena', + 'Essa', + 'Peddakurapadu', + 'Chervonopartyzansk', + 'Perth East', + 'Margosatubig', + 'Bossangoa', + 'Ngorkou', + 'Frankenthal', + 'Reguengos de Monsaraz', + 'Roverbella', + 'Annecy-le-Vieux', + 'Woodmoor', + 'Imizucho', + 'Villanueva de la Serena', + 'Kinalur', + 'Depok', + 'Ahrensburg', + 'Kingman', + 'Magu', + 'Guiping', + 'Kopavogur', + 'Hunasamaranhalli', + 'Tora', + 'Malvar', + 'Lentini', + 'Pont-du-Chateau', + 'Laghouat', + 'Jima Abajo', + 'Garbahaarrey', + 'Samsikapuram', + 'Vinanitelo', + 'Nong Wua So', + 'Jawad', + 'Tirunageswaram', + 'Koure', + 'Villabate', + 'Rezekne', + 'Nenton', + 'Canovanas', + 'Los Banos', + "Berre-l'Etang", + 'Al Qunfudhah', + 'Buenopolis', + 'Le Plessis-Robinson', + 'Bilar', + 'San Antonio de Los Altos', + 'Elblag', + 'Masamba', + 'Czeladz', + 'Tmassah', + 'San Vicente del Raspeig', + 'Tejutla', + 'Albano Laziale', + 'Sibkund', + 'Goio-Ere', + 'Kanoya', + 'Dahibhat Madhopur', + 'Santa Maria Ajoloapan', + 'Srebrenica', + 'Bacoor', + 'Novopavlovka', + 'Marcory', + 'Argos', + 'Ivandrika', + 'Iqaluit', + 'Kondazhi', + 'Kos', + 'Newhaven', + 'Bakwa', + 'Mirante da Serra', + 'Limbach-Oberfrohna', + 'Asadabad', + 'Damulog', + 'Bishops Stortford', + 'Jaguaribara', + 'Vadasikarambattu', + 'Kairaki', + 'Koidu-Bulma', + 'Alto-Cuilo', + 'Mondoro', + 'Buon Ho', + 'Laibin', + 'Prymorskyi', + 'Berane', + 'Wulongpu', + 'Bernalillo', + 'Koturkolkara', + 'Bellerive-sur-Allier', + 'Jambe', + 'Kerwada', + 'Kadoma', + 'Antanamalaza', + 'Yildiz', + 'Kamrawan', + "Kan'onjicho", + 'Sekiyado', + 'Tartarugalzinho', + 'Kudra', + 'Mauban', + 'El Tambo', + 'Taquarana', + 'Winneba', + 'Damghan', + 'Balikumbat', + 'Ulundi', + 'Salo', + 'Nyakosoba', + 'Jeffersontown', + 'Lievin', + 'Santeramo in Colle', + 'Casale Monferrato', + 'Palma del Rio', + 'Yizhu', + 'Ankafina Tsarafidy', + 'Nisarpur', + 'Tadpatri', + 'Peebles', + 'Nawalgarh', + 'Ulliyil', + 'Paittur', + 'Chhajli', + 'Concepcion Batres', + 'Nkpor', + 'DuPont', + 'Burgum', + 'Kaposvar', + 'Labangan', + 'Santany', + 'Appingedam', + 'Barton upon Irwell', + 'Bagli', + 'Hatta', + 'Leeds and the Thousand Islands', + 'Catende', + 'Friedeburg', + 'Udaipura', + 'Aranzazu', + 'Umga', + 'Vize', + 'Coriano', + 'Janjgir', + 'Brwinow', + 'Teays Valley', + 'Ban Kaeng', + 'Huebampo', + 'Oadby', + 'Hovsan', + 'Torre-Pacheco', + 'Kolluru', + 'Araban', + 'Ishiki', + 'Daganzo de Arriba', + 'Jagannadapuram', + 'Quinhamel', + 'Nsukka', + 'Midwest City', + 'Alcala', + 'Payson', + 'Kita Chauhattar', + 'Sao Luis Gonzaga do Maranhao', + 'Quezon', + 'Mani', + 'Khammam', + 'Belo Tsiribihina', + 'Carmaux', + 'Idar', + 'Denison', + 'Lucera', + 'Buda', + 'Ankofa', + 'San Miguel', + 'Isaka', + 'Netherton', + 'Conil de la Frontera', + 'Gorazde', + 'Manullahpatti', + 'Preah Vihear', + 'Coello', + 'Sopo', + 'Valadares', + 'Suruc', + 'Ros Comain', + 'Alto Hospicio', + 'Bargas', + "Anzola dell'Emilia", + 'Eichenzell', + 'Lucelia', + 'Betsiaka', + 'Sidi Bibi', + 'Thale', + 'South Stormont', + 'Kaladgi', + 'Alpen', + 'Zumbagua', + 'Encruzilhada do Sul', + 'Megrine', + 'Panorama', + 'Chitrada', + 'Adamantina', + 'Harsum', + 'Unguturu', + 'San Juan Capistrano', + 'Klasterec nad Ohri', + 'Fernley', + 'Aldenhoven', + 'Pien', + 'Cansancao', + 'Kuje', + 'Valenca do Piaui', + 'Fraga', + 'Carleton Place', + 'Castle Bromwich', + 'Leichi', + 'Armacao dos Buzios', + 'Valenton', + 'Myawadi', + 'Leandro N. Alem', + 'Merlo', + 'Lugazi', + 'Foster City', + 'Shurugwi', + 'Labe', + 'Ruwa', + 'Inongo', + 'Coto Brus', + 'Tiruchengodu', + 'Dharmasagaram', + 'Ommen', + 'Khajawa', + 'Urandi', + 'Fayroz Koh', + 'Bajpe', + 'Epalinges', + 'Longreach', + 'Omdurman', + 'Campos Altos', + 'East Lampeter', + 'Podilsk', + 'Bemaharivo', + 'Al Ahmadi', + 'Vallapuram', + 'Sirvel', + 'Gaurdah', + 'Cottica', + 'Izmail', + 'Orangeburg', + 'Crystal Beach', + 'Toumoukro', + 'Chittarkottal', + 'Klipphausen', + 'Donji Vakuf', + 'Tazah Khurmatu', + 'Laur', + 'Raiyam', + "Saint-Jean-d'Illac", + 'Dubrovnik', + 'Mikkabi', + 'Masasi', + 'Artvin', + 'Maisaram', + 'Ostwald', + 'Wuzhou', + 'San Bartolome Milpas Altas', + 'Zhunan', + 'Pine', + 'Susner', + 'Sibalom', + 'Ormoc', + 'Dimataling', + 'Maroantsetra', + 'Trevignano', + 'La Presa', + 'North Dumfries', + 'Munguia', + 'Mangalam', + 'Kraainem', + 'Bathgate', + 'Russell', + 'Oliveira do Bairro', + 'Vettur', + 'Basavana Bagevadi', + 'Ub', + 'Yepocapa', + 'Spearfish', + 'Stassfurt', + 'Irvine', + 'Bratislava', + 'Coatbridge', + 'Hudli', + 'Beni Amrane', + 'Mansourah', + 'Addanki', + 'Volnovakha', + 'Lagoa Real', + 'Salua', + 'Talata-Vohimena', + 'Kyotera', + 'Rovato', + 'Vilankulo', + 'Nanterre', + 'Timahdit', + 'Blandford Forum', + 'Bombardopolis', + 'eManzimtoti', + 'Garges-les-Gonesse', + 'Araceli', + 'Weslaco', + 'Catembe', + 'Ta Khmau', + 'Zempoala', + 'Terracina', + 'Sint-Amandsberg', + 'Monte Santo de Minas', + 'Umm Salal `Ali', + 'Mudbidri', + 'Dores do Indaia', + 'Osilnica', + 'Eger', + 'Chi Linh', + 'El Omaria', + 'Itapui', + 'Ban Bueng', + 'Balad', + 'Blooming Grove', + 'Kivertsi', + 'Bacabal', + 'Fujin', + 'Combs-la-Ville', + 'Aschheim', + 'Kettering', + 'Chinggil', + 'Nangandao', + 'Avalepalli', + 'London', + 'Aguachica', + 'Leidschendam', + 'Sannicandro Garganico', + 'Cambrils', + 'Tenenkou', + 'Lincoln City', + 'San Gennaro Vesuviano', + 'Dumaran', + 'Yuki', + 'Llanes', + 'Ksar Hellal', + 'Clanton', + 'Khomeyn', + 'Petrovaradin', + 'Whitecourt', + 'San Sebastian Salitrillo', + 'Steubenville', + 'Santiago Atitlan', + 'Cecil', + 'Barnegat', + 'Alto Garcas', + 'Xinfeng', + 'Innsbruck', + 'Santa Margarita de Mombuy', + 'Goya', + 'Inisa', + 'Temascalapa', + 'Ouake', + 'Ambararatabe', + 'Las Delicias', + 'Zawal', + 'Longkoucun', + "Fil'akovo", + 'Concepcion del Uruguay', + 'Seixal', + 'Empedrado', + 'Meram', + 'Matagob', + 'Boufatis', + 'Juanjui', + 'Butha-Buthe', + 'Mutterstadt', + 'Koulikoro', + 'Narhat', + 'Hateg', + 'Americana', + 'Copacabana', + 'Lajkovac', + 'Beasain', + 'Miyoshi', + 'Mooka', + 'Zhuangwei', + 'Dobeln', + 'Brest', + 'Hartlepool', + 'Jajpur', + 'Moncagua', + 'Tarnow', + 'Holt', + 'Hancheng', + 'Ciudad Guzman', + 'Boudouaou el Bahri', + 'Suhiya', + 'Oteapan', + 'Zhoujia', + 'Hohhot', + 'Bulle', + 'Helensburgh', + 'San Jose Villanueva', + 'Lethem', + 'Landen', + 'Gonaives', + 'Mahopac', + 'Tuyen Quang', + 'Shaowu', + 'Celbridge', + 'Artsyz', + 'Heshan', + 'Boutilimit', + 'Bure', + 'Jangid', + 'Doberlug-Kirchhain', + 'Kawara', + 'Meldola', + 'Xinglong', + 'Ntungamo', + 'Shubra al Khaymah', + 'Eldama Ravine', + 'Sukuta', + 'Ain Tedeles', + 'Bara', + 'Barei', + 'Caballococha', + 'Locorotondo', + 'Pine Creek', + 'Mechanicsville', + 'Baxdo', + 'Mysliborz', + 'Androtsy', + 'Newcastle under Lyme', + 'San Jose de Chiquitos', + 'Forchheim', + 'Sabana Iglesia', + 'Saint-Max', + 'Cordenons', + 'Dent', + 'Verdejante', + 'Enkoping', + 'Paradise Valley', + 'Arnaud', + 'Janapul', + 'Sintra', + 'Misserete', + 'Lapinig', + 'Singhbari', + 'Librazhd', + 'Amberg', + 'Pires do Rio', + 'Kenora', + 'Satosho', + 'Anosivelo', + 'Coos Bay', + 'Pathum Thani', + 'Curchorem', + 'Bergambacht', + 'Dahbed', + 'Haslingden', + 'Shahin Dezh', + 'Faridabad', + 'Patera', + 'Saint-Cyr-sur-Loire', + 'Ilhota', + 'Chianki', + 'Garcia', + 'Meine', + 'Kalamazoo', + 'Spitalfields', + 'Maralal', + 'Aruvapalam', + 'Pedro Juan Caballero', + 'Antanifotsy', + 'Suhl', + 'Hancha', + 'Sansepolcro', + 'Villa Martin Colchak', + 'Ksar Chellala', + 'Voznesensk', + 'Requinoa', + 'Vemalwada', + 'Athieme', + 'Neuburg', + 'Tikamgarh', + 'Mazidagi', + 'Florence-Graham', + 'Ikeja', + 'Dinara', + 'Ampanihy', + 'Woodinville', + 'El Palomar', + 'Shafinagar', + 'Jandrapeta', + 'Finglas', + 'Pikesville', + 'Sidney', + 'Tiffin', + 'Belvedere Park', + 'Bad Breisig', + 'Afega', + 'Canilla', + 'Hisarcik', + 'Hertford', + 'Chichkah', + 'Santa Rosa', + 'Palampur', + 'Sanmu', + 'Apuiares', + 'Courtenay', + 'Aiuaba', + 'Hidirbey', + 'Chikuma', + 'Hiddenhausen', + 'Shikohabad', + 'Chilliwack', + 'Tagalft', + 'Chelmno', + 'Colfontaine', + 'Upper Dublin', + 'Ungheni', + 'Estiva', + 'Sveta Ana', + 'Watsa', + 'Gilbert', + 'At Turrah', + 'Payariq Shahri', + 'Ibipeba', + 'San Juan de los Lagos', + 'Barcelona', + 'Ambalamanasy II', + 'Homnabad', + 'Bad Freienwalde', + 'Chandauli', + 'Comitancillo', + 'Bad Konig', + "Sint-Job-in-'t-Goor", + 'Templin', + "D'Iberville", + 'Adelanto', + 'Tepeapulco', + 'Moreni', + 'Blois', + 'Moberly', + 'Encruzilhada', + 'Tokol', + 'Kaneohe', + 'Macomia', + 'Tlalpujahua de Rayon', + 'Kaglipur', + 'Baiceng', + 'Muisne', + 'Fareham', + 'Polistena', + 'Razampeta', + 'Ipojuca', + 'Sandrakatsy', + 'Dagiapara', + 'Madhurapur', + 'Zhuangyuan', + 'Shkoder', + 'Hillsboro', + 'Vidin', + 'Quatipuru', + 'Dubrajpur', + 'Capenda Camulemba', + 'Jaisinghpur', + 'Sonneberg', + 'Gonesse', + 'Toging am Inn', + 'Laranjal Paulista', + 'Tucuru', + 'Arcueil', + 'Francisco I. Madero', + 'Tarumizu', + 'Estrela de Alagoas', + 'Charleston', + 'Cowes', + 'Sessa Aurunca', + 'Fougeres', + "Gricignano d'Aversa", + 'New Germany', + 'Paraparaumu', + 'Pleasant Prairie', + 'Alfena', + 'Payakumbuh', + 'Ramree', + 'Maromme', + 'Kovvur', + 'Derince', + 'Caledon', + 'Jieshang', + 'Navan', + 'Kumbo', + 'Santa Rita do Passa Quatro', + 'Ridgefield', + 'Kllokot', + 'Niramaruthur', + 'Despujols', + 'Narammala', + 'Oroqen Zizhiqi', + 'San Luis del Palmar', + 'Mallawi', + 'Polzela', + 'Troy', + 'El Refugio', + 'Marau', + 'Thaton', + 'Gerede', + 'Lakhanapuram', + 'Chinchina', + 'Duenas', + 'Devizes', + 'Amarillo', + 'Dakor', + 'Brotas', + 'North Auburn', + 'Soamanonga', + 'Villa Alemana', + 'Sathamba', + 'Kanazawa', + 'Namayumba', + 'Ngozi', + 'Mavalli', + 'Maxhutte-Haidhof', + 'Ratnapuram', + 'Midoun', + 'Gostyn', + 'Olopa', + 'Fugangcun', + 'Perungala', + 'Mount Washington', + 'Tekirdag', + 'Cerro Azul', + 'Lahra Muhabbat', + 'Ontario', + 'Santa Maria Chiquimula', + 'North Hempstead', + 'Arma', + 'Awash', + 'Felanitx', + 'Olmaliq', + 'Torremolinos', + 'Gbawe', + 'Sivrihisar', + 'Preussisch Oldendorf', + 'Herrin', + 'Vellalur', + 'Doboj', + 'Guaramirim', + 'Bafoulabe', + 'Nolensville', + 'Acomb', + 'Balaungi', + 'San Juan Ixcaquixtla', + 'Cadereyta', + 'Tarra', + 'Ponta do Sol', + 'Ikaruga', + 'Kaboua', + 'Inagawa', + 'Jacaltenango', + 'Nankana Sahib', + 'Ciudad Melchor de Mencos', + 'Braine-le-Chateau', + 'Gerzat', + 'Abaiara', + 'Maricopa', + 'Saint Neots', + 'Sao Goncalo do Sapucai', + 'Myebon', + 'Markapur', + 'Nova Zagora', + 'Gombong', + 'Gambela', + 'Magsingal', + 'Brezovica', + 'Greifswald', + 'Saint-Nazaire', + 'Poinciana', + 'Hombori', + 'Tsivory', + 'Weiz', + 'Jaguaripe', + 'Kibaya', + 'Wallasey', + 'Nangal Township', + 'Chongzuo', + 'Takamori', + 'Pajo', + 'Daboh', + 'Jisr ez Zarqa', + 'Tizi Rached', + 'Nangal Chaudhri', + 'North Huntingdon', + 'Balilihan', + 'San Josecito', + 'Steinfeld', + 'Velika Gorica', + 'New Cassel', + 'Kyaukme', + 'Kafr Batna', + 'Tepetzintla', + 'Takaharu', + 'Valdepenas', + 'Old Harbour', + 'Siruguppa', + 'Arasur', + 'Grajewo', + 'Aci Catena', + 'Itau de Minas', + 'Zundert', + 'Djakotome', + 'Toulou', + 'Sahar', + 'Opfikon', + 'Damanhur', + 'Yengema', + 'Subic', + 'Kolambugan', + 'Taougrite', + 'Eriyodu', + 'Kiskunmajsa', + 'Germasogeia', + 'Peresecina', + 'Yoshikawa', + "Coeur d'Alene", + 'Mobaye', + 'Borda da Mata', + 'Vecses', + 'Kui Buri', + 'Newport Beach', + 'Palo del Colle', + 'Ripoll', + 'Galsi', + 'Aldama', + 'La Grita', + 'Paese', + 'Sambir', + 'Chapelle', + 'Canteras', + 'Vega Alta', + 'Bhachau', + 'Nanfengcun', + 'Nelson', + 'Chikuzen', + 'Burrillville', + 'Nambour', + 'Sanganakallu', + 'Punch', + 'Borehamwood', + 'Bagan Si Api-api', + 'Tiogollo', + 'Pratteln', + 'Coleford', + 'Huajing', + 'Amoucha', + 'Kannivadi', + 'Winfield', + 'Kimilili', + 'Bugganipalle', + 'Mont-Dore', + 'Chaukhata', + 'Long Lama', + 'Taysan', + 'Caico', + 'Sakardih', + 'Owando', + 'Foleshill', + 'Katlehong', + 'Falkoping', + 'Vellithiruthi', + 'Pancas', + 'Rojales', + 'Juazeiro', + 'Sluis', + 'Matala', + 'Banikoara', + 'Ilicakoy', + 'Montalban', + 'La Algaba', + 'Makeni', + 'Mangrol', + 'Shirdi', + 'Kamphaeng Phet', + 'Prelouc', + 'Podujeve', + 'Redencao', + 'Isaka-Ivondro', + 'Pollensa', + 'Eemnes', + 'Kogon Shahri', + 'Asarhi', + 'Bang Khla', + 'San Andres de Sotavento', + 'Shertallai', + 'Fenglu', + 'El Doncello', + 'Prachatice', + 'Biel/Bienne', + 'Tsabit', + 'Mirante do Paranapanema', + 'Kotoura', + 'Port St. Lucie', + 'Minamichita', + 'Arapiraca', + 'Balcarce', + 'Scionzier', + 'Kharv-e Sofla', + 'Redan', + 'Rolandia', + 'Turrialba', + 'Tsaravary', + 'Sayda', + 'Banepa', + 'Soller', + 'Kaith', + 'Mahdasht', + 'Sandhausen', + 'Redditch', + 'Bemiss', + 'Callaway', + 'Rioblanco', + 'Maesteg', + 'Saronno', + 'Fulgazi', + 'Revuca', + 'Lysychansk', + 'Mandan', + 'Wangjiazhai', + 'Capela de Santana', + 'Toukoto', + 'Vwawa', + 'Dublin', + 'Newark upon Trent', + 'Zacoalco de Torres', + 'Pustomyty', + 'Sangi', + 'Zymohiria', + 'Clinton', + 'Nenjiang', + 'Nova Ubirata', + 'Cosautlan', + 'Kibuye', + 'Darfield', + 'Cornaredo', + 'Ciudad Choluteca', + 'Tvoroyri', + 'Giussano', + 'Manambur', + 'Benfreha', + 'Medarametla', + 'Meridianville', + 'Al Qa`idah', + 'Treinta y Tres', + 'Nagar Nahusa', + 'Kedainiai', + 'Kursenai', + 'Shunan', + 'Dzolokpuita', + 'Lyon', + 'Kleve', + 'Sadon', + 'Fort Pierce', + 'Sanski Most', + 'Zedelgem', + 'Paracho de Verduzco', + 'Hattem', + 'Cantel', + 'Miary-Taheza', + 'Maintirano', + 'Denham Springs', + 'Ap Khanh Hoa', + 'Kaler', + 'Le Pradet', + 'Uxbridge', + 'Cugnaux', + 'Altdorf', + 'Oberhausen', + 'Caacupe', + 'Moyo', + 'Muchun', + 'Roquevaire', + 'Tubod', + 'Bacolor', + 'Nijgaon Paranpur', + 'Sao Pedro da Aldeia', + 'Adrian', + 'Remedios de Escalada', + 'Pehowa', + 'Orlando', + "Mosciano Sant'Angelo", + 'Kasempa', + 'Tomigusuku', + 'Naic', + 'Blanes', + 'Iguacu', + 'Ben Arous', + 'Ben', + 'Westborough', + 'Parkent', + 'Nokaneng', + 'Goianesia', + 'Raniwara Kalan', + 'Lambersart', + 'Handewitt', + 'Beni Haoua', + 'Pargas', + 'Temascalcingo', + 'Abano Terme', + 'Petmanhalli', + 'Karis', + 'Dezhou', + 'Guigue', + 'Melitopol', + 'Tajerouine', + 'Malaimarpuram', + 'Asti', + 'Hanimciftligi', + 'Isua', + 'Enkesen', + 'Birtouta', + 'Relizane', + 'Igrapiuna', + 'Belle Chasse', + 'Calubian', + 'Ararat', + 'Caucete', + 'Hybla Valley', + 'Wipperfurth', + 'Bom Jesus da Lapa', + 'North Salt Lake', + 'Dayan', + 'Zouar', + 'Parang', + 'Mandi Bahauddin', + 'Rovereto', + 'Jinta', + 'Araua', + 'Eugenopolis', + 'Chota Mollakhali', + 'Nabha', + 'Mit Nama', + 'Baia Mare', + 'Katridih', + 'Iwamuro-onsen', + 'Sao Miguel do Iguacu', + 'Suesca', + 'Al Mudaybi', + 'Nauen', + 'Nansio', + 'Nada', + 'Camaligan', + 'Catandica', + 'Ostrov', + 'Wargal', + 'Simarbani', + 'Santa Venera', + 'Bad Gandersheim', + 'Kumbadaje', + 'Samrong', + 'Higashiomi', + 'Chandragiri', + 'General Enrique Mosconi', + 'Seregno', + 'La Florida', + 'Envigado', + 'Landerneau', + 'Sao Pedro da Agua Branca', + 'Orion', + 'Mudgere', + 'Kentwood', + 'Hirono', + 'Kongen', + 'Swamimalai', + 'Tiruchendur', + 'Piploda', + 'Cajamarca', + 'Upata', + 'Lobau', + 'Les Pavillons-sous-Bois', + 'Karungu', + 'Araruama', + 'Fraser', + 'Queven', + 'Kibingo', + 'Senden', + 'Zarbdor Shaharchasi', + 'Goner', + 'West Athens', + 'Butembo', + 'Vadakku Valliyur', + 'Huasco', + 'Brandsen', + 'Halikko', + 'Sabinanigo', + 'Niagara Falls', + 'Sarikei', + 'Okegawa', + 'Reichenbach/Vogtland', + 'Triuggio', + 'Villa Hayes', + 'Tiruttangal', + 'Hasuda', + 'Marpingen', + 'Toms River', + 'Belalcazar', + 'Gembloux', + 'Shuizhai', + 'Ihumwa', + 'Zacualpan de Amilpas', + 'Doddappanayakkanur', + 'Covina', + 'Brikama Ba', + 'Yerkoy', + 'Essenbach', + 'Xiushui', + 'Obuase', + 'Riga', + 'Pokhara', + 'Somoniyon', + 'Itogon', + 'Zelzate', + 'Valence', + 'Bourges', + 'Samthar', + 'Elavalli', + 'Ibitinga', + 'Chembrassheri', + 'Pilao Arcado', + 'Carignan', + 'Sipilou', + 'Colonia Nicolich', + 'Valbom', + 'Naogaon', + 'Decan', + 'Nellore', + 'Makilala', + 'Borgne', + 'Santa Clarita', + 'Vadso', + 'Nandura Buzurg', + 'Polavaram', + 'Bike', + 'Lapinlahti', + 'Cambanugoy', + 'Umri', + 'Kribi', + 'Dogachi', + 'Kara-Kulja', + 'Karibib', + 'La Salvetat-Saint-Gilles', + 'Sidi Ben Adda', + 'Tinpiple', + 'Santa Perpetua de Moguda', + 'Dushanove', + 'Plewiska', + 'Filderstadt', + 'Yerres', + 'Verkhivtseve', + 'Lop Buri', + 'Saldana', + 'Gujo', + 'Dodge City', + 'Belari', + 'Minano', + 'Hamira', + 'Keles', + 'Bichena', + 'Lagoa da Prata', + 'Ormskirk', + 'Heide', + 'Borj Hammoud', + 'Zhuzhou', + 'Heilbronn', + 'Geertruidenberg', + 'Salumbar', + 'Kings Park', + 'Neralakaje', + 'Ciudad Apodaca', + 'Amircan', + 'Samalkha', + 'Hvannasund', + 'Lier', + 'Skadovsk', + 'Nong Khai', + 'Soderhamn', + 'Aghbala', + 'Ban Wisit', + 'Ahram', + 'Dianopolis', + 'North College Hill', + 'Barrechid', + 'Elandakuttai', + 'Yatagan', + 'Macae', + 'Fukayacho', + 'Camaqua', + 'Ypacarai', + 'Santa Paula', + 'Manjo', + 'Sarmada', + 'La Seyne-sur-Mer', + 'Heze', + 'Auch', + 'Nuvem', + 'Lowicz', + 'Erdokertes', + 'Kothia', + 'Bassenge', + 'Bagh-e Malek', + 'New Port Richey', + 'Soisy-sous-Montmorency', + 'Toluprpatti', + 'Niimi', + 'Huaniu', + 'Marratxi', + 'Schwerte', + 'Kontich', + 'Jaynagar', + 'Saint-Fargeau', + 'Tamm', + 'Ain Sefra', + 'Glassboro', + 'Pitoa', + 'Hohenhameln', + 'Babati', + 'Madnur', + 'Chicopee', + 'Tanza', + 'Burke Centre', + 'Guntur', + 'Arvayheer', + 'Ban Pa Hung', + 'Santa Margarita', + 'Manihari', + 'Lamorlaye', + 'Mengen', + 'Pullach im Isartal', + 'Orleaes', + 'Patuakhali', + 'Kharan', + 'Berriozar', + 'Arad', + 'Bra', + 'Bucimas', + 'Lindas', + 'Datca', + 'Vlotho', + 'Zabalj', + 'San Jose del Cabo', + 'Aflao', + 'Joliette', + 'Mamidipalli', + 'Ganguvarpatti', + 'Sitamarhi', + 'Hockessin', + 'Piamonte', + 'Arroyohondo', + 'Alapli', + 'Conflans-Sainte-Honorine', + 'San Lawrenz', + 'Cangas de Narcea', + 'Apaseo el Alto', + 'Castelfranco Emilia', + 'Boucan Carre', + 'Mongat', + 'Sutton', + 'Koungheul', + 'Capodrise', + 'Manhumirim', + 'Mohnyin', + 'San Francisco de Macoris', + 'Veraval', + 'Sint-Martens-Lennik', + 'Ban Bo Haeo', + 'Colinas', + 'Ajaigarh', + 'Arroyito', + 'Gravelines', + 'Little Lever', + 'Sayama', + 'Vitoria do Mearim', + 'Ariana', + 'Thatcham', + 'Chateaudun', + 'Laranja da Terra', + 'Sidi Azzouz', + 'Lokoja', + 'Chamgardan', + 'Worcester Park', + 'Bagnolo Mella', + 'Dewas', + 'Safo', + 'Villanueva y Geltru', + 'Shangxiao', + 'Ayabe', + 'Neo Psychiko', + 'Appenzell', + 'Pototan', + 'Carlos Casares', + 'Vandoeuvre-les-Nancy', + 'Blagoevgrad', + 'Amirli', + 'Jalalpur Jattan', + 'Bilis Qooqaani', + 'Hemsbach', + 'Woodway', + 'Stow', + 'Fukuyama', + 'Geilenkirchen', + 'Villa Isabela', + 'Mosjoen', + 'High Wycombe', + 'Yumbel', + 'Ampasimazava', + 'Mulbagal', + 'Payao', + 'New Garden', + 'Newburn', + "Ak'ordat", + 'Northborough', + 'Pueblo Bello', + 'Prijepolje', + "Espigao D'Oeste", + 'Horstel', + 'Seropedica', + 'Terrytown', + 'Kinnelon', + 'Menggala', + 'Atimonan', + 'Grand Baie', + 'Dodoni', + 'Engenheiro Coelho', + 'Santa Croce Camerina', + 'Mambajao', + 'Fateh Nangal', + 'Karimnagar', + 'Oliveira dos Brejinhos', + 'Neykkarappatti', + 'Hooglede', + 'Malacatan', + 'Gbadolite', + 'Bhai Rupa', + 'Khallikot', + 'Uiwang', + 'Kamianka', + 'Omuta', + 'Okagaki', + 'Stepanavan', + 'Kisujszallas', + 'Digar', + 'Glenwood', + 'Siqba', + 'Olsztyn', + 'Zargar', + 'Leamington', + 'Seymour', + 'Ban Mae Ngon Khilek', + 'Wyomissing', + 'Anyang', + 'Paranacity', + 'Paderno Dugnano', + 'Haselunne', + 'Kampong Thum', + 'Paldorak', + 'Brandermill', + 'Wunsiedel', + 'Ribeirao do Largo', + 'Shirud', + 'Melong', + 'Romeoville', + 'Palma Soriano', + 'Filadelfia', + 'Taiyuan', + 'Medicina', + 'Kara', + 'Perunturuttu', + "Sant'Eufemia Lamezia", + 'Villa Canales', + 'Boankra', + 'Dildarnagar', + 'Arraijan', + 'Sonzacate', + 'Manghit', + 'La Valette-du-Var', + 'Loon op Zand', + 'Mutis', + 'Ewa Gentry', + 'Congonhal', + 'Tirua', + 'Firuzoba', + 'Mushin', + 'Okazaki', + 'Thornton', + 'Torbali', + 'Rumburk', + 'Sgamna', + 'Bel-Air', + 'Cassia', + 'Oliveira de Azemeis', + 'Sangrur', + 'Thongwa', + 'Cunco', + 'Bonn', + 'Kalanadu', + 'Magalia', + 'Ficarazzi', + 'Bemarivo', + 'Hadim', + 'Forster', + 'Mahis', + 'Axixa', + 'Putignano', + 'Claye-Souilly', + 'Jaguariuna', + 'Kilifi', + 'Santo Cristo', + 'Gandlapenta', + 'Langenhagen', + 'Orillia', + 'Tshilenge', + 'Donggou', + 'West Lincoln', + 'Victoria', + 'Hoh Ereg', + 'Queensbury', + 'Snovsk', + 'Dabrowa Gornicza', + 'Rosenberg', + 'Nayanakulam', + 'Kreminna', + 'Bissora', + 'Brezno', + 'Xireg', + 'Wittlich', + 'Kanyana', + 'Ohrdruf', + 'Libagon', + 'Fot', + 'Ban Ratchakrut', + 'Xinnongcun', + 'Sotik', + 'Hindarx', + 'Engelskirchen', + 'Marcos', + 'San Miguel Acatan', + 'Mariano Escobedo', + 'Capdepera', + 'Camarate', + 'Corinth', + 'Mejicanos', + 'Sirohi', + 'Takhli', + 'Hodonin', + 'Dabat', + 'Humansdorp', + 'Doncaster', + 'Ballitoville', + 'Combita', + 'Beilngries', + 'Soanierana Ivongo', + 'Mella', + 'Poti', + 'Dongola', + 'Deptford', + 'Azaourisse', + 'Itabirinha de Mantena', + 'Saluzzo', + 'Dumbravita', + 'Cacongo', + 'Mechelen', + 'Sablan', + 'Doaba', + 'Borre', + 'Benghazi', + 'Vasilevo', + 'Nyuzen', + 'Thames Centre', + 'Chavara Gramam', + 'Nivelles', + 'Fairmont', + 'Villa Luzuriaga', + 'Carletonville', + 'Varash', + 'Montemurlo', + 'Mukocho', + 'Sa Kaeo', + 'Gonegandla', + 'Bebandem', + 'Kulp', + 'Plainsboro', + 'Pedro Leopoldo', + 'Mokolo', + 'Mesolongi', + 'Formosa do Rio Preto', + 'Vellmar', + 'Yoko', + 'Sax', + 'Sirvar', + 'Boadilla del Monte', + 'Ziama Mansouria', + 'Brasov', + 'Remedios', + 'Rita', + 'Thane', + 'Fukusaki', + 'Pontypridd', + 'Beysehir', + 'Uchquduq Shahri', + 'Feilding', + 'Silves', + 'Galvan', + 'Great Linford', + 'Zimna Voda', + 'Hirado', + 'Edinburgh of the Seven Seas', + 'Vellodu', + 'Prayagraj', + 'Clermont-Ferrand', + 'La Crosse', + 'San Cristobal Verapaz', + 'Apollo Beach', + 'Cerete', + 'Leskovac', + 'Poltava', + 'Anan', + 'Al Fujayrah', + 'Leramatang', + 'Guastatoya', + 'Fort Wellington', + 'Erumad', + 'Genet', + 'Jondor Shaharchasi', + 'Pudong', + 'Dip', + 'Bystrc', + 'Gakuch', + 'Napoleon', + 'Mathigeri', + 'Hathapur', + 'Kinh Mon', + 'Eidsberg', + 'Quincy-sous-Senart', + 'Yardimli', + 'San Juan Cancuc', + 'Kolbuszowa', + 'Conegliano', + 'Eski Ikan', + 'Hildburghausen', + 'Tirubhuvane', + 'Nikaido-kaminoshocho', + 'Simbahan', + 'Havixbeck', + 'Para', + 'Blomberg', + 'Tionk Essil', + 'Clearview', + 'Imassogo', + 'Whittlesey', + 'Panchgani', + 'Gumani', + 'Kaikkudi', + 'Amatepec', + 'Klotze', + 'Kohtla-Jarve', + 'Vilanova del Cami', + 'Belison', + 'Kakinada', + 'Monte Alto', + 'Walldorf', + 'Alangalang', + 'Carson City', + 'Moises Padilla', + 'Ad Dir`iyah', + 'Hlatikulu', + 'Vigna di Valle', + 'Mosta', + 'Eastern Goleta Valley', + 'Ballenger Creek', + 'Tsukubamirai', + 'Metu', + 'Modachchur', + 'Sukabumi', + 'San Nicolas de los Arroyos', + 'Umingan', + 'Tinnevelly', + 'San Juan Opico', + 'San Vicente de Chucuri', + 'Jacunda', + 'Kaura Namoda', + 'Lodi', + 'Nakhon Sawan', + 'Dunwoody', + 'Katahra', + 'Zhonghechang', + 'Troyes', + 'Goalundo Ghat', + 'Saadatpur Aguani', + 'Ji-Parana', + 'Baroy', + 'Scoresbysund', + 'Santurce-Antiguo', + 'Gagny', + 'La Libertad', + 'Beypore', + 'Puerto Cumarebo', + 'Rasol', + 'Kamalia', + 'Singhana', + 'Vancouver', + 'Angouleme', + 'Cacapava', + 'Laurinburg', + 'Ilhavo', + 'Rangamati', + 'Leogane', + 'Ilkeston', + 'Luganville', + 'Somerville', + 'Biot', + 'Nanyang', + 'Sidi Yahya Zaer', + 'Saltangara', + 'Ban Doi Suthep', + 'San Lorenzo de Esmeraldas', + 'Srinagar', + 'Alberton', + 'Khenchela', + 'Belcista', + 'Chiramanangad', + 'Ordu', + 'Channubanda', + 'Niandjila', + 'Tellicherry', + 'Steenokkerzeel', + 'Conceicao do Rio Verde', + 'Kusapin', + 'Sanjat', + 'Nanxicun', + 'Cedros', + 'Soanenga', + 'La Orotava', + 'Pasvalys', + 'Verdal', + 'Romans-sur-Isere', + 'Katangi', + 'Zhob', + 'Frosinone', + 'Plan-de-Cuques', + 'Bree', + 'Sveti Nikole', + 'Ciudad Tecun Uman', + 'Helsinge', + 'Velez', + 'Balcova', + 'Beni Yakhlef', + 'Arumuganeri', + 'Condado', + 'Xiangkhoang', + 'Parsons', + 'Rouissat', + 'Cetraro', + 'Ozieri', + "Say'un", + 'San Luis Jilotepeque', + 'Vynohradiv', + 'Nampula', + 'Kunsan', + 'Hounde', + 'Robertson', + 'Oarai', + 'Sigus', + 'Golborne', + 'Richmond Heights', + 'Gateshead', + 'Zhenbeibu', + 'Chesham', + 'Canals', + 'Bakau', + 'Belas', + 'Hurricane', + 'Oyo', + 'Ciudad Piar', + 'Palmerston', + 'Chik Ballapur', + 'Bressuire', + 'Mountain Park', + 'Pantelimon', + 'Gooik', + 'Flitwick', + 'Kulattur', + 'Plumstead', + 'Verzej', + 'Kapasiawan', + 'Olds', + 'Nidgundi', + 'Santo Antonio', + 'Dimitrovgrad', + 'Oued Zenati', + 'Kahramanmaras', + 'Chengtangcun', + 'Gonikoppal', + 'Skidaway Island', + 'Moriguchi', + 'Devonport', + 'Samamea', + 'Tsimafana', + 'Lakeside', + 'San Matias', + 'Visconde do Rio Branco', + 'Rivarolo Canavese', + 'Normal', + 'Bahharet Oulad Ayyad', + 'Hurzuf', + 'Allendale', + 'Saint-Charles-Borromee', + 'Netivot', + 'Tunasan', + 'Mazagao', + 'Czarnkow', + 'Ubatuba', + 'Baco', + 'Aralik', + 'Rosamond', + 'Masindi', + 'Vargem da Roca', + 'Mbeya', + 'Byram', + 'Court-Saint-Etienne', + 'Karath', + 'Sidcup', + 'Anage', + 'Midway', + 'Mountain Ash', + 'Agouna', + 'Iakora', + 'Adendorf', + 'Warni', + 'Nakhon Nayok', + 'G`ozg`on', + 'Achacachi', + 'Soamanova', + 'Sao Mamede de Infesta', + 'Brumadinho', + 'Kosamba', + 'Sabuncu', + 'Alicante', + 'Youganning', + 'Neviges', + 'Jaro', + 'Juangriego', + 'Meshkin Dasht', + 'Veinticinco de Mayo', + 'Buyan', + 'Khirbat Ghazalah', + 'Dabola', + 'Bundehra', + 'Bierbeek', + 'Gherla', + 'Tammampatti', + 'Avranches', + 'Dubbo', + 'Southport', + 'Ban Om Noi', + 'Koumra', + 'Bridgeview', + 'Madipakkam', + 'Fiche', + 'Gura Humorului', + 'Satsumasendai', + 'Nautan Dube', + 'Ngaputaw', + 'Erlin', + 'Segorbe', + 'Khandela', + 'Bikrampur Bande', + 'Diavata', + 'Taurage', + 'Tula de Allende', + 'Minato', + 'Tumba', + "Trostyanets'", + 'La Matanza de Acentejo', + 'Sathmalpur', + 'Coycoyan de las Flores', + 'Sierra Madre', + 'Masjed Soleyman', + 'Yokoshibahikari', + 'Trezzano sul Naviglio', + 'Leon', + 'Xinpu', + 'Karacabey', + 'Graaff-Reinet', + 'Maputo', + 'Bargoed', + 'Ndali', + 'Vettikkavala', + 'Bat Yam', + 'Boxmeer', + 'Anao', + 'Riccione Marina', + 'Marilao', + "Saint-Martin-d'Heres", + 'Badamdar', + 'Shaoguan', + 'Asker', + 'Benedito Novo', + 'Az Zubayr', + 'Nemyriv', + 'Madanpur', + 'Hakha', + 'Bumba', + 'Fushe Kosove', + 'Shahrisabz', + 'Clondalkin', + 'Delvada', + 'Waldwick', + 'Pemagatshel', + 'Tubingen', + 'Ejido', + 'Awankh', + 'Shyorongi', + 'Houghton le Spring', + 'Albert Lea', + 'Lazdijai', + 'Sanando', + 'Galugah', + 'Nagcarlan', + 'Huong Thuy', + 'Nettuno', + 'Baalbek', + 'Kunnamkulam', + 'Oiso', + 'Barra do Sul', + 'Bishunpur Hakimabad', + 'North Adams', + 'Shamsa', + 'Madhuban', + 'Tirumala', + 'Ganthier', + 'Guben', + 'Lakamane', + 'Preakness', + 'Caraguatatuba', + 'Ferrol', + 'Partibanur', + 'Venkatadripalem', + 'Aquidaba', + 'Sedico', + 'Baixo Guandu', + 'Navadwip', + 'Zgharta', + 'Morro Bay', + 'Miramichi', + 'Jose Batlle y Ordonez', + 'Zuwarah', + 'Espinal', + 'Brcko', + 'Cham', + 'Kika', + 'Helixi', + 'Pillaro', + 'Lam Tin', + 'Trzebiatow', + 'Nidda', + 'Paulo Lopes', + "Santa Barbara d'Oeste", + 'San Pedro de Ycuamandiyu', + 'Wenchang', + 'Hovd', + 'Scunthorpe', + 'Maspalomas', + 'Oskemen', + 'Raibhir', + 'Palmview', + 'Herrsching am Ammersee', + 'Frederikssund', + 'Yachimata', + 'Rosario de la Frontera', + 'Sinimbu', + 'Valdagno', + 'Roseto degli Abruzzi', + 'Sidi Allal Tazi', + 'Gantt', + 'Pulimel', + 'Ambatomainty', + 'Kumaripur', + 'Haldwani', + 'Zwiesel', + 'Nanqiaotou', + 'Jovellar', + 'Mizhou', + 'Asafabad', + 'Kielczow', + 'Ciying', + 'Erice', + "Palmeira d'Oeste", + 'Comiso', + 'Colac', + 'St. Peters', + 'Gutersloh', + 'Chililabombwe', + 'Joure', + 'Axixa do Tocantins', + 'Celeken', + 'Khvansar', + 'Shepperton', + 'Baghdad', + 'Darwin', + 'Pasaco', + 'Diamond Harbour', + 'Tiruvallur', + 'Hackettstown', + 'Santo Antonio da Platina', + 'General Eugenio A. Garay', + 'St. Helens', + 'Hollister', + 'Patra', + 'Casalmaggiore', + 'Villarrubia de los Ojos', + 'Brejo da Madre de Deus', + 'Attapu', + 'Bambous', + 'Neuenhof', + 'Massape', + 'Hurlingham', + 'Longxing', + 'Xinjun', + 'Yaofeng', + 'Ipubi', + 'Sarikaya', + 'Pambadi', + 'Qinggang', + 'Pirai do Norte', + 'Tha Mai', + 'Scranton', + 'Dang`ara', + 'Murliganj', + 'Villa Sola de Vega', + 'Diedorf', + 'Panchi', + 'Pagadian', + 'Unna', + 'Dongyuancun', + 'Joacaba', + 'San Andres de Giles', + 'Niigata', + 'Bihat', + 'Torqabeh', + 'Rio do Fogo', + 'Yogyakarta', + 'Candido Sales', + 'Barangka', + 'Paramati', + 'Imito', + 'Sugar Land', + 'Kyaliwajjala', + 'Silappadi', + 'Golfito', + 'Kilcock', + 'Moinesti', + 'Gujrat', + 'Keevallur', + 'Culasi', + 'Inanantonana', + 'Sidi Ghiles', + 'Cacolo', + 'Nierstein', + 'Guia Lopes da Laguna', + 'Takieta', + 'Lumaco', + 'Gatteo', + 'Weehawken', + 'Logan', + 'Olimpia', + 'Sidi Zouine', + 'Pardanjan', + 'Celle', + 'Santa Brigida', + 'Kifisia', + 'Leimen', + 'Denizciler', + 'Berezhany', + 'Rangkasbitung', + 'Pagidyala', + 'Infanta', + 'Hackensack', + 'Nakhon Pathom', + 'Tamsaout', + 'Towson', + 'San Teodoro', + 'Mahmuda', + 'Port-Gentil', + 'Federal Way', + 'Kondapalle', + 'Missao Velha', + 'Castres', + "Ash Shuhada'", + 'Almusafes', + 'Limbiate', + 'Jiguani', + 'Amherstburg', + 'Ryde', + 'Nazare Paulista', + 'Lqoliaa', + 'Balua', + 'Pathrajolhania', + 'Ain Karma', + 'Ekangar Sarai', + 'Dunhua', + 'Cherupulassheri', + 'Vutukuru', + 'Cesario Lange', + 'Coevorden', + 'Xiayang', + 'Converse', + 'Shivpuri', + 'Lido di Iesolo', + 'Capao do Leao', + 'Yangliuqing', + 'Kanata', + 'Vientiane', + 'Darhan', + 'Pola', + 'Bardai', + 'Taskopru', + 'Marcq-en-Baroeul', + 'Chestnuthill', + 'Tonse West', + 'Ibirapitanga', + 'Carmo de Minas', + 'Nirala', + 'Gardner', + 'Chorozinho', + 'Novo Selo', + 'Koilkuntla', + 'Ixmiquilpan', + 'Pinole', + 'Kirkby', + 'Rokytne', + 'Haivoron', + 'Charana', + 'Shreveport', + 'Dunkerque', + 'Businga', + 'El Salvador', + 'Teustepe', + 'Begowal', + 'Al `Ayn', + 'Xiaguanying', + 'Mhow', + 'Vittorio Veneto', + 'Savignano sul Rubicone', + 'Bitola', + 'Chomutov', + 'Natividad', + 'Pont-a-Mousson', + 'Ballincollig', + 'Ballymoney', + 'Peixe', + 'As Saff', + 'San Antonio de Areco', + 'Ibi', + 'Hathwan', + 'Esperance', + 'Wangjiabian', + 'Pokrovsk', + 'Davao', + 'Anniston', + 'Rodental', + 'Maple Ridge', + 'Esneux', + 'Ahwa', + 'Monticello Conte Otto', + 'Royal Leamington Spa', + 'Carei', + 'Real', + 'Burjasot', + 'Pingdingshan', + 'Wumayingcun', + 'Asane', + 'Souto Soares', + 'Arvada', + 'Palu', + 'Narasapur', + 'Ivry-sur-Seine', + "Saint-Ouen-l'Aumone", + 'High Blantyre', + 'Draganesti-Olt', + 'Pattoki', + 'Cologno Monzese', + "Ville-d'Avray", + 'Dornava', + 'Ambatomanoina', + 'Qinbaling', + 'So', + 'Villanchirai', + 'Emet', + 'Agatogba', + 'Saint-Laurent-du-Var', + 'Airway Heights', + 'Brasil Novo', + 'Fangting', + 'Mutukuru', + 'Erlensee', + 'Mangur', + 'Olivenca', + "L'Isle-Jourdain", + 'Kodmial', + 'Manresa', + 'Antsahadinta', + 'Fort-Shevchenko', + 'Eisenhuttenstadt', + 'Nicastro', + 'Widekum', + 'Delmenhorst', + 'San Gwann', + 'Soalala', + 'Soweto', + 'Pachor', + 'Naugachhia', + 'Mount Isa', + 'Afmadow', + 'Suzukawa', + 'Radcliffe', + 'Boksburg', + 'Hipperholme', + 'Harim', + 'Chinnampalaiyam', + 'Tilari', + 'Giporlos', + 'Perleberg', + 'Bankya', + 'Capua', + 'Lilio', + 'Qinzhou', + 'Aratuipe', + 'Itaborai', + 'Oldebroek', + 'Tibbar', + 'Douar Ain Chkef', + 'Nueva Tolten', + 'Tetela de Ocampo', + 'Denby Dale', + 'Viareggio', + 'Gangadhar', + 'Durgapur', + 'Arcahaie', + 'Kupiansk', + 'Achhnera', + 'Cherupazhasshi', + 'Benalla', + 'Labhgaon', + 'Bou Fekrane', + 'Chahbounia', + 'Middlebury', + 'Wallerfangen', + 'Fribourg', + 'Chilly-Mazarin', + 'Tibu', + 'Wachtersbach', + 'Valparaiso', + 'Vettavalam', + 'Zamania', + 'Farmers Branch', + 'Neqab', + 'Ayinikkad', + 'Messkirch', + 'Nagamangala', + 'Joinvile', + "M'diq", + 'Schwaigern', + 'Ban Bang Phun', + 'Xudat', + 'Silay', + 'Thikriwala', + 'Lake Wylie', + 'Doukombo', + 'Tual', + 'Phu Quoc', + 'Huludao', + 'Jabonga', + 'Uetze', + 'San Mauro Pascoli', + 'Sao Desiderio', + 'Brignais', + 'Rancho Cordova', + 'Pelezi', + 'Sangota', + 'Ikhlaspur', + 'Guaratuba', + 'Mibu', + 'Arris', + 'Lysander', + 'Kameda-honcho', + 'Mzuzu', + 'Shibukawa', + 'Dongyuya', + 'Allahpur', + 'Harrodsburg', + 'Brig-Glis', + 'Villa San Giovanni', + 'Zhangaozen', + 'Alabang', + 'Saint-Jacques-de-la-Lande', + 'Rampur Rajwa', + 'Te Anau', + 'Ashwaraopeta', + 'Fridley', + 'Hukou', + 'Yerere', + 'Itajuipe', + 'Toda Rai Singh', + 'Oconomowoc', + 'Hudiksvall', + 'Ploiesti', + 'Camilo Ponce Enriquez', + 'Aheqi', + 'Povoa de Santa Iria', + 'Salaspils', + 'Marti', + 'Heswall', + 'Barcelos', + 'Axim', + 'Datoda', + 'Kandra', + 'Montreuil', + 'Lakhnadon', + 'Tarnok', + 'Brades', + 'Sabana Yegua', + 'Ed Daein', + 'Mchinji', + 'Jaral del Progreso', + 'Bouskoura', + 'Gradignan', + 'Norresundby', + 'Bairagnia', + 'Chai Prakan', + 'Cocos', + 'Mwingi', + 'Lucao', + 'Matauna', + 'Pihani', + 'Taishan', + 'Mocamedes', + 'Didieni', + 'Ankiabe-Salohy', + 'Dammapeta', + "La'tamna", + 'Upi', + 'Annakunnu', + 'Iizuka', + 'Pattani', + 'Qiqihar', + 'Maharlika Village', + 'Harborne', + 'Chamusca', + 'Nanshuicun', + 'Milledgeville', + 'Seven Pagodas', + 'Semirom', + 'Candiba', + 'Antofagasta', + 'Tayug', + 'Konobougou', + 'Profesor Salvador Mazza', + 'Perry Heights', + 'Charouine', + 'Unterschleissheim', + 'Kyzyl-Kyya', + 'Hadjout', + 'Zhailuo', + 'Bobigny', + 'Champs-Sur-Marne', + 'Amatan', + 'Tadmait', + 'Zao', + 'Temperance', + 'Grenaa', + 'Zebala', + 'Steynsrus', + 'Buka', + 'Itapororoca', + 'Maranchi', + 'Sikandra Rao', + 'Neftcala', + 'Nguekhokh', + 'Pawtucket', + 'Mechanicsburg', + 'Mold', + 'Tipitapa', + 'Gobindgarh', + 'Bemanevika', + 'Sureshjan', + 'Abomey-Calavi', + 'Caracuaro', + 'Vohimarina', + 'Didim', + 'Getulio Vargas', + 'Hoogvliet', + 'Denton', + 'Rodgau', + 'Abbiategrasso', + 'Oum Drou', + 'Sagwara', + 'Huetor-Tajar', + 'Canovellas', + 'Calan', + 'Droitwich', + 'Gradacac', + 'Czechowice-Dziedzice', + 'Newton in Makerfield', + 'Zabreh', + 'Otavi', + 'Molina', + 'Duncanville', + 'Kumaramputtur', + 'Visnagar', + 'Ichikawa', + 'Shiojiri', + 'Trinity', + 'Shchuchinsk', + 'Kemberg', + 'Isla', + 'Bedele', + 'Cocorna', + 'Pozo Colorado', + 'Motobu', + 'Rodinghausen', + 'Osterhofen', + 'Beckwith', + 'Yafran', + 'Buguey', + 'Hawkesbury', + 'Seria', + 'New London', + 'Agia Varvara', + 'Talisayan', + 'Pothuhera', + 'Tiruppuvanam', + 'Koumpentoum', + 'Sesquile', + 'Freehold', + 'El Haouaria', + 'Tall Rif`at', + 'Nova Timboteua', + 'Novi Iskar', + 'Choghadak', + 'Trinitapoli', + 'Orimattila', + 'Mussoorie', + 'Kozan', + 'Mahatsinjo', + 'Guajiquiro', + 'Bear', + 'Zhoushan', + 'Safford', + 'M.A. Rasulzada', + 'Kalaun', + 'Grijo', + 'San Cristobal Totonicapan', + 'Marbach am Neckar', + 'Panukulan', + 'Salcininkai', + 'Neptune', + 'Cerro de Pasco', + 'Pisac', + 'Puttankulam', + 'Vordingborg', + 'Coto de Caza', + 'Ahumada', + 'Angicos', + 'Jeonju', + 'Detroit', + 'La Gi', + 'Puerto Ayacucho', + 'La Reja', + 'Mymensingh', + 'Bondy', + 'Altoona', + 'Loberia', + 'Novooleksiivka', + 'Vriddhachalam', + 'Tirumalaippatti', + 'Kontcha', + 'Chennai', + 'Nagykata', + 'Havre de Grace', + 'Jimani', + 'Beaune', + 'Montornes del Valles', + 'Dothan', + 'Saiki', + 'Indaial', + 'Nantwich', + 'Foshan', + 'Abu Hamad', + 'Prilly', + 'Chelora', + 'Santa Isabel Ishuatan', + 'Urpaar', + 'Highfields', + 'Choix', + 'Poway', + 'Ormeau', + 'Pulivalam', + 'Maruim', + 'Minobu', + 'Circleville', + 'Noksan', + 'Almada', + 'Sali', + 'New Brunswick', + 'Ottapparai', + 'Sadabe', + 'Tomi', + 'Ramanayakkanpalaiyam', + 'Golpazari', + 'Grunheide', + 'Punalur', + 'Essex Junction', + "Ibra'", + 'Balupur', + 'Collegno', + 'Gzira', + 'Santa Cruz', + 'Les Irois', + 'Lobos', + 'Slubice', + 'Ichinohe', + 'Phonsavan', + 'Had Oulad Issa', + 'Diffun', + 'Cuijk', + 'Algete', + 'Laascaanood', + 'Sayo', + 'Sayada', + 'Xonqa', + 'Numan', + 'Benetuser', + 'Ilaka Atsinanana', + 'Debno', + 'Nagtipunan', + 'Chegutu', + 'Yabu', + 'Maskanah', + 'Tiruvankod', + 'Japaratinga', + 'Horodok', + 'Maevatanana', + 'Lanester', + 'Jequitinhonha', + 'Gibraltar', + 'Acqui Terme', + 'Acasusso', + 'Cumberland', + 'Yildizeli', + 'Coronel Bogado', + 'Daruvar', + 'Dupax del Sur', + 'Gharb', + 'Becerril', + 'Peachtree City', + 'Najrij', + 'Pabna', + 'Millstone', + 'Blidet Amor', + 'Kayapa', + 'Progress', + 'San Pedro del Pinatar', + 'Ambakireny', + 'Afranio', + 'Salangaippalaiyam', + 'Cupira', + 'Elmas', + 'Taunton', + 'Bianyang', + 'Piracaia', + 'Pudsey', + 'Qiryat Ata', + 'Lusaka', + 'Carterton', + 'Robbinsville', + 'Namorona', + 'Vicam Pueblo', + 'Baytown', + 'Kapfenberg', + 'Makoua', + 'Sobral de Monte Agraco', + 'Belek', + 'Rio de Oro', + 'Biga', + 'Shiliguri', + 'Agdas', + 'Bougara', + 'Mit Damsis', + 'Alto Alegre dos Parecis', + 'Cantillana', + 'Waldfeucht', + 'Urcos', + 'Kishanpura Kalan', + 'Buchloe', + 'Suffolk', + 'Gummersbach', + 'Khandhar', + 'Coracora', + 'Mpondwe', + 'Lalin', + 'Xiulin', + 'Chanwari', + 'Kumba', + 'Aware', + 'Colgong', + 'Zeven', + 'Mariano Comense', + 'Campos Lindos', + 'Diu', + 'Aligarh', + 'Nova Pazova', + 'Barhni', + 'Djibo', + 'Soddy-Daisy', + 'Seneffe', + 'Tarn Taran', + 'Dragasani', + 'Bannewitz', + 'Badagabettu', + 'Angichettippalaiyam', + 'Heroica Nogales', + 'Osecina', + 'Kuhsan', + 'Grimstad', + 'Tufanbeyli', + 'Sweetwater', + 'Chandur', + 'Novyi Buh', + 'Alzey', + 'Telsiai', + 'Purwokerto', + 'Tarakeswar', + 'Slupca', + 'Malone', + 'Nowrangapur', + 'Khorol', + 'Gudofredo Viana', + 'Od', + 'Tavriisk', + 'Shakhtarsk', + 'Silago', + 'Muskego', + 'Kartarpur', + 'Shofirkon Shahri', + 'Zhangjiakou Shi Xuanhua Qu', + 'Lloyd', + 'Togane', + 'Minabe', + 'Paloma Creek South', + 'Koelwar', + 'Takestan', + 'Cotorra', + 'Lugang', + 'Osaki', + 'Adiyakkamangalam', + 'Fatipura', + 'Jiaoxiyakou', + 'Lakkireddipalle', + 'Calamar', + 'Eastbourne', + 'Manlleu', + 'Santo Andre', + 'Glowno', + 'Ozorkow', + 'Herzliyya', + 'Zaladanki', + 'Druzhkivka', + 'Naron', + 'Nasim Shahr', + 'Penicuik', + 'Kamianets-Podilskyi', + 'Rezzato', + 'Wandlitz', + 'Eufaula', + 'El Hamma', + 'Kattirippulam', + 'Tafo', + 'Bad Sassendorf', + 'Tuticorin', + 'Venado Tuerto', + 'Kanasanapalle', + 'Paulistana', + 'Tauberbischofsheim', + 'Mino', + 'Bapaura', + 'Lao Cai', + 'Winchendon', + 'Akyazi', + 'Baclayon', + 'Radhanpur', + 'Inkerman', + 'Donsol', + 'Vila Real de Santo Antonio', + 'Bottrop', + 'South Orange Village', + 'Grezzana', + 'Konkavaripalle', + 'Nybro', + 'Dharmastala', + 'Waldbrol', + 'Shimomura', + 'Hangal', + 'Ribeirao das Neves', + 'Hachirud', + 'Asmara', + 'Mercogliano', + 'Isparta', + 'Choshi', + 'Rosetta', + 'Shimotsuma', + 'Souagui', + 'Nishigo', + 'Duitama', + 'Salkhad', + 'Tenares', + 'Paita', + 'Zephyrhills', + 'Nacaome', + 'Lezignan-Corbieres', + 'Moncks Corner', + 'Maracena', + 'Gutao', + 'Coseley', + 'Susurluk', + 'Hoofddorp', + 'Taufkirchen', + 'Lebork', + 'West Bridgford', + 'Roda', + 'Laatatra', + 'Sin-le-Noble', + 'Lake Morton-Berrydale', + 'Junik', + 'Gorey', + 'Ferrara', + 'Falla', + 'Atotonilco el Grande', + 'Barnala', + 'Port Chester', + 'Rutland', + 'Gistel', + 'Palestina de los Altos', + 'Manujan', + 'Koja', + 'Vitre', + 'Antsambalahy', + 'Ewo', + 'Silivri', + 'Nitte', + 'Riyadh', + 'Ginir', + 'Figueras', + 'Porvoo', + 'Belamoty', + 'Vista Hermosa de Negrete', + 'Heysham', + 'Laoac East', + 'Ad Dammam', + 'Chisinau', + 'Lanus', + 'Lockport', + 'Powder Springs', + 'Panamarattuppatti', + 'Bisingen', + 'Cotacachi', + 'Bayanan', + 'Ozoir-la-Ferriere', + 'Ouyen', + 'Kuttuparamba', + 'Velimese', + 'Nabeul', + 'Anisio de Abreu', + 'Uozu', + 'Cumaribo', + 'Bhadravati', + 'Kuroshio', + "Barano d'Ischia", + "Qian'an", + 'Oulmes', + 'Bethulie', + 'Tebessa', + 'Columbia City', + 'Kalvarpatti', + 'Nesconset', + 'Quilali', + 'Uddevalla', + 'Teplice', + 'Pindobacu', + 'Xihu', + 'Altonia', + 'Asudapuram', + 'North Fayette', + 'Konstantynow Lodzki', + 'Castellanza', + 'Penalolen', + 'Zimatlan de Alvarez', + 'Minad', + 'Martellago', + 'Chitral', + 'Yumbo', + 'Dunajska Streda', + 'Didy', + 'Horquetas', + 'Pinas', + 'Farragut', + 'Castro Alves', + 'Saatli', + 'Naranapuram', + 'Pine Castle', + 'Penzberg', + 'Fomeque', + 'Goodmayes', + 'Aru', + 'Dobrovo', + 'Martinez de la Torre', + 'West Glens Falls', + 'Vaddadi', + 'Barra da Estiva', + 'Whitefield', + 'Milford city', + 'Guarulhos', + 'Fangchenggang', + 'Edwardsville', + 'Broussard', + 'Lieto', + 'Quimili', + 'Kaiyun', + 'Mocha', + 'Volkach', + 'Vazante', + 'Thu Duc', + 'Penapolis', + 'Nanchital de Lazaro Cardenas del Rio', + 'Phayao', + 'Talwat', + 'Tineo', + 'Opwijk', + 'Diabali', + 'Andranovorivato', + 'Huron East', + 'Poirino', + 'Juquitiba', + 'Tizi Gheniff', + 'Rudolstadt', + 'Kozhinjampara', + 'Huangyoutang', + 'Triel-sur-Seine', + 'Bordj el Kiffan', + 'Unwana', + 'Vanthli', + 'Gramado', + 'Ogawara', + 'Arbatache', + 'Mubende', + 'Itaperuna', + 'Beccles', + 'Balabagan', + 'Jeomchon', + 'Fountain Inn', + 'Passaic', + 'Apaxco de Ocampo', + 'Bhairi', + 'Kalayapuram', + 'Tlahualilo de Zaragoza', + 'Mangasamudram', + 'Kvivik', + 'Arnstadt', + 'South Lebanon', + 'Vinnytsia', + 'Bhimbar', + 'Panakkudi', + 'Paraisopolis', + 'Deux-Montagnes', + 'Antarvedipalem', + 'Pushing', + 'Ichinomiya', + 'Padre Bernardo', + 'Jiblah', + 'Bestwig', + 'Tuba', + 'Porto Amboim', + 'Gonohe', + 'Caernarfon', + 'Oostakker', + 'Ataq', + 'General Panfilo Natera', + 'Sabratah', + 'Andira', + 'Ichtegem', + 'Coban', + 'Sangli', + 'Balhapur', + 'Narayanraopet', + 'Paimio', + 'Alvorada do Sul', + 'El Transito', + 'Tonk', + 'Methil', + 'Ban Phonla Krang', + 'Arac', + 'Selydove', + 'Pecinci', + 'Benidorm', + 'Binkolo', + 'Kailaras', + 'Quibdo', + 'Nieuw Nickerie', + 'Magstadt', + 'Bhirua', + 'Pallipalaiyam', + 'Ampasimatera', + 'Vila do Conde', + 'Fariman', + 'Darihat', + 'Jurong', + 'Zurrieq', + 'Oschatz', + 'Godella', + 'Martano', + 'Wuhu', + 'Gafanha da Nazare', + 'Elsmere', + 'Malior', + 'Sidi el Mokhfi', + 'Mingxing', + 'Elsen', + 'Zinvie', + 'Orange', + 'Rakovnik', + 'Courcouronnes', + 'Eregli', + 'Shengaocun', + 'Badkulla', + 'Zile', + 'North Highlands', + 'Bhubaneshwar', + 'Deurne', + 'Bassano del Grappa', + 'Paonta Sahib', + 'Chapantongo', + 'Samannud', + 'Narayanpet', + 'Chintapalle', + 'Myslenice', + 'Pama', + 'Pedro II', + 'Imperial', + 'Tuvagudi', + 'Erftstadt', + 'Ningbo', + 'Burglengenfeld', + 'Coatepec', + 'Horr-e Riahi', + 'Duayaw-Nkwanta', + 'Unchagao', + 'Sayram', + 'Herouville-Saint-Clair', + 'Nurhak', + 'Joniskis', + 'Fish Town', + "Vranov nad Topl'ou", + 'Thief River Falls', + 'North Miami', + 'Szarvas', + 'Poing', + 'Xanxere', + 'Bulolo', + 'Tin Shui Wai', + 'Auriol', + 'La Calera', + 'Mannachchanellur', + 'San Miguelito', + 'Harsefeld', + 'Zdunska Wola', + 'Hellin', + 'Tours', + 'Uryzhar', + 'Villebon-sur-Yvette', + "'Ain Kihal", + 'Ocean Acres', + 'Paranagua', + 'Coaraci', + 'Saaminki', + 'Ullur', + 'Muttatodi', + 'Abashiri', + 'Tadikalapudi', + 'Mahin', + 'Duderstadt', + 'Utazu', + 'Oleiros', + 'Bolekhiv', + 'Beuvry', + 'Ilagan', + 'Lakhipur', + 'Tepotzotlan', + 'Vallendar', + 'Quimavango', + 'Cobh', + 'Ayutla', + 'Pau', + 'Ngong', + 'Doorn', + 'Libenge', + 'Bin-Houye', + 'Shahr-e Kord', + 'Santiago de Maria', + 'Sabya', + 'Sunninghill', + 'Alhama de Murcia', + 'Usakos', + 'Boke', + 'Ingichka', + 'Montgomery Village', + 'Almonte', + 'Campagnano di Roma', + 'Akbou', + 'Andaingo Gara', + 'Herzberg', + 'Sande', + 'Aracataca', + 'Dipaculao', + 'La Celle-Saint-Cloud', + 'Soja', + 'Semari', + 'Bareilly', + 'Mahna', + 'Wandsworth', + 'Danwan', + 'Manganam', + 'Dinant', + 'Delran', + 'Shangzhen', + 'Mallampalli', + 'Czluchow', + 'San Borja', + 'Sanya', + 'Galion', + 'Tinchlik', + 'Show Low', + 'Mbouda', + 'Hong Kong', + 'Cedar Hill', + 'Baraawe', + 'Rabaul', + 'Matsukawa', + 'Farum', + 'Jishi', + 'Rush', + 'Ubay', + 'Schleusingen', + 'Hengken', + 'Pawai', + 'Margherita di Savoia', + 'Togba', + 'Faradabad', + 'Soron', + 'Ban Tha Mai I', + 'Santa Marta de Tormes', + 'Llantrisant', + 'Huauchinango', + 'Darmaha', + 'Hadano', + 'Porto Real', + 'Pakri', + "N'Djamena", + 'Skuvoy', + 'Novopokrovka', + 'Dighirpar', + 'Summerside', + 'Young', + 'Campagna', + 'Chavuttahalli', + 'Malalbergo', + 'San Victor Abajo', + 'Robertsganj', + 'Toubakoro', + 'Namakadu', + 'Clayton', + 'Xalqobod', + 'Moerdijk', + 'Bergerac', + 'Linxi', + 'La Barca', + 'Hopewell', + 'Riverview', + 'Nanfang', + 'Garwolin', + 'Gannan', + 'Cowell', + 'Zhongguyue', + 'Cacapava do Sul', + 'Fenyang', + 'Chimore', + 'Bahadurganj', + 'Dantewara', + 'San Feliu de Guixols', + 'Puente-Genil', + 'Basmanpur', + 'Baar', + 'Chinautla', + 'Iriga City', + 'Gonglang', + 'Pecs', + 'La Paz Centro', + 'Lienz', + 'Japaratuba', + 'Chiba', + 'Havlickuv Brod', + 'Ranchos', + 'Corning', + 'Zinder', + 'Sankt Gallen', + 'Al Lith', + 'Kaminoyama', + 'Morarano', + 'Sokyriany', + 'Untergruppenbach', + 'Makapanstad', + 'Hullahalli', + 'Empoli', + 'Melton', + 'Abiramam', + 'Villapinzon', + 'Le Vesinet', + 'Den Chai', + 'Khasab', + 'General Deheza', + 'Rideau Lakes', + 'Banora Point', + 'Dillenburg', + 'Acarape', + 'Olympia', + 'Zilina', + 'Chuarrancho', + 'Jaipur Chuhar', + 'Scaggsville', + 'Bat Khela', + 'Villa Jaragua', + 'Oosterzele', + 'La Banda', + 'West Pensacola', + 'Cleckheaton', + 'Gizo', + 'Guaimaca', + 'Chalkida', + 'Namegata', + 'Shibam', + 'Duekoue', + 'Bir Anzarane', + 'Sidi Yahia', + 'Port Hedland', + 'Cisterna di Latina', + 'Dolo', + 'Castro Daire', + 'Kolattur', + 'Hachioji', + 'Sakti', + 'Stanford', + 'Asakapalle', + 'Mazhang', + 'Claypole', + 'Sokal', + 'Byureghavan', + 'Ghasri', + 'Harbin', + 'Renqiu', + 'Bhaur', + 'Canillo', + 'Capellen', + 'Kenton', + 'Balasamudram', + 'Sondershausen', + 'Coburg', + 'Makrana', + 'Zvishavane', + 'Petapa', + 'Piparia', + 'Kiamba', + 'Meyerton', + 'Yi Xian', + 'Tewkesbury', + 'Menomonee Falls', + 'Wilkinsburg', + 'Cildir', + 'Sao Mateus do Maranhao', + 'Belleville', + 'Sandefjord', + 'Santa Branca', + 'Hinckley', + 'Tomblaine', + 'Chizhou', + 'Hardenberg', + 'Victor Harbor', + 'Broadlands', + 'Iskourane', + 'Yuanli', + 'Erattukulakkada', + 'Selcuklu', + 'Colesville', + 'Port Hope', + 'Sidi Amer El Hadi', + 'Bowringpet', + 'Kalpakathukonam', + 'Adonara', + 'Mesquite', + 'Papara', + 'Karambakkudi', + 'El Aguila', + 'West Derby', + 'Bhanvad', + 'Borj el Qoble', + 'Kamikita-kita', + 'Rio Pardo de Minas', + 'Paso Canoas', + 'Holalu', + 'Owani', + 'Katoya', + 'Semdinli', + 'Manaratsandry', + 'San Martin Texmelucan de Labastida', + 'Palo', + 'Vestavia Hills', + 'Deogarh', + 'San Juan del Cesar', + 'Teorama', + 'Secaucus', + 'Tepecoyo', + 'Kadur', + 'Belampona', + 'Ghattu', + 'Sao Jose do Calcado', + 'Shinto', + 'Al Hibah', + 'Gateway', + 'Werdohl', + 'Lenzburg', + 'Gjilan', + 'Zory', + 'Vodurivandlagudem', + 'Hoddesdon', + 'Rawmarsh', + 'Yajalon', + 'Taisheng', + 'Kizhattur', + 'Yongcong', + 'Macomer', + 'Iwamizawa', + 'Bail-Hongal', + 'Maglod', + 'Jauja', + 'Kadiyadda', + 'Tiruvengadam', + 'Alovera', + 'Sao Goncalo do Rio Abaixo', + 'Velike Lasce', + 'Foxborough', + 'Collo', + 'Cincinnati', + 'Manambolo', + 'Rugeley', + 'Kulob', + 'Saparua', + 'Plympton', + 'Malekan', + 'Calheta', + 'Kalanak', + 'Sines', + 'San Ignacio Cohuirimpo', + 'Grottaferrata', + 'Skhira', + 'Harker Heights', + 'El Nido', + 'Morangis', + 'Belp', + 'Estremoz', + 'Baft', + 'State College', + 'Likak', + 'Kanjiza', + 'Schwabmunchen', + 'Adzope', + 'Balamban', + 'Yaojiafen', + 'Klosterneuburg', + 'Coracao de Jesus', + 'Pereshchepyne', + 'Dayr Abu Sa`id', + 'Tofol', + 'Vasad', + 'Rychnov nad Kneznou', + 'Sontha', + 'Mandza', + 'Jatibonico', + 'North Whitehall', + 'Ringnod', + 'Palamel', + 'Pampa', + 'Ahrensfelde', + 'Barwat Pasrain', + 'Haider Khel', + 'Zuera', + 'Yampil', + 'Wyoming', + 'El Outaya', + 'Kikinda', + 'Zefat', + 'Naganuma', + 'Chingford', + 'Weisswasser/Oberlausitz', + 'Stockton', + 'Tournefeuille', + 'Wolfenbuttel', + 'Kieta', + 'Manambaro', + 'Abejorral', + 'Tchitato', + 'Horsforth', + 'Kunithala', + 'Yesilhisar', + 'Gulshan', + 'Sanuki', + 'Laanoussar', + 'Kerouane', + 'Huaycan', + 'Curtorim', + 'Tamallalt', + 'Coronado', + 'Mata Verde', + 'Charenton-le-Pont', + 'Tamri', + 'Villiersdorp', + 'Miorimivalana', + "Ain M'Lila", + 'Siruvachchur', + 'Valdez', + 'Bou Arfa', + 'Bonifacio', + 'Valpoy', + 'Jun Bel', + 'Northcote', + 'Macenta', + 'Guira de Melena', + 'Moranha', + "Jem'at Oulad 'Abbou", + 'Upminster', + 'Bulawayo', + 'Laguna', + 'Adria', + 'Qatlupur', + 'Rumphi', + 'Omaezaki', + 'Ksar El Kebir', + 'Cruzeiro', + 'Semic', + 'Moparipalaiyam', + 'Quanzhou', + 'Wisil', + 'Meadow Lakes', + 'Qrendi', + 'Ang Thong', + 'Nandi Hills', + 'Grayslake', + 'Teguise', + 'Kakunodatemachi', + 'Marupe', + 'Mayen', + 'Miguel Alves', + 'Karnal', + 'Owings Mills', + 'Lewe', + 'Dyykan-Kyshtak', + 'Kentville', + 'Ilijas', + 'San Pedro de Coche', + 'Yermal', + 'Zipaquira', + 'Linganaboyinacherla', + 'Es Sebt', + 'Luisiana', + 'Tamahu', + 'Kudowa-Zdroj', + 'Susice', + 'Saucillo', + 'Castelletto sopra Ticino', + 'Repentigny', + 'Manganj', + 'Algemesi', + 'Methuen Town', + 'Reota', + 'Aiyampuzha', + 'Nisia Floresta', + 'Tukwila', + 'Cayey', + 'Phu Tho', + 'Pishin', + 'Manuel B. Gonnet', + 'Moguer', + 'Petit-Goave', + 'Oulad Salmane', + 'Kadikoy', + 'Cocoa Beach', + "Bulung'ur Shahri", + 'Ambalavato', + 'Qal`eh Ganj', + 'Quierschied', + 'Barrow in Furness', + 'Beifan', + 'Cubulco', + 'Hallbergmoos', + 'Porcari', + 'Kombolcha', + 'Kirkagac', + 'Cacule', + 'Daraga', + 'Qandala', + 'Bagaha', + 'Isoanala', + 'Baulia', + 'Boultham', + 'Samundri', + 'Amangal', + 'Perambalur', + 'Gose', + 'San Pedro La Laguna', + 'Mahendragarh', + 'Offenburg', + 'Sagae', + 'Uttamapalaiyam', + 'Lesquin', + 'Novohrad-Volynskyi', + 'Takab', + 'Shahmirpet', + 'Dom Basilio', + 'Witten', + 'Lavandevil', + 'Katowice', + 'Valavakattumula', + 'Mel Seval', + 'Santa Catarina Otzolotepec', + 'Engenheiro Paulo de Frontin', + 'Alta', + 'Uran', + 'Santo Tirso', + 'Hochstadt an der Aisch', + 'Texistepeque', + 'Stevens Point', + 'Karadipara', + 'Diafarabe', + 'Travnik', + 'Sibi', + 'Bichkunda', + 'Tarpon Springs', + 'Cuicatlan', + 'Bealanana', + 'Nalhati', + 'Surak', + 'Kurumbalur', + 'Whangarei', + 'Hatten', + 'Valrico', + 'Johnstone', + 'Enterprise', + 'Olutanga', + 'Aguelhok', + 'Mezdra', + 'Tanghin-Dassouri', + 'Kamien Pomorski', + 'Santo Domingo Tehuantepec', + 'Ap Phu My', + 'Stamboliyski', + 'Kilis', + 'Ho Nai', + 'Taree', + 'Teulada', + 'Virac', + 'Vigia', + 'Shahrak-e Pars', + 'Zhengzhou', + 'Vikarabad', + 'Liubotyn', + 'Tremonton', + 'Tirmitine', + 'Genas', + 'Evato', + 'Pisa', + 'Kathua', + 'El Bordo', + 'Surin', + 'Bariri', + 'Trelaze', + 'Thatto Heath', + 'Takon', + 'Praya', + 'Baoding', + 'Nakashunbetsu', + 'Eloy', + 'Sao Miguel de Touros', + 'Moulay Driss Zerhoun', + 'El Pinar', + 'Mongomo', + 'Andranovelona', + 'Malbork', + 'Soatanana', + 'Hillerod', + 'Cape Elizabeth', + 'Tirwa', + 'Salatiga', + 'Roncaglia', + 'Selu', + 'Borgo Maggiore', + 'Shelbyville', + 'Bandar-e `Asaluyeh', + 'Ferizaj', + 'Ufeyn', + 'Devanhalli', + 'Akyurt', + 'Jawalgeri', + 'Cambita Garabitos', + 'Manalapan', + 'Surendranagar', + 'Tadinada', + 'Heumen', + 'Inzago', + 'Bulach', + 'Tinipuka', + 'Manohisoa', + 'Leisure City', + 'Hilvan', + 'La Estrella', + 'Vulcan', + 'Wallan', + 'Ramareddi', + 'Gava', + 'Beroy Atsimo', + 'Huejotzingo', + 'Tucupita', + 'Ouled Beni Messous', + 'Bedzin', + 'Bidston', + 'Marilandia do Sul', + 'Sechelt', + 'Beernem', + 'Palwal', + 'Puerto Iguazu', + 'Banchpar', + 'Targu Ocna', + 'Sacile', + 'Lajeado', + 'Razole', + 'Peringalam', + 'Cavaillon', + 'Panaji', + 'Kesennuma', + 'Linhai', + 'Silvi Paese', + 'Bairnsdale', + 'Mehidpur', + 'Sivrice', + 'Waynesville', + 'Oulu', + 'Mairena del Alcor', + 'Omatjete', + 'Mokena', + 'Awlouz', + 'Douar Bni Malek', + 'Bonao', + 'Al Karak', + 'Pitimbu', + 'Pandami', + 'Monzon', + 'Molbergen', + 'Sahit', + 'Castleford', + 'Perungulam', + 'Tonekabon', + 'Jasdan', + 'Dharmajigudem', + 'El Cacao', + 'Salavan', + 'Plato', + 'Fontenay-aux-Roses', + 'San Luis de La Loma', + 'Dinbela', + 'Hawthorn Woods', + 'Corabia', + 'Arboledas', + 'Utrecht', + 'Douar Lehouifrat', + 'Dois Vizinhos', + 'Cabo Bojador', + 'Amalou', + 'Rock Falls', + 'Sitebe', + 'Vakkam', + 'Dasnapur', + 'Ankirondro', + 'Rajupalem', + 'El Cajon', + 'Gunbarrel', + 'Yulee', + 'Chatan', + 'Palmer', + 'Fronteiras', + 'Chicureo Abajo', + 'Newberry', + 'Severance', + 'Sattenapalle', + 'Koping', + 'Muhlhausen', + 'Sylvania', + 'San Elizario', + 'Hameenkyro', + 'Confresa', + 'Barbate de Franco', + 'Ararangua', + 'Boston', + 'Frankfurt (Oder)', + 'Ganshoren', + 'Mehdipur', + 'Ambodinonoka', + 'Cajari', + 'La Primavera', + 'Brahmanandapuram', + 'Oued Rhiou', + 'Polegate', + 'Dodoma', + 'Mecheraa Asfa', + 'Aracas', + 'Rushden', + 'Korosten', + 'Deodora', + 'Pell City', + 'Beppu', + 'Greenburgh', + 'Analaiva', + 'Haar', + 'Cervantes', + 'Uelzen', + 'Budd Lake', + 'Puyang', + 'Aladag', + 'Hajeb el Aioun', + 'Camajuani', + 'Savannah', + 'Pithapuram', + 'Eldersburg', + 'San Policarpo', + 'Mennecy', + 'Ibirapua', + 'Gholia Kalan', + 'Pickerington', + 'Qadsayya', + 'Waldniel', + 'Bagalur', + 'Sao Pedro do Piaui', + 'Bar-le-Duc', + 'Nakhyaungcharipara', + 'Tralee', + 'Penukonda', + 'Ghosai', + 'Pasaquina', + 'Gevgelija', + 'Chokkampatti', + 'Illzach', + 'Huadian', + 'Baryshivka', + 'Siofok', + 'Yutan', + 'Njombe', + 'Khlung', + 'Dabouziya', + 'Tumpat', + 'Tangpingcun', + 'Chinna Salem', + 'Basaithi', + 'Aranguez', + 'Marand', + 'Tanudan', + "Bet She'an", + 'Pingxiang', + 'Westerlo', + 'Jomasho`y', + 'Brussels', + 'Kempele', + 'Kiranomena', + 'Porto Esperidiao', + 'Padre Burgos', + 'Banda del Rio Sali', + 'Cortlandt', + 'Tepatitlan de Morelos', + 'Eau Claire', + 'Saint-Constant', + 'Schermbeck', + 'Cuautitlan', + 'Kolnur', + 'Ehden', + 'Dazhuangzi', + 'Pebble Creek', + 'Meihekou', + 'Qianjiang Shequ', + 'Matamoros', + 'Calama', + 'Stoneham', + 'Dassel', + 'Khanna', + 'Cankaya', + 'Palos Hills', + 'Elurpatti', + 'Kallakkurichchi', + 'Monte San Giovanni Campano', + 'Orhaneli', + 'Bungoono', + 'Blankenberge', + 'Cordele', + 'Kure', + 'Bolgatanga', + 'Bansalan', + 'Race', + 'Morales', + 'Courcelles', + 'Evaz', + 'Pantepec', + 'Nyirbator', + 'Bellegarde-sur-Valserine', + 'Barahi', + 'Espinosa', + 'Fatehgarh', + 'Tangcun', + 'Brzeziny', + 'Pardwal', + 'Gonzales', + 'San Juan y Martinez', + 'Hirriyat Raznah', + 'Rokhaty', + 'Jiangyin', + 'Andahuaylas', + 'Pamiers', + 'Pinarbasi', + 'Rio Vermelho', + 'Boca del Rio', + 'Timana', + 'Cambridge', + 'Shancheng', + 'San German', + 'Mata Roma', + 'Compiegne', + 'Kadanganeri', + 'Argelato', + 'Torokszentmiklos', + 'Ardesen', + 'Cariamanga', + 'Korahia', + 'Saarwellingen', + 'South Abington', + 'Balete', + 'Fuman', + 'Amdel', + 'Dedemsvaart', + 'Plasencia', + 'Sharonville', + 'Villamarchante', + 'Slobozia', + 'Jinji', + 'Malak Abad', + 'Adakplame', + 'Manoke', + 'Companiganj', + 'Ozu', + 'Friedrichshafen', + 'Loudi', + 'Meerut', + 'Sheohar', + 'Sanjiang Nongchang', + 'Jos', + 'Zhangmu Touwei', + 'Siuna', + 'Clervaux', + 'Nocera Inferiore', + 'Borvayeh-ye Al Bu `Aziz', + 'Uki', + 'Humble', + 'Valaparla', + 'Ajim', + 'Kok-Janggak', + 'Etropole', + 'Idukki', + 'Olmue', + 'Maracanau', + 'Ar Rass', + 'Anivorano Avaratra', + 'Jonesboro', + 'Sanlucar de Barrameda', + 'Estahban', + 'Sujapur', + 'Yilong', + 'Ciudad Altamirano', + 'Rudnyy', + 'Wladyslawowo', + 'Hekinan', + 'Cittanova', + 'Schonebeck', + 'Mangalore', + 'Pehonko', + 'Kacanik', + 'Cauquenes', + 'Chikni', + 'Yongcheng', + 'Town and Country', + 'Assai', + 'Liujiaxia', + 'Harwich', + 'Attur', + 'Ganjam', + 'Matar', + 'Grovetown', + 'Hazro', + 'Findlay', + 'Lower Merion', + 'Hennef', + 'Musikot-Khalanga', + 'Vinjam', + 'Sao Jose', + 'Gengenbach', + 'Salmon Creek', + 'Katoomba', + 'Arrapalli', + 'Krasnystaw', + 'Ban Chang', + "Tai'an", + 'Manucan', + 'Bopolu', + 'Tissint', + 'Vohimasy', + 'Ocana', + 'Dickinson', + 'Middelkerke', + 'Jarajus', + 'El Jadid', + 'Altinyayla', + 'Rongcheng', + 'Puthupalli', + 'Pedro Afonso', + 'Muurame', + 'Castro Valley', + 'Cookstown', + 'Santa Maria das Barreiras', + 'Konand', + 'Dilbeek', + 'Meicheng', + 'Hamma Bouziane', + 'Rottweil', + 'Sao Luis do Curu', + 'Dowlatabad', + 'South Strabane', + 'Schenectady', + 'Baihar', + 'Blacklick Estates', + 'Villa Nougues', + 'Al Qurayyat', + 'Sao Jose de Mipibu', + 'Huddersfield', + 'Chibuto', + 'Groutville', + 'Vert-Saint-Denis', + 'Patzicia', + 'Kankon', + 'La Chaux-de-Fonds', + 'Altenbeken', + 'Blonie', + 'Anda', + 'Masterton', + 'Wieringerwerf', + 'Peruwelz', + 'Ilawa', + 'Dirba', + 'Shankarpalli', + 'Longquan', + 'Bodippatti', + 'Nasukarasuyama', + 'Sam', + 'Ban Nong Prue', + 'Monreale', + 'Barreira', + 'Hetanpur', + 'Shishgarh', + 'Tafresh', + 'Saltcoats', + 'Gopalasamudram', + 'Kafr Saqr', + 'Espinho', + 'Peine', + 'Radevormwald', + 'Pompano Beach', + 'Chavinda', + 'Yishi', + 'Janauba', + 'Ankalgi', + 'Pluderhausen', + 'Sopot', + 'Varpalota', + 'Sanming', + 'Santander', + 'Niskayuna', + 'Gumushacikoy', + 'Obalapuram', + 'Bollate', + 'Ghardaia', + 'Davorlim', + 'San Jacinto del Cauca', + 'Ridderkerk', + 'Athiringal', + 'Jimenez', + 'Ketrzyn', + 'Grugliasco', + 'Segoubougou', + 'Lanji', + 'Cocorote', + 'Mizil', + 'Francisco Beltrao', + 'Toksun', + 'Haka', + 'Bilasuvar', + 'Sabaneta de Yasica', + 'Abqaiq', + 'Hinundayan', + 'King', + 'Satun', + 'Fujioka', + 'Ankadinondry-Sakay', + 'Sentani', + 'Bady Bassitt', + 'Concepcion Tutuapa', + 'Woking', + 'Manlius', + 'Mutsamudu', + 'Khuzdar', + 'Czernica', + 'Balzers', + 'Saydnaya', + 'Suthalia', + 'Stevenson Ranch', + 'Dar Ould Zidouh', + 'South Salt Lake', + 'Voerde', + 'South San Francisco', + 'Sarstedt', + 'Neuwied', + 'Northview', + 'Diez', + 'Cuscatancingo', + 'Worth', + 'Wrentham', + 'Pueblo West', + 'Bispham', + 'Shillong', + 'Goes', + 'Talayazham', + 'Kosk', + 'Imus', + 'Montigny-les-Cormeilles', + 'El Guetar', + 'Campinas', + 'Muggio', + 'Latsia', + 'Leopoldsburg', + 'Tagazhi', + 'Tacaimbo', + 'Karimkunnum', + 'Olifantshoek', + 'Cocal', + 'Petrovac na Mlavi', + 'Likasi', + 'Marmaris', + 'Qarabulaq', + 'Dalin', + 'Straseni', + 'Tamluk', + 'Lemon Grove', + 'Shoreview', + 'Ewa Beach', + 'Ado-Ekiti', + 'Rio Verde', + 'Marina', + "Xi'an", + 'Jutai', + "Quartu Sant'Elena", + 'Santa Ursula', + 'Padiala', + 'Sigatoka', + 'Bien Unido', + 'Yotoco', + 'Goundam', + 'Iconha', + 'Pathari', + 'Kotra', + 'Brookside', + 'Palos de la Frontera', + 'Ban Bang Phlap', + 'Halwan', + 'Arth', + 'Jiaoxiling', + 'Dicholi', + 'Uruapan', + 'Bromont', + 'Yaprakli', + 'Junagadh', + 'Sirvintos', + 'Nandayure', + 'Karimama', + 'Pangantocan', + 'Kanpur', + 'Sao Manuel', + 'Schwelm', + 'North Bergen', + 'Curvelo', + 'Zafargarh', + 'Travilah', + 'Arifiye', + 'Eusebio', + 'Ba`qubah', + 'Nutakki', + 'Hermantown', + 'Kremenets', + 'Enrile', + 'Tavarede', + 'Wiesmoor', + 'Soke', + 'Bandraboua', + 'Dera Allahyar', + 'Welzheim', + 'Calbuco', + 'Markovci', + 'Dumka', + 'Dumaria', + 'Bogue', + 'Manuel Tames', + 'Longjumeau', + 'Igny', + 'Medenine', + 'Govindapalle', + 'Justice', + 'Surappalli', + 'Fara', + 'Sao Francisco do Maranhao', + 'Nandiyalam', + 'Tarrasa', + 'Amaliada', + 'El Hadjira', + 'Hickory', + 'Mandramo', + 'Celendin', + 'Almansa', + 'Yamatotakada', + 'Rhaude', + 'Lingtang', + 'Kfar Kidde', + 'Abadla', + 'Nova Serrana', + 'La Victoria', + 'Adis Zemen', + 'Nanticoke', + 'Douar El Arbaa Bou Quorra', + 'Passo Fundo', + 'Ain el Mediour', + 'Ban Lam Sam Kaeo', + 'Hinwil', + 'Moradabad', + 'Douar Toulal', + 'Hong', + 'Lubbock', + 'Higashikagura', + 'Trentola', + 'Prymorsk', + 'Trzcianka', + 'Borujerd', + 'Elefsina', + 'Teruel', + 'Mugdampalli', + 'Manampatrana', + 'Sisak', + 'Simpelveld', + 'Jonava', + 'Solola', + 'Santo Anastacio', + 'Nalayh', + 'Dumarao', + 'Imgarr', + 'Puerto Triunfo', + 'Dhubaria', + 'San Jose de Aerocuar', + 'Destrnik', + 'Dasso', + 'Calanogas', + 'Ash Shaykh Badr', + 'Klerksdorp', + 'Sardasht', + 'Udamalpet', + 'Xihuangni', + 'Agudos do Sul', + 'Ciudad de Atlixco', + 'Huntley', + 'Leonberg', + 'North Glengarry', + 'Baciu', + 'Hemmoor', + "Montopoli in Val d'Arno", + 'Settimo Torinese', + 'Pul-e `Alam', + 'Kizilcahamam', + 'Bulnes', + 'Koiridih', + 'Ho', + 'Heber', + 'Bristol', + 'Turbaco', + 'Arbaa Laaounate', + 'Ayyagarpet', + 'Xiluo', + 'Nordre Fale', + 'Stara Pazova', + 'Uchinada', + 'Bhikkiwind Uttar', + 'Guerou', + 'Landeh', + 'Lilburn', + 'Calvizzano', + 'San Jose del Rincon Centro', + 'Bagh', + 'Mau', + 'Vincennes', + 'Warrenville', + 'Madiun', + 'Boma', + 'Vettam', + 'Magione', + 'Piscataway', + 'Souk et Tnine Jorf el Mellah', + 'Marly-le-Roi', + 'Khansahibpuram', + 'Otsu', + 'Vitoria', + 'Motril', + 'Asheboro', + 'Cobham', + 'Kalilangan', + 'Dahua', + 'Pozo Almonte', + 'Salem', + 'Babhangaon', + 'Zero Branco', + 'Berberati', + 'Laharpur', + 'Nihal Singhwala', + 'Pesnica', + 'Santa Cruz da Baixa Verde', + 'Newburyport', + 'Alcala de Guadaira', + 'Sodegaura', + 'Al Mazar ash Shamali', + 'Aransas Pass', + 'Xuddur', + 'Port-de-Paix', + 'Kirkstall', + 'Erd', + 'Saint-Martin-Boulogne', + 'Wulong', + 'Nagdha Simla', + 'Roeselare', + 'Lubin', + 'Barberino di Mugello', + 'Cranberry', + 'Oldenzaal', + 'Mascouche', + 'Ashgabat', + 'Mtwara', + 'Mankal', + 'Este', + 'Caririacu', + 'Piranshahr', + 'Xiaozhengzhuang', + 'Khamis Mushayt', + 'Prineville', + 'Sidi Namane', + 'Tapachula', + 'Massawa', + 'Ramewadi', + 'Abancay', + 'Biratnagar', + 'Chilakhana', + 'Tadepalle', + 'Passau', + 'Imerimandroso', + 'Nilanga', + 'Tsukuba-kenkyugakuen-toshi', + 'Veldhoven', + 'Longjiang', + 'Armidale', + 'Alcoy', + 'Gabaldon', + 'Klippansbruk', + 'Yongyang', + 'Canton', + 'Hammonton', + 'Pena Forte', + 'Panamaram', + 'Kankan', + 'Sanaa', + 'Kilchberg', + 'Fulton', + 'Prainha', + 'Andranofasika', + 'Barela', + 'Bhogpur', + 'Crayford', + 'Glenshaw', + 'San Lorenzo', + 'Thiais', + 'Bahce', + 'Oliveira do Hospital', + 'Bhander', + 'Taitung', + 'Caldas da Rainha', + 'Decatur', + 'Basaon', + 'Matteson', + 'Al Hammam', + 'Tayyibat al Imam', + 'Jhakhra', + 'Stiring-Wendel', + 'Vlissingen', + 'Zhongling', + 'Palm Springs', + "Qacha's Nek", + 'Kambaneri Pudukkudi', + 'Khardah', + 'Artashat', + 'Bua Yai', + 'Point Pedro', + 'Tozeur', + 'Luxitun', + 'San Sebastian de Mariquita', + 'Saint-Sauveur', + 'Rumilly', + 'Mortad', + 'Kaithwar', + 'Abertillery', + 'Sulzbach', + 'Tillaivilagam', + 'Veroia', + 'Zeitz', + 'Graz', + 'Kitakyushu', + 'Picasent', + 'Rahden', + 'Mecatlan', + 'Tehri', + 'Mauganj', + 'Burshtyn', + 'Espargos', + 'Travis Ranch', + 'Chokkanathapuram', + 'Bhalil', + 'Pateros', + 'Nambuangongo', + 'Beckum', + 'Sanary-sur-Mer', + 'Budili', + 'Akalapura', + 'Belo Vale', + 'Pavlohrad', + 'Swinton', + 'Cento', + 'Nantou', + 'Flora', + 'Carnaiba', + 'Piotrkow Trybunalski', + 'Conceicao da Feira', + 'Dohazari', + 'Sugito', + 'Si Sa Ket', + 'Sirjan', + 'Zaandam', + 'Titisee-Neustadt', + 'Rafelbunol', + 'Abadou', + 'Suhareke', + 'Pipariya', + 'Binidayan', + 'Kunzelsau', + 'Panjgur', + 'Juana Diaz', + 'Afzalpur', + 'Porirua', + 'Elbasan', + 'Hardi', + 'Forbesganj', + 'Shah Alam', + 'Tiruvegapra', + 'Cat', + 'Clichy-sous-Bois', + 'Qumqo`rg`on', + 'Ban Tha Thong', + 'Auburn Hills', + 'Castelo Branco', + 'Borgo', + 'Rich', + 'Karukachal', + 'Maltby', + 'Notteroy', + 'Glens Falls North', + 'Cunha', + 'Alebtong', + 'Balneario de Camboriu', + 'Anandapuram', + 'Bambalang', + 'Touggourt', + 'Taihe', + 'Saidoke', + 'Jori Kalan', + 'Golet', + 'Sankaranayinar Kovil', + 'Sao Sepe', + 'Saugus', + 'Capaci', + 'Puerto Morazan', + 'Fao Rai', + 'Caracase', + 'Tranas', + 'San Giorgio del Sannio', + 'Abeche', + 'Pak Thong Chai', + 'Sola', + 'Bensalem', + 'Talen', + 'Cidreira', + 'Phangnga', + 'Kalavapudi', + 'Kapsabet', + 'Nawan Shahr', + 'Pyu', + 'Mitsinjo', + 'Bradford', + "N'Gaous", + 'Mionica', + 'Echelon', + 'Antonio Carlos', + 'La Tuque', + 'Nairn', + 'Steenbergen', + 'Sidi Abdelaziz', + 'Jijel', + 'Manzanares', + 'Zofingen', + 'Ryuyo', + 'Tigard', + 'Belm', + 'Piastow', + 'Sebring', + 'Vastervik', + 'Hengshuicun', + 'Riverside', + 'Montecristi', + 'Aprilia', + 'Penistone', + 'Mogeiro', + 'Sao Joao de Ver', + 'Robles', + 'Saint-Germain-en-Laye', + 'Bethelsdorp', + 'Saka', + 'Butia', + 'Gudipallipadu', + 'Pulivendla', + 'Huntingdon', + 'Vila Teixeira da Silva', + 'Antsakanalabe', + 'Amneville', + 'Lummen', + 'Dbaiye', + 'Semmarikulan', + 'Risalpur Cantonment', + 'Acala del Rio', + 'Porur', + 'Amarchinta', + 'Olamze', + 'Kairouan', + 'Qiantangcun', + 'Montegrotto Terme', + 'Bensheim', + 'Lenvik', + 'Pariquera-Acu', + 'Sambre', + 'Tekkebhagam', + 'Blerick', + 'Parobe', + 'Clive', + 'Davos', + 'Devanakavundanur', + 'Tupran', + 'Rossano', + 'Raman', + 'Hosan', + 'Erding', + 'Ban Tha Kham', + 'Huercal-Overa', + 'Fairmount', + 'Kamabougou', + 'Alcaudete', + 'El Banco', + 'Saksohara', + 'Ferkessedougou', + 'Darsur', + 'Gagnef', + 'Mostaganem', + 'Opatija', + 'Cambira', + 'Moninnpebougou', + 'Pinia', + 'Sillod', + 'Santo Antonio do Sudoeste', + 'Baxt', + 'Yomitan', + 'Porto Firme', + 'Kalungu', + 'Tarime', + 'Dingxi', + 'Garuva', + 'Alton', + 'Poprad', + 'Comandante Fontana', + 'Luanzhou', + 'Cuitzeo del Porvenir', + "Sant'Agata de' Goti", + 'Tsinjoarivo', + 'Tamiahua', + 'Barnis', + 'Minami-Alps', + 'Jalam', + 'Arandu', + 'Spanish Fork', + 'Tiruvennanallur', + 'Mohammadia', + 'Bayt Sahur', + 'Wilhelmshaven', + 'Claudio', + 'Nelali', + 'San Andres Villa Seca', + 'North Dundas', + 'Kilminnal', + 'Alamogordo', + 'Antsakabary', + 'Ibicarai', + 'Eral', + 'Zdolbuniv', + 'Drochia', + 'Saikaicho-kobago', + 'Coroneo', + 'Gabasumdo', + 'River Forest', + 'Ecatepec', + 'Bekopaka', + 'Fort McMurray', + 'Ban Mae Tuen', + 'Dizicheh', + 'Biggin Hill', + 'Lower Bicutan', + 'Tunceli', + 'Kurim', + 'Curiti', + 'Seminole', + 'Tori-Bossito', + 'Lake Havasu City', + 'San Benito Abad', + "'Ain el Bell", + 'Bocsa', + 'Batuco', + 'Ughara', + 'Kakdwip', + 'Nallajerla', + 'Lumbang', + 'Sarbogard', + 'Bowral', + 'Kristinehamn', + 'Meiwa', + 'Tetela del Volcan', + 'Rupenaguntla', + 'Palera', + 'Attleboro', + 'Vitomarci', + 'Guraahai', + 'Henley on Thames', + 'Stanford le Hope', + 'Cazones de Herrera', + 'Middleborough', + 'Port Perry', + 'Saryaghash', + 'Tay', + 'Marlow', + "Granarolo del l'Emilia", + 'Whitby', + 'Pahou', + 'Santa Maria di Sala', + 'Rani Sawargaon', + 'Moore', + 'Tizimin', + 'Gabane', + 'Lichtenau', + 'Wetter (Ruhr)', + 'Quva', + 'Ipaucu', + 'Tahla', + 'Enschede', + 'Padinjaremuri', + 'Oued Essalem', + 'Sanatikri', + 'Razanj', + 'Ban Bang Lamung', + 'Camberley', + 'Avignon', + 'Sittingbourne', + 'Maduraivayal', + 'Onate', + 'Arboga', + 'Duluth', + 'Iluppur', + 'Tehachapi', + 'Tijucas', + 'Bahadarpur', + 'Bugugoucun', + 'Bayi', + 'Nakuru', + 'Fontenay-sous-Bois', + 'Tachikawa', + 'Amroha', + 'Boo', + 'Fochville', + 'Idak', + 'Campbellsville', + 'Limonade', + 'Lucea', + 'Tetouan', + 'Sesori', + 'Criciuma', + 'Bedford', + 'Osvaldo Cruz', + 'Karacoban', + 'Rafael Castillo', + 'Faranah', + 'Paina', + 'Santa Maria del Tule', + 'Puno', + 'Cove', + 'Sanankoroba', + 'Nowogard', + 'Pallipattu', + 'Bhalwal', + 'Torre Annunziata', + 'Gavinivaripalem', + 'Harike', + 'Kameyama', + 'Amala', + 'Struthers', + 'Ilindu Kothi', + 'Rio Grande', + 'Coral Gables', + 'San Francisco Libre', + 'Esteban Echeverria', + 'Than', + 'Bere', + 'Villa San Jose', + 'Gaurihar Khaliqnagar', + 'Yonabaru', + 'Kretinga', + 'Banolas', + 'Kalocsa', + 'Atlautla', + 'Al Muzayrib', + 'Tsitondroina', + 'Picture Rocks', + 'Ostbevern', + 'Purwa Utar', + 'Pontchateau', + 'Bandiagara', + 'Midar', + 'Abasingammedda', + 'Chinnamandem', + 'Zhydachiv', + 'Fayzobod', + 'Bad Bevensen', + 'Nedumpura', + 'Azua', + 'Earley', + 'Sao Sebastiao do Maranhao', + 'Nossa Senhora do Livramento', + 'Desaguadero', + 'The Village', + 'Richfield', + 'Medway', + 'Panglao', + 'Erraguntla', + 'Tapilula', + 'South Park', + 'Shovot', + 'Samaca', + 'Fehrbellin', + 'Pouytenga', + 'Wenping', + 'Mendefera', + 'Matanao', + 'Bataguacu', + 'Kljuc', + 'Saint-Malo', + 'Hiriyur', + 'Copan', + 'Comodoro', + 'Ampasimpotsy-Gara', + 'Issum', + 'Thundersley', + 'Nandavaram', + 'Ivisan', + 'Uvinza', + 'Bituruna', + 'Salzhemmendorf', + 'Oguchi', + 'Maubin', + 'Pullman', + 'Lower Gwynedd', + 'Kidal', + 'Ghariyah al Gharbiyah', + 'Sagarpur', + 'Cottage Lake', + 'Macon', + 'Siayan', + 'Fatick', + 'Hinda', + 'Betio', + 'Ramos Arizpe', + 'Mbuyapey', + 'Tirana', + 'Uzyn', + 'Jolo', + 'Knoxville', + 'Durant', + 'Khmelnytskyi', + 'Mong Duong', + 'Mujui dos Campos', + 'Hendaye', + 'Despatch', + 'Borne', + 'Goirle', + 'Petnjica', + 'Barda', + 'Feliz', + 'Ut Bulag', + 'Fasano', + 'Gtarna', + 'Greensboro', + 'Boskovice', + 'Barrafranca', + 'Estero', + 'Osmangazi', + 'Demre', + 'Manjil', + 'Temascaltepec de Gonzalez', + 'Mendota Heights', + 'Nalgora', + 'Gwangju', + 'Qitai', + 'Zakhu', + 'Krishnarajpet', + 'Hayes', + 'Vitthalapuram', + 'Burla', + 'Salay', + 'Skuodas', + 'Nagano', + 'Dambal', + 'Sarauni Kalan', + 'Bocaiuva', + 'Chimbas', + 'South Pasadena', + 'Dar El Kebdani', + 'Matanzas', + 'Jarocin', + 'Sarioglan', + 'Ibate', + 'Kisenzi', + 'Shibirghan', + 'Viseu de Sus', + 'Majhariya Sheikh', + 'Qulicun', + 'Aswan', + 'Kowloon City', + 'Yverdon-les-Bains', + 'Sera', + 'Mililani Mauka', + 'Acworth', + 'Tibubeneng', + 'Saijo', + 'Wetzikon', + 'Americo Brasiliense', + 'Nova Cruz', + 'Pantao-Ragat', + 'Chichibu', + 'Kyankwanzi', + 'Jabalya', + 'Caparica', + 'Rewa', + 'Castiglione delle Stiviere', + 'Demir Kapija', + 'Ramat Gan', + 'Porto-Novo', + 'River Road', + 'Canarana', + 'Penagam', + 'Maldah', + 'Rapho', + 'Vilvoorde', + 'Gauting', + 'Aschaffenburg', + 'Samux', + 'Ekamba', + 'Kepsut', + 'Esfahan', + 'Andramy', + 'Sarezzo', + 'Miracatu', + 'Kramatorsk', + 'Daping', + 'Tunuyan', + 'Bonyhad', + 'Sao Joao da Barra', + 'Kidsgrove', + 'San Jeronimo', + 'Chinnachauku', + 'Abai', + 'Kaipram', + 'Paleng', + 'Devarshola', + 'Utraula', + 'Meadville', + 'Satupa`itea', + 'Pacatuba', + 'Saint-Georges', + 'Remigio', + 'Puerto Suarez', + 'Arden-Arcade', + 'Kankanalapalle', + 'Mankada', + 'Willow Grove', + 'Tamaki', + 'Pijnacker', + 'Naryai ka Puri', + 'Al Mazyunah', + 'Bharhopur', + 'Ekero', + 'Deh', + 'El Jem', + 'Curacavi', + 'Ladue', + 'Barki Ballia', + 'Yecapixtla', + 'Petaluma', + 'Bradley Gardens', + 'Caapora', + 'Dinklage', + 'Karad', + 'Niuchangqiao', + 'Mahalingpur', + 'Walcz', + 'Mannamturuttu', + 'Les Clayes-sous-Bois', + 'Garhara', + 'Mumaradikop', + 'Davutlar', + 'Haripur', + 'Sahsaul', + 'Pachora', + 'Testour', + 'Ratchaburi', + 'Marovoay', + 'Valente', + 'Pouso Alegre', + 'Chinnavadampatti', + 'Mariana', + 'Unjha', + 'Keighley', + 'Detva', + 'Allouez', + 'Bamako', + 'Adrasmon', + 'Ramannapeta', + 'Fort Lauderdale', + 'Webuye', + 'Peligros', + 'Leeton', + 'Bollene', + 'Lianjiang', + 'Antsirabe Afovoany', + 'Ada', + 'Tixkokob', + 'Kamagaya', + 'Cassa de la Selva', + 'Takeocho-takeo', + 'Butig', + 'Algiers', + 'Straelen', + 'Mechelen-aan-de-Maas', + 'Esmeralda', + 'Nuevo Casas Grandes', + 'Houilles', + 'Itano', + 'San Javier', + 'Emiliano Zapata', + 'Seattle', + 'Narino', + 'Tiruvannamalai', + 'Clausthal-Zellerfeld', + 'Asola', + 'Antonina', + 'Dun Dealgan', + 'Rifle', + 'Maharajgani', + 'Las Cruces', + 'Mossel Bay', + 'Tinglayan', + 'Arapgir', + 'Locogahoue', + 'White House', + 'Havana', + 'Zhetisay', + 'Navinipatti', + 'Duptiair', + 'Abcoude', + 'Capinopolis', + 'Arucas', + 'Nova Lima', + 'Cunhinga', + 'Haftkel', + 'Andacollo', + 'Kawachinagano', + 'Mountlake Terrace', + 'Kalynivka', + 'Perintalmanna', + 'Rio Real', + 'Cottingham', + 'Bas Goynuk', + 'Jianshe', + 'Wermelskirchen', + 'Hariharpara', + 'Jucuapa', + 'Choa Saidan Shah', + 'Agaram', + 'Carmen', + 'Anguo', + 'Challakere', + 'Rahimpur', + 'Pitanga', + 'Zumarraga', + 'Gohna', + 'Coyuca de Catalan', + 'Matinhos', + 'Torun', + 'Taluqan', + 'Aurad Shahjahani', + 'Persembe', + 'Bochil', + 'Nadvirna', + 'Niagadina', + 'Gubeng', + 'Sitalkuchi', + 'Dashtobod', + 'Lascano', + 'Bergisch Gladbach', + 'Dumalag', + 'Wahga', + 'Guarambare', + 'Raniyah', + 'Maddur', + 'Buba', + 'Mobarakeh', + 'Ouaklim Oukider', + 'Ito', + 'Lonar', + 'Okhargara', + 'Esil', + 'Devrek', + 'Pefka', + 'Chamonix-Mont-Blanc', + 'Bad Bergzabern', + 'Las Condes', + 'Ptolemaida', + 'Helena-West Helena', + 'Chimboy Shahri', + 'Franconia', + 'Szentes', + 'San Vito', + 'Panasapadu', + 'Ogden', + 'Harpalpur', + 'Road Town', + 'Montague', + 'Issoire', + 'Bagac', + 'Shuichecun', + 'Aylesbury', + 'Jijiga', + 'Dettingen an der Erms', + 'Thakraha', + 'Worth am Rhein', + 'Moissy-Cramayel', + 'Possneck', + 'Punjai Turaiyampalaiyam', + 'Ustka', + 'Velair', + 'As Sukhnah', + 'Carlisle', + 'Alausi', + 'Chantal', + 'Ouro Fino', + 'Korsun-Shevchenkivskyi', + 'North Andover', + 'Melekeok', + 'Mbake', + 'San Vito dei Normanni', + 'Cheranallur', + 'Whitewater', + 'Port Moresby', + 'Bryans Road', + 'Vegachi', + 'Yolombo', + 'Kewanee', + 'Mingjian', + 'Thonotosassa', + 'Isapur', + 'Kostrzyn nad Odra', + 'Friesoythe', + 'Lalam', + 'Maruturu', + 'Pudu', + 'Zahed Shahr', + 'Dungu', + 'Manica', + 'Draa el Mizan', + 'Tolmezzo', + 'Farsley', + 'Agame', + 'Pachauth', + 'Sittard', + 'Mableton', + 'Guaratingueta', + 'Lucenec', + 'Catchiungo', + 'Pitalito', + 'Bensville', + 'Shimohata', + 'Zawiat Moulay Brahim', + 'Anilao', + 'Eloi Mendes', + 'Erlanger', + 'Minamiaso', + 'Diffa', + 'Shark', + 'Osterburg', + 'Barwell', + 'Ambalanur', + 'Sigaboy', + 'Iretama', + 'Alucra', + 'Sivandipuram', + 'Opalenica', + 'Fray Bentos', + 'Kottur', + 'Yasugicho', + 'Anacortes', + 'Sangola', + 'Vanimel', + 'Guatape', + 'Zorbig', + 'Pitogo', + 'Busayra', + 'Puerto Carreno', + 'Nandigama', + 'Ampasimanolotra', + "Ma'ai", + 'Mapiri', + 'Serra', + 'Santa Iria da Azoia', + 'Pyeongtaek', + 'Hartford', + 'Hokuei', + 'Caldicot', + 'Tucacas', + 'Santiago Texacuangos', + 'Turvo', + 'Torredonjimeno', + 'Fontanafredda', + 'Rhede', + 'Agri', + 'Mandawa', + 'Sibate', + 'Tutrakan', + 'Dinga', + 'San Andres Cholula', + 'Sarpsborg', + 'Palashi', + 'Attili', + 'Pilas', + 'Libonik', + 'Tulum', + 'Tomarza', + 'Pato Branco', + 'Currumbin', + 'Fourmies', + 'Caxias', + 'Okondja', + 'Cantu', + 'Jeremoabo', + 'Ciudad General Escobedo', + 'Amberomanga', + 'Magdiwang', + 'Baardheere', + 'Ubala', + 'Chakwai', + 'Uibai', + 'Achuapa', + 'Nastola', + 'Partapnagar', + 'Naujan', + 'Letchworth', + 'Calceta', + 'Jafarabad', + 'Mamnur', + 'Merthyr Tudful', + 'Sept-Iles', + 'Bekalta', + 'Cambrai', + 'Rokiskis', + 'Pamukova', + 'Bad Laasphe', + 'Harper', + 'Registro', + 'Fern Down', + 'Perkiomen', + 'Lushar', + 'Lautaro', + 'Bhainsahi', + 'Welling', + 'Imaricho-ko', + 'Caerphilly', + 'Cunda dia Baze', + 'Monte Quemado', + 'Khipro', + 'Reriutaba', + 'DeBary', + 'Talapalli', + 'Taverny', + 'Jarjanaz', + 'Targu-Mures', + 'Bhasawar', + 'Siverek', + 'Grenade', + 'Sinincay', + 'Soledade', + 'Chattogram', + 'Srikurmam', + 'Daitocho', + 'Rombas', + 'Surabaya', + 'Le Mans', + 'Varzea Alegre', + 'Jonuta', + 'Batavia', + 'Pulimakkal', + 'Giaveno', + 'Sarangpur', + 'Septemes-les-Vallons', + 'Dolynska', + 'Targovishte', + 'Aldridge', + 'Abilene', + 'Santa Maria Jacatepec', + 'Khash', + 'Reni', + 'Denville', + 'Caracal', + 'Puebla', + 'Paravakkottai', + 'Great Bookham', + 'Bodo', + 'Mountougoula', + 'Mogi Guacu', + 'Yanagawamachi-saiwaicho', + 'Datu Odin Sinsuat', + 'Morbach', + 'Chincholi', + 'Cangzhou', + 'Pichilemu', + 'Saravan', + 'Sainte-Adele', + 'Rapar', + 'Langford Station', + 'Porthcawl', + 'Hilvarenbeek', + 'Jalarpet', + 'Bellview', + 'Initao', + 'Antaretra', + 'Iriona', + 'Peonga', + 'Vadavalli', + 'Port Talbot', + "Arbi'a Tighadwiyn", + 'Ciudad Vieja', + 'Newtown', + 'Sarasota', + 'Rohtak', + "'s-Hertogenbosch", + 'Ras Baalbek', + 'Harinakunda', + 'Calahorra', + 'Beitbridge', + 'Las Tablas', + 'Zhongli', + 'Dauin', + 'Saraqib', + 'Karranah', + 'Solaro', + 'Dehra Dun', + 'Riolandia', + 'Wai', + 'Golubovci', + 'Lomza', + 'Nkurenkuru', + 'Oudenburg', + 'North Laurel', + 'Ammi Moussa', + 'Puruliya', + 'Ayungon', + 'Mailavaram', + 'Ban Dong Mada', + 'San Sebastian', + 'Le Petit-Couronne', + 'Bani Walid', + 'Molins de Rey', + 'Bekoratsaka', + 'Luperon', + 'Abakaliki', + 'Vila Nova de Cerveira', + 'Rocca Priora', + 'Raurkela', + 'Romny', + 'Santa Marcela', + 'Chinhoyi', + 'Tanki Leendert', + 'Dej', + 'Berber', + 'Perryton', + 'Haarlem', + 'Xique-Xique', + 'Mirdoddi', + 'El Rodeo', + 'Truskavets', + 'Zugdidi', + 'Dharhwa', + 'Esslingen', + 'Kakching Khunou', + 'Campos Sales', + 'Saundhonwali', + 'Hemei', + 'Pindorama', + 'Cospicua', + 'Sevur', + 'Fiume Veneto', + 'South Hill', + 'Charipara', + 'Borio', + 'Mount Barker', + 'Soliera', + 'San Clemente', + 'Polonne', + 'Barreiras', + 'Cabrero', + 'Sainte-Therese', + 'Merredin', + 'Doume', + 'Guaynabo', + 'Mohyliv-Podilskyi', + 'Santiago de Chuco', + 'Auriflama', + 'Sao Jeronimo da Serra', + 'Rowland Heights', + 'Telkathu', + 'Zhentang', + 'Ifrane', + 'Chailaha', + 'Acoyapa', + 'Grunberg', + 'Illescas', + 'Jacare', + 'Grabels', + 'Anshan', + 'Caloundra', + 'Ruyigi', + 'Kalleribhagam', + 'Rochester', + 'Ban Rawai', + 'Dera', + 'Takanezawa', + 'Palmer Ranch', + 'Rampatti', + 'Heyunkeng', + 'Sittwe', + 'Hiep Hoa', + 'Gorlitz', + 'Yanggezhuang', + 'Ogawa', + 'Zafarwal', + 'Cachoeirinha', + 'Ra`ananna', + 'Raymore', + 'Deneysville', + 'Nurpur', + 'Nosibe', + 'Minamishiro', + 'Northbrook', + 'Colonia General Felipe Angeles', + 'Acacias', + 'G`uzor', + 'Oberwil', + 'Yanjiang', + 'Iflissen', + 'Vsetin', + 'Nimes', + 'Sangrampur', + 'Germering', + 'Coulsdon', + 'Lebon Regis', + 'Malaimachchampatti', + 'Rarz', + 'Clacton-on-Sea', + 'Droylsden', + 'Maroli', + 'Xintian', + 'Wangi', + 'Blaine', + 'Aliganj', + 'Michendorf', + 'Kalaikunda', + 'Betigeri', + 'Talipao', + 'Bheja', + 'Red Wing', + 'Merrillville', + 'Minamisatsuma', + 'Ramsbottom', + 'Catarina', + 'Korb', + 'Lingyuan', + 'Rendon', + 'Ambongo', + 'Aliyabad', + 'Millington', + 'Brdovec', + 'Gujar Khan', + 'Heeze', + 'Binondo', + 'Aix-les-Bains', + 'Civril', + 'Aranyaprathet', + 'Bargteheide', + 'Ambodiangezoka', + 'Talsint', + 'Silvania', + 'Jindayris', + 'Pacuarito', + 'Nuku`alofa', + 'Imi Mokorn', + 'Panjipara', + 'Portales', + 'Ksebia', + 'Sodo', + 'Hoppegarten', + 'Amparo', + 'Pirayu', + 'Narat', + 'Bagaura', + 'Puerto Gaitan', + 'Hoyerswerda', + 'Beckley', + 'Tsarazaza', + 'Mari`', + 'Panitan', + 'Phoenixville', + 'Kinkala', + 'Pilachikare', + 'Chellaston', + 'Ranchuelo', + 'Schuylkill', + 'Can-Avid', + 'Northam', + 'Dedham', + 'Bad Camberg', + 'Oyten', + 'Guaymas', + 'Gaeta', + 'Ankadinandriana', + 'Holalagondi', + 'North Guwahati', + 'Saint-Gaudens', + 'Rancho San Diego', + 'Vicar', + 'San Pedro Pochutla', + 'Uhingen', + 'Policoro', + 'Nabilatuk', + 'Bauyrzhan Momyshuly', + 'Mulakad', + 'Kesan', + 'Asosa', + 'Dewangarh', + 'Ash Shaykh Zuwayd', + 'Nunihat', + 'Itki Thakurgaon', + 'Korsor', + 'Cenovi', + 'Aroali', + 'Schweinfurt', + 'Hirpardangal', + 'Nadisal', + 'El Amim', + 'Zeewolde', + 'Plaisance', + 'Mokronog', + 'Xiaojiangcun', + 'Muhradah', + 'Santa Rosa de Lima', + 'Halvad', + 'Welkenraedt', + 'Kronberg', + 'Lagoa Santa', + 'Liutuancun', + 'Iguig', + 'Blue Island', + 'Codru', + 'Caramoran', + 'Stuart', + 'Marino', + 'Tacheng', + 'Bandar-e Gaz', + 'San Leandro', + 'Athi River', + 'Leirvik', + 'Wattrelos', + "As Suwayda'", + 'Singampunari', + 'Velampalaiyam', + 'Semarang', + 'Garland', + 'Coronel Oviedo', + 'Waldkirch', + 'Gusau', + 'Campton Hills', + 'Kalangala', + 'Palafrugell', + 'Esfarayen', + 'Qazaly', + 'Chelsea', + 'Gokce', + 'Harrisonville', + 'Weare', + 'Shiji', + 'Bakharia', + 'Soanindrariny', + 'Dhanbad', + 'Jayaque', + 'Tanque Verde', + 'Northenden', + 'Matias Cardoso', + 'St. Charles', + 'Gaomi', + 'Moreira Sales', + 'Bharatpur', + 'Chauki', + 'Gokcebey', + 'Kishtwar', + 'Hambantota', + 'Ez Zahra', + 'Ain Beida', + 'Bauru', + 'Chickasha', + 'Simpsonville', + 'Taman Johor Jaya', + 'Skelleftea', + 'Bellaa', + 'Narlidere', + 'Lanciano', + 'Bellmawr', + 'Tataltepec de Valdes', + 'Molo', + 'Kiskunfelegyhaza', + 'Kaisiadorys', + 'San Dionisio', + 'Corpus Christi', + 'Tutin', + 'Betanty', + 'Ambatomanjaka', + 'Koprukoy', + 'Matuga', + 'South Normanton', + 'Kalamata', + 'Trieste', + 'Balikesir', + 'Magsaysay', + 'Fuji', + 'Dorado', + 'Teyateyaneng', + 'Foiano della Chiana', + 'Borba', + 'Gaborone', + 'Puerto Armuelles', + 'Edgware', + 'Ahirauliya', + 'Felpham', + 'Amboaboa', + 'Paso del Macho', + 'Raymond Terrace', + 'Kambam', + 'Los Arabos', + 'Johnston', + 'Placentia', + 'Kharkiv', + 'Sault Ste. Marie', + 'East Glenville', + 'Tamza', + 'Akalgarh', + 'Nwoya', + 'Ordubad', + 'Bulan', + 'Musashimurayama', + 'Lipa City', + 'Tirupporur', + 'Kannudaiyampatti', + 'Sendafa', + 'Angelholm', + 'Carneiros', + 'Vellikulangara', + 'Andanappettai', + 'Mira', + 'Wangqing', + 'Asahikawa', + 'Stryi', + 'Kittery', + 'Rosso', + 'Ramsgate', + 'Diabigue', + 'Anjarkandi', + 'Santiago Tulantepec', + 'Kaul', + 'Pruszkow', + 'Karlskrona', + 'Porto-Vecchio', + 'Pasadena Hills', + 'Ban Bung Kha', + 'Wilmslow', + 'San Juan de Limay', + 'Puran Bigha', + 'Paramagudi', + 'Xima', + 'Khashuri', + 'San Lorenzo della Costa', + 'Medikunda', + 'Belpara', + 'Mountain Home', + 'Burnaby', + 'Sapouy', + 'Rhar el Melah', + 'Pessac', + 'Grevenmacher', + 'Santo Antonio do Monte', + 'Gros Islet', + 'Tarabha', + 'Wimauma', + 'Kumage', + 'Sivamalai', + 'Salmas', + 'Bad Salzdetfurth', + 'Taohuajiang', + 'Tsirang', + 'Dera Ismail Khan', + 'Westbrook', + "'Ayn Bni Mathar", + 'Veppattur', + 'Nettappakkam', + 'Saint-Herblain', + 'Cassano delle Murge', + 'San Jose Acatempa', + 'Sripur', + 'Rae Bareli', + 'Dazaifu', + 'Benguema', + 'Dobni Para', + 'Hajipur', + 'Villars-sur-Glane', + 'Tejen', + 'Itabaiana', + 'Exu', + 'Ninove', + 'Riposto', + 'Baraki Barak', + 'Surat Thani', + 'Setlagode', + 'Rajhanpur', + 'Wancheng', + 'Bom Conselho', + 'Karkkila', + 'Arden Hills', + 'Jasim', + 'Tangainony', + 'Ganguru', + 'Tamiami', + 'Anjiajia', + 'Angren', + 'Balimbing', + 'Vallejo', + 'Sabae', + 'Podgorze', + 'Rio Pardo', + 'Sarzeau', + 'Koniz', + 'El Segundo', + 'Werther', + 'Phalia', + 'Sidi Abd el Moumene', + 'Rajanagaram', + 'Riachao das Neves', + 'Betrandraka', + 'Graben-Neudorf', + "Ighrem n'Ougdal", + 'Kourimat', + 'Reno', + 'Verwood', + 'Naspur', + 'Douar El Mellaliyine', + 'Pichanal', + 'Octeville', + 'Hamadan', + 'Rackeve', + 'Khesht', + 'Anew', + 'South Fulton', + 'Cabo Verde', + 'Cooma', + 'Unnao', + 'Kandanati', + 'Sainkhera', + 'Hasbrouck Heights', + 'Antananarivo', + 'Puca Urco', + 'Hebron', + 'Qal`ah-ye Now', + 'Bara Malehra', + 'Vinales', + 'Kotturu', + 'Ainaro', + 'Kawambwa', + 'Santiago Ixcuintla', + 'Donzdorf', + 'Thong Pha Phum', + 'West Carrollton', + 'Imbau', + 'Shatrana', + 'Nalerigu', + 'Namli', + 'Cumbernauld', + 'Al Musayfirah', + 'Lulea', + 'Jamaica', + 'Vijayapati', + 'Monte Porzio Catone', + 'Carshalton', + 'Gumia', + 'Arvand Kenar', + 'Karaund', + 'Minamiaizu', + 'Gronau', + 'Jinxing', + 'Sixaola', + 'Oulad Hammou', + 'Trongsa', + 'Khenichet-sur Ouerrha', + 'Naifaru', + "K'ebri Dehar", + 'Namagiripettai', + 'East London', + 'Neibu', + 'Himi', + 'Cameron Highlands', + 'Amfilochia', + 'Saranambana', + 'Qingshan', + 'Ishikawa', + 'Monkseaton', + "Al Jahra'", + 'Simmerath', + "At Ta'if", + 'Bama', + 'Meknes', + 'Tiahounkossi', + 'Tucuma', + 'Lower Salford', + 'Punto Fijo', + 'East Greenbush', + 'Ambodimanary', + 'Nilambur', + 'Jalalabad', + 'Qingnian', + 'Barberton', + 'Atari', + 'Jilikul', + 'Cranbrook', + 'Lobogo', + 'Xiegang', + 'La Habra', + 'Alingsas', + 'Onverwacht', + 'Pimpri-Chinchwad', + 'Yatangcun', + 'Coueron', + 'Yaren', + 'Kazincbarcika', + 'Tanta', + 'Rasaunk', + 'Campoalegre', + 'Jurua', + 'Kallur', + 'Cekerek', + 'Tarwara', + 'Burketown', + 'South St. Paul', + 'Corgao', + 'An Thanh B', + 'Borgholzhausen', + 'Tahlequah', + 'Vierzon', + 'Qagan Us', + 'Baravat', + 'Vrsac', + "Za'roura", + 'Bennington', + 'Talamba', + 'Nandyal', + 'Laiwu', + 'Tacuarembo', + 'Cuncolim', + 'Xiaping', + 'Landazuri', + 'Cuautla', + 'Pires Ferreira', + 'Banaz', + 'Cote-Saint-Luc', + 'Manacor', + 'Tantangan', + 'Emmaus', + 'Sonora', + 'Pontiac', + 'Chinandega', + 'Armutlu', + 'Besni', + 'Qingquan', + 'Ceyu', + 'Majuro', + 'Herxheim', + 'Bluefield', + 'Oerlinghausen', + 'Aigues-Mortes', + 'Tosya', + "L'Ile-Saint-Denis", + 'Ban Ngio Ngam', + 'Fortim', + 'Rio Gallegos', + 'Schleiz', + 'Zoudjame', + 'Ondangwa', + 'Ampasimanjeva', + 'Altamont', + 'Wittelsheim', + 'Jayapura', + 'Arni ka Khera', + 'Commack', + 'Morombe', + 'Akdagmadeni', + 'Reidsville', + 'Chinnakkampalaiyam', + 'Girau do Ponciano', + 'Ardrossan', + 'Cherutana Tekku', + 'Kondur', + 'Gwelej', + 'Traiguen', + 'Sannois', + 'Zarach', + 'Lhuentse', + 'Redondo Beach', + 'Maynard', + 'Carmopolis de Minas', + 'Chino Hills', + 'Sohta', + 'Velddrif', + 'Tarifa', + 'Wheatfield', + 'Lasko', + 'Ho Chi Minh City', + 'Osakarovka', + 'Sirajganj', + 'Waidhofen an der Ybbs', + 'Lieusaint', + 'Worsborough', + 'Mandoto', + 'Kochkor', + 'Hindang', + 'Wolmirstedt', + 'Angri', + 'Bagnolet', + 'Hatogaya-honcho', + 'Panelas', + 'Campestre', + 'Boras', + 'Danderesso', + 'Vila Verde', + 'Amborondra', + 'Itapemirim', + 'Muthutala', + 'Qeshm', + 'Halawa', + 'Dosso', + 'Villazon', + 'Breukelen', + 'Pandi', + 'Audenge', + 'Ngou', + 'Urucara', + 'Baham', + 'Mudukulattur', + 'Tubarao', + "Dek'emhare", + 'Sidi Yakoub', + 'Bandhi', + 'Karsiyaka', + 'Shibin al Qanatir', + 'Ajas', + 'Dunedin', + 'Asturias', + 'California', + 'Catania', + 'Vinh', + 'Celano', + 'Tierra Colorada', + 'Dosquebradas', + 'Dunblane', + 'Brejao', + 'Toguere-Koumbe', + 'Lianshan', + 'Roghun', + 'Gokdepe', + 'Aul', + 'Gotvand', + 'Harnes', + 'Choloma', + 'Mamanguape', + 'Miguelturra', + 'Karlovo', + 'Goole', + 'Emir Abdelkader', + 'Sironko', + 'Bokaro Steel City', + 'Altos del Rosario', + 'Saint-Jean-de-Vedas', + 'Hyosha', + 'Tamiang Layang', + 'Scartho', + 'Oltu', + 'Sillanwali', + 'Tadangam', + 'Loures', + 'Kalecik', + 'Sao Jose do Egito', + 'Morelia', + 'Mizan Teferi', + 'Dar Bel Hamri', + 'Ingeniero Guillermo N. Juarez', + 'Vendrell', + 'Wembley', + 'Fritissa', + 'Narasingapuram', + 'Hatvan', + 'Gharyala', + 'Cachoeira Paulista', + 'Pata Putrela', + 'Batang', + 'Burnham-on-Sea', + 'Stuarts Draft', + 'Balzan', + 'Pollachi', + 'Rajpura', + 'Drachten', + 'Agadallanka', + 'Habikino', + 'Mabole', + 'Katipunan', + 'Pachhapur', + 'Banaso', + 'Grinnell', + 'Euskirchen', + 'Ahar', + 'Iranshahr', + 'Mostardas', + 'Shimotsucho-kominami', + 'Maharagama', + 'Owego', + 'Madan', + 'Tomaszow Lubelski', + 'Oisterwijk', + 'Sao Francisco', + 'San Martin Jilotepeque', + 'Iperu', + 'Balaoan', + 'Dubno', + 'Tall `Afar', + 'Kaufungen', + 'Sohwal', + 'Guamuchil', + 'Fosses', + 'Casillas', + 'Ban Noen Phoem', + 'Cuellar', + 'Aurelino Leal', + 'Zeulenroda', + 'Porto Tolle', + 'Lower Hutt', + 'Castelnau-le-Lez', + 'Matabhanga', + 'Ban Ko', + 'Balsa Nova', + 'Brentwood', + 'Brumath', + 'Luban', + 'Mainaguri', + 'Buwenge', + 'Qabqa', + 'Al `Aziziyah', + 'Frutillar Alto', + 'Vatluru', + 'Casale', + 'Ambararata', + 'Studenicani', + 'Lanskroun', + 'Sveti Ivan Zelina', + 'Tokamachi', + 'Bishopstoke', + 'Broxburn', + 'Timizart', + 'Maroharatra', + 'Isselburg', + 'Vandiyur', + 'Los Llanos de Aridane', + 'Winder', + 'Banga', + 'Trophy Club', + 'Olmsted Falls', + 'Zanesville', + 'Zhudong', + 'Bapatla', + 'Peringanad', + 'Cedar Falls', + 'Leanja', + 'Eydhafushi', + 'Paoua', + 'Andribavontsona', + 'Pergamino', + 'Kouri', + 'Bellmore', + 'Bondeno', + 'Comines', + 'Volendam', + 'Pio IX', + 'Cotija de la Paz', + 'Koratla', + 'Tallbisah', + 'Hakubacho', + 'Helsingborg', + 'Braila', + 'Samalpur', + 'Afourar', + 'Briceno', + 'Kabanga', + 'Ambohimahamasina', + 'Quirima', + 'Hakone', + 'Nova Londrina', + 'Nuneaton', + 'Plaisir', + 'Kirikhan', + 'Eccles', + 'West Little River', + 'Nideggen', + 'Gyor', + 'Parchim', + 'Hagere Hiywet', + 'Mirinzal', + 'Lupi Viejo', + 'Buckeye', + 'Ravutulapudi', + 'Landupdih', + 'Santa Barbara de Pinto', + 'Haysville', + 'Dimbokro', + 'Toulouse', + 'Mangdongshan', + 'Vladicin Han', + 'Soma', + 'Springfield', + 'Wedel', + 'Dokuchaievsk', + 'Thai Binh', + 'Mbulu', + 'Muconda', + 'Pearl', + 'Pleszew', + 'Qualicum Beach', + 'Harihans', + 'Yangtangxu', + 'Boa Esperanca do Sul', + 'Saravia', + 'Wepangandla', + 'Morristown', + 'Karaikandi', + 'Elverum', + 'Mexicaltzingo', + 'Kamdoli', + 'Tayabas', + 'Hillsborough', + 'Joao Monlevade', + 'Syracuse', + 'Elliot Lake', + 'Ciudad Guadalupe Victoria', + 'Shampur', + 'Beverstedt', + 'Miajadas', + 'Erode', + 'Sao Pedro do Ivai', + 'Volkermarkt', + 'Lagoa de Itaenga', + 'Kokstad', + 'Jinshui', + 'Miesbach', + 'Lakeland South', + 'Dong Hoi', + 'Illingen', + 'San Dona di Piave', + 'Liantang', + 'Jujutla', + 'Ad Darb', + 'Osicala', + 'Huehuetla', + 'Yambol', + 'Muthallath al Azraq', + 'Asago', + 'Periya Soragai', + 'Anar', + 'Velika Plana', + 'Becej', + 'Hengnan', + 'Brad', + 'Tudiyalur', + 'Skhour Rehamna', + 'Seonar', + 'Muktagacha', + 'Saffron Walden', + 'Cilacap', + 'Ankiliabo', + 'Ramon Magsaysay', + 'Sarenja', + 'Yamanobe', + 'Marcinelle', + 'Abergele', + 'Saraunja', + 'Arankhola', + 'Calexico', + 'Dalaguete', + 'Owatonna', + 'Jalpa de Mendez', + 'Villalbilla', + 'Frameries', + 'Lakewood Park', + 'Erlun', + 'Thandwe', + 'Mons-en-Baroeul', + 'Netishyn', + 'Mindelo', + 'Repelon', + 'Sidon', + 'Ouinhri', + 'Miramar Beach', + 'Cam Ranh', + 'Certaldo', + 'Vedurupavaluru', + 'Alginet', + 'Egil', + 'Ruteng', + 'Chesapeake Ranch Estates', + 'Sao Jose do Jacuipe', + 'Perafita', + 'Barnsley', + 'Hammond', + 'Simav', + 'South Euclid', + 'Tiquipaya', + 'Moorpark', + 'Chernivtsi', + 'Joigny', + 'Cary', + 'Pennadam', + 'Yongping', + 'Sagnay', + 'Lalibela', + 'Tazoult-Lambese', + 'Orta Nova', + 'Dumangas', + 'Trujillo', + 'Borovnica', + 'Buttayagudem', + 'Kegalle', + 'Oshikango', + 'San Juan de Arama', + 'Hathiaundha', + 'Eirunepe', + 'Pakka Kalan', + 'Kita', + 'Msoga', + 'Solothurn', + 'Welkom', + 'La Quinta', + 'Stamford', + 'Serpa', + 'Cadelbosco di Sopra', + "'Ain Tolba", + 'Chaiwu', + 'Kundiawa', + 'Clorinda', + 'Liushuquan', + 'Pingquan', + 'Ouesso', + 'Jiaganj', + 'Ibiraci', + 'Varaklani', + 'Whakatane', + 'Brzesko', + 'Fort Oglethorpe', + 'Pervomaisk', + 'Guiuan', + 'Hadiaya', + 'El Zulia', + 'Mahates', + 'Rio Segundo', + 'Patos', + 'Summerland', + 'Zalantun', + 'Khundawandpur', + 'Soledad de Graciano Sanchez', + 'Xocavand', + 'Denizli', + 'Mangai', + 'Chedaopo', + 'Rathfarnham', + 'Sibanicu', + 'Oxnard', + 'Surubim', + 'Bergama', + 'Keta', + 'Ochiai', + 'Yellanda', + 'Wilsonville', + 'Iguidiy', + 'Cordoba', + 'Tsarahonenana', + 'Flint', + 'Valentim Gentil', + 'Gumushane', + 'Cherbourg', + 'Hosir', + 'Makariv', + 'Pikine', + 'Acayucan', + 'Trebisov', + 'Bagong Pag-Asa', + 'Barr Elias', + 'Santa Coloma de Farnes', + 'Simao', + 'Ballarat', + 'Andoharanomaitso', + 'Bethlehem', + 'Mevani', + 'Sao Miguel dos Campos', + 'Heinsberg', + 'Al Kufah', + 'Pont Sonde', + 'Turkmenbasy', + 'Goldasht', + 'Hamar', + 'Pastores', + 'Veruela', + "Nong'an", + 'Senica', + 'Steinau an der Strasse', + 'Wibsey', + 'Long Khanh', + 'Lubny', + 'Aalsmeer', + 'Citong', + 'Charxin', + 'Soyapango', + 'Avanos', + 'Milas', + 'Padmanabhapuram', + 'Les Palmes', + 'Rangoon', + 'Guichen', + 'Vadakkum', + 'Holon', + 'Faraskur', + 'Cifteler', + 'Malente', + 'Salgado', + 'Udalguri', + 'Broadwater', + 'Olot', + 'Kirkintilloch', + 'Amares', + 'West Drayton', + 'Pliezhausen', + 'Manicore', + 'Kanniparamba', + 'Larisa', + 'Stans', + 'Entraigues-sur-la-Sorgue', + 'Bhelsi', + 'Nuwara Eliya', + 'Montignoso', + 'Yavoriv', + 'Thillangeri', + 'Gajendragarh', + 'Narva', + 'Chirak', + 'Forestville', + 'Waitangi', + 'Yicheng', + 'Machagai', + 'Rasivarai Tottam', + 'Poienile de sub Munte', + 'Dogubayazit', + 'Abhia', + 'Etampes', + 'Puerto San Martin', + 'Panama City Beach', + 'Mittahalli', + 'Rudravaram', + 'Nassau', + 'Bladel', + 'Bauan', + 'Bryne', + 'Mandi Bamora', + 'Schenefeld', + 'Gosaingaon', + 'Nallur', + 'Soanierana', + 'Akhnur', + 'Pedda Adsarlapalli', + 'Waalwijk', + 'Sekondi', + 'Kelo', + 'Squinzano', + 'Itikalapalle', + 'Tattamangalam', + 'Segue', + 'Jaltocan', + 'Coram', + 'Entre Rios', + 'Hafizabad', + 'Konarak', + 'Betsizaraina', + 'Ulanhot', + 'Augsburg', + 'Modica', + 'Nova Olinda', + 'Eastvale', + 'Lechang', + 'Wanaka', + 'Ormesby', + 'Malegaon', + 'Rincon de Romos', + 'Bihta', + 'Mukilteo', + 'Codajas', + 'Hindalgi', + 'Radviliskis', + 'Vasai-Virar', + 'Salor', + 'Eraura', + 'Labrea', + 'Nanuet', + 'Maryborough', + 'Bramhabarada', + 'Rubeho', + 'Scherpenzeel', + 'Osan', + 'Obando', + 'San Antonio', + 'Mamarappatti', + 'Padangpanjang', + 'Wau', + 'Thiene', + 'Avocado Heights', + 'Lake Forest Park', + 'Sallaumines', + 'Nova Venecia', + 'Varadero', + 'Amalner', + 'Debaltseve', + 'Levoca', + 'Beachwood', + "Motta Sant'Anastasia", + 'Kreuztal', + 'Kirundo', + 'Ingeniero White', + 'Tasucu', + 'Spelle', + 'Radomsko', + 'Baleyara', + 'Wencheng', + 'Ambohijanahary', + 'Oullins', + 'Ain Oulmene', + 'Sakete', + 'Alattur', + 'Sobradinho', + 'Hoki', + 'Calgary', + 'Praia', + 'Atbasar', + 'Kisslegg', + 'Chapaev', + 'Chipindo', + 'Livramento de Nossa Senhora', + 'Odemira', + 'Molango', + 'Mortugaba', + 'Kuttanallur', + 'Dahegam', + 'Gardena', + 'Round Lake', + 'San Nicolas', + 'Kinhalu', + 'Osorio', + 'Alanganallur', + 'Santa Cruz del Norte', + 'La Grange', + 'Habra', + 'Ciamis', + 'Zhuji', + 'Maduru', + 'Manyas', + "'Ain el Arbaa", + 'Alampalaiyam', + 'Ribera', + 'Qorashina', + 'Penafiel', + 'Tiraspol', + 'Pitou', + 'Pallikondai', + 'Kabuga', + 'Westerland', + 'Luckau', + 'Saddlebrooke', + 'Bijeljina', + 'Villepreux', + 'Karianga', + 'Tomar do Geru', + 'Guaraniacu', + 'Moravska Trebova', + 'Sunrise Manor', + 'King of Prussia', + 'Lemon Hill', + 'Condeuba', + 'Sikonge', + 'Metlili Chaamba', + 'Maplewood', + 'Qasigiannguit', + 'Banjar', + 'Cucuta', + 'Vazquez', + 'Sour el Ghozlane', + 'Oran', + 'Long Thanh', + 'Sao Domingos do Prata', + 'Fungurume', + 'Grado', + 'Sezze', + 'San Pedro Tapanatepec', + 'Uvalde', + 'Buenaventura', + 'Kanhauli', + 'Tadaoka-higashi', + 'Svitlovodsk', + 'Samut Songkhram', + 'Scherpenheuvel', + 'Filomeno Mata', + 'Mallappulasseri', + 'Royken', + 'Westerville', + 'Nioro', + 'Devendranagar', + 'Itabashi', + 'Itapuranga', + 'Chanteloup-les-Vignes', + 'Santa Lucia di Piave', + 'Satu Mare', + 'Tuni', + 'Chouafa', + 'Sevilla La Nueva', + 'Chaugain', + 'Cuquio', + 'Palm Coast', + 'Antanimieva', + 'Baltasar Brum', + 'Harnaut', + 'Lagoa do Carro', + 'Arenys de Munt', + 'Landecy', + 'Tancitaro', + 'Shanawan', + 'Mangoli', + 'Baharestan', + 'Pia', + 'Witney', + 'Guillena', + 'Namchi', + 'Naryn', + 'Ujhana', + 'Itigi', + 'Douar Oulad Sidi Moussa', + 'Natuba', + 'Hazel Park', + 'Farias Brito', + 'Corumba de Goias', + 'Rhyl', + 'Barkuhi', + 'Piracuruca', + 'Ranst', + 'Koka', + 'Yunshan', + 'Jiran', + 'Zanhuang', + 'Grahamstown', + 'Erandio', + 'Podili', + 'Neerpelt', + 'Leczna', + 'Richterich', + 'Narsingdi', + 'Arnhem', + 'Vodil', + 'Hakkari', + 'Simbach am Inn', + 'Panzos', + 'Chichester', + 'Driouch', + 'Tezontepec', + 'Palmeiras de Goias', + 'Tizi-n-Bechar', + 'Rosyth', + 'Greenlawn', + 'Mali', + 'Sidi Chiker', + 'Kwiha', + 'Busaar', + 'Gangammapeta', + "Be'er Ya`aqov", + 'Potangal', + 'Murayama', + 'Bang Bua Thong', + 'Lerma', + 'Santo Antonio do Taua', + 'Fair Lakes', + 'Creteil', + 'Qigexingcun', + 'Ymittos', + 'Flemington', + 'Northlake', + 'Edlapadu', + 'Angalakuduru Malepalle', + 'Pila', + 'Sinop', + 'La Higuerita', + 'Plymstock', + 'Cateel', + 'Augustow', + 'Ianantsony', + 'Bando', + 'Sizhan', + 'Uppalaguptam', + 'Damu', + 'Bordj Okhriss', + 'Panjab', + 'North Versailles', + 'Adh Dhakhirah', + 'Grove City', + 'Le Poire-sur-Vie', + 'Keshod', + 'Corby', + 'Inhassoro', + 'Hinesville', + 'Espartinas', + 'Gien', + 'Chennirkara', + 'Time', + 'Ziar nad Hronom', + 'Ravels', + 'Pakxan', + 'Namin', + 'Yaragol', + 'Maimbung', + 'Kominato', + 'Bayghanin', + 'South Bend', + 'Manzanillo', + 'Hartselle', + 'Balarampuram', + 'Cay', + 'Estepa', + 'Heliopolis', + 'Analila', + 'Korba', + 'Gusinje', + 'Lake Magdalene', + 'Ambahikily', + 'Rijen', + 'Tuta', + 'Jardinopolis', + 'Cypress Gardens', + 'Bijawar', + 'Soeda', + 'Buena Vista Tomatlan', + 'Lalejin', + 'Muro del Alcoy', + 'Lincoln Park', + 'Beecher', + 'Tansandra', + 'Ambohimanga Atsimo', + 'Manjhi', + 'Nahiyat al Karmah', + 'Rainhill', + 'Elizabethtown-Kitley', + 'Palotina', + 'Alfter', + 'Meppayyur', + 'Domoni', + 'Abay', + 'Nettetal', + 'Paithan', + 'Hafshejan', + 'Dubliany', + 'Balaka', + 'Barauli', + 'Kleinblittersdorf', + 'Belpasso', + 'Shopokov', + 'Eugene', + 'Navalyal', + 'Cieszyn', + 'Bom Retiro do Sul', + 'Taylor', + 'Perehinske', + 'Montale', + 'Attingal', + 'Fresnes', + 'Marofinaritra', + 'Arumbakkam', + 'Djebahia', + 'Diai Koura', + 'Candelaria Loxicha', + 'Boituva', + 'Broni', + 'Orangetown', + 'Pastos Bons', + 'Derinkuyu', + 'Stratford', + 'Ocotlan de Morelos', + 'Trbovlje', + 'Kitui', + 'Dhone', + 'Mohdra', + 'Zwijndrecht', + 'Chocen', + 'Hameenlinna', + 'Londonderry', + 'Ujre', + 'Koko', + 'Pomfret', + 'Rosdorf', + 'Monopoli', + 'Lingig', + 'Ravenna', + 'Herenthout', + 'Bovalino Marina', + 'Wheaton', + 'Almoradi', + 'Estanzuelas', + 'Sao Sebastiao do Passe', + 'Pont-y-pwl', + 'Puliyur', + 'Lagoa da Confusao', + 'Nighoj', + 'Giannouli', + 'Paivalike', + 'Yalamakuru', + 'Aplahoue', + 'Kotabumi', + 'Los Corrales de Buelna', + 'Marumori', + 'Sujnipur', + 'Pleasant Valley', + 'Buala', + 'Downers Grove', + 'Magallanes', + 'Bayt al Faqih', + 'Hualien', + 'Edgewater Park', + 'Pascani', + 'Ukkayapalle', + 'Cabrayil', + 'Xizhou', + 'Ravulapalem', + 'Villefontaine', + 'Al Fayyum', + 'Ulukisla', + 'Hargawan', + 'Tummanatti', + 'Pearl City', + 'Mucuge', + 'Selibe Phikwe', + 'Itsukaichi', + 'Seravezza', + 'Gopalganj', + 'Bodoco', + 'Paderborn', + 'Butare', + 'Sandur', + 'Zgornja Hajdina', + 'Stilfontein', + 'Xuan Trung', + 'Alampur Gonpura', + 'Thimiri', + 'Buhriz', + 'Ballston', + 'Kotla Qasim Khan', + 'Mannegudam', + 'Marinka', + 'Machakos', + 'Mankoeng', + 'Mirandola', + 'Dinard', + 'Komen', + 'Highlands Ranch', + 'Kanra', + 'Boldaji', + 'Kampenhout', + 'Ardea', + 'Long Branch', + 'Katiola', + 'Rancagua', + 'Taima', + 'Innsbrook', + 'Fedosiia', + 'Vohitrindry', + 'Tres Pontas', + 'Kharial', + 'Belinta', + 'Tsu', + 'Zhucaoying', + 'West Chicago', + 'Exeter', + 'Ovar', + 'Fort Portal', + 'Upper Merion', + 'Tiruvarpu', + 'Karmiel', + 'Ivanec', + 'Project Six', + 'Luofeng', + 'Muttayyapuram', + 'Kawaminami', + 'Bila Tserkva', + 'Pfungstadt', + 'Hebi', + 'Kizugawa', + 'Gumusova', + 'Wellingborough', + 'Shimencun', + 'Ourense', + 'Sollies-Pont', + 'Jatani', + 'Ushuaia', + 'Drongen', + 'Lagarto', + 'Las Guaranas', + 'Minas Novas', + 'Ponnampatti', + 'Hola Prystan', + 'Cojutepeque', + 'Randolph', + 'Matsue', + 'Adohoun', + 'Divonne-les-Bains', + 'Talsur', + 'Auki', + 'Obo', + 'Spilamberto', + 'Konodimini', + 'Hanam', + 'Gopavaram', + 'Xambioa', + 'Terra Rica', + 'Pitea', + 'Jussara', + 'Ahuimanu', + 'Hampstead', + 'Bala Cynwyd', + 'El Rosal', + 'Labasa', + 'Garner', + 'Las Maravillas', + 'Toowoomba', + 'Kukshi', + 'Buzau', + 'Tahoua', + 'Koboko', + 'Carlopolis', + 'Wildeshausen', + 'Hershey', + 'Landsmeer', + 'Swarna', + 'Wordsley', + 'Gagarin Shahri', + 'Bad Laer', + 'Adigoppula', + 'Kratovo', + 'Desavilakku', + 'Qalat', + 'Senador Pompeu', + 'Solonopole', + 'Bainet', + 'Mandurah', + 'Kyaukpyu', + 'Willistown', + 'Pirri', + 'Aweil', + 'Akividu', + 'Majdanpek', + 'Qal`eh Tall', + 'Garopaba', + 'Sograha', + 'Al Jizah', + 'Shurobod', + 'Warden', + 'Danielskuil', + 'Paidha', + 'Vargem Alta', + 'Chamestan', + 'Kumasi', + 'Kafr ash Shaykh', + 'Chirundu', + 'Arzachena', + 'Dobrada', + 'Arcot', + 'Matlock', + 'Usmanpur', + 'Silute', + 'Al Qunaytirah', + 'Jablanica', + 'Gaada', + 'Tarazu', + 'Lagoa do Ouro', + 'Yuksekova', + 'Mirfield', + 'Kurmuk', + 'Macia', + 'Karuvakulam', + 'Nice', + 'Ignalina', + 'East Franklin', + 'Malhada', + 'Imbituba', + 'Kasumi', + 'Tiangua', + 'Leonforte', + 'Enumulapalle', + 'Pushpattur', + 'Romford', + 'El Talar de Pacheco', + 'Osakasayama', + 'Lanuza', + 'Bordighera', + 'Bennane', + 'Japura', + 'Yesilli', + 'Marcos Paz', + 'Hickam Housing', + 'Ban Khamen', + 'Shirguppi', + 'Chemmaruthi', + 'Woodlesford', + 'Povazska Bystrica', + 'Kotia', + 'Gunri', + 'Nawagarh', + 'Judenburg', + 'Hammam-Lif', + 'Matina', + 'Verkhnodniprovsk', + 'San Lucas', + 'Jacaraci', + 'Glarus', + 'Manoli', + 'Monticello', + 'Urziceni', + 'Pryluky', + 'Langhirano', + 'Buriti Bravo', + 'Skenderaj', + 'Bari', + 'Cho Moi', + 'Kahla', + 'Grojec', + 'Berea', + 'Wyandotte', + 'Jaguapita', + 'Modavandisatyamangalam', + 'Danesfahan', + 'Larache', + 'North Potomac', + 'Kharupatia', + 'Kisangani', + 'Nuoro', + 'Janakkala', + 'Bhatkhori', + 'Mungaoli', + 'Yalova', + 'Qaratog', + 'Yesilova', + 'Tualatin', + 'Sandviken', + 'Bickenhill', + 'Inwood', + 'Meckenheim', + 'Davidson', + 'Kanhaipur', + 'Longton', + 'Sendurai', + 'Paniem', + 'Jujharpur', + 'Sasolburg', + 'Rambouillet', + 'March', + 'Ewing', + 'Seogwipo', + 'Huber Heights', + 'Calabanga', + 'Mallasamudram', + 'Dharawat', + 'Ngara', + 'Pensacola', + 'Tuzluca', + 'Lauenburg', + 'Kidangazhi', + 'Ucu Seles', + 'Enebakk', + 'Nalua', + 'Aubiere', + 'Manfredonia', + 'Nacunday', + 'Street', + 'Sao Bernardo do Campo', + 'Sidhauli', + 'Igarape-Acu', + 'Teodoro Schmidt', + 'Barra do Corda', + 'Tangua', + 'Yibin', + 'Ozdere', + 'Chak Habib', + 'Medchal', + 'Surab', + 'Snyder', + 'Teus', + 'Oulad Slim', + 'Ingurti', + 'Manaure', + 'Jaipur', + 'Zlate Moravce', + 'Castillejos', + 'Kalakeri', + 'Mahavelona', + 'Iesi', + 'Bedford Heights', + 'Dinokana', + 'Shimogamo', + 'Mohammad Yar', + 'Ban Kham Pom', + 'Nannestad', + 'Espiye', + 'Jiji', + 'Bulandshahr', + 'Wetteren', + 'Valattur', + 'Hejian', + 'Wete', + 'Beverley', + 'Marar', + 'Olecko', + 'Chalatenango', + 'Dommasandra', + 'Chilamatturu', + 'Petit-Trou de Nippes', + 'Ratnagiri', + 'Crozet', + 'Birganj', + 'Dehiba', + 'Vettweiss', + 'Kucuk Dalyan', + 'Nirmal', + 'Simi Valley', + 'Wels', + 'Fredericton', + 'Nykoping', + 'Salgueiro', + 'Naantali', + 'Koliakkod', + 'Cruz das Almas', + 'Cardoso Moreira', + 'Sao Paulo', + 'Lugovoy', + 'Sheffield Lake', + 'Focsani', + 'Saarijarvi', + 'La Magdalena Chichicaspa', + 'Clarkstown', + 'Bongabong', + 'Raisen', + 'Aguadas', + 'Pomaz', + 'Shahpur Undi', + 'Wervershoof', + 'Cochoapa el Grande', + 'Malaybalay', + 'Mesudiye', + 'Caravaggio', + 'Moviya', + 'Manay', + 'Kitanakagusuku', + 'Basi', + 'Kant', + 'Garmeh', + 'Weeze', + 'Kenieran', + 'Epping', + 'Serra do Salitre', + 'Sabzevar', + 'Timbiqui', + 'Andilanatoby', + 'West Point', + 'Nneyi-Umuleri', + 'Brembate', + 'Bendigo', + 'Pitrufquen', + 'Mobara', + 'Tuku', + 'Herkimer', + 'Rokunohe', + 'Aliso Viejo', + 'Shirataka', + 'Huajiang', + 'Imam Sahib', + 'Toyoake', + 'Cidade Velha', + 'Bull Run', + 'Leguevin', + 'Sao Domingos', + 'Sidi Barani', + 'Ejeda', + 'Xishrov', + 'Ebebiyin', + 'Oruvadalkotta', + 'Gurwalia Biswas', + 'Mariupol', + 'Ulft', + 'Necocli', + 'Ambriz', + 'Saltsjobaden', + 'Oulad Dahmane', + 'Adelaide River', + 'Bikaner', + 'Condoto', + 'Shah Latif Town', + 'Manantheri', + 'Mossley', + 'Piracununga', + 'Parappanangadi', + 'Revel', + 'Atmakuru', + 'Pullappalli', + 'Shengli', + 'Salto', + 'Los Mochis', + 'Pattittara', + 'Dahuaishu', + 'Yamasa', + "Val-d'Or", + 'Holic', + 'Tarxien', + 'Meishan', + 'Baghant', + 'Panrepatti', + 'Edemissen', + 'Rockport', + 'Chavassheri', + 'Swampscott', + 'Torroella de Montgri', + 'Chauk Azam', + 'Moul El Bergui', + 'Isla-Cristina', + 'Oulad Ayyad', + 'Qorveh', + 'Yoshiicho-shimobaru', + 'Jangipur', + 'Port Alberni', + 'Beauchamp', + 'Ratba', + 'Beni Douala', + 'Kunduz', + 'Seosaeng', + 'Etaples', + 'Bludenz', + 'Golaghat', + 'Sujanpur', + 'Doctor Arroyo', + 'Bugho', + 'Borgarnes', + 'Solanea', + 'Boulogne-Billancourt', + 'Khosrowshahr', + 'Paithan Kawai', + 'Kaltenkirchen', + 'Jordan', + 'Motiong', + 'Daraj', + 'Manhattan', + 'Debica', + 'Bina', + 'Nanjanad', + 'Campina', + 'Antsahavaribe', + 'Maizal', + 'Saint-Nicolas', + 'Round Lake Beach', + 'Kamalasai', + 'Yeola', + 'Dangila', + 'East Bradford', + 'Noale', + 'Germencik', + 'De Aar', + 'Taihecun', + 'Kilinochchi', + 'Karivalamvandanallur', + 'Vinany', + 'Pudtol', + 'Visselhovede', + 'Perigny', + 'Izalco', + 'Dhuri', + 'Rudarpur', + 'San Marzano sul Sarno', + 'Kambar', + 'Le Hochet', + 'Skalavik', + 'Dharhara', + 'Agutaya', + 'Montpelier', + 'Santa Pola', + 'Budelsdorf', + 'Gyapekurom', + 'Medan', + 'Egilsstadhir', + 'Somasso', + 'Carsamba', + 'Nepalganj', + 'Bobil', + 'San Guillermo', + 'Hyrum', + 'Dhauni', + 'Al Hisn', + 'Hengkou', + 'Nenagh', + 'Lake Monticello', + 'Kokiladanga', + 'Aves', + 'Iwaizumi', + 'Santa Quiteria do Maranhao', + 'Kusugal', + 'Misano Adriatico', + 'Liangyi', + "Qa'em Shahr", + 'Okene', + 'Dorsten', + 'Inglewood', + 'Waianae', + 'Nerupperichchal', + 'Ghandinagar', + 'Le Portel', + 'Nazare', + 'Douar Lehgagcha', + 'Boende', + 'Aracruz', + 'Atar', + 'Golhisar', + 'Niar', + 'Kristiansund', + "Mu'minobod", + 'Ilchester', + 'Jodoigne', + 'Mudanjiang', + 'Parapatti', + 'Youssoufia', + 'Has', + 'Rzeszow', + 'View Park-Windsor Hills', + 'Redland', + 'Bni Sidel', + 'Cumbum', + 'Sue', + 'Ban Pae', + 'Eghezee', + 'Lebedinovka', + 'Demerval Lobao', + 'Teplodar', + 'Moose Jaw', + 'Rahika', + 'Edwards', + 'Imdina', + 'Zuhres', + 'Gillette', + 'Tixter', + 'Valenii de Munte', + 'Zhangliangcun', + 'Failsworth', + 'Dormentes', + 'Chowchilla', + 'Osasco', + 'Cheste', + 'Elmadag', + 'Timberwood Park', + 'Tiruttani', + 'Souba', + 'Maripad', + 'Tiltil', + 'Piossasco', + 'Comstock Park', + 'Sagrada Familia', + 'San Raimundo', + 'Killeen', + 'Miracema', + 'Singapore', + 'Olocuilta', + 'Ribeirao do Pinhal', + 'Itaquitinga', + 'Haltom City', + 'Chanderi', + 'Jarabulus', + 'Chennampatti', + 'Izumo', + 'Taruma', + 'Sottaiyampalaiyam', + 'Ichikawamisato', + 'Brockton', + 'Pandua', + 'Willow Street', + 'Truro', + 'Topola', + 'Blumenau', + 'Braine-le-Comte', + 'Duga Resa', + 'Liesveld', + 'Cairu', + 'Kolongo-Bozo', + 'Huanghua', + 'La Oliva', + 'Yokkaichi', + 'Tres Lagoas', + 'Westervoort', + 'Michelstadt', + 'Cavalcante', + 'La Lima', + 'Ampanotokana', + 'Roma', + 'Latacunga', + 'Eckington', + 'Cam Pha', + 'Kangqiao', + 'Remanso', + 'Jipijapa', + 'Dbarwa', + 'Cody', + 'Narsarsuaq', + 'Windhoek', + 'Razgrad', + 'Xinhualu', + 'Aosta', + "M'Chouneche", + 'Aydarken', + 'Analamitsivalana', + 'Boro', + 'Yelur', + 'Manteno', + 'As Sarw', + 'Ban Bueng Phra', + 'Puliyankudi', + 'Istaravshan', + 'Pithaura', + 'Onex', + 'Chakdarra', + 'Kareli', + 'Dinshaway', + 'Matalam', + 'Yongju', + 'Kumarapuram', + 'Yidu', + 'St. Joseph', + 'Fort Bonifacio', + 'Mecayapan', + 'Saint-Dizier', + 'Sharurah', + 'Uruara', + 'Macusani', + 'Stollberg', + 'Avelino Lopes', + 'Eschenbach', + 'Kamianka-Buzka', + 'Merced', + 'Ishizaki', + 'Ratan', + 'Majholi', + 'Portimao', + 'Guyancourt', + 'Ampasimena', + 'Dengshangcun', + 'Teno', + 'Cachira', + 'Davaci', + 'Tamamura', + 'Clifton Park', + 'Ma`bar', + 'Aartselaar', + 'Blundellsands', + 'Richards Bay', + 'Bussolengo', + 'Ratnahalli', + 'Bestensee', + 'Manjacaze', + 'Solano', + 'Mercerville', + 'Wujie', + 'Luderitz', + 'Campo Formoso', + 'Pobiedziska', + 'Ennamangalam', + 'Claiborne', + 'Elesvaram', + 'Begogo', + 'San Salvo', + 'Tambura', + 'Zumbo', + 'Margate', + 'Naini Tal', + 'Mao', + 'Castelfranco di Sotto', + 'Guachaves', + 'Artemisa', + 'Ovalle', + 'Piedras Blancas', + 'Dhanauri', + 'Vengapalli', + 'Tnine Sidi Lyamani', + 'Balindong', + 'Busia', + 'Codigoro', + 'Shiqiao', + 'Eidson Road', + 'Malita', + 'Kapatagan', + 'Ervalia', + 'Sibuco', + 'Rattaphum', + 'Wolfratshausen', + 'Jucurucu', + 'Lismore', + 'Lagangilang', + 'Ech Chaibat', + 'Iguaba Grande', + 'Bayan Hot', + 'Senjan', + 'Mihara', + 'Sandwich', + 'Huanglongsi', + 'Hualaihue', + 'Campos do Jordao', + 'Radauti', + 'Hersham', + 'Rutherford', + 'Kavallemmavu', + 'Bindura', + 'Niono', + 'Hammam Dalaa', + 'Varto', + 'Qina', + 'Aringay', + 'Trofarello', + 'Goshikicho-aihara-minamidani', + 'Taozhou', + 'Wickford', + 'Casale sul Sile', + 'Pointe a Raquettes', + 'Dolianova', + 'Morpeth', + 'Safdarabad', + 'Asamankese', + 'Mifune', + 'South Huntington', + 'Antri', + 'Ambohimahazo', + 'Nangis', + 'Lincolnwood', + 'Vieira do Minho', + 'Marigliano', + 'Sankt Valentin', + 'Ota-ku', + 'Montes Claros', + 'Scottsboro', + 'Gulni', + 'Bogovinje', + 'Tlalnelhuayocan', + 'Little Canada', + 'Bni Bouayach', + 'Porto Rafti', + 'Pontal', + 'Milha', + 'Kigoma', + 'Littleborough', + 'Tarur', + 'Myers Corner', + 'Salamedu', + 'Hernando', + 'Sava', + 'Rowley Regis', + 'Qax', + 'Mor', + 'Jalalaqsi', + 'Fereydun Kenar', + 'Aubergenville', + 'Puranattukara', + 'Vondrozo', + 'Pombal', + 'Upper Providence', + 'Huizhou', + 'Mali Idos', + 'Townsville', + 'Bolbec', + 'Nanpara', + 'Tocantins', + 'Oulgaret', + 'Marmara Ereglisi', + 'Pedra', + 'Sahapur', + 'Puerto Lleras', + 'Soccorro', + 'Enerhodar', + 'Adams', + 'Khetko', + 'Bacobampo', + 'Naxxar', + 'Wilton', + 'Manantenina', + 'Montecatini Terme', + 'Ocumare del Tuy', + 'Kas', + 'Nagarpara', + 'Tagounite', + 'Magnago', + 'Paripueira', + 'Guerra', + 'Ecaussinnes-Lalaing', + 'Tittachcheri', + 'Cariacica', + 'Missaglia', + 'Niscemi', + 'Ajdabiya', + 'Santa Cruz das Palmeiras', + 'Bogucice', + 'Centurion', + 'Aalst', + 'Tupelo', + 'Kochhor', + 'Riihimaki', + 'Sjobo', + 'Dongguan', + 'Melbourne', + 'Goluchow', + 'Devapur', + 'Merimbula', + 'North Port', + 'Bou Izakarn', + 'Cuisnahuat', + 'Cunday', + 'Sao Bernardo', + 'Everett', + 'Gyal', + 'Cedar Grove', + 'Piriapolis', + 'Mamakating', + 'Narathiwat', + 'Wood River', + 'Kebemer', + 'Salzburg', + 'Roebourne', + 'Port-au-Prince', + 'Ulliyeri', + 'Simferopol', + 'Navojoa', + 'Kuna', + 'Ikeda', + 'Dinan', + 'Champadanga', + 'Kottayam', + 'Itajai', + 'Moulay Abdallah', + 'Ponnuru', + 'Pakribarawan', + 'Canakkale', + 'Mestre', + 'Kannampalaiyam', + 'Lang Suan', + 'Libiaz', + 'Naftalan', + 'Guanzhai', + 'Juruti', + 'Hautmont', + 'Coro', + 'Navani', + 'Cagdianao', + 'Bauska', + 'Waltershausen', + 'Karippira', + 'Kodiyeri', + 'Huercal de Almeria', + 'Lebane', + 'Tando Allahyar', + 'El Bordj', + 'Polva', + 'Asasa', + 'Capilla de Guadalupe', + 'Vadapalanji', + 'Mekhe', + 'Leigh-on-Sea', + 'Velykyi Bychkiv', + 'Sihali Jagir', + 'Tepetitlan', + 'Sincelejo', + 'Caorle', + 'Burrel', + 'Ebetsu', + 'Niagara-on-the-Lake', + 'Tucson Mountains', + 'Manawar', + 'Uige', + 'Ankaramy', + 'Guinobatan', + 'Andrakata', + 'Patulul', + 'Patnos', + 'Bengkulu', + 'Strakonice', + 'Bairo', + 'Sint Anthonis', + 'Astorp', + 'Chantepie', + 'Stara Gora', + 'Fierenana', + 'Phenix City', + 'Schmalkalden', + 'Nadi', + 'Vimodrone', + 'Maramag', + 'Satipo', + 'El Oued', + 'Dogbo', + 'Needham', + 'Mitu', + 'North Vancouver', + 'Belmonte', + 'Turmanin', + 'Beandrarezona', + 'Shiraz', + 'Edgewood', + 'Eqbaliyeh', + 'West Whiteland', + 'Nchelenge', + 'Sason', + 'Suknah', + 'Alhandra', + 'Boghni', + 'Versoix', + 'Beverwijk', + 'Satyun', + 'Village Green-Green Ridge', + 'Luuk', + 'Speedway', + 'Yanbu', + 'Bhitarwar', + 'Kluang', + 'Sastamala', + 'Mulangodi', + 'Olivares', + 'Kondhali', + 'Inkollu', + 'Piedras Negras', + 'Consuelito', + 'Uruguaiana', + 'Nowy Targ', + 'Puvali', + 'Hulshout', + 'Pleasant Grove', + 'Armant', + 'Viga', + 'Igbanke', + 'Plover', + 'Bene Beraq', + 'Yopougon', + 'San Nicolas de los Ranchos', + 'Piui', + 'Gieraltowice', + 'Tarana', + 'Yanai', + 'Chariyakulam', + 'Bogota', + 'Minamisanriku', + 'Werl', + 'Manuel Ribas', + 'Heusenstamm', + 'Grosseto', + 'Villeparisis', + 'Serramanna', + 'Sudlohn', + 'Mawatagama', + 'Starokostiantyniv', + 'Vaikam', + 'Ipiales', + 'New Baltimore', + 'Quinta de Tilcoco', + 'Borgaro Torinese', + 'Tapiratiba', + 'Cazzago San Martino', + 'Esperanca', + 'Yirol', + 'Mila', + 'Tangalan', + 'Sardarshahr', + 'Palmetto', + 'Heddesheim', + 'Grossburgwedel', + 'Pradera', + 'Bela Vista do Paraiso', + 'Kilwinning', + 'Dunn Loring', + 'Bcharre', + 'Bhagirathpur', + 'Cabadbaran', + 'Hino', + 'Runcorn', + 'Santiago de Compostela', + 'Sotouboua', + 'Tecate', + 'Morasar', + 'Vanono', + 'Sankt Ingbert', + 'Jajce', + 'Xinyu', + 'Hochst im Odenwald', + 'Kawthoung', + 'Cherry Hill', + 'Riebeeckstad', + 'Cacheu', + 'Tranovaho', + 'Douar Sgarta', + 'Tlemcen', + 'Mandabe', + 'Idil', + 'Bohicon', + 'East Cocalico', + 'Wekiwa Springs', + 'Magdalena Milpas Altas', + 'South Charleston', + 'Dougba', + 'Glenwood Springs', + 'Dumfries', + 'Moyale', + 'Mabini', + 'Santa Terezinha de Goias', + 'Evergreen', + 'Watford', + 'Rabor', + 'Castel Mella', + 'Tanabe', + 'Tafaraoui', + 'Hilter', + 'Shujaabad', + 'Videm', + 'Kecskemet', + 'Obertraubling', + 'Folomana', + 'Novi Becej', + 'Molalla', + 'Hagta', + 'Tisina', + 'Raikal', + 'Wendell', + 'Canet-en-Roussillon', + 'Kennedy', + 'Herzberg am Harz', + 'San Simon', + 'Miyatoko', + 'Pali', + 'Copiapo', + 'Muhldorf', + 'Bambadinca', + 'Igbaras', + 'Kothi', + 'Souq Sebt Says', + 'San Rafael Abajo', + 'Halisahar', + 'Mamaroneck', + 'Varisshiyakuni', + 'Matsoandakana', + 'Burgas', + 'Bhanghi', + 'Beverly', + 'Raichur', + 'Tahannawt', + 'Tota', + 'Lunel', + 'Souk Tlet El Gharb', + 'Racine', + 'Stockerau', + 'Mazamitla', + 'Guachucal', + 'Malepur', + 'Sarikamis', + 'Gothenburg', + 'Suganwan', + 'Kirikkale', + 'Maghull', + 'Chonchi', + 'Ospitaletto', + 'Maria da Fe', + 'Chalandri', + 'Bobon', + 'Naqadah', + 'Martinopole', + 'Enghien', + 'Savigny-le-Temple', + 'Ramchandarpur', + 'Caio', + 'Belo', + 'Bukowno', + 'Tel Aviv-Yafo', + 'Avabodji', + 'Puerto America', + 'Hanson', + 'Huangshan', + 'Bulusan', + 'Domazlice', + 'Linkenheim-Hochstetten', + 'Suaza', + 'Mangalagiri', + 'Rocky River', + 'Tanakoub', + 'Canicatti', + 'Las Rosas', + 'Mahlaing', + 'Jelenia Gora', + 'Mathila', + 'Aizawa', + 'Itatiaia', + 'Neuri', + 'Chivacoa', + 'Arvorezinha', + 'Armanaz', + 'Buritizeiro', + 'Bien Hoa', + 'Bruchkobel', + 'Dolo Bay', + 'Lefkada', + 'Tasiusaq', + 'Pallappatti', + 'Kanchrapara', + 'San Roque', + 'Jefferson', + 'Santiago de Baney', + 'Kofu', + 'Harchandpur', + 'Moss Bluff', + 'Vushtrri', + 'Mithi', + 'Smederevska Palanka', + 'Apan', + 'Deodha', + 'Nallihan', + 'Guapo', + "Ghinda'e", + 'Sar-e Pul', + 'Christiansburg', + 'Pont-a-Celles', + 'Overijse', + 'Trecastagni', + 'Oulad Embarek', + 'Dayr Hafir', + 'Caldas de Reyes', + 'Arkalochori', + 'Gafsa', + 'Cipolletti', + 'Kaikaram', + 'Shuangshuicun', + 'Iquitos', + 'Hazaribagh', + 'Bedidy', + 'Mayate', + 'Chumbicha', + 'Dilasag', + 'Karapa', + 'Petaling Jaya', + 'Castel Gandolfo', + 'Yevpatoriia', + 'Kuchaiburi', + 'Hanyu', + 'Horn Lake', + 'Kadiria', + 'Zhongba', + 'Godoy Cruz', + 'Malappuram', + 'East Peoria', + 'Markranstadt', + 'Livonia', + 'Dongen', + 'Golbasi', + 'Bhagwangola', + 'Elektrenai', + 'Hat Yai', + 'Saint John', + 'Torihama', + 'Galimuyod', + 'Raikot', + 'Bagre', + 'Komijan', + 'Ibia', + 'Holyhead', + 'Akwatia', + 'Elavanasur', + 'Tandag', + 'Chilwell', + 'Ciudad Ojeda', + 'Ciudad Serdan', + 'Radlett', + 'Vembarpatti', + 'Atripalda', + 'Gourock', + 'Anama', + 'Amblecote', + 'Excelsior Springs', + 'Dadu', + 'Offa', + 'Brindisi', + 'Kovilpatti', + 'Chrudim', + 'Cesson-Sevigne', + 'Mediouna', + 'Zengqiao', + 'Aguilares', + 'Siay', + 'Mananjary', + 'Kolarovo', + 'Melissia', + 'Stari Trg', + 'Viadana', + 'Stolberg', + 'Berekum', + 'Farako', + 'Joyabaj', + 'San Giorgio di Piano', + 'Carbonera', + 'Mackay', + 'Meltham', + 'Challapalle', + 'Saricam', + 'Tres Barras', + 'Cannes', + 'Upper Darby', + 'Arlington', + 'Sanxi', + 'Sanankoro Djitoumou', + 'Birur', + 'Manne Ekeli', + 'Regenstauf', + 'Midsalip', + 'Reggello', + 'Corinda', + 'Babatngon', + 'Bungku', + 'Zhangshu', + 'Idiofa', + 'Amboanana', + 'Langley', + 'Fishersville', + 'Mateare', + 'Linda', + 'Helena', + 'Hohenstein-Ernstthal', + 'Korla', + 'Bairuo', + 'Limoeiro do Ajuru', + 'Gandevi', + 'Rogers', + 'Dexing', + 'Padugaipattu', + 'Ilmenau', + 'Litovel', + 'Yasenivskyi', + 'Boca da Mata', + 'Sarkad', + 'Pawayan', + 'Avon Park', + 'Kondarangi Kiranur', + 'Novgorodskoye', + 'Nanattuparai', + 'Krishnapur', + 'Mendez-Nunez', + 'Alacam', + 'Pelissanne', + 'Colotlan', + 'Raahe', + 'Ridder', + 'Jisr ash Shughur', + 'Hoveyzeh', + 'Rokkasho', + 'Muquem de Sao Francisco', + 'Tsuru', + 'Ross-Betio', + 'Escanaba', + 'Tacana', + 'Traverse City', + 'Gounarou', + 'Fontaine', + 'Sahuarita', + 'Bracciano', + 'Araripina', + 'Gambissara', + 'Lumphat', + 'Gwangmyeongni', + 'Kirrayach', + 'University Place', + 'Unai', + 'Paracin', + 'Juara', + 'Ware', + 'Hilltown', + 'Summit Park', + 'Mislata', + 'Rottofreno', + 'Uniontown', + 'Kulu', + 'Bunia', + 'Ciudad Bolivar', + 'Gitagum', + 'Uniao dos Palmares', + 'Erumaippatti', + 'Pichhor', + 'Yuanlin', + 'Bulungu', + 'Kadakola', + 'Harur', + 'Raunheim', + 'Mothihari', + 'Malahide', + 'Macapa', + 'Mahabo', + 'Andonabe Atsimo', + 'Hita', + 'Salesopolis', + 'Tirkakara', + 'Malhargarh', + 'Oakengates', + 'Agrinio', + 'Whitehall', + 'Lapuyan', + 'Botticino Sera', + 'Tinoc', + 'Jiquilpan de Juarez', + 'Rockville Centre', + 'Ibatiba', + 'Ballina', + 'Broken Arrow', + 'Ziniare', + 'Clayton le Moors', + 'Fredensborg', + 'Kharod', + 'Umm al `Abid', + 'Goldenrod', + 'Yacopi', + 'Halsur', + 'Opoczno', + 'Kottapalle', + 'Ubrique', + 'Sodrazica', + 'Irakleia', + 'Sao Luis Gonzaga', + 'Sao Paulo do Potengi', + 'North Gates', + 'Alsip', + 'Choele Choel', + 'Antsiatsiaka', + 'Beocin', + 'Tenancingo', + 'Monselice', + 'Marpalli', + 'Yangqingcun', + 'Columbine', + 'Sahamadio', + 'San Nicolas Buenos Aires', + 'Tiruvur', + 'Anosimparihy', + 'Lariano', + 'Chaval', + 'Karavan', + 'Kingsbury', + 'Koduru', + 'Bangolo', + 'Heidenau', + 'Petrolina', + 'Archena', + 'Natonin', + 'Casalnuovo di Napoli', + 'Nova Odessa', + 'Kariz', + 'Caen', + 'Scugog', + 'Bangalore', + 'Zijinglu', + 'Catford', + 'Chakapara', + 'Safita', + 'Acquaviva', + 'Satai', + 'Ghabaghib', + 'La Spezia', + 'Iquique', + 'Barhampur', + 'Chalungalpadam', + 'Kerur', + 'Gulnar', + 'Banigbe', + 'Metamorfosi', + "Ma'muniyeh", + 'Tura', + 'Seelze', + 'White Plains', + 'Reddippatti', + 'Ticuantepe', + 'Ntoroko', + 'Nairobi', + 'Glubczyce', + 'Safi', + 'Larne', + 'Sebin Karahisar', + 'Donegal', + 'Pasto', + 'Ceyhan', + 'East Grand Forks', + 'Qarqan', + 'Ferney-Voltaire', + 'Caybasi', + 'Ayanavelikulangara Tekku', + 'Derventa', + 'Homburg', + 'Zaouiet Says', + 'Jethuli', + 'Ketti', + 'Rochdale', + 'Zevio', + 'Made', + 'Eleskirt', + 'Iijima', + 'Rubirizi', + 'Mombris', + 'Santa Teresita', + 'Pedappai', + 'Vineland', + 'Saint-Amable', + 'Awara', + 'Betton', + 'Alsbach-Hahnlein', + 'Tongzhou', + 'Nopala de Villagran', + 'Propria', + 'Jawkatia', + 'Morrovalle', + 'Bella Vista', + 'Lufeng', + 'Lebach', + 'Wanda', + 'East Highland Park', + 'Phu Yen', + 'Masangshy', + 'Bou Sfer', + 'Kumirimora', + 'Shibushi', + 'Falticeni', + 'Wallington', + 'Bryn', + 'Dornstadt', + 'Sembe', + 'Onan', + 'Brandizzo', + 'Voerendaal', + 'Qaladizay', + 'Butwal', + 'Kunzell', + 'Dindanko', + 'Knezha', + 'Half Moon Bay', + 'Upleta', + 'Cambuslang', + 'Zupanja', + 'Ronse', + 'Seneca Falls', + 'Karlshamn', + 'Attippattu', + 'Terzigno', + 'Brumado', + 'Bangaon', + 'Szczecin', + 'Jauharabad', + 'Pisticci', + 'Gukeng', + 'Conceicao do Araguaia', + 'Saltash', + 'Parvomay', + 'Ban Wat Phrik', + 'Cabaiguan', + 'Awa', + 'Nemours', + 'Yukuhashi', + 'Bujari', + 'Jiwachhpur', + 'Rongwo', + 'Puerto Nare', + 'Anjomachi', + 'Lagbe', + 'Mickleover', + 'Mianyang', + 'Eshowe', + 'Bonnievale', + 'Radekhiv', + 'Sagauli', + 'Ankirihitra', + 'University Park', + 'Punjai Lakkapuram', + 'Hamlin', + 'Feni', + 'Legnica', + 'Gandhali', + 'Deyang', + 'Scotts Valley', + 'North Ogden', + 'Tocantinopolis', + 'Princesa Isabel', + 'Weert', + 'Chos Malal', + 'West Odessa', + 'Mawanella', + 'Aljaraque', + 'Majhariya', + 'Nagayalanka', + 'Tring', + 'Lenox', + 'Kebila', + 'Fatehpur Bala', + 'Mucheln', + 'Sangolqui', + 'Gohadi', + 'Komaki', + 'Malaudh', + 'Coquitlam', + 'Florencia', + 'Kalisizo', + 'Eslamshahr', + 'Tychy', + 'Lazaro Cardenas', + 'Pallarimangalam', + 'Yang Talat', + 'Arsuz', + 'Bhavani', + 'Maniwa', + 'Santa Luzia do Itanhy', + 'Oranienburg', + 'Huangshi', + 'Zaranj', + 'Halver', + 'Areado', + 'Hadjadj', + 'Balykchy', + 'Effingham', + 'Valley Stream', + 'Pinhao', + 'West New York', + 'Heishanzuicun', + 'Bordentown', + 'Inirida', + 'La Huerta', + "Sant'Egidio alla Vibrata", + 'Dharapuram', + 'Ad Dulu`iyah', + 'Kaarina', + 'Rekovac', + 'Manitowoc', + 'Goroka', + 'Motul', + 'Narasingam', + 'Beckenham', + 'Kirkwood', + 'Kolondieba', + 'Pasadena', + 'Qo`ng`irot Shahri', + 'Winsen', + 'Mers el Kebir', + 'Poro', + 'Knjazevac', + 'Dajiecun', + 'Tangjin', + 'Karbinci', + 'Beykoz', + 'Amadeo', + 'Manamelkudi', + 'Tekanpur', + 'Busolwe', + 'Ac-cahrij', + 'Coroico', + 'Inhambane', + 'Jacinto', + 'Carlentini', + 'Skippack', + 'Federal', + 'Valenzano', + 'Zhangcun', + 'Loenen', + 'Muswellbrook', + 'Tamazunchale', + 'Moatize', + 'Port Salerno', + 'Lisbon', + 'Santo Estevao', + 'Irondequoit', + 'Levallois-Perret', + 'San Antonio Suchitepequez', + 'Zvecan', + 'Douar Lamjaara', + 'Winter Springs', + 'Bimawan', + 'Pentapadu Kasba', + 'Montijo', + 'Sena Madureira', + 'Virei', + 'Varzaneh', + 'Sariveliler', + 'Vatakemuri', + 'Machelen', + 'Nabunturan', + "Yong'ancun", + 'Dalsingh Sarai', + 'Etoumbi', + 'Funadhoo', + 'Antur', + 'Superior', + 'Matouying', + 'Abucay', + 'Kengarai', + 'Paterna', + 'Saqqez', + 'Spring Creek', + 'Garaimari', + 'Nan Zhuang', + 'Jones', + 'Bou Khadra', + 'Rosh Ha`Ayin', + 'Adalhat', + 'Capaya', + 'Kuruman', + 'Chaoyangdicun', + 'Villacarrillo', + 'South Portland', + 'Bambui', + 'Apalit', + 'Biddupur', + 'Baqiabad', + 'Kostanjevica na Krki', + 'Rechaiga', + 'Morwell', + 'Koundian', + 'Higashimiyoshi', + 'Castro', + 'At Tur', + 'Pirnagar', + 'Vandalia', + 'Nellipaka', + 'Zerong', + 'Goodlands', + 'Hatay', + 'Bielsko-Biala', + 'Grosse Pointe Woods', + 'Ajijic', + 'Dhanupra', + 'Pachrukha', + 'Handwara', + 'Ranopiso', + 'Vallenar', + 'Santa Maria do Suacui', + 'Loyalist', + 'Chinameca', + 'Mahazoma', + 'Silifke', + 'Falakata', + 'Ghordaur', + 'Black Forest', + 'Dale City', + 'Ranbirsinghpura', + 'Ibiassuce', + 'Tetagunta', + 'Pereiro', + 'Marmagao', + 'Amparihy', + 'Ereymentau', + 'Heishuikeng', + 'As Salt', + 'Los Patios', + 'Masyaf', + 'Ban Sai Yoi', + 'Abre Campo', + 'El Maknassi', + 'Rosaryville', + 'Dupax Del Norte', + 'Kosai', + 'La Queue-en-Brie', + 'Rasulpur', + 'Chelm', + 'Caserta', + 'Devmaudaldal', + 'Biancavilla', + 'Nilaiyur', + 'Budhanilkantha', + 'Weyburn', + 'Schertz', + 'Bela Simri', + 'Kimje', + 'Jerome', + 'Rangapuram', + 'Sandalpur', + 'Nea Erythraia', + 'Entebbe', + 'San Bartolome', + 'Al Karnak', + 'Estepona', + 'Lempdes', + 'Zengjiaba', + 'Lipkovo', + 'Buckingham', + 'Bevoay', + 'Sokouhoue', + 'Tecklenburg', + 'Bochum', + 'San Luis Talpa', + 'Ijui', + 'Woodlyn', + 'Capivari', + 'Purna', + 'Bayugan', + 'Imbert', + 'Durgauti', + 'Belen de los Andaquies', + 'Menghan', + 'Gangapur Athar', + 'Inza', + 'Tiaong', + 'Cagliari', + 'Inverness', + 'Ciudad Sahagun', + 'Gerakas', + 'Vubatalai', + 'Ratauli', + 'Catalao', + 'Telsen', + 'Secovce', + 'Sirugamani', + 'Lake Norman of Catawba', + 'Vevey', + 'Quakers Hill', + 'Struga', + 'Amarwara', + 'Mont-Saint-Martin', + 'Nova Crnja', + 'Hirehalli', + 'Ablu', + 'Llavallol', + 'El Almendro', + 'Ad Darwa', + 'Bietigheim-Bissingen', + 'Ul', + 'Indianola', + 'Ankisabe', + 'Minster', + "Wik'ro", + 'Barapire', + 'A Yun Pa', + 'Retirolandia', + 'Shahkot', + 'Itaberaba', + 'Pilappulli', + 'Coesfeld', + 'Azare', + 'Dix Hills', + 'Nazira', + 'Velen', + 'Mnichovo Hradiste', + 'Cedar City', + 'Bofete', + 'Haiphong', + 'Tokmok', + 'Goianapolis', + 'Texas City', + 'Marakkara', + 'Cameli', + 'Lundazi', + 'Antanambaobe', + 'Akhmim', + 'Abhayapuri', + 'Saint-Philbert-de-Grand-Lieu', + 'Lutterworth', + 'Vinci', + 'Kidlington', + 'Sao Joao', + 'Leopold', + 'Vigevano', + 'Croix-des-Bouquets', + 'Zele', + 'Ibateguara', + 'West Lealman', + 'Taran', + 'Pingshang', + 'Douar Ait Sidi Daoud', + 'San Martin', + 'Bajram Curri', + 'Fouesnant', + 'Hosbach', + 'Jiamusi', + 'Mendi', + 'Gennevilliers', + 'Suramala', + 'Lambunao', + 'Guatuba', + 'Hassi Bahbah', + 'Mineral del Monte', + 'Menglie', + 'Chitarpur', + 'Cristopolis', + 'Ad Diwem', + 'Tocopilla', + 'Calella', + 'Bedwas', + 'Sarur', + 'Wuhai', + 'Flero', + 'Jork', + 'Juma Shahri', + 'Hawtat Sudayr', + 'Viyapuram', + 'La Nucia', + 'Chalhuanca', + '`Utaybah', + 'Ilkley', + 'Bloomfield', + 'Dobhawan', + 'Chempalli', + 'Tipo-Tipo', + 'Risch', + 'Marosangy', + 'Kirchheim bei Munchen', + 'Ubaira', + 'Maragondon', + 'Al `Aydabi', + 'Lamia', + 'Chippewa Falls', + 'Ora', + 'Androy', + 'Valle del Guamuez', + 'Sykies', + 'Barahari', + 'Peters', + 'Naxcivan', + 'Anderlues', + 'Doraville', + 'Banaue', + 'Eersel', + 'Ganapatipalaiyam', + 'Bernal', + 'Tole Bi', + 'Badiangan', + 'Newry', + 'Boom', + 'Baguineda', + 'Thulusdhoo', + 'Kanniyakumari', + 'Ramagiri Udayagiri', + 'Tangjiacun', + 'Naranattenvanpatti', + 'Megarine', + 'Angallu', + 'Yeni Suraxani', + 'Gudimulakhandrika', + 'Port-Alfred', + 'Bantval', + 'Banska Bystrica', + 'Ratanpur', + 'Sihui', + 'Ambohimangakely', + 'Mineshita', + 'San Rafael', + 'Bhambia Bhai', + 'Nauheim', + 'Russellville', + 'Naushahro Firoz', + 'Sarvestan', + 'Pagalungan', + 'Carsibasi', + 'Emirgazi', + 'Ciudad Rio Bravo', + 'Proddatur', + 'Bruz', + 'Bartlett', + 'Yeadon', + 'Timbedgha', + 'Rishton', + 'Thaba-Tseka', + 'Pappampatti', + 'Alianca', + 'Skowhegan', + 'Gongzhuling', + 'Port Arthur', + 'Budingen', + 'Vallam', + 'Agios Ioannis Rentis', + 'Birine', + 'Quirino', + 'Honeygo', + 'Usulutan', + 'Abram', + 'Taranto', + 'Tyrnavos', + 'Nagongera', + 'Farsan', + 'Santa Rita', + 'Bueno Brandao', + 'Amingarh', + 'Port Loko', + 'Hellevoetsluis', + 'Surigao', + 'Pattamadai', + 'Balsas', + 'Matipo', + 'Onitsha', + 'Strasshof an der Nordbahn', + 'Rochefort', + 'Doral', + 'Limoeiro', + 'Ambalanjanakomby', + 'Cohoes', + 'Jaguaruana', + 'Vadakethara', + 'Prospect Heights', + 'Bonita', + 'Jarvenpaa', + 'Baj Baj', + 'Alamadi', + 'Nanzhou', + 'Sanatoga', + 'Arruda dos Vinhos', + 'Malapatan', + 'Soroti', + 'Dargahan', + 'Meze', + 'Pemangkat', + 'Dinagat', + 'Villemomble', + 'Donetsk', + 'Viru', + 'Dazhangzicun', + 'Vigneux-sur-Seine', + 'Amolatar', + 'Nemili', + 'Nagykanizsa', + 'Kedgaon', + 'Boumia', + 'Guisa', + 'Maur', + 'Saint-Laurent-du-Maroni', + 'Chenggong', + 'Rengali', + 'Bacau', + 'Kaunra', + 'Ballyfermot', + 'Aurahi', + 'Perito Moreno', + 'Shetang', + 'Suwon', + 'Virappanchathiram', + 'Celje', + 'Turhal', + 'Taquari', + 'Ikot Ekpene', + 'Allinagaram', + 'Bolintin Vale', + 'Maga', + 'Kolnad', + 'Sori', + 'Totoro', + 'San Rafael Obrajuelo', + 'Alayaman', + 'Ouani', + 'Tadla', + 'Pirque', + 'Cuxhaven', + 'Zhongzai', + 'Aliwal North', + 'Plum', + 'Saint Helier', + 'Dongcha', + 'Gokarn', + 'Sassandra', + 'Balneario do Rincao', + 'Kafr Buhum', + 'Hlukhiv', + 'Sandacho', + 'Arizona City', + 'Silamalai', + 'Mohanpur', + 'Diaobingshancun', + 'Ratekau', + 'Halgar', + 'Albertville', + 'Gadani', + 'Ascheberg', + 'Godda', + 'Tangdong', + 'Sholinghur', + 'Zuidlaren', + 'Jadopur Shukul', + 'Ekalbehri', + 'Puerto Plata', + 'Ambatturai', + 'Masanwa', + 'Crystal', + 'Dasungezhuang', + 'Eenhana', + 'Xiangtan', + 'Arbon', + 'Jerez de la Frontera', + 'Barkagaon', + 'Sumter', + 'Maroochydore', + 'Rach Gia', + 'Mirna Pec', + 'St. John', + 'Medianeira', + 'Muqui', + 'Farah', + 'Dedougou', + 'Manduri', + 'Monserrat', + 'Myrnohrad', + 'Segbana', + 'Gelnhausen', + 'Chuanliaocun', + 'Reshetylivka', + 'College Station', + 'Wum', + 'Imst', + 'Kumar Khad', + 'Bjarred', + 'Ambatofisaka II', + 'Nocatee', + 'Kanel', + 'Philipstown', + 'Torrevieja', + 'Frankfort Square', + 'Gudur', + 'Oak Grove', + 'Santana', + 'Polukallu', + 'Kanhai', + 'Trapani', + 'Sanchez-Mira', + 'Wangtuan', + 'Punta del Este', + 'Zevenaar', + 'Ban Tha Pha', + 'Sainte-Marie', + 'Karonga', + 'Cremlingen', + 'Ramallah', + 'Gatumba', + 'Caowotan', + 'Tomah', + 'Lorraine', + 'Odumase', + 'Amritsar', + 'Vlasim', + 'Yangcunzai', + 'Prataparampuram', + 'Indurti', + 'Tanjay', + 'Brzeg', + 'Manthani', + 'Passa Quatro', + 'Dallas', + 'Chinnatadagam', + 'Roxborough Park', + 'Ban Wang Pradu', + 'Were Ilu', + 'Tourcoing', + 'Shimokizukuri', + 'Dehui', + 'Ithaca', + 'Hamtic', + 'Saint-Zotique', + 'Mossendjo', + 'Ban Bo Phlap', + 'Arar', + 'Meekatharra', + 'Ghouazi', + 'Amora', + 'Yongji', + 'Nurtingen', + 'Pantar', + 'Yantai', + 'Shamli', + 'Sidi Kasem', + 'League City', + 'Ylojarvi', + 'Chakpi Karong', + 'Lehman', + 'Gunzenhausen', + 'Pirpirituba', + 'Titz', + 'Vynnyky', + 'Nicolas Romero', + 'Tekit', + 'Prinzapolka', + 'Nueva Esparta', + 'Hedongcun', + 'Lobougoula', + 'Socuellamos', + 'East Hemet', + 'Sebiston', + 'Ban Laem', + 'Mukono', + 'Mineiros do Tiete', + 'Warrnambool', + 'Normandia', + 'Monte Alegre', + 'Volda', + 'Yasothon', + 'Kearny', + 'Kahan', + 'Mihqan', + 'Playa Vicente', + 'Shimoga', + 'Konseguela', + 'Outapi', + 'Layton', + 'Brock Hall', + 'Kilsyth', + 'Al Mukalla', + 'Dhamtari', + 'Alijo', + 'Alacuas', + 'Higashiizu', + 'Lopez', + 'Ta`izz', + 'Corato', + 'Chuhuiv', + 'Albacete', + 'Gundumal', + 'Sidi Embarek', + 'Calais', + 'Camapua', + 'Portogruaro', + 'Bad Langensalza', + 'Tecolotlan', + 'Roseburg', + 'Saint-Jerome', + 'Sandila', + 'Coldwater', + 'Zhongcun', + 'Westonaria', + 'Pattanam', + 'Guihulngan', + 'Phaphot', + 'Huili Chengguanzhen', + 'Mabinay', + 'Bothell East', + 'Banavar', + 'Nallippalaiyam', + 'Quetigny', + 'Tsuno', + 'Geiro', + 'Zarrin Shahr', + 'Balpyq Bi', + 'San Antonio Oeste', + 'Krasnohorivka', + 'Bled', + 'Ama', + 'Lototla', + 'Azzano', + 'Ivai', + 'Mitanty', + 'Calicut', + 'Chelghoum el Aid', + 'Kitee', + 'Koffiefontein', + 'Fair Oaks Ranch', + 'Sao Marcos', + 'Disuq', + 'Bouzeghaia', + 'Hudson', + 'Fortuna Foothills', + 'Raska', + 'Rafah', + 'Birsfelden', + 'Loon', + 'Ivrindi', + 'Pacho', + 'Sasaguri', + 'Oroville', + 'Odiongan', + 'Bagua Grande', + 'Dera Ghazi Khan', + 'Moorslede', + 'Pilkha', + 'Kalar', + 'Odawara', + 'Monte Belo', + 'Ludwigsfelde', + 'Lopary', + 'Boriguma', + 'Storrs', + 'Khrystynivka', + 'Villa Aldama', + 'Sirali', + 'Bulwell', + 'Vadacheri', + 'Aytre', + 'Kwidzyn', + 'Furukawa', + 'Sukand', + 'Esquimalt', + 'Caraubas', + 'Antsohimbondrona', + 'Waltham Abbey', + 'Sagua la Grande', + 'Taulaha', + 'Roslyn', + 'Hale Dyamavvanahalli', + 'Cortona', + 'Minnal', + 'Brunoy', + 'Kunisakimachi-tsurugawa', + 'Rincao', + 'Golden Glades', + 'Matsuda-soryo', + 'Pirauba', + 'Ghriss', + 'Mopipi', + 'Chateauroux', + 'Chortkiv', + 'Theniet el Had', + 'Balkh', + 'Casalecchio di Reno', + 'Guacimo', + 'Arona', + 'Novaci', + 'Diakon', + 'Taunusstein', + 'Sawangan', + 'Jigani', + 'Parabita', + 'Candaba', + 'Jardim Alegre', + 'Berovo', + 'Cascina', + 'Ambohijanaka', + 'Sao Tome', + 'Cleethorpes', + 'Kalmunai', + 'Vaslui', + 'Fort William', + 'Bolobo', + 'Sanlucar la Mayor', + 'El Hajeb', + 'Quesnel', + 'Leuwiliang', + 'Caraibas', + 'Sugaon', + 'Villers-Cotterets', + 'Santamesa', + 'Santa Teresinha', + 'Anosibe-Ifanja', + 'Agarpur', + 'Kutavettur', + 'Itapetinga', + 'Lubsko', + 'Banbalah', + 'Tala', + 'Arcachon', + 'Muturkha', + 'Miyazu', + 'Akcadag', + 'Castaic', + 'Xindian', + 'Los Hidalgos', + 'Kekava', + 'Jacala', + 'Lapseki', + 'Cartaxo', + 'Londerzeel', + 'Ermua', + 'Ipiranga', + 'Trat', + 'Guiglo', + "Sant'Antioco", + 'Linton Hall', + 'Huaiyin', + 'San Salvador de Jujuy', + 'Ksour Essaf', + 'El Quisco', + 'Central', + 'Muttupet', + 'Juan de Herrera', + 'Qaryat Suluq', + 'Kressbronn am Bodensee', + 'Kall', + 'Thingangyun', + 'Lauf', + 'Villa de Alvarez', + 'Sipacate', + 'Andirin', + "Cournon-d'Auvergne", + 'Bhayandar', + 'Borongan', + 'Meerane', + 'Pekanbaru', + 'Szczytno', + 'Obu', + 'Chinnalapatti', + 'Angalakurichchi', + 'Caldas de Montbuy', + 'Altata', + 'Sonepur', + 'Benisa', + 'San Lucas Toliman', + 'Japeri', + 'Ecublens', + 'Kowloon', + 'Ivancna Gorica', + 'Bougzoul', + 'Jelcz-Laskowice', + 'Engenheiro Beltrao', + 'Stonecrest', + 'Cheltenham', + 'Ospino', + 'Qingdao', + 'Port Lavaca', + 'Weichanglu', + 'Gudiyattam', + 'Fujikawa', + 'Bamhni', + 'Porterville', + 'El Khemis des Beni Chegdal', + 'Surany', + 'Knurow', + 'Znamianka', + 'Huntersville', + 'El Eulma', + 'Bungoma', + 'Al Kharjah', + 'Rivesaltes', + 'Denzlingen', + 'Pul-e Khumri', + 'Niedernhausen', + 'Nova Floresta', + 'Cazin', + 'Virginia Beach', + 'Cassino', + 'Finnentrop', + 'Khajuri', + 'Cotabato', + 'Mahamaibe', + 'Aksu', + 'Campbellton', + 'Panay', + 'Otofuke', + 'Sarria', + 'Oklahoma City', + 'Waggaman', + 'Powdersville', + 'Ambongamarina', + 'Mirpur Khas', + 'Jamhor', + 'Florham Park', + 'Seram', + 'Senda', + 'Mancha Real', + 'Mint Hill', + 'Eggenstein-Leopoldshafen', + 'Bachhraon', + 'Friern Barnet', + 'Toboso', + 'Caledonia', + 'Dawley', + 'Bataredh', + 'Podenzano', + 'Hemiksem', + 'Pielisjarvi', + 'Pursa', + 'Marsabit', + 'Malar', + 'Tabubil', + 'White Meadow Lake', + 'Winter Garden', + 'Ryuo', + 'Jequie', + 'Ravar', + 'Gudalur', + 'Yenisehir', + 'Brookings', + 'Hamtramck', + 'Orizona', + 'Saint-Ouen', + 'Wietze', + 'Hwange', + 'Barentu', + 'Yedappalli', + 'Caucagua', + 'Gudensberg', + 'New Hope', + 'Benaguacil', + 'Aymangala', + 'Waltham', + 'Horndean', + 'Torquay', + 'Amulung', + 'Woolwich', + 'Sentjur', + 'Gondauli', + 'Krosno', + 'Nagasaki', + 'Luebo', + 'Sogutlu', + 'Valle de Santiago', + 'Ranzan', + 'Chachoengsao', + 'Golpayegan', + 'Mocuba', + 'Warrington', + 'Yercaud', + 'Capinzal', + 'Paullo', + 'Ozark', + 'Kargahia Purab', + 'Villanueva', + 'Jbabra', + 'Chunar', + 'Likiskiai', + 'Al Husayniyah', + 'Rockcreek', + 'Chichicastenango', + 'Siasi', + 'Beni Zouli', + 'Saint-Hilaire-de-Riez', + 'Devgeri', + 'Arniya', + 'Dennis', + 'Bopa', + 'East Norriton', + 'Marancheri', + 'Kalghatgi', + 'Andkhoy', + 'Dinanagar', + 'Kaifeng Chengguanzhen', + 'Jieshangya', + 'Yima', + 'Gamu', + 'Safashahr', + 'Pervari', + 'Agoue', + 'Presidente Franco', + 'Bad Homburg', + 'Taveta', + 'Croxley Green', + 'Bani', + 'Liberal', + 'Santa Maria de Ipire', + 'Hranice', + 'Mery-sur-Oise', + 'Ipupiara', + 'Takanabe', + "G'allaorol Shahri", + 'Syke', + 'Sembabule', + 'Kenley', + 'Bishnah', + 'Santa Lucia Milpas Altas', + 'Ahfir', + 'Virudunagar', + 'Cuapiaxtla de Madero', + 'Erquelinnes', + 'Galanta', + 'Islington', + 'Alaca', + 'Ganca', + 'Colcapirhua', + 'Katano', + 'White Oak', + 'Burgos', + 'Macalelon', + 'Bullhead City', + 'Obukhivka', + 'Krupka', + 'Rovaniemi', + 'Vrapciste', + 'Puerto Padre', + 'Cristalina', + 'Drug', + 'Bocono', + 'Abaji', + 'Annoeullin', + 'Savja', + 'El Coco', + 'Vauvert', + 'Rumbek', + 'Tettnang', + 'Tatsuno', + 'Olton', + 'Kabinda', + 'Miami Shores', + 'Chinnasekkadu', + 'Cotia', + 'Sapiranga', + 'Bad Rappenau', + 'Sharunah', + 'Cormeilles-en-Parisis', + 'Dossenheim', + 'Ozarow Mazowiecki', + 'Pichucalco', + 'Dowlaiswaram', + 'Palm Harbor', + 'Longba', + 'Chinique', + 'Roscoe', + 'Douar Oulad Bouziane', + 'Dayr az Zawr', + 'French Valley', + 'Kalasa', + 'Cave Spring', + 'Yakage', + 'Phagu', + 'Taishituncun', + 'Sumisip', + 'Littleport', + 'Kulachi', + 'Saravena', + 'Aguai', + 'Nederweert', + 'Haskoy', + 'Saguday', + 'Shushtar', + 'Cuilapa', + 'Formigine', + 'Kalateh-ye Mir Ab', + 'Farsund', + 'Ripon', + 'Munich', + 'Himora', + 'Banamba', + 'Mkokotoni', + 'Anori', + 'Hartsville', + 'Thikri', + 'Xiantao', + 'Libmanan', + 'Shahritus', + 'Hayle', + 'Arakkal', + 'Hoa Thanh', + 'Chetumal', + 'Cormano', + 'Witham', + 'Cleveland Heights', + 'Ludus', + 'Tolanaro', + 'Saint-Doulchard', + 'Pradopolis', + 'Lourdes', + 'Siswar', + 'Alabaster', + 'Kasibu', + 'Saint-Leu-la-Foret', + 'Sakaddi', + 'Entre Ijuis', + 'Bilina', + 'Boyovut', + 'Ambohinihaonana', + 'Patrasaer', + 'North Augusta', + 'Ciudad Nezahualcoyotl', + 'Jenks', + 'Shiyuan', + 'San Carlos Yautepec', + 'Kabarore', + 'Durban', + 'Dajabon', + 'Leognan', + "Chech'on", + 'Guelma', + 'Rossmoor', + 'Cano Martin Pena', + 'Standerton', + 'Kaimuh', + 'Bellflower', + 'Sibutu', + 'Guateque', + 'Vejer de la Frontera', + 'Gornalwood', + 'Serhetabat', + 'Kondayampalaiyam', + 'Andal', + 'Kingersheim', + 'Ait Ben Daoudi', + 'Ixhuatlancillo', + 'Song Doc', + 'Schoten', + 'Gan Yavne', + 'Koprivnice', + 'Wadi', + 'Belvidere', + 'Haibach', + 'Chishtian', + 'Ras El Oued', + 'New Haven', + 'Alamuru', + 'Qaminis', + 'Saint-Eustache', + 'Port Wentworth', + 'Hashimoto', + 'Thara', + 'Qingzhen', + 'Sancti Spiritus', + 'Andiyappanur', + 'Pozi', + 'Songnam', + 'Alken', + 'Horsens', + 'Al Hashimiyah', + 'Vanadzor', + 'Fatehpur', + 'Necochea', + 'Sagbayan', + 'La Virgen', + 'Villagran', + 'Elyria', + 'Perunkolattur', + 'Talavadi', + 'Melrose', + 'Penaranda', + 'Aroeiras', + 'Caucasia', + 'Maryland Heights', + 'Ambatomifanongoa', + 'Fairfield', + 'Liuquancun', + 'Siqueira Campos', + 'Villa Carlos Paz', + 'Blitta', + 'Ciudad de Melilla', + 'Arajpur', + 'Pantano Grande', + 'Central Signal Village', + 'Tirumuruganpundi', + 'Vilakkudi', + 'Kavadarci', + 'La Plata', + 'Short Pump', + 'Shivganj', + 'Fangasso', + 'Douar Ain Maatouf', + 'Harlau', + 'Egypt Lake-Leto', + 'Greiz', + 'Al Khums', + 'Inabanga', + 'Ylivieska', + 'Halmstad', + 'West St. Paul', + 'Boudouaou', + 'Banaybanay', + 'Pinheiro Machado', + 'Bongao', + 'Parras de la Fuente', + 'Tabhka Khas', + 'Basingstoke', + 'Ovejas', + 'Bandwar', + 'Abu Ghurayb', + 'Bekescsaba', + 'Arutla', + 'Kadwa', + 'Kyaukse', + 'North Bethesda', + 'Jilotepec', + 'Kafr `Awan', + 'Takua Pa', + 'Aberystwyth', + 'Bataipora', + 'Timmarasanayakkanur', + 'Aradippou', + 'Khampat', + 'Caln', + 'Ambodimadiro', + 'Paso de Ovejas', + 'Manavalakurichi', + 'Chajari', + 'Jacarei', + 'Bals', + 'Bishkek', + 'Ikela', + 'Saladoblanco', + 'Sirsilla', + 'Dara', + 'Wuchang', + 'Haiyang', + 'Arida', + 'Kalaiyamputtur', + "'Tlat Bni Oukil", + 'Dixon', + 'Mogi das Cruzes', + 'Tizi Ouzou', + 'Hopkinton', + 'Buchanan', + 'Amami', + 'Juiz de Fora', + 'Kargipinar', + 'Saint-Orens-de-Gameville', + 'Charlton', + 'Dortyol', + 'Parepare', + 'Cadaado', + 'Boryspil', + 'Marina del Rey', + 'Uchen', + 'Betulia', + 'Kerema', + 'Chartoun', + 'Shajapur', + 'Brooks', + 'Nova Odesa', + 'Sogne', + 'Sarahandrano', + 'Nea Ionia', + 'Sumidouro', + 'Yayladagi', + 'Cesky Krumlov', + 'Tay Ninh', + 'Ottweiler', + 'Pedro Brand', + 'Neka', + 'Masse', + 'Darcheh', + 'Adelphi', + 'Melur', + 'Bergneustadt', + 'Salempur', + 'Avanigadda', + 'Guruvarajukuppam', + 'Chandwa', + 'Saclepea', + 'Kathanian', + 'Kari', + 'San Jacinto', + 'Southall', + 'Jinja', + 'Sungaiselam', + 'Sirmaur', + 'Friedland', + 'Alpine', + 'Kerben', + 'Lubaczow', + 'Ambodimangavolo', + 'Chuxiong', + 'Ciudad Mante', + 'Chorkuh', + 'Indalvai', + 'Fengruncun', + 'Azpeitia', + 'Sallisaw', + 'Karanganyar', + 'Tokmak', + 'Baripada', + 'St. Marys', + 'Platon Sanchez', + 'Bugembe', + 'Nihtaur', + 'Kharsod B', + 'Shahriar', + 'Jabbeke', + 'Mogtedo', + 'Mayorga', + 'Rastatt', + 'Prairie Village', + 'Hato Corozal', + 'Dubuque', + 'Rocha', + 'Pataskala', + 'Blue Ash', + 'Sarandi', + 'Nkayi', + 'Coldstream', + 'Guru Har Sahai', + 'Gardanne', + 'Waukesha', + 'Saran', + 'Alfred and Plantagenet', + 'Tiadiaye', + 'Huchuan', + 'Lujan de Cuyo', + 'Fundao', + 'Ben Tre', + 'Balingasag', + 'Safipur', + 'Mitoyo', + 'At Taji', + 'Doany', + 'Fuyuan', + 'Hayesville', + 'Schwerin', + 'Phelan', + 'Bethanie', + 'Weymouth', + 'Kibanseke Premiere', + 'Buey Arriba', + 'Ibotirama', + 'Yamaguchi', + 'Vadasseri', + 'Wantage', + 'Iaboutene', + 'Karlivka', + 'Manabo', + 'San Kamphaeng', + 'El Hermel', + 'Corrente', + 'Voisins-le-Bretonneux', + 'Vemuluru', + 'Jasauli Patti', + 'Paraguacu Paulista', + 'Umreth', + 'Rakovski', + 'Kottagudi Muttanad', + 'Sao Felix do Araguaia', + 'Huong Tra', + 'Yukon', + 'Miena', + 'Datang', + 'Petrich', + 'Tholen', + 'Mosopa', + 'Fara in Sabina', + 'Alesund', + 'Penwortham', + 'Poyo', + 'Ruma', + 'Scorze', + 'Cedral', + 'Forest Grove', + 'Szeged', + 'Firavahana', + 'Bagno a Ripoli', + 'Caravelas', + 'Horn-Bad Meinberg', + 'Porto Alegre do Norte', + 'Shtime', + 'Raba', + 'Chisec', + 'Lagunia Raghukanth', + 'Ensenada Berisso', + 'Do`stobod', + 'Amarpur', + 'Lunca Cetatuii', + 'Dagupan City', + 'Saumlaki', + 'Karkamb', + 'Sobrado de Paiva', + 'Kalappalangulam', + 'Joao Lisboa', + 'The Dalles', + 'Slateng Dua', + 'Lingwala', + 'Latiano', + 'Orangeville', + 'Borazjan', + 'Pizarro', + 'Pilich', + 'Veintiocho de Noviembre', + 'Huntington Park', + 'Jesus Menendez', + "'Ain Abessa", + 'Descalvado', + 'Krapina', + 'Harwood Heights', + 'Banco Filipino International Village', + 'Guadarrama', + 'Channagiri', + 'Mokri', + 'Sanjiang', + 'Kokri', + 'Tirkha', + 'Zhangjiakou', + 'Veresegyhaz', + 'Zhuanghe', + 'Balkonda', + 'Siemiatycze', + 'Ban Saeo', + 'Torrijos', + 'Faches-Thumesnil', + 'Campo Alegre de Lourdes', + 'Francofonte', + 'Alarobia', + 'Worthing', + 'Rochelle', + 'Ilvesheim', + 'Billings', + 'Batangas', + 'Vochaiko', + 'Zaragoza', + 'Heppenheim', + 'La Mana', + 'Son Tay', + 'Magilampupuram', + 'Logrono', + 'Chiavari', + 'Gujan-Mestras', + 'Katako-Kombe', + 'Chaparral', + 'Batie', + 'Foligno', + 'University', + 'Iruttarakonam', + 'Kotla', + 'East Hanover', + 'Jomboy Shahri', + 'Changanacheri', + 'Kensington', + 'Dobje', + 'Maisons-Laffitte', + 'Oroquieta', + 'Parambatt Kavu', + 'Dois Riachos', + 'Latina', + 'Babhanganwa', + 'Ambohimahasoa', + 'Pandino', + 'Chefchaouene', + 'Teixeiras', + 'Saveh', + 'Glens Falls', + 'Tonj', + 'Bhulwal', + 'Luozi', + 'Adoni', + 'Mahazony', + 'Setagaya', + 'Banning', + 'Minamishimabara', + 'Tuz Khurmatu', + 'Sundarapandiyam', + 'Kodaira', + 'Santa Cruz de la Palma', + 'Candiota', + 'Pittsford', + 'Yakeshi', + 'Lengerich', + "Da'an", + 'Pacaembu', + 'East Point', + 'Miacatlan', + 'Sendamangalam', + 'Sidi Khaled', + 'Landquart', + 'Reinbek', + 'Millau', + 'Itaosy', + 'Bellefontaine', + 'Aracoiaba', + 'Melipilla', + 'Monor', + 'Collinsville', + 'Lenggries', + 'Pio Duran', + 'Rhondda', + 'Abu Sir Bana', + 'Cholavaram', + 'Kamepalle', + 'Yeppoon', + 'Sangenjo', + 'Hradec Kralove', + 'Kibeho', + 'Ahmetli', + 'Yurihonjo', + 'Pontassieve', + 'Cahokia Heights', + 'Ubaidullahganj', + 'Brakel', + 'Berezan', + 'Noya', + 'Reggio Emilia', + 'Huesca', + 'Bopfingen', + 'Bhainsa', + 'Dhupgari', + 'Itaiba', + 'Camenca', + 'Chmistar', + 'Sao Luis do Quitunde', + 'Foix', + 'Kond Rud', + 'Laterza', + 'Schopfheim', + 'Kasuga', + 'Ouadhia', + 'Dala', + 'Sonagazi', + 'Songjiangcun', + 'Mamidalapadu', + 'Biarritz', + 'Theodore', + 'Ngudu', + 'Ntorosso', + 'Buxar', + 'Kaliyaganj', + 'Milanoa', + 'Spisska Nova Ves', + 'Fuli', + 'Kisi', + 'Jhargram', + 'Zaporizhzhia', + 'Gazipasa', + 'Suyo', + 'North Saanich', + 'San Ricardo', + 'Komatipoort', + 'Mallapuram', + 'Yitiaoshan', + 'Kargilik', + 'Kinoni', + 'Qazyqurt', + 'Cajazeiras', + 'Semnan', + 'Pandan', + 'Koceljeva', + 'Ambodimotso Atsimo', + 'Valenza', + 'Puerto de la Cruz', + 'Tepetlaoxtoc', + 'Adjala-Tosorontio', + 'Zillah', + 'Diamondhead', + 'Hallstadt', + 'Anseong', + 'Libacao', + 'Bharella', + 'Yilan', + 'Cadolzburg', + 'Papantla de Olarte', + 'Fengguangcun', + 'Jhelum', + 'Dalanping', + 'Tegucigalpa', + 'Batala', + 'El Kerma', + 'Yangzhou', + 'Passira', + 'Hagonoy', + 'Kambaduru', + 'Jambusar', + 'Teddington', + 'Murambi', + 'Corsham', + 'Arzew', + 'Nittedal', + 'Paingkyon', + 'Maravilha', + 'Edison', + 'Montlhery', + 'Mokameh', + 'Canegrate', + 'Tezoatlan de Segura y Luna', + 'Iraucuba', + 'Kattivakkam', + 'Lino Lakes', + 'Kalyani', + 'El Playon', + 'Kabbur', + 'San Miguel Xoxtla', + 'Zirndorf', + 'Guama Abajo', + 'Sevlievo', + 'Kawai', + 'Edingen-Neckarhausen', + 'Chimalapadu', + 'Huatan', + 'Cascavel', + 'Naravarikuppam', + 'Pasighat', + 'Chimthana', + 'Priolo Gargallo', + 'Diema', + 'Yingshouyingzi', + 'Hardoi', + 'Shpola', + 'Alipur Duar', + 'Parambu', + 'Igaratinga', + 'Xinxing', + 'Damboa', + 'Edgemere', + 'Cypress Lake', + 'Lemont', + 'Karedu', + 'Puerto Villamil', + 'Thornaby on Tees', + 'Basarabeasca', + 'Bolama', + 'Islip', + 'Sonkach', + 'Cinco Ranch', + 'Tibiao', + 'Kuala Belait', + 'Nahazari', + 'Culion', + 'Villa Alegre', + 'Santiago Amoltepec', + 'Edgewater', + 'Sinuni', + 'Posusje', + 'Castellbisbal', + 'Bhainsoda', + 'Jindrichuv Hradec', + 'Pattiyurgramam', + 'Manlin', + 'Kabayan', + 'Boulder Hill', + 'Pastpar', + 'Mablethorpe', + 'Kodumur', + 'Kegen', + 'Wugang', + 'Cerquilho Velho', + 'Bent Jbail', + 'Bornova', + 'Hoganas', + 'Queretaro', + 'Ar Rayyan', + 'Bondues', + 'Pierrelaye', + 'Baiyin', + 'La Sierpe', + 'Selendi', + 'Antony', + 'Mathukumnel', + 'Kingoonya', + 'Nueva Gerona', + 'Alitagtag', + 'Simojovel de Allende', + 'Kralendijk', + 'Kanal', + 'Umm al Qaywayn', + 'Dodola', + 'Risaralda', + 'Lugoff', + 'Steinheim am Albuch', + 'Sussen', + 'Mulavana', + 'Loanda', + 'Holiday', + 'Catriel', + 'Rotonda', + 'North Codorus', + 'Amanganj', + 'Sahakevo', + 'Cadca', + "Ch'onan", + 'Ponte San Giovanni', + 'Villa Altagracia', + 'Maywood', + 'Saraburi', + 'Tyldesley', + 'Satuluru', + 'Neufahrn bei Freising', + 'Zigon', + 'Calne', + 'Serere', + 'Cumberland Hill', + 'Ivanjica', + 'Konz', + 'Perth Amboy', + 'Alerce', + 'Berikai', + 'Foum Zguid', + 'Had Dra', + 'Cinere', + 'Dalkeith', + 'Fresnillo', + 'Yaizu', + 'Ciro Redondo', + 'Despotovac', + 'Kakkat', + 'Bassum', + 'Najran', + 'Segrate', + 'Mexico', + 'Kinrooi', + 'Moknine', + 'Andreba', + 'Bresso', + 'Venadillo', + 'Reoti', + 'Baharly', + 'Los Palmitos', + 'Jordania', + 'Great Missenden', + 'Puerto Cortes', + 'Los Alcazares', + 'Agbangnizoun', + 'Chausa', + 'Capistrano', + 'West Richland', + 'Pingyuanjie', + 'Coomera', + 'Bteghrine', + 'Occhiobello', + 'Olagadam', + 'Deerfield', + 'Aroroy', + 'Macedon', + 'Spout Springs', + 'East Patchogue', + 'Ebina', + 'Soria', + 'Ouled Mimoun', + 'Navapur', + 'Balumath', + 'Hennenman', + 'Barkot', + 'Denduluru', + 'Raca', + 'Kumbhari', + 'Bayaram', + 'Liuzhou', + 'Belmonte Mezzagno', + 'Harvard', + 'Wandiwash', + 'Cukurcayir', + 'Dinkelsbuhl', + 'Chero', + 'Ngolobougou', + 'Quilevo', + 'Saint-Claude', + 'Sonari', + 'Zaventem', + 'Venecia', + 'Sharjah', + 'Shoreham-by-Sea', + 'Governador Archer', + 'Cerese', + 'Lazarivo', + 'Angara-Debou', + 'Kaele', + 'Mahe', + 'Leighton Buzzard', + 'Erfelek', + 'Willimantic', + 'Sao Joaquim da Barra', + 'Littau', + 'Kayanna', + 'Temperley', + 'Jirwa', + 'Moses Lake', + 'Merriam', + 'Cerreto Guidi', + 'San Miniato', + 'Tumby Bay', + 'Wielun', + 'Tirukkattuppalli', + 'North Las Vegas', + 'Mahemdavad', + 'Nagaiyampatti', + 'Atyrau', + 'Chorley', + 'Ravansar', + 'Bettioua', + 'Green River', + 'Chicacao', + 'Karukkalvadi', + 'Dar el Beida', + 'Langarivo', + 'Tettu', + 'Santa Barbara', + 'Ban Pong', + 'San Nicolas de los Garza', + 'Tres Coroas', + 'Muttattuteruvu', + 'Coracao de Maria', + 'Nieuwpoort', + 'Laatzen', + 'Kusumbe', + 'Xiangyuncun', + 'Coral Springs', + 'Escaldes-Engordany', + 'Durpalli', + 'Kundian', + 'Acatlan de Perez Figueroa', + 'Mendes', + 'Sansale', + 'Batan', + 'Paslek', + 'San Severino Marche', + 'Madinat `Isa', + 'Youwarou', + 'Paghman', + 'Aruvikkara', + 'Osthofen', + 'Tianzhong', + 'Marudur', + 'Crensovci', + 'Ceerigaabo', + 'Brusciano', + 'Sabana de La Mar', + 'Aileu', + 'Kutaisi', + 'Maebashi', + 'Wanggezhuang', + 'Banska Stiavnica', + 'Tola', + 'Hovelhof', + 'Johannesburg', + "Sant Sadurni d'Anoia", + 'Binalonan', + 'Dingcheng', + 'Henderson', + 'Bloxwich', + 'Gberouboue', + 'Dharmaram', + 'Abomsa', + 'Imtarfa', + 'Rancho Santa Margarita', + 'Meuselwitz', + 'Ad Dali`', + 'Nilandhoo', + 'Bayramaly', + 'Lufkin', + 'Talcher', + 'Menaka', + 'Rupauli', + 'Ipanema', + 'Tindwara', + 'Piendamo', + 'Gennep', + 'Kachnar', + 'Vitez', + 'Oporapa', + 'Litvinov', + 'Lich', + 'Hericourt', + 'Sanghar', + 'Sao Lourenco', + 'Bir Ali Ben Khalifa', + 'Hlucin', + 'Erith', + 'Ebejico', + 'Qulsary', + 'Pichor', + 'Lubon', + 'Lufilufi', + 'Mayamankurichchi', + 'Kulittalai', + 'Marshall', + 'Weil der Stadt', + 'Mahabako', + 'Lemoore', + "Al M'aziz", + 'Behat', + 'Mauriti', + 'Memphis', + 'Antas', + 'Novo Mesto', + 'Zinkiv', + 'Mi`rabah', + 'Ksar Sghir', + 'Mandapam', + 'Cupang', + 'Mina', + 'Bytom', + 'Kikuyo', + 'Yankton', + 'Spilimbergo', + 'Gottmadingen', + 'Vanersborg', + 'Harduli', + 'Wierden', + 'Paloncha', + 'Chikmagalur', + 'Qubadli', + 'Longwood', + 'Nay Pyi Taw', + 'Wald-Michelbach', + 'Kafr al Battikh', + 'Orai', + 'Juchique de Ferrer', + 'Swiebodzice', + 'Behbahan', + 'Sehore', + 'Te Awamutu', + 'Shacheng', + 'Kano', + 'Ciftlikkoy', + 'Op', + 'Yendi', + 'Sun Valley', + 'Guabari', + 'Kalyanpur Bamaiya', + 'Rauco', + 'Pedro de Toledo', + 'Kandhkot', + 'Degollado', + 'Kasai', + 'Krasnik', + 'Abu Khashab', + 'Middlesbrough', + 'Cifuentes', + 'Ganxi', + 'Mae Sot', + 'Raubling', + 'Myoko', + 'McLean', + 'Trail', + 'Reggada', + 'Migdal Ha`Emeq', + 'Banni', + 'La Grange Park', + 'Ojiya', + 'Youngstown', + 'East Longmeadow', + 'Perunkalattu', + 'Guimaraes', + 'Sete Lagoas', + 'Bhandaria', + 'Stretford', + 'Corrientes', + 'Hurst', + 'Satuek', + 'Suhum', + 'Majiagoucha', + 'Caltagirone', + 'Asthal Bohar', + 'Chipiona', + 'Aravan', + 'Kiboga', + 'Ephrata', + 'Lystrup', + 'Nowa Deba', + 'Vangaindrano', + 'New Westminster', + 'Cestas', + 'Bithan', + 'Saraiya', + 'Bad Soden am Taunus', + 'Cinnaminson', + 'Mansidao', + 'Heroldsberg', + 'Opovo', + 'Shinhidaka', + 'Mildura', + 'Sahatsiho-Ambohimanjaka', + 'Dok Kham Tai', + 'Szombathely', + 'Nova Olinda do Norte', + 'Brantford', + 'Cobram', + 'Sulzbach-Rosenberg', + 'Key Biscayne', + 'South Ubian', + 'Varde', + 'Scandiano', + 'Santa Cruz do Sul', + 'Cedar Park', + 'Erstein', + 'Matozinhos', + 'Buxin', + 'Chortiatis', + 'Camrose', + 'Ettimadai', + 'Montagnana', + 'Ivybridge', + 'Middlewich', + 'Tivat', + 'Babai Kalan', + 'Al Qusayr', + 'Vatolatsaka', + 'Cruz', + 'Linstead', + 'Lakkundi', + 'Serrolandia', + 'Shuibian', + 'Llanelli', + 'Six-Fours-les-Plages', + 'Entre Rios de Minas', + 'Saude', + "Karbala'", + 'Godinlabe', + 'Ocatlan', + 'Paranatama', + 'Begampur', + 'Khulm', + 'Alegria', + 'Partanna', + 'Bothaville', + 'Vaal Reefs', + "Alvorada D'Oeste", + 'Cotes de Fer', + 'Sibirila', + 'Heshancun', + 'Adustina', + 'Uberlingen', + 'Shawano', + 'Santa Fe', + 'Bolivia', + 'Maniche', + 'Chikura', + 'Amstelveen', + 'Mwinilunga', + 'Tetiiv', + 'Losal', + 'Ortakent', + 'Mirassol', + 'Yerba Buena', + 'Kasongo-Lunda', + 'Zongdi', + 'Pirmed', + 'Ashton', + 'La Union', + 'Anaco', + 'Front Royal', + 'Faro', + 'Votorantim', + 'Bengbu', + 'Aldaya', + 'Khem Karan', + 'Dushanbe', + 'Campulung Moldovenesc', + 'Quixeramobim', + 'Chabet el Ameur', + 'Pingtung', + 'Fukuoka', + 'Itapiuna', + 'Khiram', + 'Lami', + 'Morubagalu', + 'South Kingstown', + 'Chatham', + 'Cefalu', + 'Pattanakkad', + 'Silvan', + 'Souk Et-Tleta des Oulad Hamdane', + "Mai'Adua", + 'Ban Bang Khu Wat', + 'Ardal', + 'Collegedale', + 'Irbid', + 'Lanham', + 'San Pablo Jocopilas', + 'Santuario', + 'Betma', + 'Inezgane', + 'Pout', + 'Batcha', + 'Irlam', + 'Aldoar', + 'Atakpame', + 'Ban Bo Luang', + 'Finchley', + 'Tamalous', + 'Bagnols-sur-Ceze', + 'Matsushima', + 'Yecuatla', + 'Wuxue', + 'Ocean Springs', + 'Petilia Policastro', + 'Benenitra', + 'Elgin', + 'Guntupalle', + 'Itirucu', + 'Chengde', + 'Castelvetro di Modena', + 'Ramkali', + 'Marki', + 'Loudima Poste', + 'Usta Muhammad', + 'Banfield', + 'Patna', + 'Lendinara', + 'Toliara', + 'Tioribougou', + 'Mosbrough', + 'Yenangyaung', + 'Analanampotsy', + 'Zgornje Gorje', + 'Al Qubbah', + 'Lakeshore', + 'Kocaali', + 'Sombor', + 'Punta Arenas', + 'Elmira', + 'Serekunda', + 'Ban Khlong', + 'Ried im Innkreis', + 'Olivehurst', + 'Naruto', + 'Plainville', + 'Nakhal', + 'Hampton Bays', + 'Yaita', + 'Ambatosia', + 'Guindulman', + 'Wommelgem', + 'Jasper', + 'Sao Joao da Madeira', + 'Curiuva', + 'Destin', + 'Zigong', + 'Kushijima', + 'Fameck', + 'Telmar', + 'Noboribetsu', + 'Guanare', + 'Adis `Alem', + 'Angatuba', + 'Santa Cruz Zenzontepec', + 'Dongguazhen', + 'Castle Rock', + 'Millbrook', + 'Erlenbach am Main', + 'Kamloops', + 'South Jordan', + 'Colombia', + 'Baguio City', + 'Turkaguda', + 'Momchilgrad', + 'Aiyappan Kovil', + 'Strada', + 'Rostock', + 'Lichtenfels', + 'Al Majaridah', + 'Bagh-e Bahadoran', + 'Irvington', + 'Maheshwar', + 'Jhagarua', + 'Birchington', + 'Iba', + 'Wang Nam Yen', + 'Heilbad Heiligenstadt', + 'Meinerzhagen', + 'Shandiz', + 'Fallston', + 'Landivisiau', + 'San Juan de Aznalfarache', + 'Ettaiyapuram', + 'Kota Kinabalu', + 'Roseira', + 'Fusagasuga', + 'Bovisio Masciago', + 'Vilpatti', + 'Old Forge', + 'Kadiyam', + 'Goraya', + 'Almelo', + 'Rescaldina', + 'Bad Munstereifel', + 'Atebubu', + 'Panapakkam', + 'Morag', + 'Ferrier', + 'Velaux', + 'Sellersburg', + 'Kambhampadu', + 'Ubbergen', + 'Quilandi', + 'Digos', + 'Slavutych', + 'Terre Haute', + 'Schlitz', + '`Unayzah', + 'Zaoyang', + 'Beauvais', + 'Allacapan', + 'Shangjing', + 'Nayagarh', + 'Kanasin', + 'Velasco Ibarra', + 'Agan', + 'Nouna', + 'Puthiyangadi', + 'Salar', + 'Povoa de Lanhoso', + 'Brotas de Macaubas', + 'Donggang', + 'Inuyama', + 'Balakan', + 'Camara de Lobos', + 'Tanauan', + 'Ribas do Rio Pardo', + 'Fagundes', + 'San Rafael Petzal', + 'Iraci', + 'Kadena', + 'Ramos Mejia', + 'Vaasa', + 'Goribidnur', + 'Changping', + 'Oregon', + 'Schiller Park', + 'Mereeg', + 'Toro', + 'Lynnwood', + 'Iiyama', + 'Punta Alta', + 'Cervello', + 'Sariosiyo', + 'Kunitachi', + 'Thu Dau Mot', + 'Viljandi', + 'Kurichedu', + 'Saugerties', + 'Noveleta', + 'Paripiranga', + 'Odesa', + 'Ibirama', + 'Coamo', + 'Smolyan', + 'Montilla', + 'Khan Bebin', + 'Ambovonomby', + 'Serinyol', + 'Ayapel', + 'San Antonio de los Cobres', + 'Kadiri', + 'Quatre Bornes', + 'Bareh', + 'Buritis', + 'Los Cerrillos', + 'Potenza', + 'Sughrain', + 'Nehbandan', + 'Ampthill', + 'Palmeira das Missoes', + 'Dombovar', + 'Angadanan', + 'Yuanping', + 'Martapura', + 'Curumani', + 'Benhao', + 'Ban Bang Toei', + 'Stadskanaal', + 'Secanj', + 'Renaico', + 'Buckhall', + 'Tirutturaippundi', + 'Komotini', + 'Suroth', + 'Guene', + 'Canudos', + 'Bhagsar', + 'Lubben (Spreewald)', + 'La Ciotat', + 'Stirling', + 'Praia da Vitoria', + 'Pakra', + 'Harpanahalli', + 'Tururu', + 'Andranomanelatra', + 'Sirnia', + 'Steyr', + 'Lake Stickney', + 'Villa Ojo de Agua', + 'Tuyserkan', + 'Saint-Jean-de-la-Ruelle', + 'Ranapur', + 'Kheiredine', + 'Villa El Carmen', + 'Sherborne', + 'Kerai', + 'Barughutu', + 'Tienen', + 'Cherkasy', + 'Turmero', + 'Ahigbe Koffikro', + 'Rio Azul', + 'Baro', + 'Koshanam', + 'Malinalco', + 'Afula', + 'Perpignan', + 'Timayy al Imdid', + 'Kotgir', + 'Forbach', + 'Malka', + 'Palm Beach', + 'Falmouth', + 'Beihai', + 'Kalbarri', + 'Ivato', + 'Picos', + 'Marcon', + 'Pieksamaki', + 'Carlet', + 'Salobe', + 'Yacimiento Rio Turbio', + 'Omiyacho', + 'Kodikkulam', + 'Shorapur', + 'Dniprorudne', + 'Neutraubling', + 'Kizhakkemanad', + 'Banagi', + 'Mount Gambier', + 'Malkajgiri', + 'Sokhumi', + 'Douera', + 'Chekfa', + 'Mairi', + 'Ortaca', + 'Loria', + 'San al Hajar al Qibliyah', + 'Galeana', + 'Aguadulce', + 'Carepa', + 'Jogipet', + 'Tambacounda', + 'Hingham', + 'Huntington Beach', + 'Yeniciftlik', + 'Talupula', + 'Naruar', + 'Tudela', + 'Ayyampalaiyam', + 'Kolwara', + 'Cisauk', + 'Jettihalli', + 'Surt', + 'Allen Park', + 'Casier', + 'Zhaxi', + 'Itu', + 'Quilombo', + 'Gharghur', + 'Keerbergen', + 'Gbarnga', + 'Degana', + 'Gretna', + 'Cerro Maggiore', + 'Santa Cruz Michapa', + 'Diamou', + 'Tiruppanandal', + 'Bayji', + 'Njeru', + 'Sour', + 'Martinengo', + 'Rocklin', + 'Saint-Vallier', + 'Yabrud', + 'Alfortville', + 'Kopoky', + 'Phuoc Long', + 'Giarre', + 'Kindu', + 'Ain Cheggag', + 'Babila', + 'Busumbala', + 'Basmenj', + 'Tangjia', + 'Karimunjawa', + 'Bhagatpur', + 'Murtazabad', + 'Konganapuram', + 'Kapelle', + 'Sidi Lamine', + 'Nezahualcoyotl', + 'Prague', + 'Chandreru', + 'Nelliyalam', + 'Kouhu', + 'Bollullos par del Condado', + 'Chiryu', + 'Ain Defla', + 'Trzin', + 'Llandybie', + 'Ushtobe', + 'Mecitozu', + 'Lys-les-Lannoy', + 'Campo Ere', + 'Antambohobe', + 'Kanifing', + 'Mulukuku', + 'Micoud', + 'Siran', + 'Markt Schwaben', + 'Porangatu', + 'Manimala', + 'Liege', + 'Shiremoor', + 'Itaperucu', + 'Puerto Pilon', + 'Bourg-la-Reine', + 'Kadalur', + 'Kodikuthi', + 'Thame', + 'Soahany', + 'Kyritz', + 'Longtoushan Jiezi', + 'Meiktila', + 'Centenario do Sul', + 'Cunit', + 'Engerwitzdorf', + 'Ebreichsdorf', + 'Newbury', + 'Almoloya de Alquisiras', + 'Krishnarayapuram', + 'Kadamakudi', + 'Yongzhou', + 'Sartell', + 'Madeley', + 'Koszutka', + 'Iacanga', + 'Ekhari', + 'Kratie', + 'Nyeri', + 'Qazigund', + 'Langdu', + 'Pong Nam Ron', + 'Timon', + 'Gyeongsan', + 'Minerbio', + 'Singia', + 'East Niles', + 'Mmopone', + 'Masaurhi Buzurg', + 'Liantangcun', + 'Banino', + 'Altopascio', + 'Tillaberi', + 'Riachao do Jacuipe', + 'Nalikkalpatti', + 'Romanshorn', + 'Xinjing', + 'Cha-am', + 'Cumpana', + 'Shenandoah', + 'Pathanamthitta', + 'Tata', + 'Lake Elsinore', + 'Vilavur', + 'Ogdensburg', + 'Santa Maria de Cayon', + 'Rakvere', + 'Baretha', + 'Ankaraobato', + 'Miguel Pereira', + 'West Islip', + 'Tekkekara Kizhakku', + 'Chita', + 'Futrono', + 'Cordon', + 'Ampefy', + 'Santo Domingo de los Colorados', + 'Mtskheta', + 'Dassa-Zoume', + 'San Cristobal', + 'Corona', + 'Telemaco Borba', + 'Pedregulho', + 'Walajapet', + 'Kurnool', + 'Bois-Colombes', + 'Bitritto', + 'Baia Formosa', + 'Mopti', + 'Sassari', + 'General Pinedo', + 'Capim Grosso', + 'Sanok', + 'Nawashahr', + 'Radom', + 'Esbjerg', + 'Scheveningen', + 'Woodland Park', + 'Ljubno', + 'Tirkarur', + 'Mississauga', + 'Oudewater', + 'Plochingen', + 'Villa Donato Guerra', + 'Kuangfu', + 'Mabuhay', + 'Deh-e Shu', + 'Anjur', + 'Sung Noen', + 'Fife', + 'Mhangura', + 'Chalchuapa', + 'Herisau', + 'Cesis', + 'Flensburg', + 'Prado', + 'Fujisaki', + 'Winterveld', + 'Mlimba', + 'Chintakunta', + 'Marsaxlokk', + 'Munakata', + 'Corciano', + 'Sibilia', + 'Pullalacheruvu', + 'Trescore Balneario', + 'Querfurt', + 'Palestine', + 'Salta', + 'Scafati', + 'Atbara', + 'Unhel', + 'Rosenheim', + 'Khambhat', + 'Serafina Corea', + 'Guayos', + 'Ladson', + 'Martinho Campos', + 'Rondon do Para', + 'Gandajika', + 'Konigsbrunn', + 'Dabrowa Tarnowska', + 'Dudhpura', + 'Hurdiyo', + 'Vi Thanh', + 'Blaubeuren', + 'Berubari', + 'Wujiaqu', + 'Warrensville Heights', + 'Duc Pho', + 'Misungwi', + 'Jeju', + 'Diamond Springs', + 'Rabot', + 'Chateau-Gontier', + 'Culver City', + 'Chanhassen', + 'Campamento', + 'Venturosa', + 'Bom Principio', + 'Huquan', + 'Odlabari', + 'Burbage', + 'Siladon', + 'Nutley', + 'Kinzau-Vuete', + 'Ickenham', + 'Belavabary', + 'Kariyapatti', + 'Dzierzoniow', + 'North Cowichan', + 'Mine', + 'Srvanampatti', + 'Uba', + 'Monte Alegre de Sergipe', + 'Espanola', + 'Lenart v Slovenskih Goricah', + 'Hirske', + 'Nallikodur', + 'Kisara', + 'Salamanca', + 'North New Hyde Park', + 'Prudente de Morais', + 'Peragamanna', + 'Kaippakancheri', + 'Venray', + 'Kitakata', + 'Napindan', + 'Beinasco', + 'Anyksciai', + 'Piera', + 'Morab', + 'Charef', + 'Fraserburgh', + 'Maghalleen', + 'Palatine', + 'Batuan', + 'Maldegem', + 'Kalabahi', + 'Hollola', + 'Quimbele', + 'Ippy', + 'Oirschot', + 'Hrib-Loski Potok', + 'Ras el Metn', + 'Dalaba', + 'Thun', + 'Caguas', + 'Jadayampalaiyam', + 'Biaora', + 'Brno', + 'Fino Mornasco', + 'Lohagaon', + 'Silale', + 'Kouka', + 'Tulchyn', + 'Caimito', + 'Teijlingen', + 'Szczecinek', + 'Oulad Rahmoun', + 'Khat Azakane', + 'Koupela', + 'Giddalur', + 'Francisco Sa', + 'Adda-Doueni', + 'Santa Catarina Mita', + 'Nova Olimpia', + 'Sabalito', + 'Hire Vadvatti', + 'Duvva', + 'Iloilo', + 'Catalca', + 'Pribram', + 'McCalla', + 'Oleggio', + 'Lajes', + 'Entroncamento', + 'Vidapanakallu', + 'Moju', + 'East Brunswick', + 'Samalkot', + 'Birnin Konni', + 'Ipaba', + 'Sebba', + 'Joao Camara', + 'Sho`rchi', + 'De Witt', + 'Ebolowa', + 'Nandipeta', + 'Bokaro', + 'Port Glasgow', + 'Vellakkinar', + 'Goldenstedt', + 'Al Jawf', + 'Adliswil', + 'Castelnaudary', + 'Bula', + 'Swan Hill', + 'Brikcha', + 'Valkurti', + 'Lake Hopatcong', + 'Ingeniero Jacobacci', + 'Kemise', + 'Obidos', + 'Central Saanich', + 'Shioya', + 'Ulus', + 'Chicago Heights', + 'Tracy', + 'Al Matariyah', + 'Chelmza', + 'Ban Wat Chan', + 'Chelles', + 'Basni', + 'Moriya', + 'Arzignano', + 'Bharanikavu Tekku', + 'Paikpar', + 'Lake Elmo', + 'Pottanikad', + 'Gorakhpur', + 'Rose Hill', + 'Sallimedu', + 'Kearns', + 'Berehove', + 'Kidderminster', + 'Marieville', + 'Lillers', + 'Voreppe', + 'Rovenky', + 'Spittal an der Drau', + 'Vohipaho', + 'Selnica ob Dravi', + 'Musile di Piave', + 'Sado', + 'El Ancer', + 'Lamego', + 'Kaeng Khro', + 'Laguna Hills', + 'Bag', + 'Krotoszyn', + 'Makhu', + 'Kotha Guru', + 'Bandar-e Bushehr', + 'Borsbeek', + 'Manbij', + 'Mabaruma', + 'Godohou', + 'Landau an der Isar', + 'Zamboanga City', + 'Bendarhalli', + 'Vanavasi', + 'Karlovac', + 'Saraland', + 'Vaucresson', + 'Clusone', + 'Kumi', + 'Turayf', + 'Konakondla', + 'Adet', + 'Yuanhucun', + 'Iramaia', + 'Buttar Khurd', + 'New Richmond', + 'McDonough', + 'Monte Cristo', + 'Save', + 'Khutaha', + 'Erzin', + 'Lovrenc na Pohorju', + 'Tangail', + 'Tadmur', + 'Midyat', + 'Guanajay', + 'Colombes', + 'Garbsen', + 'Leribe', + 'Gamail', + 'Abu Hulayfah', + 'Azcoitia', + 'Abelardo Luz', + 'Kamavarapukota', + 'Bolongongo', + 'Paraiso', + 'Dum Duma', + 'Auckland', + 'Peterlee', + 'Turiacu', + 'Xaignabouli', + 'Huangyan', + 'Camarillo', + 'Volksrust', + 'San Jose de Guaribe', + 'Roca Sales', + 'Glogow Malopolski', + 'Tototlan', + 'Murrells Inlet', + 'Oxford', + 'Amodei', + 'Harbel', + 'Hue', + 'Kajhi Hridenagar', + 'Silvani', + 'Voorschoten', + 'Villeneuve-sur-Lot', + 'Kavaklidere', + 'Chibemba', + 'Avellino', + 'Brejinho', + 'Oulad Cherif', + 'Abra de Ilog', + 'Kurtkoti', + 'Tovala', + 'Gqeberha', + 'Almoloya', + 'Sao Joao do Paraiso', + 'Pella', + 'Zabre', + 'Higashimatsushima', + 'Fairland', + 'Arambakkam', + 'Skvyra', + 'Lamacaes', + 'Lumbreras', + 'Campbelltown', + 'Anoviara', + 'Jurawanpur Karari', + 'Imatra', + 'Medford', + 'Ban Phan Don', + 'Marietta', + 'Pennagaram', + 'Rotselaar', + 'Tezze sul Brenta', + 'Ponnada', + 'Golmud', + 'Ehningen', + 'Amambai', + 'Princeton', + 'Tepeji del Rio de Ocampo', + 'Mosonmagyarovar', + 'Tianguistengo', + 'Mongagua', + 'Elkhorn', + 'Langenthal', + 'Mehrabpur', + 'Obra', + 'Kabo', + 'Ban Mueang Nga', + 'Nga Bay', + 'Kannankurichchi', + 'Bayou Blue', + 'Et Tira', + 'Solita', + 'Bauchi', + 'Uribia', + 'Painkulam', + 'Calkini', + 'Sanchor', + 'Zoersel', + 'Tomiya', + 'Huvin Hippargi', + 'Teodoro Sampaio', + 'Lambton Shores', + 'Tissemsilt', + 'Kolding', + 'Quillacollo', + 'Tenosique', + 'Birnagar', + 'Laeken', + 'Talant', + 'Richterswil', + 'Kundgol', + 'Vitry-sur-Seine', + 'Conwy', + 'Oqtosh Shahri', + 'Yellapur', + 'Kumbakonam', + 'Jizhou', + 'Visoko', + 'Kassala', + 'Bloemendaal', + 'Perl', + 'Kaniv', + 'Ensenada', + 'Armavir', + 'Hirnyk', + 'Turgutalp', + 'Gaoual', + 'Mazzarino', + 'Batley', + 'Trichinopoly', + 'Ban Kang', + 'Manado', + 'Gerstetten', + 'Nechmeya', + 'Khan Yunis', + 'Anatuya', + 'Sosale', + 'Nindiri', + 'Uzwil', + 'Kesli', + 'Srbobran', + 'Jacmel', + 'Malibu', + 'Mulongo', + 'Balehonnur', + 'Froyland', + 'Vernal', + 'Kollegal', + 'San Juan', + 'Wayland', + 'Sirdala', + 'Redwood City', + 'Patalia', + 'Lycksele', + 'Alcantaras', + 'Majdal Shams', + 'Villa de San Diego de Ubate', + "Sa-ch'on", + 'Jalakandapuram', + 'Husum', + 'Coral Terrace', + 'Chropaczow', + 'Khalari', + 'Talence', + 'Arroyo Naranjo', + 'Gundrajukuppam', + 'Hinis', + 'Luoyang', + 'Argenteuil', + 'Dnipro', + 'Zajecar', + 'Usgao', + 'Serra Branca', + 'Hunduan', + 'Anosipatrana', + 'Constantina', + 'Sirur', + 'Bandar Seri Begawan', + 'Zavrc', + 'Rampur Parhat', + 'Saito', + 'Jurupa Valley', + 'Pedras de Fogo', + 'Kalymnos', + 'Jerissa', + 'Beni Khiar', + 'Ginebra', + 'Recco', + 'Kanajanahalli', + 'Miani', + 'Rionegro', + 'Honavar', + 'Bolu', + 'Shepherdsville', + 'Babura', + 'Vilnohirsk', + 'Saint-Loubes', + 'Machados', + 'Roselle', + 'Valle de Bravo', + 'Stoke Gifford', + 'Settiyarpatti', + 'Citrus Heights', + 'Amityville', + 'Muntinlupa City', + 'Poco Branco', + 'Ciudad Cortes', + 'Muncie', + 'Ballenstedt', + 'Kipushi', + 'Simri', + 'Satrovka', + 'Parasi', + 'Dumra', + 'Tha Luang', + 'Galatina', + 'Chiampo', + 'Robinson', + 'Chelsfield', + 'Hersbruck', + 'Ambodivoanio', + 'Carache', + 'Hagfors', + 'Hazlet', + 'Luchong', + 'Maili', + 'Kiangara', + 'Dengtacun', + 'Tuodian', + 'Prudhoe', + 'Codogno', + 'Philippeville', + 'Agsu', + 'Onomichi', + 'Kajiado', + 'Louang Namtha', + 'Sobral', + 'Hermosa', + 'Ouargla', + 'Welwyn Garden City', + 'Niel', + 'San Ramon', + 'Eutin', + 'Patarra', + 'Plainview', + 'Iioka', + 'Shamshernagar', + 'Ravanusa', + 'Madha', + 'Mmadinare', + 'Rawalpindi', + 'Calcinaia', + 'Chak Pahar', + 'Sunbury', + 'Cruzeiro do Oeste', + 'Pemberton', + 'Putian', + 'Sapucaia', + 'Worpswede', + 'Anaheim', + 'Tupancireta', + 'Lyaskovets', + 'Setouchi', + 'Tanay', + 'Takerbouzt', + 'Tarabuco', + 'Volpago del Montello', + 'Al `Arish', + 'Foley', + 'Avion', + 'Dina', + 'Kazerun', + 'Tuljapur', + 'Tocuyito', + 'Arakawa', + 'Sao Jose do Rio Preto', + 'Labuan', + 'Kadama', + 'Kollo', + 'Nieuwkoop', + 'Butalangu', + 'Haeryong', + 'Minbu', + 'Nobeoka', + 'Heunghae', + 'Chaidari', + 'Hoa Thuong', + 'Oelde', + 'Finote Selam', + 'Valayam', + "O'Fallon", + 'Araquari', + 'Nazaria', + 'Hofgeismar', + 'Masaya', + 'Saarlouis', + 'Schluchtern', + 'Mankara', + 'Mersa', + 'Sioux Falls', + 'Altinopolis', + 'South Plainfield', + 'Eccleston', + 'Louangphabang', + 'Kudachi', + 'Outjo', + 'Arluno', + 'Almus', + 'Zehak', + 'Elsenfeld', + 'Garag', + 'Agano', + 'Ferndale', + 'Yui', + 'Kasba Maker', + 'Krakow', + 'Aghbalou Aqourar', + 'Zakynthos', + 'Zhuchangba', + 'Tan Van', + 'Xiezhou', + 'Cinisi', + 'Drogheda', + 'Balurghat', + 'San Sebastian de los Reyes', + 'Kavaratti', + 'Egirdir', + 'Tolentino', + 'Iwanai', + 'Bhongir', + 'Nuzvid', + 'Amaha', + 'Jemaat Oulad Mhamed', + 'Karlovy Vary', + 'Kengri', + 'Khawaspur', + 'Palm Bay', + 'Murungattoluvu', + 'Autlan de Navarro', + 'Bursa', + 'Bruhl', + 'Montemor-o-Velho', + 'Berbera', + 'Mattoon', + 'Mboro', + 'Texcaltitlan', + 'Ellicott City', + 'Banbridge', + 'Bandipur', + 'Talakkad', + 'Ostroda', + 'Imphal', + 'Porto Belo', + 'Chattanooga', + 'Cogan', + 'Oued el Kheir', + 'Girard', + 'Tongshan', + 'Bhogapuram', + 'Cabanglasan', + 'Caleta Olivia', + 'Lozovo', + 'Hythe', + 'Izra', + 'Toba', + 'Sipocot', + 'Borgosatollo', + 'Messina', + 'Manadhoo', + 'Jumri Tilaiya', + 'Ollerton', + 'Riverhead', + 'Dinguiraye', + 'Salehpur', + 'Horizon City', + 'Tettuppatti', + 'Manorville', + 'Gumaca', + 'Barpathar', + 'Ankasakasabe', + 'Rio Caribe', + 'Mutare', + 'Kiramangalam', + 'Voorst', + 'El Ancor', + 'Marsta', + 'Wombourn', + 'Bagong Silangan', + 'Mogiyon', + 'Maracaibo', + 'Amay', + 'Ostrow Wielkopolski', + 'Herent', + 'Yecla', + 'Barrington', + 'Cananeia', + 'Crowborough', + 'Kandori', + 'Toconao', + 'Cazombo', + 'Basel', + 'Halavagalu', + 'Baile an Bhiataigh', + 'Baraboo', + 'Kambla', + 'Hung Yen', + 'Sun City Center', + 'Panniperumthalai', + 'Gwacheon', + 'Hikawa', + 'Zamora', + 'Las Lajas', + 'Dan Gorayo', + 'Racale', + 'La Paloma', + 'Kon Tum', + 'Oloron-Sainte-Marie', + 'Piacabucu', + 'Bourke', + 'Piraeus', + 'Ciudad Benito Juarez', + 'Peje', + 'Sagar', + 'Sivagiri', + 'Biyahmu', + 'Vianopolis', + 'Curitiba', + 'Larkspur', + 'Harenkarspel', + 'Reghin', + 'Kapchorwa', + 'Hohenau', + 'Villa Alsina', + 'Tagami', + 'We', + 'Lac des Oiseaux', + 'Pariharpur', + 'Volodymyrets', + 'Areraj', + 'Chupinguaia', + 'Anantpur', + 'Gif-sur-Yvette', + 'Bronkhorstspruit', + 'Krimpen aan den IJssel', + 'Rock Island', + 'Ban Huai So Nuea', + 'Barcellona-Pozzo di Gotto', + 'Tumbippadi', + 'Pontoise', + 'Bandar Emam', + 'Reconquista', + 'Braniewo', + 'Vettaikkaraniruppu', + 'Ancuabe', + 'Hitachi', + 'Malolos', + 'Ngaparou', + 'Bayamon', + 'Abyek', + 'Tlahuiltepa', + 'Lugano', + 'Crispiano', + 'Padre Hurtado', + 'Wlodawa', + 'Placheri', + 'General Martin Miguel de Guemes', + 'Yangyuhe', + 'La Chapelle-Saint-Mesmin', + 'Darmanesti', + '`Ayn al `Arab', + 'Yorkshire', + 'Calcinato', + 'Sadiqpur Maraul', + 'Tadley', + 'Nkokonjeru', + 'Fatime', + 'Selby', + 'Kafr Ruma', + 'South Brunswick', + 'Yahotyn', + 'North Grenville', + 'Mahajjah', + 'Wangyuanqiao', + 'Hrastnik', + 'Luckenwalde', + 'Kalaiya', + 'Matagalpa', + 'Bulbula', + 'Shirva', + 'Nogoya', + 'Leulumoega', + 'San Diego Country Estates', + 'Magong', + 'Kourouma', + 'Vorkadi', + 'Shibuya', + 'Madathapatti', + 'Cilegon', + 'Betun', + 'Mantes-la-Jolie', + 'Tokar', + 'Medog', + 'Medleri', + 'El Viso del Alcor', + 'Sao Joaquim de Bicas', + 'Cyuve', + 'Igarapava', + 'Hamilton Township', + 'Baldock', + 'Evren', + 'Sangarebougou', + 'Sohagpur', + 'Razua', + 'Naurhiya', + 'Tromsdalen', + 'Mairipora', + 'Yesilyurt', + 'Malacatancito', + 'Szydlowiec', + 'Masiaboay', + 'Taozhuangcun', + 'Gongyefu', + 'Qiziltepa', + 'Pivnichne', + 'Anandpur', + 'Machinga', + 'Irmo', + 'Guntapalli', + 'Indang', + 'Baetov', + 'Sarvar', + 'Eberbach', + 'Douar Oulad Driss', + 'Tanagura', + 'Ikare', + 'Yumurtalik', + 'Kapuvar', + 'Margraten', + 'General Las Heras', + 'Yinhua', + 'Badnawar', + 'Grao Mogol', + 'Dalja', + 'Abensberg', + 'Roque Perez', + 'Rustampur', + 'Cajati', + 'Sarso', + 'Samadh Bhai', + 'Azazga', + 'Kenar', + 'Ealing', + 'Versailles', + 'Kalima', + 'Stillorgan', + 'Portao', + 'Kwinana', + 'Gemena', + 'Oulad Chikh', + 'Arslanbob', + 'Sumenep', + 'Kleppe', + 'Anisoc', + 'Chapel Hill', + 'Timisoara', + 'Saint-Denis', + 'Temescal Valley', + 'Alayor', + 'Siuri', + 'Puerto Colombia', + 'San Andres Timilpan', + 'Tadjenanet', + 'La Londe-les-Maures', + 'Tivaouane', + 'Singaraja', + 'Frisco', + 'Pozzuoli', + 'Robe', + 'Kutina', + 'Escoublac', + 'Ginosa', + 'Weil im Schonbuch', + "Jeffrey's Bay", + 'Emporia', + 'Arbaoua', + 'Saint-Augustin-de-Desmaures', + 'Asslar', + 'Bischofshofen', + 'Vakhsh', + 'Upper Macungie', + 'Karpuzlu', + 'Dattapara', + 'Vaikuntam', + 'Haverhill', + 'Craiova', + 'Santo Amaro da Imperatriz', + 'Orpington', + 'Tibri', + 'Arlov', + 'Luwuk', + 'Pipra Naurangia', + 'Yingkou', + 'Sidi Okba', + 'Torrox', + 'Toki', + 'Matera', + 'Cristinapolis', + 'Ivano-Frankivsk', + 'Ramu', + 'Mangawan', + 'Ishioka', + 'Leones', + 'Mougins', + 'Maco', + 'Hanko', + 'Backnang', + 'Bakamune', + 'Gubbi', + 'Bazhou', + 'Guangzhou', + 'Badajoz', + 'Masdi', + 'Estcourt', + 'Kale', + 'Amaraji', + 'Guire', + 'Mesa', + 'Utebo', + 'Al Khawr', + 'Pararia', + 'Hapur', + 'Maigo', + 'Grimmen', + 'Saratoga Springs', + 'Villejuif', + 'Tucapel', + 'Zhoujiajing', + 'Nicosia', + 'Tirumalaiyampalaiyam', + 'Tamorot', + 'Radford', + 'Ambarakaraka', + 'Maragogipe', + 'Mettlach', + 'Nautla', + 'Nuevo Paysandu', + 'Kuppachchipalaiyam', + 'Steinbach am Taunus', + 'Calverton', + 'Ainring', + 'Olanchito', + 'Calayan', + 'Girardota', + 'Salem Lakes', + 'South Houston', + 'Maharajpur', + 'Saboya', + 'San Jose de Maipo', + 'Chennimalai', + 'Kecioren', + 'Auchi', + 'Elk Plain', + 'Galbois', + 'Shuilin', + 'Malabuyoc', + 'Buyuk Cakirman', + 'Rishra', + 'Aruja', + 'Piata', + 'Uvira', + 'Sohana', + 'Garmdarreh', + 'Wulingshancun', + 'Nijoni', + 'Kudlu', + 'Braunfels', + 'Attappampatti', + 'Pedana', + 'Guayaquil', + 'Crotone', + 'Anse a Pitre', + 'Svishtov', + 'Couzeix', + 'Seinajoki', + 'Schagen', + 'Weatherford', + 'Hidaka', + 'Klazienaveen', + 'Al Harah', + 'Qabb Elias', + 'Al Fashn', + 'Berja', + 'Prieska', + 'Ban Son Loi', + 'Togo', + 'Asagiri', + 'Zafferana Etnea', + 'The Bottom', + 'Kannapolis', + 'Ussel', + 'Nukus', + 'Susehri', + 'Yawata-shimizui', + 'Bhayala', + 'Leagrave', + 'Hermitage', + 'Seynod', + 'Lubawa', + 'Gran', + 'Nazerabad', + 'Shenzhen', + 'Lone Tree', + 'Birecik', + 'Ikongo', + 'Amudalavalasa', + 'Rajgarh', + 'Xalpatlahuac', + 'Lunga-Lunga', + 'Haubourdin', + 'Ambolomoty', + 'Nyamata', + 'Sukhsena', + 'Wanzleben', + 'Brazopolis', + 'Pupiales', + 'Smederevo', + 'Bischofsheim', + 'Mato Verde', + 'Jawhar', + 'Shiraoi', + 'Huanghuajing', + 'San Rafael Arriba', + 'Freienbach', + 'Edegem', + 'Sao Sebastiao de Lagoa de Roca', + 'Yangirabot', + 'Chhapera', + 'Izu', + 'East St. Louis', + 'Chegur', + 'Naawan', + 'Shuangxianxiang', + 'Kalamaria', + 'Shangzhi', + 'Bardiha Turki', + 'Alfeld', + 'Sacele', + 'Pereira Barreto', + 'Tiptree', + 'Bala Koh', + 'Buyende', + 'Macuspana', + 'Gediz', + 'Tocancipa', + 'Taneichi', + 'Makabana', + 'Richland Hills', + 'Puerto Limon', + 'Dighwara', + 'Radovis', + 'Hobbs', + 'Quinchia', + 'Santa Catarina Masahuat', + 'Roquetas de Mar', + 'Willowick', + 'Chahana', + 'Samai', + 'Tasiilaq', + 'Anao-aon', + 'Kattanam', + 'Derbisek', + 'Changuinola', + 'Schwalmstadt', + 'Camposampiero', + 'Mayumba', + 'Raipur', + 'Stanmore', + 'Wesseling', + 'Poldasht', + 'Keora', + 'Hettstedt', + 'Kihoku', + 'Cabimas', + 'Stadthagen', + 'Gorontalo', + 'Cherial', + 'Khonobod', + 'Barntrup', + 'Bhojpur Kadim', + 'Jaltenco', + 'Attendorn', + 'Andranovory', + 'Binche', + 'Muscat', + 'Ludwigsburg', + 'Ain Zora', + 'Dabutou', + 'Landgraaf', + 'Kaunas', + 'Sandwa', + 'Dingolfing', + 'Rothenbach an der Pegnitz', + 'Sheghnan', + 'Mawai', + 'Sunnyside', + 'Esteio', + 'Carnoustie', + 'Sakura', + 'Monte Compatri', + 'Rize', + 'Walvisbaai', + 'Gouande', + 'Bardipuram', + 'Rockford', + 'Pipra Dewas', + 'Jagdishpur', + 'Sonwan', + 'Bridgewater', + 'Akpassi', + 'Zoumi', + 'Tongchuanshi', + 'Guagua', + 'Ayacucho', + 'Lom', + 'Wang Sombun', + 'Pran Buri', + 'Rass el Djebel', + 'Pita', + 'Ait Hani', + 'Saraikela', + 'Cepagatti', + 'Monte Patria', + 'Bessemer', + 'Santa Juliana', + 'Obuse', + 'Schilde', + 'Varel', + 'Almhult', + 'Wesel', + 'Xanten', + 'Bastogne', + 'Glyka Nera', + 'Tasova', + 'Rampur Shamchand', + 'Maarssen', + 'La Asuncion', + 'Streaky Bay', + 'Ridgefield Park', + 'Tichi', + 'Busuanga', + 'Arakvaz-e Malekshahi', + 'Ban Bong Tai', + "Vel'ke Kapusany", + 'Maracas', + 'Rio das Flores', + 'Moers', + 'Bexleyheath', + 'Savsat', + 'Morsott', + 'Germersheim', + 'Pungulam', + 'Sussex', + 'Bilwi', + 'Abare', + 'El Tumbador', + 'Guebwiller', + 'Bourg-en-Bresse', + 'Koskapur', + 'Orvieto', + 'Quartz Hill', + 'Espita', + 'Vechelde', + 'Banting', + 'Cancun', + 'Humahuaca', + 'Hockley', + 'Barwan', + 'Gravesend', + 'Palhalan', + 'Galway', + 'Shotley Bridge', + 'Guioyo', + 'Xaxim', + 'Dunkirk', + 'Amreli', + 'Balqash', + 'Buruanga', + 'Nachchandupatti', + 'Nejo', + 'Lumba-a-Bayabao', + 'Harri', + 'Gaoliying Ercun', + 'Erumakkuzhi', + 'Campina Grande', + 'Palimbang', + 'Al Jabayish', + 'Gurdaspur', + 'Spaichingen', + 'Le Pontet', + 'Villiers-sur-Marne', + 'Doruma', + 'Isa', + 'Puerto San Jose', + 'Gunupur', + 'Salcea', + 'Maruttuvakkudi', + 'Nsanje', + 'Mahamda', + 'Pola de Siero', + 'Radenci', + 'Chincha Alta', + 'Ed Damazin', + 'Red Bank', + 'Whitehaven', + 'Oulad Driss', + 'Edinburg', + 'Floresta', + 'Vili', + 'Saghar Sultanpur', + 'Salon-de-Provence', + 'Batufah', + 'Dalkola', + 'Fostoria', + 'Yauco', + 'Maridi', + 'Nueva Imperial', + 'Saint-Jean-sur-Richelieu', + 'Tupaciguara', + 'Palmdale', + "Notre-Dame-de-l'Ile-Perrot", + 'Rio Largo', + 'Ntossoni', + 'Nagnur', + 'Wallkill', + 'Breda', + 'Solindabad', + 'Penkridge', + 'Agui', + 'George Mason', + 'Aliartos', + 'Itatinga', + 'Tekkampatti', + 'Turicato', + 'Chorleywood', + 'Grootfontein', + 'Kassa', + 'Kotli', + 'Sayreville', + 'Kottavalasa', + 'North Plainfield', + 'Madanapalle', + 'Santa Eulalia', + 'Farnborough', + 'Lerwick', + 'Rohar', + 'Holalkere', + 'Gavirate', + 'Pace', + 'Bharokhara', + 'Oraiokastro', + 'Senekane', + 'Ambohitrarivo', + 'Regente Feijo', + 'Ambotaka', + 'El Crucero', + 'Gummudipundi', + 'Romilly-sur-Seine', + 'Tanggemu Nongchang', + 'Chubek', + 'Quattro Castella', + 'Sai Buri', + 'Piuma', + 'Cerveteri', + 'West Rancho Dominguez', + 'Carmona', + 'Santa Maria', + 'Cayiralan', + 'Wennigsen', + 'Batalha', + 'Vatican City', + 'Bahrain', + 'Tenente Ananias Gomes', + 'Lusambo', + 'Angra dos Reis', + 'Pilkhua', + 'San Marcelino', + 'Kuching', + 'Moche', + 'Straza', + 'Ban Ueam', + 'Kibale', + 'Shahdadpur', + 'West Long Branch', + 'Killai', + 'Chandia', + 'Los Teques', + 'Hpa-An', + 'Wenden', + 'Sao Bento', + 'Wentorf bei Hamburg', + 'Farajok', + 'Uppugunduru', + 'Samkir', + 'Samraong', + 'Pind Dadan Khan', + 'Rajmahal', + 'Paz de Ariporo', + 'Rincon de la Victoria', + 'Vyshhorod', + 'Khaniadhana', + 'Fuxing', + 'Xibang', + 'Antanananivo', + 'Peekskill', + 'Kanhauli Manohar', + 'Kengtung', + 'Yahyali', + 'Yecun', + 'Albolote', + 'Etrotroka', + 'Robstown', + 'Bacoli', + 'Godfrey', + 'Savastepe', + 'Netphen', + 'Cam', + 'Moortebeek', + 'Koufalia', + 'Tabatinga', + 'Qadirganj', + 'Tlanchinol', + 'Kokopo', + 'Parelhas', + 'Bergkamen', + 'Crossville', + 'Charallave', + 'Mahugaon', + 'Tazhakudi', + 'Nawa Nagar Nizamat', + 'Rizhao', + 'Bilasipara', + 'Kfar Aabida', + "E'erguna", + 'Rio Branco', + 'Buique', + 'Maesawa', + 'Rabak', + 'Noqui', + 'Maxcanu', + 'Otegen Batyr', + 'Nellipoyil', + 'Oakville', + 'Serowe', + 'Conselheiro Pena', + 'Ban Fang Tuen', + 'Santa Isabel', + 'Sukhasan', + 'Ocos', + 'Cai Lay', + 'Mangamila', + 'Sikar', + 'Ogoshi', + 'Wimbledon', + 'Islam Qal`ah', + 'Iawar', + 'Kuala Lipis', + 'Gressier', + 'South Laurel', + 'Greendale', + 'Ilfracombe', + 'Prestonpans', + 'Stonegate', + 'Willich', + 'Forssa', + 'Enghien-les-Bains', + 'Balintawak', + 'Kitgum', + 'Pucallpa', + 'Bhilai Karanja', + 'Uzundere', + 'Boshof', + 'Maradi', + 'Bekasi Kota', + 'Mission', + 'Lecherias', + 'Valverde del Camino', + 'Bojaca', + 'Khowai', + 'Heerlen', + 'Bucyrus', + 'Ipameri', + 'Kadaiyam', + 'Guanagazapa', + 'Lencois', + 'Riosucio', + 'Sredets', + 'Rio de Janeiro', + 'Peringuzha', + 'Rabat', + 'Kupang', + 'Louis Trichardt', + 'Tlalixtac de Cabrera', + 'Quincy', + 'Kerrville', + 'Khandala', + 'Waconia', + 'Aventura', + 'Mahabad', + 'Ciudad Miguel Aleman', + 'Al Hamdaniyah', + 'Mankur', + 'Dumbea', + 'Sungailiat', + 'Huitan', + 'Naubatpur', + 'Soubre', + 'Kaniama', + 'Dholbaja', + 'Kuala Kapuas', + 'Oplotnica', + 'Rampur Hat', + 'Sangaree', + 'Fatoma', + 'El Arba Des Bir Lenni', + 'Huamantla', + 'Enamadala', + 'Alibunar', + 'Madirovalo', + 'Bampur', + 'Karachi', + 'Lehi', + 'Cross Lanes', + 'Abou el Hassan', + 'Telwa', + 'Ga-Kgapane', + 'Navraftor', + 'Janapadu', + 'Garesnica', + 'Awantipur', + 'El Fanar', + 'Ayyampettai', + 'Shangzhuangcun', + 'Gainesville', + 'Santanopolis', + 'La Goulette', + 'Chiknayakanhalli', + 'Murtosa', + 'Tirano', + 'Heckmondwike', + 'Mantenopolis', + 'Villa Corzo', + 'Gacko', + 'Etterbeek', + 'Dargot', + 'Kattamuru', + 'Thazhamel', + 'Armilla', + 'Eisenstadt', + 'Elbeuf', + 'Metsamor', + "'Ain el Melh", + 'Karunagapalli', + 'Dayr al Balah', + 'Qazvin', + 'Brokopondo', + 'Keetmanshoop', + 'Chopadandi', + 'Shinas', + 'Silverdale', + 'Orcutt', + 'La Concepcion', + 'Kebili', + 'Tecoluca', + 'Kursaha', + 'Laiyuan', + 'Hilliard', + 'Summit', + 'Vadakku Viravanallur', + 'Phanat Nikhom', + 'Vila Rica', + 'Vantaa', + 'Saint-Amand-les-Eaux', + 'Inole', + 'Barrancas', + 'Upala', + 'Albania', + 'Puerto Galera', + 'Calulo', + 'Mioveni', + 'Yangiariq', + 'Newport News', + 'Puerto Villarroel', + 'San Juan del Rio', + 'Badarwas', + 'Chertsey', + 'Montalvo', + 'Nulivedu', + 'Ain Nouissy', + 'Linhares', + 'Sao Joao da Ponte', + 'Palos Heights', + 'Mondolfo', + 'Vibo Valentia', + "Colle di Val d'Elsa", + 'Tache', + 'Mixquiahuala de Juarez', + 'Montegiardino', + 'Franca', + 'Iwo', + 'Northfield', + 'Neyyattinkara', + 'Hirna', + 'Acquaviva delle Fonti', + 'Voiron', + 'Iturama', + 'Aklanpa', + 'Raymondville', + 'Ambohimandroso', + 'Lakshmipur', + 'Torgelow', + 'Trento', + 'Neiva', + 'Umuarama', + 'San Mauro Torinese', + 'Pago Pago', + 'Ulricehamn', + 'San Cristobal Cucho', + 'Owendo', + 'Basht', + 'Conthey', + 'Damba', + 'Senmayacho-senmaya', + 'Gdansk', + 'Antilla', + 'Husavik', + 'Frankfurt', + 'Seaham', + 'Zagan', + 'Westerly', + 'Sivasli', + 'Nablus', + 'El Wak', + 'Saddle Brook', + 'Tepelene', + 'Vyskov', + 'Tavagnacco', + 'Ndende', + 'Salanso', + 'Laranjeira', + 'Tranquebar', + 'Battulapalle', + 'Bhilwara', + 'Cork', + 'Ennis', + 'Danjiangkou', + 'North Logan', + 'Viralimalai', + 'Firoza', + 'Bettiah', + 'Atharga', + 'Tallimarjon Shahri', + 'Yangi Marg`ilon', + 'Lohariandava', + 'Opera', + 'Gampaha', + 'Teboulba', + 'Solofra', + 'Portoferraio', + 'Daijiazhuang', + 'Jiexiu', + 'Sinkolo', + 'Joutseno', + 'Donauworth', + 'South Glengarry', + 'Nawabganj', + 'Lodhran', + 'Rio San Juan', + 'Kriva Palanka', + 'Rozaje', + 'Manapla', + 'Negresti-Oas', + 'Plaine Magnien', + 'Satsuma', + 'Fatehabad', + 'Rio do Antonio', + 'Calafat', + 'Preganziol', + 'Spalding', + 'Xinji', + 'Kilkottai', + 'Guding', + 'Milici', + 'Paranavai', + 'Skopun', + "Debre Mark'os", + 'Isingiro', + 'Port-Margot', + 'Ludinghausen', + 'Grossenluder', + 'Fenyuan', + 'Araras', + 'Terme', + 'Conneaut', + 'Winnetka', + 'Pelhrimov', + 'Pir Bakran', + 'Sukkampatti', + 'Shrirangapattana', + 'Vatra Dornei', + 'Palmaner', + 'Goose Creek', + 'Hailsham', + 'Douar Tabouda', + 'Douar Echbanat', + 'Adre', + 'Quinto di Treviso', + 'Lagoa Vermelha', + 'Ambatomivary', + 'Maidencreek', + 'Huanuco', + 'Sommerda', + "Sant'Arcangelo di Romagna", + 'Huancavelica', + 'Erramvaripalem', + 'Gyomaendrod', + 'Szigetvar', + "Xin'an", + 'Behror', + 'Antanimenabaka', + 'Sonbarsa', + 'Cuajinicuilapa', + 'Lovosice', + 'Carlos Barbosa', + 'Mlada Boleslav', + 'Wesley Chapel', + 'Adelsdorf', + 'Temascal', + 'Novovolynsk', + 'Brattleboro', + 'Yapacani', + 'Sao Sebastiao do Curral', + 'Makinsk', + 'Ojinaga', + 'Shaoxing', + 'Numazu', + 'Edakkazhiyur', + 'Riesi', + 'Bagamanoc', + 'Inkster', + 'Sugarland Run', + 'Savenay', + 'Eidhi', + 'Elvas', + 'Pannimadai', + 'Dorverden', + 'Luanshya', + 'Grays', + 'Tenente Portela', + 'Iuna', + 'Okmulgee', + 'Busra al Harir', + 'Kil Perambalur', + 'Urucurituba', + 'Villa Aberastain', + 'Sitakund', + 'Ras el-Barr', + 'Luwero', + 'Itaguacu', + 'Vadasinor', + 'Sile', + 'Inhapi', + 'Tanguieta', + 'Olney', + 'Sonpur', + 'Gillitts', + 'Fond du Lac', + 'Aboso', + 'Oujda-Angad', + 'Tiquisate', + 'Chuqung', + 'Jose Abad Santos', + 'Holsbeek', + 'Lioua', + 'Wiang Haeng', + 'Sirugudi', + 'Gomparou', + 'Kodangipatti', + 'Jitwarpur Kumhra', + 'Dhaula', + 'Monte Carmelo', + 'Zhytomyr', + 'Copertino', + 'Panggezhuang', + "M'lang", + 'Caconde', + 'Sao Sebastiao', + 'Rurrenabaque', + 'Canaman', + 'Vienna', + 'Shichinohe', + 'Catia La Mar', + 'Pachchaimalaiyankottai', + 'Bema', + 'Guadalupe', + 'Fouchana', + 'Takatsuki', + 'Nakrekal', + 'Greenfield', + 'Mirabel', + 'Sharon', + 'Valenzuela', + 'Shiroi', + 'Polotitlan de la Ilustracion', + 'Skawina', + 'Merrifield', + 'Bitetto', + 'Hinsdale', + 'Kalafotsy', + 'Reddigudem', + 'El Colegio', + 'Santa Ana Jilotzingo', + 'Korkuteli', + 'Xieqiaocun', + 'Nikko', + 'Purulha', + 'Al Hasakah', + 'Puigcerda', + 'Diabugu', + 'Sao Joao de Pirabas', + 'Yegainnyin', + 'Itasca', + 'Italva', + 'Olavarria', + 'Dikirnis', + 'Tantoucun', + 'Al Khmissat', + 'Woodland', + 'Vasco Da Gama', + 'Amalapuram', + 'Los Amates', + 'Gotha', + 'Burscheid', + 'Tanippadi', + 'Xinyingheyan', + 'St. Pete Beach', + 'Chieri', + 'Ahmadpur', + 'Capela do Alto', + 'Ebn Ziad', + 'Moimenta da Beira', + 'Schwarzenbek', + 'Sasthankotta', + 'Lannion', + 'Oupeye', + 'Digne-les-Bains', + 'Kami', + 'Widnes', + 'Bala Cangamba', + 'Edmonds', + 'Qabr as Sitt', + 'Lorient', + 'Antsinanantsena', + 'Liteni', + 'Belsand', + 'Tibau do Sul', + 'Melpanaikkadu', + 'Uranganpatti', + 'Canasgordas', + 'Totnes', + 'King Edward Point', + 'Ben Daoud', + 'Aarau', + 'San Sebastiano al Vesuvio', + 'Bagam', + 'Hobart', + 'Shimada', + 'Roelofarendsveen', + 'Broomfield', + 'Palenga', + 'Rainham', + 'Molepolole', + 'Hayama', + 'Guines', + 'Gonubie', + 'Filiasi', + 'Gethaura', + 'General Rodriguez', + 'Guilderland', + 'Acarigua', + 'Lowell', + 'Subulussalam', + 'Boiro', + 'Workington', + 'Sebes', + 'Konibodom', + 'Grevesmuhlen', + 'Mixco', + 'St. Andrews', + 'Tirora', + 'Gubat', + 'Edattirutti', + 'Bad Wildbad', + 'Newport Pagnell', + 'Jadcherla', + 'Mountain House', + 'Pindare-Mirim', + 'Hollins', + 'Barddhaman', + 'Castro-Urdiales', + 'Bandora', + 'Masandra', + 'Charo', + 'Qiryat Bialik', + 'Turangi', + 'Lavras da Mangabeira', + 'Hanover', + 'Middleburg', + 'Umm Qasr', + 'Kingswinford', + 'Dachne', + 'Surcin', + 'Al Manaqil', + 'Sorombo', + 'Maruteru', + 'Chaona', + 'Morbi', + 'Rani Shakarpura', + 'Tiko', + 'Picarras', + 'Keratsini', + 'West Deptford', + 'Natividade', + 'Kalale', + 'Hohenkirchen-Siegertsbrunn', + 'Huachipato', + 'Ubaitaba', + 'Shashijie', + 'Nalambur', + 'Monte Mor', + 'Halikner', + 'Pinczow', + 'Vellavadanparappu', + 'Ankli', + 'Oakdale', + 'Ar Rommani', + 'Phulwaria', + 'Berchem', + 'Ban Bang Muang', + 'Mangalapur', + 'Maki', + 'Puente Alto', + 'Paoy Paet', + 'Parsagarhi', + 'Gobernador Galvez', + 'Narpes', + 'Kodumudi', + 'Vostochnyy', + 'Grabouw', + 'Volketswil', + 'Qabala', + 'Falan', + 'Sankt Veit an der Glan', + 'Bargur', + 'Zalishchyky', + 'Paraiso do Norte', + 'Guymon', + 'Corsicana', + 'Malargue', + 'Cheran', + 'Makhmalpur', + 'Mitchellville', + 'Ouamri', + 'Kegeyli Shahar', + 'Nalbari', + 'Caazapa', + 'Bebedouro', + 'Crixas', + 'Jaisinghnagar', + 'Cuimba', + 'Ankireddikuntapalem', + 'Biknur', + 'Parauli', + 'Solec Kujawski', + 'La Riviera', + 'Sakae', + 'Ville Bonheur', + 'Galkot', + 'Tessalit', + 'Joao Alfredo', + 'Vinkovci', + 'Karuppur', + 'Vedappatti', + 'Palocabildo', + 'Nanjing', + 'Haidarnagar', + 'Kabankalan', + 'Acapetahua', + 'Yashio', + 'Wasaga Beach', + 'Gorzow Wielkopolski', + 'Erzurum', + 'Jordbro', + 'Ilmajoki', + 'Cuauhtemoc', + 'Soldanesti', + 'Kontagora', + 'Baleno', + 'Umbertide', + 'Cividale del Friuli', + 'Maidenhead', + 'Taupo', + 'Potters Bar', + 'Hubli', + 'Kakching', + 'Aguadilla', + 'Dobrna', + 'Souk Ahras', + 'Giessen', + 'Feriana', + 'Simdega', + 'Kadugli', + 'Makati City', + 'Harqalah', + 'Flowery Branch', + 'Spartanburg', + 'Royal Oak', + 'Fukuroi', + 'Kishmat Dhanbari', + 'Somerset East', + 'Behisatse', + 'Tummalapenta', + 'Central District', + 'Tubize', + 'Issy-les-Moulineaux', + 'Lezhe', + 'Buriti dos Lopes', + 'Krusevo', + 'Shibuya-ku', + 'Cachoeira', + 'Takahata', + 'Bozeman', + 'Yongqing', + 'Rio Vista', + 'Primavera', + 'Bainbridge Island', + 'Lo Miranda', + 'Sanharo', + 'Sentjernej', + 'Coral Hills', + 'Serres', + 'Kapan', + 'Caruaru', + 'Shijonawate', + 'Para de Minas', + 'Buco Zau', + 'Shaker Heights', + 'Zhaoyu', + 'Koto-ku', + 'Parvatgiri', + 'Evergem', + 'Asalem', + "Mu'tah", + 'Mahalleh-ye Shirinu', + 'Douar Oulad Hssine', + 'Villa Purificacion', + 'Arras', + "Sao Lourenco d'Oeste", + 'Bad Krozingen', + 'Olgiate Comasco', + 'Hanumannagar', + 'Echemmaia Est', + 'Lennox', + 'Kalimpong', + 'Kitob', + 'Las Palmas', + 'Guacima', + 'Urgut Shahri', + 'Carnot', + 'Lambertville', + 'Sabara Bangou', + 'Shahr-e Herat', + 'Longford', + 'Kambove', + 'Cartagena', + 'Odessa', + 'Bani Suwayf', + 'Toqsu', + 'Bibbiano', + 'Torres Vedras', + 'Bareja', + 'Maying', + 'Gross-Umstadt', + 'Anzoategui', + 'Holstebro', + 'Baturite', + 'Neduvannur', + "Terra Roxa d'Oeste", + 'Cencheng', + 'Marondera', + 'Azumino', + 'Sanha', + 'Porto Grande', + 'Ansermanuevo', + 'Perigueux', + 'Makronia', + "Bek'oji", + 'Los Blancos', + 'Argeles-sur-Mer', + 'Khorabar', + 'Fushe-Kruje', + 'Fotsialanana', + 'Aomar', + 'Guardamar del Segura', + 'Ksibet el Mediouni', + 'Georgian Bluffs', + 'Stockport', + 'Saray', + 'Buckley', + 'Daventry', + 'Jaqma', + 'Guifoes', + 'Patpara', + 'Bonhill', + 'Presidente Figueiredo', + 'Cutral-Co', + 'Morgan City', + 'Cernusco sul Naviglio', + 'Beheloka', + 'Bargny', + 'Brits', + 'Nakasato', + 'San Jose de Feliciano', + 'Lithia Springs', + 'Kushtia', + 'Nasipit', + 'Alsdorf', + 'Rockland', + 'Homestead', + 'Ziketan', + 'Ludvika', + 'Erbil', + 'Meguro', + 'Teolo', + 'Lapovo', + 'Streetly', + 'Dar es Salaam', + 'Middleton', + 'Ayancik', + 'Canoas', + 'Kanrangana', + 'Parczew', + 'Puck', + 'Mario Campos', + 'Bedourie', + 'Bohumin', + 'Freha', + 'Juventino Rosas', + 'Kolaras', + 'Oued Sebbah', + 'Kinattukkadavu', + 'Peravurani', + 'Barahona', + 'Kalabo', + 'Kumluca', + 'Saint-Priest', + 'Tagkawayan', + 'Durazno', + 'Ouled Rabah', + 'Susangerd', + 'Reynosa', + 'Araraquara', + 'Ambinanynony', + 'Puerto Madryn', + 'Villa Juarez', + 'Bletchley', + 'Santo Tomas de Janico', + 'El Arahal', + 'Bijapur', + 'Villeneuve-la-Garenne', + 'Nantong', + 'Semera', + 'Dean Funes', + 'Koblenz', + 'Montebello', + 'Sideradougou', + 'Luzhou', + 'Yuanchang', + 'Tulbagh', + 'Hitoyoshi', + 'Wijk bij Duurstede', + 'Damascus', + 'Branchburg', + 'Bahutal', + 'Payyanpalli', + 'Gjovik', + 'Pudukkottai', + 'Lakhdaria', + 'Paracambi', + 'El Bagre', + 'Ban Cho Ho', + 'Villamartin', + 'Natogyi', + 'Wildwood', + 'Tall Salhab', + 'Fort Erie', + 'Kirtipur', + 'Quirihue', + 'Masamagrell', + 'Medak', + 'Futtsu', + 'Cuamba', + 'Zbarazh', + 'Beniel', + 'Azzaba', + 'Banaruyeh', + 'Denver', + 'Slough', + 'Khenifra', + 'Nanbu', + 'Ibarra', + 'Galashiels', + 'Malyn', + 'Biei', + 'Eranapuram', + 'Miechow', + 'San Benedetto del Tronto', + 'St. Matthews', + 'Muhlenberg', + 'Cape Coast', + 'Massa Lombarda', + 'Raybag', + 'Villeta', + 'Jogbani', + 'Agua Blanca', + 'Kaluderica', + 'Dinnington', + 'Jepara', + 'Bhimadolu', + 'Ortona', + 'Xincheng', + 'Huancayo', + 'Crewe', + 'Koduvilarpatti', + 'Eggertsville', + 'Agege', + 'Gharyan', + 'Sucupira do Norte', + 'Datian', + 'Kaldsletta', + 'Paillaco', + 'Anjanazana', + 'Sulphur', + 'Kakrala', + 'Walin', + 'Francisco Caballero Alvarez', + 'Frenda', + 'Robertsport', + 'Simanggang', + 'Flers', + 'Tindouf', + 'Azna', + 'Umrat', + 'Chhimluang', + 'Felixlandia', + 'Tung Tau Tsuen', + 'Soledar', + 'Mejorada del Campo', + 'Nijlen', + 'Przeworsk', + 'Upper Leacock', + 'Machico', + 'Arakkapadi', + 'Gawan', + 'Tracuateua', + 'Tilbury', + 'Indiaroba', + 'Villa Constitucion', + 'Gil', + 'Soyaniquilpan', + 'Nanchang', + 'Guaxupe', + 'Gardone Val Trompia', + 'Lampang', + 'Kahror Pakka', + 'Kundurpi', + 'Gallatin', + 'Belper', + 'Beruri', + 'El Pinon', + 'Chengjiao Chengguanzhen', + 'Ad Darbasiyah', + 'Neo Karlovasi', + 'Mersin', + 'Glyfada', + 'Bastak', + 'Wolfersheim', + 'Albenga', + 'Kuqa', + 'Isanlu', + 'Kaji', + 'Barranquilla', + 'Sai Kung Tuk', + 'Hish', + 'Overpelt', + 'La Vista', + 'Livingston', + 'Bangassi-Nangou', + 'Purcellville', + 'Mashpee', + 'Alushta', + 'Tikar', + 'Katima Mulilo', + 'Duvvuru', + 'Paragominas', + 'Mosquera', + 'Yuzhnoukrainsk', + 'Senador Jose Porfirio', + 'Mutuipe', + 'Castenaso', + 'Schmolln', + 'Mporokoso', + 'Tawsalun', + 'Malo', + 'Myedu', + 'Orsta', + 'Staten Island', + 'Goch', + 'Dulag', + 'Bedlington', + 'Mangatarem', + 'Lubok Antu', + 'Zongo', + 'Toktogul', + 'Sabana Grande de Boya', + 'Mullheim', + 'Mogilno', + 'Abra Pampa', + 'Sidi Baizid', + 'Rostusa', + 'Glen Allen', + 'Barra de Sao Francisco', + 'Jocotitlan', + 'Pensilvania', + 'Petropavl', + 'Radlje ob Dravi', + 'Chichaoua', + 'Irondale', + 'Namhkam', + 'Liestal', + 'Yazu', + 'Douar Olad. Salem', + 'Mecca', + 'Fergus Falls', + 'Shimotoba', + 'Purificacion', + 'Qal`at Sukkar', + 'Zhezqazghan', + 'Kenafif', + 'Chak That', + 'Roanoke Rapids', + 'Manakondur', + 'Beni Enzar', + "Nefas Mewch'a", + 'Kerap', + 'Narsingi', + 'Arys', + 'Ajaccio', + 'Lihue', + 'Herentals', + 'Tarakan', + 'San Jose del Fragua', + 'Sitio Novo de Goias', + 'Jammu', + 'Ballesteros', + 'Bayreuth', + 'Schererville', + 'Kara-Suu', + 'Mbandjok', + 'Xinpi', + 'Caudry', + 'Jalhalli', + 'Rosario de Mora', + 'Hampden', + 'Tevaram', + 'Annapolis Neck', + 'Minokamo', + 'Ghataro Chaturbhuj', + 'Neyshabur', + 'North Mankato', + 'Nea Makri', + 'Matino', + 'San Vito al Tagliamento', + 'Shangchuankou', + 'Maldon', + 'Tirupparangunram', + 'Chateauneuf-les-Martigues', + 'Sumare', + 'La Calamine', + 'Plettenberg Bay', + "Monteroni d'Arbia", + 'Ban Charoen Mueang', + 'Pukekohe East', + 'Ban Sop Tia', + 'Barbacoas', + 'Plottier', + 'Fleetwood', + 'Fultondale', + 'Karahia', + 'Grefrath', + 'Kalavai', + 'Borsa', + 'Bournville', + 'Changwon', + 'Chik Banavar', + 'Kungsbacka', + 'Yunoshima', + 'Pithiviers', + 'Zepce', + 'Rosario do Sul', + 'Nadiad', + 'Besancon', + 'Rupbas', + 'Muong Theng', + 'Dubrovytsya', + 'Someren', + 'Lianyuan', + 'Huilongping', + 'Zrenjanin', + 'Ban Wiang Phan', + 'Jucurutu', + 'Alamos', + 'Zetang', + 'Lymm', + 'Sarajevo', + 'Saint-Sebastien-sur-Loire', + 'Sereflikochisar', + 'Suai', + 'La Leonesa', + 'Tequisquiapan', + 'Faya', + 'Fort Saskatchewan', + 'Neuilly-Plaisance', + 'Elenga', + 'Dearborn', + 'Gok', + 'Ipora', + 'Mierlo', + 'Ottappalam', + 'Mirandiba', + 'Rongjiawan', + 'Sarno', + 'Volpiano', + 'Ain Touta', + 'Swietochlowice', + 'San Dimas', + 'Brenham', + 'Birkenfeld', + 'Sveti Jurij', + 'Montfermeil', + 'Bowen', + 'Xiangcheng', + 'Bariariya', + 'Albufeira', + 'Pantin', + 'Ruffano', + 'Buurhakaba', + 'Kulai', + 'Nordlingen', + 'Alcanar', + 'Ostfildern', + 'San Juan del Puerto', + 'Zunhua', + 'Yuregir', + 'Amha', + 'San Antonio de los Banos', + 'Pojuca', + 'Cernavoda', + 'Maria', + 'Saky', + 'Ciudad Victoria', + 'Qasr-e Qand', + 'North Lauderdale', + 'Manahari', + 'Kartikapalli', + 'Herne', + 'Zabki', + 'Middle River', + 'Moncion', + 'Er Regueb', + 'Ridge', + 'Tezu', + 'Djendel', + 'Tynaarlo', + 'Katra', + 'Le Plessis-Trevise', + 'Pernik', + 'Grossostheim', + 'Dharir', + 'Kherrata', + 'Guamal', + 'Mangrawan', + 'Haldibari', + 'Yingzhou Linchang', + 'Zgierz', + 'Brownsburg', + 'Radebeul', + 'Novara', + 'Markacho', + 'Lochristi', + 'Oyem', + 'Porcia', + 'Bogoso', + 'Tapa', + 'Carmagnola', + 'Alto Parnaiba', + 'Prosper', + 'Nossombougou', + 'Tohana', + 'Turlock', + 'Gualaquiza', + 'Masrakh', + "Arba Minch'", + 'Moroto', + 'Carcassonne', + 'Paraipaba', + 'Jajarm', + 'Raparla', + "Diao'ecun", + 'Oued Laou', + 'Sint-Niklaas', + 'Masuda', + 'Tilvalli', + 'Tauranga', + 'Upplands Vasby', + 'Wabash', + 'Tapas', + 'Al `Alamayn', + 'Huozhou', + 'Tshikapa', + 'Kennedy Town', + 'Wisconsin Rapids', + 'Yangiyul', + 'Quezalguaque', + 'Bougado', + 'Cuautepec de Hinojosa', + 'Mundo Novo', + 'Verl', + 'Maghnia', + 'Dzouz', + 'Principe da Beira', + 'Bamessi', + 'Soloma', + 'Basay', + 'Closter', + 'Amatenango de la Frontera', + 'Bazarak', + 'Benairia', + 'Mocharim', + 'Kuchesfahan', + 'Kurayoshi', + 'Luohe', + 'Kampot', + 'Chaska', + 'Lynden', + 'Purushottampur', + 'Konin', + 'Long Xuyen', + 'Kizhakkott', + 'Peachtree Corners', + 'Natshal', + 'Saleaula', + 'Bahia Blanca', + 'Semey', + 'Rianxo', + 'Caridad', + 'Hammam el Rhezez', + 'Kudelstaart', + 'Huizen', + 'Narasapuram', + 'Parkville', + 'Kadungapuram', + 'Garden City', + 'Daulatkhan', + 'Tectitan', + 'Maroambihy', + 'Pallini', + 'Center Point', + 'North Bay', + 'Maihar', + 'Lons', + 'Huehuetenango', + 'Qalqilyah', + 'Koryo', + 'Betafo', + 'Rheinbach', + 'Surprise', + 'Sumbawanga', + 'Thatha', + 'Kurugodu', + 'Neustadt in Holstein', + 'Jupi', + 'Santa Isabel do Para', + 'Chandrawada', + 'Tamra', + 'Cusset', + 'Le Cres', + 'Tunduma', + "Toyloq Qishlog'i", + 'Southbourne', + 'Hempfield', + 'Dhilwan', + 'Roseville', + 'Sisimiut', + 'Aizubange', + 'Lleida', + 'Muri', + 'Phalombe', + 'Antsoha', + 'Saha', + 'Muana', + 'Djambala', + 'Istanbul', + 'Andoharanofotsy', + 'Seligenstadt', + 'Djougou', + 'Boukadir', + 'Ingeniero Pablo Nogues', + 'Barwon Heads', + 'Hazleton', + 'Ciudad Tula', + 'Northeim', + 'Galela', + 'Rehoboth', + 'Bemanonga', + 'Nawucun', + 'Gallup', + 'Malayer', + 'Abu Za`bal', + 'Seia', + 'Samalsar', + 'Melfi', + 'Puduvayal', + 'Namsos', + "Bailey's Crossroads", + 'Gardere', + 'Bermo', + 'Gaillard', + 'Qiushanxiang', + 'Rokycany', + 'Cesena', + 'Alto Piquiri', + 'Mendoza', + 'Rudrangi', + 'Kotido', + 'Turbihal', + 'Comox', + 'Shangpa', + 'Tzintzuntzan', + 'Nao Kothi', + 'Cogolin', + 'Nhan Trach', + 'Royal Wootton Bassett', + 'Suhar', + 'Prestea', + 'Zahle', + 'Cherniakhiv', + 'Thinadhoo', + 'Phuthaditjhaba', + 'Pasco', + 'Madhavaram', + 'Chuo-ku', + 'Kishi', + 'Tavistock', + 'Jitpur', + 'Legnano', + 'Heusweiler', + 'Punnila', + 'Bonoufla', + "Qiryat Mal'akhi", + 'Jaguari', + 'Erraguntlakota', + 'Valdobbiadene', + 'Lupao', + 'Hille', + 'Cosamaloapan', + 'Tromso', + 'Charkhi Dadri', + 'Dolo Odo', + 'Ebersbach an der Fils', + 'Chojnow', + 'Perkasie', + 'Zhamog', + 'Seiada', + 'Bjelovar', + 'Puerto Natales', + 'Camaguey', + 'Dabakala', + 'Katagami', + 'Panchla', + 'Borgoricco', + 'St. Albans', + 'Campeche', + 'Kontela', + 'Broughty Ferry', + 'Shihuajie', + 'Alvinopolis', + 'Parilla', + 'Georgetown', + 'North Palm Beach', + 'Canela Baja', + 'Darpa', + 'Battipaglia', + 'Upland', + 'Bombinhas', + 'Houten', + 'Ibiracu', + 'Paraopeba', + 'Hokota', + 'Whitpain', + 'Nakoushi', + 'Krasnoilsk', + 'Guapimirim', + 'Bom Jesus da Serra', + 'Tesalia', + 'Arohi', + 'Pyay', + 'Tryavna', + 'Mufulira', + 'Weligama', + 'Gaillac', + 'Rettanai', + 'Marano di Napoli', + 'Jefferson City', + 'Tulagi', + 'Nirmali', + 'Passos', + 'Htison', + 'Harewa', + 'Miren', + 'Shika', + 'Alfajayucan', + 'San Pedro Masahuat', + 'Galhinna', + 'Silla', + 'Dong Ha', + 'Legazpi City', + 'Hassi Messaoud', + 'Bhadrapur', + 'Anjahambe', + 'Sirsaganj', + 'Menglang', + 'Qarchak', + 'Gandhwani', + 'Penedo', + 'Peralta', + 'Rappang', + 'Bad Bramstedt', + 'Weinfelden', + 'Aliaga', + 'Douar Sidi Laaroussi', + 'Regedor Quissico', + 'Sidi Dahbi', + 'Joppatowne', + 'Borgo a Buggiano', + 'Tiqqi', + 'Stakhanov', + 'Basmakci', + 'Oulad Fraj', + 'Moron', + 'Thunder Bay', + 'Manoharpur', + 'Kuniyamuttur', + 'Onondaga', + 'Lopare', + 'Zumaia', + 'Kahemba', + 'Altenstadt', + 'Cabuyao', + 'Kafr az Zayyat', + 'Burriana', + 'Kulharia', + 'Barinas', + 'Nelmadur', + 'Demirozu', + "Bois-d'Arcy", + 'Lahad Datu', + 'Venganellur', + 'Tsuruoka', + 'Bordertown', + 'Amrabad', + 'Sala', + 'Coronel Freitas', + 'Bassin Bleu', + 'Porto Santo Stefano', + 'Ait Yazza', + 'Thanhlyin', + 'Tournai', + 'Valea Adanca', + 'Saint-Cloud', + 'Ginowan', + "Welk'it'e", + 'Siilinjarvi', + 'Inhangapi', + 'Huazhou', + 'Eski Yakkabog`', + 'Tobelo', + 'Horsholm', + 'Springs', + 'Lorengau', + 'Manwat', + 'Khmilnyk', + 'Hrubieszow', + 'Santa Teresa di Riva', + 'Portel', + 'El Dorado Hills', + 'Pattiswaram', + 'Bad Reichenhall', + 'Dourdan', + 'Dakhram', + 'Rio de Contas', + 'Pagudpud', + 'Sao Felix', + 'Kosjeric', + 'Lebanon', + 'Fargo', + 'Mont-Saint-Aignan', + 'Wald', + 'Eckernforde', + 'Chicholi', + 'Goldap', + 'Kurashiki', + 'Santa Maria Colotepec', + 'Kawaii', + 'Narapala', + 'Maimanah', + 'Ponda', + "'Ain Roua", + 'Manyoni', + 'Mineiros', + 'Moussoro', + 'Olhao', + 'Ridley', + 'Sidi Ouassay', + 'Nancagua', + 'Sagay', + 'Majurwa', + 'Okotoks', + 'Chengannur', + 'Middletown', + 'Heredia', + 'Mbanza Kongo', + 'Texenna', + 'Urania', + 'Cantemir', + 'Albertirsa', + 'Qala', + 'Molinella', + 'Pahrump', + 'Padugupadu', + 'Utiel', + 'Harbatpur', + 'Orestiada', + 'Tiachiv', + 'Kahta', + 'Cesenatico', + 'Omaruru', + 'Siquijor', + 'America Dourada', + 'Kilkis', + 'Ascot', + 'Zhujiagua', + 'Bagheria', + 'Puerto Wilches', + 'Torre Maggiore', + 'Tomuk', + 'Huanggang', + 'Trovagunta', + 'Devanakonda', + 'Sa Pa', + 'Narashino', + 'Koganei', + 'West Memphis', + 'Pendleton', + 'Edenvale', + 'Dieburg', + 'Ankavandra', + 'Gondalga', + 'Quang Ha', + 'Xochistlahuaca', + 'Niiyama', + 'Viedma', + 'Sukhothai', + 'Cuorgne', + 'Janzur', + 'Dhansura', + 'Rio Maior', + 'Penuganchiprolu', + 'Jaora', + 'Kanzakimachi-kanzaki', + 'Hunfeld', + 'Yabayo', + 'Torre del Mar', + 'Shenyang', + 'Odacho-oda', + 'Lagdo', + 'Otuzco', + 'Hagi', + 'Pompeu', + 'Repalle', + 'Majagual', + 'Yulinshi', + 'Periyakoduveri', + 'Ubon Ratchathani', + 'Al `Amadiyah', + 'Saint-Andre', + 'Omis', + 'Arcozelo', + 'Doujing', + 'Vuhledar', + 'Hindley', + 'Aphaur', + 'Oliveira', + 'Nasir', + 'Palm Desert', + 'Khategaon', + 'Esmoriz', + 'Belmont', + 'Koani', + 'Gyomro', + 'Mackworth', + 'Rubio', + 'Zafra', + 'San Juan Evangelista', + 'Qoubaiyat', + 'Lautoka', + 'Cerkes', + 'Villeneuve-le-Roi', + 'Xinhua', + 'Touros', + 'Nyaungu', + 'Chakand', + 'Aluksne', + 'Yishui', + 'Timperley', + 'Oakwood', + 'Chakwal', + 'Tarauna', + 'Khandrauli', + 'Huaquechula', + 'Bhatinda', + 'Puerto Williams', + 'Tongliao', + 'Staffanstorp', + 'Kisumu', + 'Mangueirinha', + 'Leopoldina', + 'Luchow', + 'Harrismith', + 'Indija', + 'Campo Redondo', + 'Lastra a Signa', + 'Akhisar', + 'Alnif', + 'Tega Cay', + 'Foughala', + 'Jinjicun', + 'Kangning', + 'Treze Tilias', + 'Landau', + 'Vaires-sur-Marne', + 'Udiyavara', + 'Boquim', + 'Hekou', + 'Kirchheim unter Teck', + 'Kondakindi Agraharam', + 'Sao Mateus', + 'Simonesia', + 'Pinerolo', + 'Ptuj', + 'Mezobereny', + 'Gex', + 'Unnan', + 'Dayr Abu Hinnis', + 'Sanampudi', + 'Simon Bolivar', + 'Oteiza', + 'Gragnano', + 'Khartsyzk', + 'Beramanja', + 'Richmond Hill', + 'Juru', + 'Padavedu', + 'Ekwari', + 'Brescia', + 'La Cruz', + 'Pulimaddi', + 'Pleasant Hill', + 'Hozin', + "Sant'Ambrogio di Valpolicella", + 'Telua', + 'Bilozerske', + 'Adami Tulu', + 'Manteca', + 'Delmiro Gouveia', + 'Mindelheim', + 'Fate', + 'Hokur Badasgom', + 'Sidi Kada', + 'Talayolaparambu', + 'Connersville', + 'Gurinhem', + 'Skopje', + 'Fairless Hills', + 'Debrecen', + 'El Realejo', + 'Gadhada', + 'Schwalmtal', + 'Yamaga', + 'Bihar', + 'Pakil', + 'Loni', + 'Chilgazi', + 'Varzobkala', + 'Samandag', + 'Portela', + 'Wanghong Yidui', + 'Qianxi', + 'Dese', + 'Hathidah Buzurg', + 'Periyapuliyur', + 'Bilston', + 'Laja', + 'Makhdumpur', + 'Les Pennes-Mirabeau', + 'Himmatpura', + 'Denia', + 'Keimoes', + 'Castillo de Teayo', + 'Conselve', + 'Longmeadow', + 'Herne Bay', + 'Sao Miguel do Guama', + 'Sunland Park', + 'Israna', + 'Bassian', + 'Qift', + 'Behshahr', + 'Kotli Ablu', + 'Hidrolandia', + 'Torre Santa Susanna', + 'Kirdasah', + 'Tanjungpinang', + 'Reshuijie', + 'Wappinger', + 'Perungudi', + 'Goworowo', + 'Knin', + 'Vardhamankota', + 'Campo Limpo', + 'Sermoneta', + 'Limoeiro de Anadia', + 'Otsuchi', + 'Niamina', + 'Kasumigaura', + 'West Puente Valley', + 'Sungai Petani', + 'East Windsor', + 'Pedro Celestino Negrete', + 'Yorkton', + 'Gueznaia', + 'Telica', + 'Mong Cai', + 'El Paisnal', + 'Provo', + 'Stefan Voda', + 'Vargem', + 'Treillieres', + 'Procida', + 'Sens', + 'Aperibe', + 'Comasagua', + 'Hulbuk', + 'Talakag', + 'Puduva', + 'Hashikami', + 'Randallstown', + 'Sahline', + 'Bejar', + 'Isnapuram', + 'Teghra', + 'Eschwege', + 'Carver', + 'Franklin Park', + 'Tremedal', + 'Itahara', + 'Lancut', + 'Vettikattiri', + 'Meyzieu', + 'Al Abraq', + 'Basista', + 'Charlotte Amalie', + 'Palembang', + 'Esparraguera', + 'Villacidro', + 'Berettyoujfalu', + 'Peravali', + 'Mastic Beach', + 'Perundurai', + 'San Francisco', + 'Bangassou', + 'Kauriya', + 'Carinhanha', + 'Otjiwarongo', + 'Minot', + 'Covilha', + 'Shahe', + 'Petrila', + 'Portchester', + 'Ban Lueak', + 'Aminpur', + 'Calenga', + 'Bafata', + 'Cumnock', + 'Clichy', + 'Tabernes Blanques', + 'Siloe', + 'Qiaotouyi', + 'Candido Mota', + 'Barbosa', + 'Borcka', + 'Maryland City', + 'Lacarak', + 'Crestview', + 'Jounie', + 'Hochdorf', + 'Montelibano', + 'Tundhul', + 'Osku', + 'Monsenhor Tabosa', + 'Anosiala', + 'Ban Pa Sak', + 'Kanakir', + 'Wakayama', + 'Muttenz', + 'Makale', + 'Medesano', + 'Lamerd', + 'Bakarpur Ogairah', + 'Wetherby', + 'Nakagawa', + 'Sao Jose dos Pinhais', + "Nova Brasilandia d'Oeste", + 'Piquet Carneiro', + 'Valmadrera', + 'Pua', + 'Chak Forty-one', + 'Sandvika', + 'Stansted Mountfitchet', + 'Snoqualmie', + 'Pereiras', + 'Joaquim Nabuco', + 'Mondragon', + 'Huixcolotla', + 'Minamiuonuma', + 'Gap', + 'General Villegas', + 'Matane', + 'El Monte', + 'Minamiminowa', + 'Saint-Andre-les-Vergers', + 'Ibb', + 'Oudenaarde', + 'Lainate', + 'Box Elder', + 'Karanchedu', + 'Guanhaes', + 'Calderara di Reno', + 'Itainopolis', + 'Gattaran', + 'Muhlacker', + 'Rengo', + 'Winkler', + 'Marathalli', + 'Cambuquira', + 'Ilulissat', + 'Kalyandrug', + 'Kesabpur', + 'Santiago de Anaya', + 'Ano Liosia', + 'Vung Tau', + 'Monaco', + 'Cuesmes', + "Ra's al `Ayn", + 'Rasebetsane', + 'Tagudin', + 'Basopatti', + 'Mack', + 'Pedra Badejo', + "Les Sables-d'Olonne", + 'Hosakote', + 'Barbastro', + 'Tutoia', + 'Manpur', + 'Higashiyamato', + 'Varvarin', + 'Manhica', + 'Kachchippalli', + 'Velugodu', + 'Palin', + 'Xinchangcun', + 'Alchevsk', + 'Anzegem', + 'Doi Saket', + 'Amersham', + 'Namburu', + 'Union Hill-Novelty Hill', + 'Apopa', + 'Zafarabad', + 'Nanminda', + 'Burlington', + 'Hibbing', + 'Galloway', + 'Union City', + 'Dukinfield', + 'Montecristo', + 'Shrigonda', + 'Uchoa', + 'Tizi-n-Tleta', + 'Stellenbosch', + 'Olaine', + 'Isabel', + 'Luqa', + 'Matinha', + 'Bni Rzine', + 'Attimarappatti', + 'Circasia', + 'Ban Krot', + 'Selimpasa', + 'Xangongo', + 'Simiganj', + 'Coacoatzintla', + 'Nueva Helvecia', + 'Chenzhou', + 'Lota', + 'Lawrenceville', + 'Vayanur', + 'Tobe', + 'Ende', + 'Haslemere', + 'Calabayan', + 'Pakxe', + 'Pajapan', + 'Udhampur', + 'Tsararivotra', + 'Telerghma', + 'Haydock', + 'Surmon Chogga Grong', + 'Trebic', + 'Alafaya', + 'Kafr Sajnah', + 'Conceicao', + 'Vellore', + 'Usingen', + 'Terenure', + 'Bouca', + 'Ghat Borul', + 'Baisuhalli', + 'Daulatpur', + 'Grossrohrsdorf', + 'Lorica', + 'Hagaribommanahalli', + 'Ban Thoet Thai', + 'Loznica', + 'Ponmala', + 'Padiyur', + 'Tigoa', + 'Budva', + 'Mistassini', + "'Ain Babouche", + 'El Limon', + 'Diouna', + 'Rieti', + 'Kuttyadi', + 'Xinyang', + 'Killiney', + 'Pop', + 'Guaymango', + 'Springbok', + 'Presque Isle', + 'Timra', + 'Abuja', + 'Miyakojima', + 'Payyannur', + 'Xunyang', + 'Bailen', + 'Chambellan', + 'Bukit Mertajam', + 'Cattolica', + 'Serdar', + 'Sarare', + 'Coronel Vivida', + 'Herriman', + 'Guliston', + 'Rouvroy', + 'Garfield', + 'Elmina', + 'Sarreguemines', + 'Milwaukie', + 'Ershui', + 'Garot', + 'Czestochowa', + 'Kirklareli', + 'La Jagua de Ibirico', + 'Stroud', + 'Kocaeli', + 'Calimaya', + 'Savalou', + 'Manalalondo', + 'Cikupa', + 'Cide', + 'Idylwood', + 'East Massapequa', + 'Andijon', + 'Ibusuki', + 'Brahmapur', + 'Mount Prospect', + 'St. James', + 'Vila Franca de Xira', + 'Bhadreswar', + 'Khandwa', + 'Amporoforo', + 'Celic', + 'Gomec', + 'Fort Carson', + 'Kosiv', + 'Fria', + 'Cliftonville', + 'Narvacan', + 'Ramanathapuram', + 'Jawasa', + 'Guclukonak', + 'Samobor', + 'Ceska Trebova', + "Anan'evo", + 'Zinjibar', + 'Indianapolis', + 'Yanggok', + 'Aberdare', + 'Palm River-Clair Mel', + 'Diari', + 'Chilmil', + 'Callosa de Segura', + 'Kummersbruck', + 'Sambava', + 'Pritzwalk', + 'Karai', + 'Lachhmangarh Sikar', + 'Wade Hampton', + 'Rapla', + 'Assa', + 'Malavalli', + 'Trinec', + 'La Verne', + 'Selibaby', + 'Zagnanado', + 'Commerce City', + 'Tredyffrin', + 'Salaberry-de-Valleyfield', + 'Longtan', + 'Karatsu', + 'Badian', + 'Sao Sebastiao do Paraiso', + 'Satyamangala', + 'Balsta', + 'Gamagori', + 'Chinampa de Gorostiza', + "Ambinanin'i Sakaleona", + 'Itiquira', + 'Center', + 'Umargam', + 'Ozurgeti', + 'Montereau-faut-Yonne', + 'Amau', + 'Nova Esperanca', + 'Venezuela', + 'Kafia Kingi', + 'Tanguturu', + 'Vinto', + 'Sonosari', + 'Sturovo', + 'Date', + 'Niska Banja', + 'An Nhon', + 'Littlehampton', + 'Fuente Palmera', + 'Sokode', + 'Donghua', + 'South Holland', + 'Frimley', + 'Bachchannapet', + 'Junnardev', + 'Dole', + 'Neuotting', + 'Monforte del Cid', + 'Baguley', + 'Brimington', + 'Calauan', + 'Durmersheim', + 'Wittenberge', + 'Echizen', + 'Dobropillia', + 'Ayuquitan', + 'Viramgam', + 'Matsuura', + 'Abu Dis', + 'Winsum', + 'Gentbrugge', + 'Villa Victoria', + 'Yovon', + 'Boaz', + 'Kotta Kalidindi', + 'Great Bend', + 'Tarcento', + 'Limbazi', + 'Bajos de Haina', + 'Karakax', + 'Ibira', + 'Yakushima', + 'Goito', + 'Nabari', + 'Sabanalarga', + 'Tiou', + 'Renens', + 'Dolores', + 'Agago', + 'Madakalavaripalli', + 'Mapastepec', + 'La Lucila', + 'Cherakhera', + 'Sadaseopet', + 'Neerijnen', + 'Krathum Baen', + 'Gansbaai', + 'Mahalandi', + 'Chanaur', + 'La Argentina', + 'Jesup', + 'Bela Crkva', + 'Soteapan', + 'Siechnice', + 'Lwakhakha', + 'Uspallata', + 'Imbatug', + 'Altavas', + 'Jaglot', + 'Colangute', + 'Kumatori', + 'Andapa', + 'Choconta', + 'Sarhari', + 'Santiago Jocotepec', + 'Arauca', + 'Hailin', + 'Shahr-e Majlesi', + 'Guyuan', + 'Murfatlar', + 'Chon Buri', + 'Bacnotan', + 'Kutna Hora', + 'Xiva', + 'Mizque', + 'West Ham', + 'Roatan', + 'Doylestown', + 'Tarichar Kalan', + 'Foz do Iguacu', + 'Garching an der Alz', + 'Tifariti', + 'Longwy', + 'Tarikere', + 'Alameda', + 'Gbanhi', + 'Catunda', + 'Kirchhundem', + 'Cuenca', + 'Delareyville', + 'Cliffside Park', + 'Orting', + 'Kapadvanj', + 'Nigran', + 'Fengyicun', + 'Pazardzhik', + 'Yeddumailaram', + 'Longkeng', + 'Nikaho', + 'Altofonte', + 'Douar Oulad Youssef', + 'Lathi', + 'Osh', + 'Daytona Beach', + 'Atharan Hazari', + 'Ami', + 'Balaguer', + 'Malnate', + 'Quanzhang', + 'Rio Claro', + 'Hajdusamson', + 'Gouvea', + 'Kunming', + 'Kulasegaram', + 'Pori', + 'Braintree', + 'Bijelo Polje', + 'Mateur', + 'Kozova', + 'Fenoarivo Atsinanana', + 'Leisure World', + 'Taman Senai', + 'Nerinjippettai', + 'Washington', + 'Basapatna', + 'Mwene-Ditu', + 'Norak', + 'South Upi', + 'Yokadouma', + 'Abbigeri', + 'Kouoro', + 'Freilassing', + 'Nymburk', + 'Cangandala', + 'Dihri', + 'Arakli', + 'Huaihua', + 'Turkauliya', + 'Ramainandro', + 'Aytos', + 'Lihe', + 'Raseborg', + 'Tomioka', + 'Sakrand', + 'Belur', + 'Kaseda-shirakame', + 'Mettingen', + 'Bugalagrande', + 'Annandale', + 'Mambere', + 'Oume', + 'Al Buwaydah', + 'Eagle', + 'Ustron', + 'Pegnitz', + 'Massaguet', + 'Bibbiena', + 'Saint-Vith', + 'Geelong', + 'Glen Rock', + 'Miarinavaratra', + 'Sao Simao', + 'Gurlan', + 'La Escala', + 'Mian Channun', + 'Tono', + 'Kevelaer', + 'Jiaozhou', + 'Einsiedeln', + 'Udaypur Gadhi', + 'Molagavalli', + 'Sira', + 'Ospina', + 'Kuduru', + 'Komarolu', + 'Arnprior', + 'Breclav', + 'Qornet Chahouane', + 'Okaya', + 'Montesarchio', + 'Tecoh', + 'Dezful', + 'Quiculungo', + 'Tam Ky', + 'Qaracuxur', + 'Daxin', + 'Horsham', + 'Tafalla', + 'Yehud', + 'Abertawe', + 'Darien', + 'Shodoshima', + 'El Arenal', + 'Litomerice', + 'Caboolture', + 'Norwalk', + 'Mananara Avaratra', + 'Herten', + 'Kihihi', + 'Aira', + 'Kuttampala', + 'Noumea', + 'Phon-Hong', + 'Thair', + 'Herzogenrath', + 'Planalto', + 'Arrecifes', + 'Fuente-Alamo de Murcia', + 'Gilbues', + 'Miltenberg', + 'Bubong', + 'Henstedt-Ulzburg', + 'Rayavaram', + 'Alberique', + 'Pimpalgaon Raja', + 'Palencia', + 'Lalganj', + "L'Isle-Adam", + 'Abasolo', + 'Sirikonda', + 'Devadanappatti', + 'Pokrovka', + 'Baliguian', + 'Corum', + 'Tlacolula de Matamoros', + 'Jeremie', + 'Myaungmya', + 'Tissa', + 'Capljina', + 'Ambariokorano', + 'Gemert', + 'Akayu', + 'Bhiwandi', + 'Chishui', + 'Abbotsford', + 'La Carlota', + 'Antalya', + 'Yongjing', + 'Tres Marias', + 'Haa', + 'Meerhout', + 'Sibu', + 'Astara', + 'Gar', + 'Ndola', + 'Sao Tiago de Custoias', + 'Karrapur', + 'Chartiers', + 'Songea', + 'Mouila', + 'Streator', + 'Al Qamishli', + 'Nikaweratiya', + 'Delbruck', + 'Batonyterenye', + 'Odzak', + 'Agudos', + 'Gatineau', + 'Vineyard', + 'Saint-Rambert', + 'Kesath', + 'Sugar Grove', + 'Tuensang', + 'Los Reyes de Juarez', + 'Sardinal', + 'Eksjo', + 'Safety Harbor', + 'Kilkenny', + 'Tsiroanomandidy', + 'Agartala', + 'Agrate Brianza', + 'Nomimachi', + 'North Tonawanda', + 'Stafford', + 'Paramoti', + 'Cavdir', + 'Houmt Souk', + 'Domagnano', + 'Kaset Wisai', + 'Piedmont', + 'Nazareth', + 'Anjialava', + 'Stebnyk', + 'Golyaka', + 'Zehdenick', + 'Birstonas', + 'Chaklasi', + 'Carira', + 'Sapatgram', + 'Bankura', + 'Rosignano Marittimo', + 'Quchan', + 'Hoeilaart', + 'Angul', + 'Erechim', + 'Xinshi', + 'Jima', + 'Upper Southampton', + 'Baramula', + 'Joao Neiva', + 'Vanukuru', + 'Shiwan', + 'White Horse', + 'Morong', + 'Frascati', + 'Schrobenhausen', + 'Luvungi', + 'Itirapina', + 'Nari Bhadaun', + 'Tarnaveni', + 'Mytilini', + 'Destrehan', + 'Eybens', + 'Dianga', + 'Pasarkemis', + 'Najafabad', + 'Swiecie', + 'Cobourg', + 'San Jorge', + 'Pfastatt', + 'Kusmaul', + 'Fallersleben', + 'Pershotravensk', + 'Mahambo', + 'Sigli', + 'Annaba', + 'Turku', + 'Pedda Vegi', + 'Mesa Geitonia', + 'Charam', + 'Charentsavan', + 'Imielin', + 'Orvault', + 'Lodz', + 'Sam Ko', + 'Foz', + 'Alexandroupoli', + 'Demmin', + 'Castelleone', + 'Burauen', + 'Capilla del Monte', + 'Ettlingen', + 'Srirangapur', + 'Camamu', + 'Saruhanli', + 'Ferfer', + 'Aurangabad', + 'Taromske', + 'Hinigaran', + 'San Juan de Dios', + 'Saint-Maur-des-Fosses', + 'Bunkyo-ku', + 'Renfrew', + 'Mus', + 'Lake Jackson', + 'Xirdalan', + 'Samayac', + 'Spanish Lake', + 'Izegem', + 'Nema', + 'Dongducheon', + 'Hecelchakan', + 'Seoni', + 'Wood Green', + 'Castelfranco Veneto', + 'Bostanici', + 'Itajiba', + 'Inagi', + 'Oakham', + 'Boa Vista', + 'Puquio', + 'Chittaranjan', + 'Tibro', + 'Pitangueiras', + 'Rouached', + 'Nouakchott', + 'Nuevo San Juan Parangaricutiro', + 'Sao Jose do Campestre', + 'Heyin', + 'Vacaria', + 'Lukavac', + 'Katy', + 'Caibiran', + 'Villasis', + 'Pontianak', + 'Pallijkarani', + 'Rayapalle', + 'San Pedro Carcha', + 'Kabirwala', + 'Idhna', + 'Kiama', + 'Armadale', + 'Braunschweig', + 'Lathasepura', + 'Bajo Boquete', + 'Shal', + 'Balyqshy', + "Long'e", + 'Felgueiras', + 'Arteijo', + 'Dhanaula', + 'Kaoni', + 'Telford', + 'Laukaria', + 'Druskininkai', + 'Sayhat', + 'Skanderborg', + 'Lindome', + 'Paramanandal', + 'Quebrangulo', + 'Bida', + 'Narragansett', + 'Valle Hermoso', + 'Santa Catarina Pinula', + 'Muriedas', + 'Tawnza', + 'Arnouville-les-Gonesse', + 'Ouaoula', + 'Shahrixon', + 'Alcamo', + 'Naujoji Akmene', + 'Pappakurichchi', + 'Toyonaka', + 'Uttukkuli', + 'Ilave', + 'Villupuram', + 'Bhojpur Jadid', + 'Ischia', + 'Rio Verde Arriba', + 'Radolfzell am Bodensee', + 'Llica', + 'Erjie', + 'Qiaomaichuan', + 'Lamont', + 'Bezliudivka', + 'Five Corners', + 'Niksic', + 'Hnivan', + 'Tamale', + 'Khanapur', + 'Ichnia', + 'Sesvete', + 'Reeuwijk', + 'Qutur', + 'Moana', + 'San Pedro Atocpan', + 'Osny', + 'Toda Bhim', + 'Zaouiet Sousse', + 'Lavras', + 'Ouardenine', + 'Katakos', + 'Hetton le Hole', + 'Iseo', + 'Lehigh', + 'Hallim', + 'Bonou', + 'Moita Bonita', + 'Shakargarh', + 'Chiapa de Corzo', + 'Zhanggu', + 'Meudon', + 'Ayikudi', + 'Cunduacan', + 'Jingling', + 'Jiantang', + 'Laojiezi', + 'Bazid Chak Kasturi', + 'Elliniko', + 'Rifu', + 'Duanshan', + 'Busselton', + 'Liubashivka', + 'Maniyur', + 'Singuilucan', + 'Erkrath', + 'Mucari', + 'Tumwater', + 'Yaoquan', + 'Santa Maria Ixhuatan', + 'Sidi Bel Abbes', + 'Namanga', + 'Afsin', + 'An Phu', + 'Bamyan', + 'Mannanchori', + 'Shahr-e Qods', + 'Bierun', + 'Vijapur', + 'Boli', + 'Hirni', + 'Ibitiara', + 'Pachchampalaiyam', + 'Hutto', + 'Talibon', + 'Bilbao', + 'Readington', + 'Pasivedalajimma', + 'Sariaya', + 'Lappersdorf', + 'Jodhpur', + 'Opava', + 'Shelton', + 'Ledegem', + 'Cabedelo', + 'Tiptur', + 'Narsapur', + 'Jamira', + 'Kaufbeuren', + 'Ricany', + 'Ansonia', + 'Mirandopolis', + 'Gondizalves', + 'Brilon', + 'Tarumirim', + 'Leek', + 'Sheopuria', + 'Dongfeng', + 'Pozanti', + 'Fuyingzicun', + 'Sartrouville', + 'Vigonovo', + 'Tatakan', + 'Kountouri', + 'Gharbia', + 'Figuig', + 'Shire', + 'Swabi', + 'Wismar', + 'Chachagui', + 'Nedumudi', + 'Carrickfergus', + 'St. Thomas', + 'Jaicos', + 'Tiruvaduturai', + 'Bolinao', + 'Eumseong', + 'Edina', + 'Essey-les-Nancy', + 'Saint-Julien-en-Genevois', + 'Grosuplje', + 'Grand Haven', + 'Al Faw', + 'Androrangavola', + 'Hassi Maameche', + 'Rubi', + 'Busesa', + 'Villa Tapia', + 'Divo', + 'Benalmadena', + 'Redmond', + 'Rajaori', + 'Kraaipan', + 'Nisporeni', + 'Wandan', + 'Aguia Branca', + 'Lake Park', + 'Hoquiam', + 'Jan Kempdorp', + 'Lentvaris', + 'Waikanae', + 'Reboucas', + "Pomigliano d'Arco", + 'Meycauayan', + 'Saugor', + 'Pasacao', + 'Schinnen', + 'Glan', + 'Lavagna', + 'Betnoti', + 'Ban Wat Sala Daeng', + 'Romsey', + 'Campi Salentina', + 'Glinde', + 'Gamprin', + 'Chandanais', + 'Taiping', + 'Marivorahona', + 'Store', + 'Vardenis', + 'Salima', + 'Kadanadu', + 'Jarqo`rg`on', + 'Barlad', + 'Bethpage', + 'Boscotrecase', + 'Dhamua', + 'Yelm', + 'Tlacotepec', + 'Parame', + 'Haapsalu', + 'Diadi', + 'Huetor Vega', + 'Jozefow', + 'Diessen am Ammersee', + 'Meyrin', + 'Crofton', + 'Galvez', + 'Boulder', + 'Masi-Manimba', + 'Aubervilliers', + 'Unguia', + 'Caslav', + 'El Centro', + 'El Fasher', + 'Beersheba', + 'Hujra Shah Muqim', + 'Bajil', + 'Tomas Oppus', + 'Liancheng', + 'Arkivan', + 'Jasidih', + 'Marturu', + 'Evry', + 'Easttown', + 'Lebedyn', + 'Garhpura', + 'Chinaur', + 'Jean-Rabel', + 'Chichigalpa', + 'Talukkara', + 'Kakuda', + 'Bozoum', + 'Tuzdybastau', + 'Pudimadaka', + 'Iraquara', + 'Blanquefort', + 'Batman', + 'Iserlohn', + 'Castano Primo', + 'Uttaradit', + 'Bartlesville', + 'Sapna', + 'Humpolec', + 'Avanashipalaiyam', + 'Rawajaya', + 'Gadarwara', + 'Mascote', + 'Narkatpalli', + 'Kendale Lakes', + 'White City', + 'Moss', + 'Nnewi', + 'Douar Oulad Aj-jabri', + 'Pata Uppal', + 'Dhiban', + 'Salitre', + 'West Babylon', + 'Khulays', + 'Teplohirsk', + 'Bannu', + 'Mudki', + 'Agar', + 'Al Madad', + 'Chokwe', + 'Udala', + 'Chichli', + 'Lemington', + 'Khatima', + 'Shangrao', + 'Chiriguana', + 'Baliuag', + 'Balua Rampur', + 'Nafplio', + 'Kudat', + 'Cabusao', + 'Acatic', + 'Madougou', + 'Tiszafured', + 'Akurana', + 'Kingston', + 'Pariaman', + 'Seacroft', + 'Tecoman', + 'Alavus', + 'Kanteru', + 'Jaramana', + 'Schodack', + 'Eniwa', + 'Fuzhou', + 'Klobuck', + 'Budhma', + 'Lelydorp', + 'Great Yarmouth', + 'Missoes', + 'Porto Velho', + 'Muhanga', + 'Porto Ingles', + 'Harderwijk', + 'Ubungo', + 'Phopnar Kalan', + 'Chrysoupoli', + 'Zarzis', + 'Nanning', + 'Tekeli', + 'Drammen', + 'Montivilliers', + 'Gualeguay', + 'Nishinomiya-hama', + 'Laguna de Duero', + 'Moraga', + 'Alzano Lombardo', + 'Miki', + 'Janin', + 'Frederico Westphalen', + 'Moulins', + 'Kuningan', + 'Castelnuovo di Porto', + 'Irimbiliyam', + 'Kharar', + 'Cholavandan', + 'Csomor', + 'Amurrio', + 'Tiruchchuli', + 'Steffisburg', + 'Parksville', + 'Anontsibe-Sakalava', + 'Nueva Concepcion', + 'Le Locle', + 'Harsewinkel', + 'Kaonke', + 'Sao Joao Batista', + 'Hokuto', + 'Aubange', + 'Bni Boufrah', + 'Sirmatpur', + 'Gignac-la-Nerthe', + 'Igarata', + 'Kharki', + 'Pamarru', + 'Bhimunipatnam', + 'Perdoes', + 'Qarqaraly', + 'Iisalmi', + 'Koussane', + 'Pittalavanipalem', + 'Tilakpur', + 'Okuchi-shinohara', + 'Alfenas', + 'Mindouli', + 'Chokkalingapuram', + 'Webster', + 'Rorschach', + 'Mascali', + 'Stauceni', + 'Mucaba', + 'Heemskerk', + 'Tari', + 'Bitam', + 'Ullal', + 'Qari Kola-ye Arateh', + 'Brownwood', + 'Santee', + 'Akbarpur', + 'Mega', + 'Motosu', + 'Csorna', + 'Cavarzere', + 'Omallur', + 'Virarajendrapet', + 'Wixom', + 'Murak', + 'Niederkruchten', + 'Aldine', + 'Jalihalli', + 'Batajnica', + 'Bardibas', + 'Madina do Boe', + 'Kallar', + 'Rhennouch', + 'East Providence', + 'Oras', + 'Vittuone', + 'Corbelia', + 'San Pedro Jicayan', + 'Telnal', + 'Parmanpur', + 'Morena', + 'Olindina', + 'Sumpango', + 'Bielsk Podlaski', + 'Mogoditshane', + 'Gulsehir', + 'Kumari', + 'Yantzaza', + 'Ambalavao', + 'Vohitany', + 'Qianxucun', + 'Pragatinagar', + 'Siaya', + 'Pombos', + 'Thuan Tien', + 'Minas de Matahambre', + 'Haaren', + 'Concarneau', + 'Al Marj', + 'Desborough', + 'Brewster', + 'San Miguel Ocotenco', + 'Toba Tek Singh', + 'Quito', + 'Pushkar', + 'Bagasra', + 'Snihurivka', + 'Nsawam', + 'Raonta', + 'Pishbar', + 'Hanamsagar', + 'Lemay', + 'Douliu', + 'Linlithgow', + 'Mamqan', + 'Phulaut', + 'Colwyn Bay', + 'East Leake', + 'Bargara', + 'Phan Rang-Thap Cham', + 'Jouy-le-Moutier', + 'Whyalla', + 'La Rioja', + 'Ferrenafe', + 'Yenmangandla', + "Ben 'Aknoun", + 'Fier', + 'Baharampur', + 'Kin', + 'Staphorst', + 'Gondar', + 'Pantnagar', + 'Barnagar', + 'Campobello di Licata', + 'Rukan', + 'Masatepe', + 'Barroso', + 'Savnik', + 'Huilong', + 'Porto Seguro', + 'Tanjungbalai', + 'Sembedu', + 'Lunenburg', + 'Kampung Tengah', + 'Tatsunocho-tominaga', + 'Oosterwolde', + 'Aver-o-Mar', + 'Poughkeepsie', + 'Anjozorobe', + 'Zengcun', + 'Soavimbahoaka', + 'Warr Acres', + 'Donacarney', + 'Galesburg', + 'Alma', + 'Fuchu', + 'Lehrte', + 'Angelopolis', + 'Ventanas', + 'Barro Alto', + 'Santana do Matos', + 'Patnongon', + 'East Dereham', + 'Tibagi', + 'Rambilli', + 'Punturin', + 'Balzar', + 'Beaufort West', + 'Sendai', + 'Szekszard', + 'Kaizuka', + 'Motatan', + 'Smiths Falls', + 'Litherland', + 'Maisenhausen', + 'Gandara', + 'Ban Mae Kham Lang Wat', + 'Madrid', + 'Pasrur', + 'Shahganj', + 'Escazu', + 'Pakruojis', + 'Shiogama', + 'Kallad', + 'Matinilla', + 'Delemont', + 'Bilga', + 'Kasur', + 'Khattab', + 'Palmi', + 'Sariz', + 'Didymoteicho', + 'Pallippuram', + 'Wagrowiec', + 'Munro', + 'Aschersleben', + 'Ndalatando', + 'Cecchina', + 'Meizichong', + 'Elamattur', + 'Umred', + 'Bonita Springs', + 'Cujubim', + 'Red Oak', + 'Montlouis-sur-Loire', + 'Santa Rosa de Osos', + 'Dehmoi', + 'Pesochin', + 'Sadao', + 'Nagyatad', + 'Khusropur', + 'Klagenfurt', + 'Bou Merdes', + 'Acatenango', + 'Demiryol', + 'Sinnuris', + 'Juazeirinho', + 'Sivakasi', + 'Hunxe', + 'Lamu', + 'Ravensthorpe', + 'Streetsboro', + 'Marugame', + 'Nizampur', + 'Salina', + 'Baglung', + 'Aketao', + 'Schwedt (Oder)', + 'Medrissa', + 'Nordwalde', + 'Rielasingen-Worblingen', + 'Santiago Tianguistenco', + 'Dahuk', + 'Lubutu', + 'Ambohipihaonana', + 'Felino', + 'Kitwe', + 'Langarud', + 'San Rafael Las Flores', + 'Kurwa Mathia', + 'Deuil-la-Barre', + 'Sitampiky', + 'Westchase', + 'Korkut', + 'Aracariguama', + 'Verneuil-sur-Seine', + 'Sucy-en-Brie', + 'Vulcanesti', + 'Berkeley Heights', + 'Suhut', + 'Saratamata', + 'Owk', + 'Holzgerlingen', + 'Neckargemund', + 'Piacenza', + 'Kakkalapalle', + 'Tendo', + 'Atlanta', + 'Kamsar', + 'East Bridgewater', + 'Krizevci', + 'Mandapeta', + 'Kusumha', + 'South Valley', + 'Bhargaon', + 'Birkerod', + 'Olintla', + 'Jiabong', + 'Winona', + 'Ambinanitelo', + 'Tlajomulco de Zuniga', + 'Monument', + 'Matay', + 'Jingping', + 'Gadoon', + 'Yingmen', + 'Bondoukou', + 'Sirpanandal', + 'Materi', + 'Englewood', + 'Nea Moudania', + 'Shanglingcun', + 'Sutton in Ashfield', + 'Maule', + 'Djidja', + 'Long Eaton', + 'Wasco', + 'Satao', + 'Idrija', + 'Catacamas', + 'Oga', + 'Canelli', + 'Seydunganallur', + 'Sidi Ettiji', + 'Civitavecchia', + 'Bouc-Bel-Air', + 'Itapetim', + 'Shizuishan', + 'Ron', + 'San Sebastian Tutla', + 'Lupeni', + 'Buenaventura Lakes', + 'Morsang-sur-Orge', + 'Adamstown', + 'Daraina', + 'Penugonda', + 'Pachkhal', + 'Genemuiden', + 'Supe', + 'Sahri', + 'Ingraj Bazar', + 'Poloros', + 'Chalil', + 'Collecchio', + 'Ipiranga do Piaui', + 'Hove', + 'Ahmadli', + 'Karahalli', + 'Matosinhos', + 'Navalmoral de la Mata', + 'Bialogard', + 'Radcliff', + 'Padakanti', + 'Malasiqui', + 'Mendicino', + 'Fredericksburg', + 'Javanrud', + 'Tegal', + 'Bailesti', + 'Beeston', + 'Huaixiangcun', + 'Puranpur', + 'Manlio Fabio Altamirano', + 'Roddam', + 'Celakovice', + 'El Espinar', + 'Plattling', + 'Juan de Acosta', + 'Sidi Rahal', + 'Zrnovci', + 'Montepuez', + 'Guruvayur', + 'Kouti', + 'Sipalakottai', + 'Vuzenica', + 'Fontenay-le-Comte', + 'Jianshi', + 'Soignies', + 'Tewksbury', + 'Montelupo Fiorentino', + 'Lacin', + 'Kibaha', + 'Minakami', + 'Bedburg', + 'Cuttack', + 'Had Zraqtane', + 'Mandamari', + 'Sadabad', + 'Azhikkal', + 'Huaiyang', + 'Ovruch', + 'Khetia', + 'Monte Azul Paulista', + 'Amparihitsokatra', + 'Luling', + 'Ayaviri', + 'Pekin', + 'Dhihdhoo', + 'Sylacauga', + 'Venkatagiri', + 'Piro', + 'Rasauli', + 'Verde Village', + 'Ostrow Mazowiecka', + 'Bahon', + 'Teixeira Soares', + 'Qasr al Farafirah', + 'Ambohimalaza', + 'Tabuk', + 'Tubungan', + 'Norseman', + 'Fresno', + 'Lingamparti', + 'Burgau', + 'Coatepec Harinas', + 'Haridwar', + 'Itaugua', + 'Cruz del Eje', + 'Hidalgo', + 'Nellaya', + 'Mandishah', + 'Digboi', + 'Darlington', + 'Solim', + 'Kintampo', + 'Rancho Arriba', + 'San Isidro', + 'Atlapexco', + 'Morada Nova de Minas', + 'Dwarka', + 'Ambatofinandrahana', + 'Tha Chang', + 'Wangguanzhuang Sicun', + 'Chatellerault', + 'Benicasim', + 'Norwell', + 'Moanda', + 'Balsamo', + 'Cesson', + 'Mels', + 'Lokbatan', + 'Karlapalem', + 'Otar', + 'Gainrha', + 'Panajachel', + 'Mulhouse', + 'Rafard', + 'Tabora', + 'Barroquinha', + 'Newport East', + 'Smarjeske Toplice', + 'Mallaram', + 'Qutubpur', + 'El Tortuguero', + 'Obernai', + 'Alella', + 'Susquehanna', + 'Caxambu', + 'Sibulan', + 'Kaleyanpur', + 'Asheville', + 'Albal', + 'Seshambe', + 'Sanga', + 'Furstenau', + 'Nea Artaki', + 'Flores', + 'Hualane', + 'Altun Kawbri', + 'McCandless', + 'Essau', + 'Andoany', + 'Navodari', + 'Cherry Creek', + 'Quitilipi', + 'Ermelo', + 'Arco', + 'Rosario del Tala', + 'Ravensburg', + 'Seveso', + 'Puerto Barrios', + 'Lucon', + 'Tadworth', + 'Banzhuangcun', + 'Sinnar', + 'Ladenburg', + 'Changzhou', + 'Futog', + 'Veenoord', + 'Pardes Hanna Karkur', + 'Palhoca', + 'Schaarbeek', + 'Jarabacoa', + 'Nazca', + 'Zurich', + 'Pacoti', + 'Lemmer', + 'Weizhou', + 'Nagpur', + 'Hueyotlipan', + 'Araria', + 'Kulat', + 'Horodenka', + 'Irupi', + 'Cinco Saltos', + 'Bhainsdehi', + 'Wamena', + 'West Haverstraw', + 'Ellon', + 'Fasa', + 'Turkan', + 'Tuzi', + 'Conjeeveram', + 'Imperial Beach', + 'Simria', + 'Golden Valley', + 'Tafas', + 'Hohenbrunn', + 'Harsin', + 'Mulundo', + 'Delaware', + "Sant'Angelo Lodigiano", + 'El Reten', + 'Madukkur', + 'Padalarang', + 'Paraiba do Sul', + 'Toboali', + 'Bagcilar', + 'Dejen', + 'Carthage', + 'Yusufeli', + 'Ponduru', + 'Conceicao dos Ouros', + 'Finnkolo', + 'San Francisco Solano', + 'Vetralla', + 'Kheda', + 'Chauddagram', + 'Vinaroz', + 'Galatge', + 'Bradwell', + 'Orlova', + 'Korostyshiv', + 'San Adrian de Besos', + 'Bernburg', + 'Galatone', + 'Paradip Garh', + 'Kukraun', + 'Bruges', + 'Karuzi', + 'Hemet', + 'Vadnagar', + 'Bernardino de Campos', + 'Pansol', + 'Keystone', + 'Satuba', + 'Weinbohla', + 'Morris', + 'Piazzola sul Brenta', + 'Shalingzicun', + 'Falkensee', + 'Uberlandia', + 'Norco', + 'Narala', + 'Widhwidh', + 'Yahualica', + 'Mokrisset', + 'Bandalli', + 'Nizwa', + 'Seal Beach', + 'Shahpur Chaumukhi', + 'Bhangar', + 'Soldado Bartra', + 'Paratdiha', + 'Esme', + 'Goulmima', + 'Waiyuanshan', + 'Fukagawa', + 'Brookdale', + 'Leiktho', + 'West University Place', + 'Kaarst', + 'Sausar', + 'Pivijay', + 'Paraibuna', + 'Saint Bernard', + 'Ponta Grossa', + 'Ushiku', + 'Greenville', + 'Peduasi', + 'Le Loroux-Bottereau', + 'Sturbridge', + '`Ibri', + 'Bururi', + 'Itarana', + 'Santol', + 'Saint-Amand-Montrond', + 'Palod', + 'Erdenet', + 'Bangramanjeshvara', + 'Schongau', + 'Tirumayam', + 'Barigarh', + 'Kinna', + 'Sarmera', + 'Middlesex', + 'Xixucun', + 'Anatoli', + 'Santa Cruz Naranjo', + 'Tocoa', + 'Cabo San Lucas', + 'Ban Dan Na Kham', + 'Martigny', + 'Morganton', + 'Caseros', + 'Ljubuski', + 'Itzer', + 'Januario Cicco', + 'Derzhavinsk', + 'Timoteo', + 'Cloppenburg', + 'Chirilagua', + 'Ordino', + 'Birsinghpur', + 'Nea Michaniona', + 'Sisophon', + 'Setif', + 'Porto San Giorgio', + 'Lakheri', + 'Gobernador Gregores', + 'Mazapil', + 'Arsikere', + 'Leon Postigo', + 'Ambovombe Afovoany', + 'Corbeil-Essonnes', + 'Ouda-yamaguchi', + 'Schloss Holte-Stukenbrock', + 'Ujjain', + 'Komorowice', + 'Pueblo Juarez', + 'Cookeville', + 'Caombo', + 'Heiligenhafen', + 'Lexington', + 'Maassluis', + 'Allschwil', + 'Ogulin', + 'Galveston', + 'Tepalcatepec', + 'Jericoacoara', + 'Gurupa', + 'Combarbala', + 'Poulton le Fylde', + 'Brandon', + 'Famy', + 'Fenggang', + 'Leping', + 'Recica', + 'Founougo', + 'San Diego', + 'Athar', + 'Teresina', + 'Xinbocun', + 'Piduguralla', + 'Buyukorhan', + "'Ain Merane", + 'Bolvadin', + 'Carpenedolo', + 'Onna', + 'Nalegaon', + 'Grandola', + 'Frondenberg', + 'Hagerstown', + 'Lalmunia Munhara', + 'Shelburne', + 'Boulia', + 'Bni Drar', + 'Courbevoic', + 'Cocieri', + 'Lentate sul Seveso', + 'Claveria', + 'Margny-les-Compiegne', + 'Leoben', + 'Pavugada', + 'Werlte', + 'Turori', + 'Puerto Montt', + 'Hauppauge', + 'Nalas', + 'Daxiang', + 'Polygyros', + 'Asahni', + 'Dzhankoi', + 'Sanliurfa', + 'Motema', + 'Gadag', + 'Negele', + 'Cuevas del Almanzora', + 'David', + 'Singhanakhon', + 'Siwan', + 'Kalush', + 'Carnaubeira da Penha', + 'Odenton', + 'Caldono', + 'Ampahana', + 'Mozzate', + 'Hemer', + 'Barcaldine', + 'Kanan', + 'Itapecerica da Serra', + 'Toftir', + 'Peterhead', + 'Xishancun', + 'Worgl', + 'Hermanus', + 'Derby', + 'Aranda de Duero', + "Aci Sant'Antonio", + 'Fetesti', + 'Masantol', + 'Vyara', + 'Puerto Berrio', + 'Norton', + 'St. Augustine Shores', + 'East Moline', + 'Shahbazpur', + 'Congonhas', + 'Naples', + 'Coromandel', + 'Aguas Zarcas', + 'Ceu Azul', + 'Sabanagrande', + 'Ranti', + 'Palapye', + 'Jitauna', + 'Brejo', + 'Abrisham', + 'Placilla de Penuelas', + 'Worcester', + 'Graneros', + 'Ras el Oued', + 'Tominian', + 'Kamatgi', + 'Tonakkal', + 'Dranesville', + 'Stepnogorsk', + 'Suchteln', + 'Casorate Primo', + 'Lajedo', + 'Chichihualco', + 'Loos', + 'Chust', + 'Winchester', + 'Tifton', + 'Deblin', + 'Armagh', + 'Mahonda', + 'Cheval', + 'Dzitbalche', + 'New Bern', + 'Le Teich', + 'Lamosina', + 'Oatfield', + 'Autazes', + 'Kikuchi', + 'Bni Gmil', + 'Ligonha', + 'Nepi', + 'Vohilengo', + 'Uruburetama', + 'Bajestan', + 'Al Kharj', + 'Cuito', + 'Vadakku Ariyanayakipuram', + 'Itaqui', + 'Cerro', + 'Acajutla', + 'Adjaouere', + 'Christchurch', + 'Miantso', + 'Damaturu', + 'Teluknaga', + 'Shaoyang', + 'Parappukara', + 'Buyukcekmece', + 'Pontefract', + 'Sangju', + 'Posoltega', + 'Longshan', + 'Scottsbluff', + 'Yate', + 'Rossville', + 'Khed', + 'Lokomby', + 'Pocking', + 'Duverge', + 'Mundamala', + 'Arivonimamo', + 'Bhimphedi', + 'Akiruno', + 'Toufen', + 'Civitanova Marche', + 'Chandlers Ford', + 'Titagarh', + 'Bengkalis', + 'Roth', + 'Lugoj', + "Yong'an", + 'Sandusky', + 'Karuvambram', + 'Chambersburg', + 'Perivale', + 'Dayalpur', + 'Gafour', + 'Zeboudja', + 'Bee Cave', + 'El Amria', + 'Agioi Anargyroi', + 'Biberist', + 'Kerikeri', + 'Lequile', + 'DeSoto', + 'Waddan', + 'Sopur', + 'Ambattur', + 'Guayama', + 'Pontes e Lacerda', + 'Vellur', + 'Thebes', + 'Cedarburg', + 'Dzialdowo', + 'Mestrino', + 'Valeggio sul Mincio', + 'San Ignacio', + 'Dafni', + 'Burbach', + 'Milton Keynes', + 'Moman Barodiya', + 'Rantau Prapat', + 'Silver Spring', + 'Goldbach', + 'Bottesford', + 'Peruvancha', + 'Bad Ischl', + 'Olivar Bajo', + 'Shitan', + 'Vohilava', + 'Pinukpuk', + 'Dashao', + 'Pocharam', + 'Daireaux', + 'Putaparti', + 'Eysines', + 'Hasanparti', + 'Gumdag', + 'Chicago Ridge', + 'Quissama', + 'Colonia Leopoldina', + 'General Mamerto Natividad', + 'Steamboat Springs', + 'Walpole', + 'Mockern', + 'Sheboygan', + 'Korolevo', + 'Wichita Falls', + 'Chepes', + 'Airmont', + 'El Geneina', + 'Fruitville', + 'Hernani', + 'Casinhas', + 'Neuried', + 'Ain Aicha', + 'Correntes', + 'Loxstedt', + 'Yakoma', + 'Aqtau', + 'Aubenas', + 'Cedar Mill', + 'Drensteinfurt', + 'Bankheri', + 'Comarnic', + 'San Ignacio de Moxo', + 'Kattagan', + 'Podcetrtek', + 'Armenia', + 'Albina', + 'Ferraz de Vasconcelos', + 'Kibawe', + 'Navegantes', + 'Panna', + 'Sebt Ait Saghiouchen', + 'Nuevo Arraijan', + 'Milicz', + 'Alleppey', + 'Mondlo', + 'Tallmadge', + 'Birni', + 'Langrucun', + 'Maria Enzersdorf', + 'Birdaban', + 'Kalingalan Caluang', + 'Ormond Beach', + 'Pontedera', + 'Jilava', + 'Guduru', + 'Ashikita', + 'Port Antonio', + 'Termini Imerese', + 'Stranraer', + 'Muheza', + 'Sonapur', + 'Rothenburg ob der Tauber', + 'De Doorns', + 'Libreville', + 'Vargaur', + 'Madona', + 'Kirlampudi', + 'Usworth', + 'Ichchapuram', + 'Groairas', + 'Raipura', + 'Thanesar', + 'Gracanica', + 'Bukkarayasamudram', + 'Brunico', + 'Spremberg', + 'Sidi El Hattab', + 'Imeni Chapayeva', + 'Tandwa', + 'Ap Khanh Hung', + 'Bagulin', + 'Ziyodin Shaharchasi', + 'Catanzaro', + 'Kavak', + 'Nalchiti', + 'Pannaikkadu', + 'Vysoke Myto', + 'Punnappatti', + 'Kanchika', + 'Sibut', + 'Marblehead', + 'Mayenne', + 'Concepcion Huista', + 'Birch Bay', + 'Mulakaluru', + 'Lynchburg', + 'Titay', + 'Trou du Nord', + 'Murraysville', + 'Kalladai', + 'Ipoh', + 'Santa Maria Chilchotla', + 'Wervik', + 'Casa de Oro-Mount Helix', + 'Jamnagar', + 'Caotan', + 'Dewsbury', + 'La Pintana', + 'Betong', + 'Jhundo', + 'Saint-Gregoire', + 'Channarayapatna', + 'Lennestadt', + 'Bir Kasdali', + 'Kissidougou', + 'Palghat', + 'Winterberg', + 'San Jose Tenango', + 'Mays Chapel', + 'Barreiro', + 'Latakia', + 'Loudeac', + 'Periyapodu', + 'Bad Schussenried', + 'Himeji', + 'Ulaan-Uul', + 'Qormi', + 'Wamba', + 'Bovolone', + 'Limavady', + 'Kara-Bak', + 'Wilrijk', + 'Bissegem', + 'Richmond', + 'Quzhou', + 'Chamarru', + 'Arhavi', + 'Tartus', + 'Mboki', + 'Gramsh', + 'Jizhuang', + 'Capitola', + 'Ashby de la Zouch', + 'Saga', + 'Mage', + 'Manvi', + 'Baud', + 'Hincesti', + 'Santiago Sacatepequez', + 'Astley', + 'Moramanga', + 'Le Mars', + 'Mahinathpur', + 'Fort Mohave', + 'Turda', + 'Kadiolo', + 'Kannan', + 'Grossenkneten', + 'Tsiately', + 'Zhipingxiang', + 'Al `Amarah', + 'Florestopolis', + 'Mazara del Vallo', + 'Antonivka', + 'Laconia', + 'Bouka', + 'Vemulanarva', + 'Budakalasz', + 'Kelkheim (Taunus)', + 'Ventania', + 'Castanos', + 'Ganzhou', + 'Lauingen', + 'Borbon', + 'Lodwar', + 'Ober-Ramstadt', + 'Qardho', + 'Kathevaram', + 'Colorado', + 'Sierra Vista', + 'Walnut Creek', + 'Tainan', + 'Guaraciaba', + 'Beni Hassane', + 'Tabuse', + 'Isfisor', + 'Long Beach', + 'Mahraurh', + 'Langerwehe', + 'An', + 'Nueve de Julio', + 'Kastamonu', + 'Harmanli', + 'Welver', + 'Binisalem', + "Imi n'Oulaoun", + 'Lizzanello', + 'Itapema', + 'Colon', + 'Andolofotsy', + 'Ilafy', + 'Chivasso', + 'Al Ghayzah', + 'Weno', + 'Hostotipaquillo', + 'Talugai', + 'Tomohon', + 'Saint-Remy-de-Provence', + 'Takikawa', + 'Ramgundam', + 'Shamaldy-Say', + 'Buram', + 'Curtea de Arges', + 'Labo', + 'Chahatpur', + 'Upington', + 'Andorinha', + 'Saymayl', + 'Nkheila', + 'Lindi', + 'Rio Tercero', + 'Grevena', + 'Peschiera del Garda', + 'Las Terrenas', + 'Holzwickede', + 'Mallet', + 'Ambara', + 'San Gregorio di Catania', + 'Chiramba', + 'Marudi', + 'Donji Kakanj', + 'Strand', + 'Azzano Decimo', + 'San Remo', + 'Brasschaat', + 'Barokhar', + 'Torghay', + 'Soledad', + 'Alfredo Chaves', + 'Dongsheng', + 'Enumclaw', + 'Bouafle', + 'Navotas', + 'Ambohitoaka', + 'Marotandrano', + 'Sao Miguel do Tapuio', + 'Wevelgem', + 'Pio XII', + 'Vigodarzere', + 'Gargzdai', + 'Rennes', + 'Lucala', + 'Piney Green', + 'Stokke', + 'Al `Ayyat', + 'Agoura Hills', + 'Akabira', + 'Cabiao', + 'Sevilla de Niefang', + 'Coconuco', + 'Yaxley', + 'Morondava', + 'Francistown', + 'Shush', + 'Papillion', + 'Morawa', + 'Halstead', + 'Lower Pottsgrove', + 'Rende', + 'Opelika', + 'Tanakpur', + 'Bolsward', + 'Majene', + 'Tullinge', + 'Ifield', + 'Calumpit', + 'Brajarajnagar', + 'Laon', + 'Tamagawa', + 'Avcilar', + 'Manikpur', + 'Portet-sur-Garonne', + 'East Chicago', + 'Reddiyapatti', + 'Bananal', + 'Somero', + 'Sabara', + 'Ragay', + 'Cupertino', + 'Kuala Pembuang', + 'Andranomamy', + 'Kavar', + 'Gommern', + 'Oxelosund', + 'Cheraro', + 'Repala', + 'Sannar', + 'Blaj', + 'Clemmons', + 'Kakarati', + 'Valenciennes', + 'Batad', + 'Kyeintali', + 'Mengjiacun', + 'Agdz', + 'Ula', + 'Parede', + 'Buguias', + 'Palleja', + 'Urubamba', + 'Bobbili', + 'Damalcheruvu', + 'Wantagh', + 'Wailuku', + 'La Piedad', + 'Texarkana', + 'Isangel', + 'Rosario', + 'Tublay', + 'Zapote', + 'Albox', + 'Kandulapuram', + 'Vrindavan', + 'Amudalapalle', + 'Cabo Rojo', + 'Sternberk', + 'Telde', + 'Kreuzlingen', + 'Dudu', + 'Vouzela', + 'Hullatti', + 'Yellandu', + 'Suzaka', + 'Ouled Abbes', + 'Ankilizato', + 'Pocklington', + 'Lescar', + 'Belgrave', + 'Az Zabadani', + 'Battambang', + 'Lehigh Acres', + 'Magurele', + 'Kato', + 'Bailin', + 'Kobarid', + 'Aketi', + 'Pokrovske', + 'Pecica', + 'Coringa', + 'Dosemealti', + 'Oued Amlil', + 'Gaotan', + 'Warburg', + 'Virgem da Lapa', + 'Amdjarass', + 'Duijiang', + 'Bagado', + 'Pujali', + 'Nan', + 'Ibrahimpatan', + 'Tornio', + 'Mount Vista', + 'Sanso', + 'Nam Dinh', + 'Martinsburg', + 'Eshkanan', + 'Maringa', + 'Alamosa', + 'Suzzara', + 'Portachuelo', + 'Vempalle', + 'San Antonio La Paz', + 'Stafa', + 'Basey', + 'Sabaudia', + 'Veliki Preslav', + 'West Manchester', + 'Weissenburg', + 'Le Havre', + 'Togamalai', + 'Omitama', + 'Bihariganj', + 'Redange-sur-Attert', + 'Saint-Chamond', + 'Anklam', + 'Perwez', + 'Qivraq', + 'Chieti', + 'Gossau', + 'Tutzing', + 'Bacaadweyn', + 'Kohat', + 'Durbuy', + 'Clarence', + 'Olesno', + 'Uberaba', + 'Andorra la Vella', + 'Ranong', + 'Meshgin Shahr', + 'Senigallia', + 'Siahkal', + 'Apatin', + 'West Park', + 'Fallsburg', + 'Bom Lugar', + 'Chassieu', + 'Chauki Hasan Chauki Makhdum', + 'Hejamadi', + 'Santa Maria do Para', + 'Al Jumayliyah', + 'Harij', + 'Reedley', + 'Wafangdian', + 'Suzak', + 'Hirekerur', + 'Muar', + 'Xarardheere', + 'Tando Muhammad Khan', + 'Uchturpan', + 'Kalba', + 'Clearlake', + 'Aulendorf', + 'Parur', + 'Joanopolis', + 'Belagola', + 'Chapeltown', + 'Shildon', + 'Uruoca', + 'Gevelsberg', + 'Hindoria', + 'Przasnysz', + 'Vigasio', + 'Alamedin', + 'Namrup', + 'Luenoufla', + 'Teofilo Otoni', + 'Gopalapuram', + 'Shahhat', + 'Mesagne', + 'Maisaka', + 'San Pedro Nonualco', + 'Panevezys', + 'Quibor', + 'Frutal', + 'Casa Branca', + 'Furukawamen', + 'Presidente Vargas', + 'Vadugappatti', + 'Nefta', + 'Maipu', + 'Opa-locka', + 'Maidstone', + 'Santo Tomas La Union', + 'Wheeling', + 'Kamakhyanagar', + 'Rozzano', + 'Boljoon', + 'Aconibe', + 'Huzhou', + 'Kongolo', + 'Tacloban', + 'Escalante', + 'Wadowice', + 'Machala', + 'Lagonoy', + 'Carnaubais', + 'Lubuk Sikaping', + 'Yupiltepeque', + 'Gwoza', + 'Padilla', + 'Dhari', + 'Refahiye', + 'Sevierville', + 'Fellbach', + 'Raub', + 'Plaine du Nord', + 'Tepexi de Rodriguez', + 'Garango', + 'Nakapiripirit', + 'Grosse Pointe Park', + 'Aerzen', + 'Lingcheng', + 'Mogliano Veneto', + 'Wiesloch', + 'Tokatippa', + 'Bezou', + 'Bela', + 'Wahlstedt', + 'Huichang', + 'Raghudebbati', + 'El Sobrante', + 'Badarpur', + 'Nakskov', + 'Kurwar', + 'Siquinala', + 'Berga', + 'Jamalpur', + 'Sidi Taibi', + 'Crema', + 'Tharad', + 'Colomba', + 'Marostica', + 'Temiskaming Shores', + 'Karwar', + 'Pallavaram', + 'Dokolo', + 'Nawai', + 'Cullman', + 'Waldkraiburg', + 'Yoichi', + 'Nizip', + 'Litomysl', + 'Huong Canh', + 'Appenweier', + 'Gimbi', + 'Hulst', + 'Beshkent Shahri', + 'Ban Wat Lak Hok', + 'San Martin De Porres', + 'Chuy', + 'Fallon', + 'Domerat', + 'Ruggell', + 'Subang', + 'Kapalong', + 'Shahrak-e Enqelab', + 'Finneytown', + 'Casamassima', + 'Forks', + 'Silvassa', + 'Pharr', + 'Ait Bouziyane', + 'Sirari', + 'Saint-Etienne', + 'Halol', + 'Aregua', + 'Calatagan', + 'Palmas', + 'Milajerd', + 'Ar Riqqah', + 'Waghausel', + 'Tlaxcala', + 'Pellezzano', + 'Segovia', + 'Villeneuve-les-Maguelone', + 'Narrogin', + 'Vila Pouca de Aguiar', + 'Madagh', + 'Tanambe', + 'Rheinau', + 'Tsubame', + 'Kozluk', + 'El Ghaziye', + 'Ciudad Insurgentes', + 'Zuchwil', + 'Shangzhou', + 'Morton', + 'Ieper', + 'Hefei', + 'Qiryat Ono', + 'West Vero Corridor', + 'Rumonge', + 'Suohe', + 'Juprelle', + 'Airmadidi', + 'Pecel', + 'Pa Sang', + 'Phra Nakhon Si Ayutthaya', + 'Castrolibero', + 'Koprivnica', + 'Formiga', + 'Petatlan', + 'Xuanzhou', + 'Navirai', + 'Weilburg', + 'Belgaum', + 'Sovetskoe', + 'Mwaline al Oued', + 'Fort-de-France', + 'Belkheir', + 'Sarapiqui', + 'Manandona', + 'Ernagudem', + 'Dunaivtsi', + 'Perama', + 'Eastmont', + 'Zeerust', + 'Sao Miguel', + 'La Crescenta-Montrose', + 'Kardzhali', + 'Faisalabad', + 'Asan', + 'Trenton', + 'Kuttyattur', + 'Ban Thap Kwang', + 'Sahtah', + 'Tila', + 'Ait Tamlil', + 'Tomball', + 'Pathiyanikunnu', + 'Markdorf', + 'Kitakami', + 'Zacatecoluca', + 'Thouars', + 'Belem', + 'Albuquerque', + 'Veenendaal', + 'Namutumba', + 'Chalette-sur-Loing', + 'Shilan', + 'Consolacion', + 'Brahmanpara', + 'Pola de Lena', + 'Gislaved', + 'Arlesheim', + 'Inuma', + 'Royton', + 'Sawadah', + 'La Magdalena Tlaltelulco', + 'Wenlan', + 'Portoviejo', + 'Jaguarao', + 'Atlit', + 'Yellareddi', + 'Chima', + 'Ribnica', + 'Loves Park', + 'Vera Cruz', + 'Sape', + 'Alauli', + 'Antargangi', + 'DeForest', + 'Lethbridge', + 'Fuerte Olimpo', + 'Kiskunlachaza', + 'Mahagama', + 'Ulubey', + 'Cacimba de Dentro', + 'Thames', + 'Junqueiropolis', + 'Ganeshpur', + 'Avrille', + 'Guediawaye', + 'Flores de Goias', + 'Nevele', + 'Osisioma', + 'Valtoha', + 'Alfonso Castaneda', + 'Embu-Guacu', + 'Ballia', + 'Elmont', + 'Adampur', + 'Montalto Uffugo', + 'Locate di Triulzi', + 'Jinju', + 'Xenia', + 'Douar Jwalla', + 'Zanzibar', + 'Ansbach', + 'Cafarnaum', + 'Riacho de Santana', + 'Magburaka', + 'Hampton', + 'Kumo', + 'Adana', + 'Salisbury', + 'Begoro', + 'Rehlingen-Siersburg', + 'Tambulig', + 'Howli', + 'Port Hueneme', + 'Saire', + 'Lishanpu', + 'Mykhailivka', + 'Xiazhai', + 'Sochaczew', + 'Minbya', + 'Vienne', + 'Shegaon', + 'Hajnowka', + 'Alberobello', + 'Karatas', + 'Nyamti', + 'Tatarikan', + 'Puqiancun', + 'Andrews', + 'Kraluv Dvur', + 'Calnali', + 'Anna Regina', + 'Voss', + 'Rietberg', + 'Baracoa', + 'Caglayancerit', + 'Larkhall', + 'Fairwood', + 'Sardoba', + 'Beni Fouda', + 'Lakshminarayanapuram', + 'Kandy', + 'Pigcawayan', + 'Yuchengcun', + 'Barru', + 'Wulflingen', + 'Koktokay', + 'Santa Rosa de Cabal', + 'Ramona', + 'Rucphen', + 'Jinghai', + 'Twist', + 'Alberdi', + 'Izola', + 'Bom Jardim', + 'Benito Juarez', + 'Grinon', + 'Mundgod', + 'Manito', + 'Ban Tha Luang Lang', + 'Colesberg', + 'Hinche', + 'Sidmant al Jabal', + 'Vilnius', + 'Mount Laurel', + 'Dyero', + 'Madari Hat', + 'Patian', + 'Guimbal', + 'Linnei', + 'Astaneh-ye Ashrafiyeh', + 'Delfzijl', + 'Walla Walla', + 'Saint-Louis du Sud', + 'Shilou', + 'Puchov', + 'Trossingen', + 'Spring Valley', + 'May Pen', + 'Hadleigh', + 'Hoffman Estates', + 'Hirschaid', + 'Dorridge', + 'Ampohibe', + 'Amapa', + 'Temirtau', + 'Lakhsetipet', + 'Raeren', + 'Liffre', + 'Hassi Fedoul', + 'West Grey', + 'Jaggayyapeta', + 'Akita', + 'Burien', + 'Kranjska Gora', + 'Urla', + 'Apapa', + 'Tucano', + 'Stouffville', + 'Bayt Ummar', + 'Chamrajnagar', + 'Cagwait', + 'Sorada', + 'Saoula', + 'Cavan Monaghan', + 'Somotillo', + 'Kabarnet', + 'Ash Shatrah', + 'Forest Hill', + 'Colleyville', + 'Nzega', + 'Kaufering', + 'Bikou', + 'Ksar Sbahi', + 'Muramvya', + 'Impfondo', + 'Dalachi', + 'Janze', + 'Baruun-Urt', + 'Scornicesti', + 'Caem', + 'Darayya', + 'Foya Tangia', + 'Robertsville', + 'Ban Ho Mae Salong', + "Bi'r al `Abd", + 'Giyani', + 'Dhamar', + 'Bni Frassen', + 'Brownhills', + 'Buckie', + 'Majagua', + 'Gainsborough', + 'Ayotoxco de Guerrero', + 'East Milton', + 'Sanchi', + 'Pogradec', + 'Yurimaguas', + 'Xingyi', + 'Tripunittura', + 'Lady Lake', + 'Safotu', + 'Gabu', + 'Pitt Meadows', + 'Sahalanona', + 'Vilaseca de Solcina', + 'Shinjuku', + 'Phnom Penh', + 'Espumoso', + 'Tiantoujiao', + 'Haukipudas', + 'Cuilapan de Guerrero', + 'Bafoussam', + 'Sunjiayan', + 'Bhanpura', + 'Sarangapuram', + 'Frontera', + 'Roh', + 'Prince Albert', + 'San Bernardino', + 'Maranello', + 'Ales', + 'Shenley Brook End', + 'Morayra', + 'Maba', + 'Pelileo', + 'Majiadiancun', + 'Broadstone', + 'Eski-Nookat', + 'Axapusco', + 'Najasa', + 'Iuiu', + 'Morieres-les-Avignon', + 'Capilla del Senor', + 'Amarante', + 'Bretigny-sur-Orge', + 'Jhaua', + 'Deulgaon Mahi', + 'Waterbury', + 'Euriapolis', + 'Thika', + 'Rothrist', + 'Tinja', + 'Umarizal', + 'Vihari', + 'Tepatlaxco', + 'Tan Son', + 'Anjangoveratra', + 'Yelimane', + 'Poulsbo', + 'Hutchinson', + 'Ittikelakunta', + 'Villena', + 'Mositai', + 'Roi Et', + 'Tulsipur', + 'Munhall', + 'Staufen im Breisgau', + 'Ciudad Hidalgo', + 'Naama', + 'Vratsa', + 'Sales Oliveira', + 'Xingtai', + 'Monteriggioni', + 'Grey Highlands', + 'Camoluk', + 'Ochsenhausen', + 'Winnenden', + 'Bogen', + 'Ambinanisakana', + 'Rolla', + 'New Philadelphia', + 'Varzelandia', + 'Can Tho', + 'Tepetlixpa', + 'Gursarai', + 'Grumo Appula', + 'Rudraprayag', + 'Kelheim', + 'Pionki', + 'Kedavur', + 'Frankfort', + 'Bishamagiri', + 'Shingbwiyang', + 'Montes Altos', + 'Longtangwan', + 'Veszprem', + 'Vadnais Heights', + 'Pozarevac', + 'Zyrardow', + 'Nis', + 'Julianadorp', + 'Beigang', + 'Wuhuang', + 'Ambalakindresy', + 'Yeosu', + 'Hisar', + 'Chithara', + 'Caldwell', + 'San Remigio', + 'Budamangalam', + 'Tamani', + 'Shetpe', + 'Velyki Luchky', + 'Jaramijo', + 'East Finchley', + 'Siedlce', + 'Tadigadapa', + 'Umuahia', + 'Jalgaon', + 'Schneeberg', + 'Chilonga', + 'Zhdanivka', + 'Bananeiras', + 'Nasaud', + 'Karlsdorf-Neuthard', + 'Culemborg', + 'Luanco', + 'Analamary', + 'Hinabangan', + 'Borgomanero', + 'Don Sak', + 'Newark', + 'Salqin', + 'Tafersit', + 'Siyang', + 'Goussainville', + 'Cermenate', + 'Gurlapeta', + 'Kiri', + 'Luis Gomes', + 'Partizanske', + 'Puducherry', + 'Strzegom', + 'Sai Mai', + 'Petlawad', + 'Vicenza', + 'Debbache el Hadj Douadi', + 'Amarpura', + 'Val-des-Monts', + 'Haicheng', + 'Kempton Park', + 'Raspur Patasia', + 'Killimangalam', + 'Thanatpin', + 'Oji', + 'Neno', + 'Kato Polemidia', + 'Anapurus', + 'Shakiso', + 'Diepholz', + 'Altrincham', + 'Nowa Ruda', + 'Sirnak', + 'Pettaivayttalai', + 'Schmelz', + 'Velur', + 'Wolverton', + 'Goygol', + 'Groesbeek', + 'Kannavam', + 'Bujumbura', + 'Ihorombe', + 'Hasami', + 'Revur', + 'Nosiarina', + 'Ciudad Guayana', + 'Economy', + 'Rayachoti', + 'Naciria', + 'Rompicherla', + 'Fairbanks', + 'Timbio', + 'Santo Amaro', + 'Vares', + 'Mafeteng', + "G'oliblar Qishlog'i", + 'Kumul', + 'Kline', + 'Alampur', + 'Moda', + 'Lhasa', + 'Sirhali Kalan', + 'Lower Providence', + 'Fuengirola', + 'Naigarhi', + 'Cottonwood Heights', + 'Aldeias Altas', + 'Sibinal', + 'Sulaco', + 'Pintuyan', + 'Sombrio', + 'Negrete', + 'Tsau', + 'Casa Nova', + 'Garupa', + 'Pencheng', + 'Langnau', + 'Gokinepalle', + 'General Conesa', + 'Bandamurlanka', + 'Morarano-Gara', + 'Tartu', + 'Hombrechtikon', + 'Wegberg', + 'Argelia', + 'Neubrandenburg', + 'Bardmoor', + 'Benipati', + 'Saint-Laurent-de-la-Salanque', + 'Sungai Penuh', + 'Iklod', + 'Alem Paraiba', + 'Uitgeest', + 'Kiranur', + 'Tinambac', + 'Ait Yaich', + 'Teniet el Abed', + 'Middlesex Centre', + 'Grand-Bassam', + 'Redhill', + 'Zhaotong', + 'Mohammad Shahr', + 'Cranendonck', + 'Vanzago', + 'Wiener Neustadt', + 'Telavi', + 'Fonte Boa', + 'Svalyava', + 'Valea Lupului', + 'Kitale', + 'Campos Gerais', + 'Dayr as Sanquriyah', + 'Atmakur', + 'Boynton Beach', + 'Can', + 'Kalongo', + 'Bad Voslau', + 'Trutnov', + 'Sunagawa', + 'Bandarbeyla', + 'San Pedro Ixtlahuaca', + 'Lonquimay', + 'Kangal', + 'Bornem', + 'Luanza', + 'Tacurong', + 'Totteridge', + 'Kumcati', + "Saint George's", + 'Uttoxeter', + 'Neath', + 'Cairns', + 'Kelkit', + 'Banlung', + 'Minneola', + 'Nedre Eiker', + 'Vembaditalam', + 'Ramanayyapeta', + 'New Ross', + 'Szamotuly', + 'Lapao', + 'Sakkamapatti', + 'Anjuna', + 'Lower Southampton', + 'Uch-Korgon', + 'Salovci', + 'Bhawanigarh', + 'Venceslau Bras', + 'Hosahalli', + 'Sibsagar', + 'Pazarcik', + 'Grigoriopol', + 'Shencottah', + 'Trenque Lauquen', + 'Oak Hills', + 'Magangue', + 'Beharona', + 'Hirakawacho', + 'Katsuura', + 'Belpukur', + 'Xingang', + 'Topoloveni', + 'Auxerre', + 'Caba', + 'Amioun', + 'Lake Oswego', + 'Nicoya', + 'Piprai', + 'Takhemaret', + 'Jula Buzarg', + 'Nam Som', + 'Wad Medani', + 'Dubasari', + 'Ricaurte', + 'Kocevje', + 'Torit', + 'Kanamadi', + 'Tasso', + 'Hermiston', + 'Seven Hills', + 'Ahmed Rachedi', + 'Maryville', + 'Lunen', + 'Marondry', + 'Ridgewood', + 'Chajul', + 'Norosi', + 'Amatitlan', + 'Williamstown', + 'Uliastay', + 'Lorsch', + 'Tekkekara', + 'Manisa', + 'Piatra Neamt', + 'Serta', + 'Padarti', + 'Agaro', + 'Boa Vista do Tupim', + 'Hariana', + 'Kobayashi', + 'Jessup', + 'Chahe', + 'Bournemouth', + 'Anajatuba', + 'Tugatog', + 'Vavur', + 'Ksar el Boukhari', + 'Masku', + 'Sungandiancun', + 'Candoni', + 'Mori', + 'Saint-Omer', + 'Alhaurin el Grande', + 'Atasu', + 'Rodos', + 'Bordj el Bahri', + 'Iheddadene', + 'Golden Hills', + 'Vista', + 'Cabrican', + 'Guara', + 'Sendarappatti', + 'Djouab', + 'Altea', + 'Omagari', + 'Firou', + 'Guinguineo', + 'Poytug`', + 'Chennamangalam', + 'Ramantali', + 'Penonome', + 'Tindivanam', + 'New River', + 'Pedda Tumbalam', + 'Hendersonville', + 'East Ridge', + 'The Colony', + 'Velizy-Villacoublay', + 'Koheda', + 'Ouaregou', + 'Sint-Truiden', + 'Zonnebeke', + 'Cartersville', + 'Serro', + 'Smorumnedre', + 'Sebt Bni Garfett', + 'Budhlada', + 'Malmesbury', + 'Kranj', + 'Union de San Antonio', + 'Batu Pahat', + 'Lian', + 'Baldeogarh', + 'Katsushika-ku', + 'Kiho', + 'Decines-Charpieu', + 'Jangy-Nookat', + 'Mudgal', + 'Pocoes', + 'Nanping', + 'Mota', + 'Maoussa', + 'Wolnzach', + 'Deysbrook', + 'Bad Essen', + 'Edogawa', + 'Cromer', + 'Brejetuba', + 'Wasilkow', + 'Beni Rached', + 'Balch Springs', + 'Folsom', + 'Nakonde', + 'Priboj', + 'Sigma', + 'Monywa', + 'Raismes', + 'Patut', + 'Kangar', + 'Kostrzyn', + 'Settsu', + 'Nandongcun', + 'Moosburg', + 'Vohitrandriana', + 'Sidi Bou Othmane', + 'Had Sahary', + 'Prescott Valley', + 'Burghausen', + 'Meco', + 'Bourbonnais', + 'San Maurizio Canavese', + 'Tequixquiac', + 'Wanyuan', + 'Hungund', + 'Huambo', + 'Sopelana', + 'Madaoua', + 'Mottola', + 'North Valley Stream', + 'Schaafheim', + 'Mataas Na Kahoy', + 'Maracay', + 'Deh Bakri', + 'Turkistan', + 'Spiesen-Elversberg', + 'Lansdale', + 'Sinha', + 'Haddonfield', + 'Mechanicstown', + 'San Vicente del Caguan', + 'Middleburg Heights', + 'New Lenox', + 'Mehlville', + 'Ambalakirajy', + 'Tak', + 'Willemstad', + 'Yato', + 'Carbondale', + 'Wakiso', + 'Farsala', + 'Gudarah', + 'Dorchester', + 'Ban Pang Mu', + 'Mosgiel', + 'Grafing bei Munchen', + 'Domkonda', + 'Challans', + 'Sulmona', + 'Ayirurpara', + 'Kaman', + 'Kumaramangalam', + 'Jiaojiazhuang', + 'Kelsterbach', + 'Saratoga', + 'Haledon', + 'Apodi', + 'Kuchinda', + 'Slidell', + 'Miamisburg', + 'Ponmana', + 'Icatu', + 'Oliva', + 'Cliza', + 'Ellenabad', + 'Pikit', + 'Fengning', + 'Erbaa', + 'Koraput', + 'Bhiloda', + 'Thenia', + 'Chinoz', + 'Zaysan', + 'Sarmastpur', + 'Lerici', + 'Wachtberg', + 'Kunjatturu', + 'Mzimba', + 'Oficina Maria Elena', + 'Paco do Lumiar', + 'Swansea', + 'Showt', + 'Batroun', + 'El Hadjar', + 'Childwall', + 'Xekong', + 'Anta', + 'Khem Karan Saray', + 'Panruti', + 'Yatton', + 'Avalpundurai', + 'Kudayattur', + 'Al Miqdadiyah', + 'Humanes de Madrid', + 'Tako', + 'As Sa`diyah', + 'Santana do Livramento', + 'Terrebonne', + 'Caninde de Sao Francisco', + 'Dombachcheri', + 'Podlehnik', + 'Andergrove', + 'Mima', + 'San Vendemiano', + 'Kindi', + 'Mengdingjie', + 'Xindi', + 'Kembhavi', + 'Verden', + 'Tarboro', + 'Manga', + 'Santa Rosa Jauregui', + 'Eiras', + 'Sapporo', + 'Puerto Octay', + 'Bothell West', + 'Almolonga', + 'Asperg', + 'Nanchong', + 'Honnali', + 'Orange City', + 'Ommangi', + 'Marilia', + 'Tobyl', + 'Coatesville', + 'Laualagaon', + 'Chinacota', + 'Breuillet', + 'Sinnamary', + 'Sun Lakes', + 'Tandur', + 'La Uruca', + 'Ann Arbor', + 'Boumerdes', + 'Cizre', + 'Bicheno', + 'Ebersberg', + 'Diangoute Kamara', + 'Havanur', + 'Barra Mansa', + 'Santa Margherita Ligure', + 'Bruchhausen-Vilsen', + 'Daet', + 'Dumai', + 'Lanta', + 'Cahama', + 'Rajgadh', + 'Kapurthala', + "Sidi Smai'il", + 'Bir el Ater', + 'Yesilkoy', + 'Yanshuiguan', + 'Arantangi', + 'Gaza', + 'Alajarvi', + 'Kondalahalli', + 'Salmanshahr', + 'Campo Maior', + 'Lago Vista', + 'Savignano sul Panaro', + 'Nong Kung Si', + 'Barnia', + 'Puttige', + 'Bracebridge', + 'Hipparga', + 'Bramsche', + 'Yangi Mirishkor', + 'Kimyogarlar', + 'Khemis el Khechna', + 'Maizieres-les-Metz', + 'Beyneu', + 'Eden Isle', + 'South Burlington', + 'Ramnagar Farsahi', + 'Vught', + 'Hexiwu', + 'Bhatpalli', + 'Kokoszki', + 'Rocky Mount', + 'Ano Syros', + 'Lutayan', + 'Ridgecrest', + 'Ranpur', + 'Ituango', + 'Kapelle-op-den-Bos', + 'Ladol', + 'Ambalabe', + 'Taramangalam', + 'Kumanovo', + 'Funyan Bira', + "Itaporanga d'Ajuda", + 'Samana', + 'Nij Khari', + 'Kafr Nabudah', + 'Dombasle-sur-Meurthe', + 'Jurbise', + 'Ciudad del Carmen', + 'Toon', + 'Big Spring', + 'Talpa de Allende', + 'Hinton', + 'Araouane', + 'Morungaba', + 'Stolac', + 'Lobez', + 'Toulon', + 'San Cesareo', + 'Three Lakes', + 'Jacareacanga', + 'Marne', + 'Fredericia', + 'Convencion', + 'Tarquinia', + 'Tepe-Korgon', + 'El Ghiate', + 'Tashkent', + 'Kirchlengern', + 'Roseau', + 'Nahan', + 'Givors', + 'Ikom', + 'Bumpe', + 'Shirhatti', + 'Reggiolo', + 'San Blas', + 'Cholet', + 'Dhirwas', + 'Bata', + 'Taito', + 'Jind', + 'Sahil', + 'La Fleche', + 'Ardakan', + 'Batangafo', + 'Kaza', + 'Bac', + 'Rio Jimenez', + 'Caycuma', + 'Kippax', + 'Denyekoro', + 'Ganganagar', + 'Mahalpur', + 'Sarpavaram', + 'Punarakh', + 'Cessnock', + 'San Fausto de Campcentellas', + 'Scandicci', + 'Valle Vista', + 'Manaoag', + 'Noe', + 'Penha', + 'Couvin', + 'Bakun', + 'Porto Uniao', + 'Bolanos de Calatrava', + 'Sun City West', + 'Orzinuovi', + 'Carrara', + 'Tilhar', + 'Prudentopolis', + 'Ciudad Real', + 'Ermont', + 'Sulop', + 'Brive-la-Gaillarde', + 'El Estor', + 'Tirschenreuth', + 'Foum el Anser', + 'Ban Pong Yaeng Nai', + 'Lissegazoun', + 'Gucheng', + 'Louth', + 'Minamiise', + 'Janow Lubelski', + 'Zacualpan', + 'Bobrynets', + 'Bel Imour', + 'El Valle del Espiritu Santo', + 'Vodiriana', + 'Ad Dujayl', + 'Puerto La Cruz', + 'Cadiz', + 'Basse Santa Su', + 'Longuenesse', + 'Kalamula', + 'Ponca City', + 'Lebowakgomo', + 'Chittandikavundanur', + 'Villarrobledo', + 'Settivaripalle', + 'Maksi', + 'Whitnash', + 'Parintins', + 'Mykolaiv', + 'Somavarappatti', + 'Az Zaydiyah', + 'Chop', + 'Pingcha', + 'Gojo', + 'Anicuns', + "Vaprio d'Adda", + 'Ailan Mubage', + 'Gernsheim', + 'Ozamiz City', + 'Bamendjou', + 'Mantingan', + 'Centre Wellington', + 'Somolu', + 'Ijevan', + 'Gurun', + 'Sonhauli', + 'Hoshoot', + 'Lippstadt', + 'Payshamba Shahri', + 'Pirapora', + 'Mariyadau', + 'Touba', + 'Cavallino', + 'Broomall', + 'Saint-Paul-Trois-Chateaux', + 'Pirangut', + 'Monte San Savino', + 'Addis Ababa', + 'Wenzhou', + 'Hongshui', + 'Bamora', + 'Dadeldhura', + 'Barrhead', + 'Qizilhacili', + 'Cenon', + 'Yany Kapu', + 'Lauderdale Lakes', + 'Franklin Farm', + 'Licheng', + 'Lacchiarella', + 'Oshwe', + 'Blantyre', + 'Birpur', + 'Bucha', + 'San Manuel', + 'Laguna Beach', + 'Donna', + 'Clute', + 'Achchampeta', + 'Rajapalaiyam', + 'Koszeg', + 'Venancio Aires', + 'Romang', + 'Four Corners', + 'Sturgeon Bay', + 'Tuscaloosa', + 'Goianira', + 'Chattamangalam', + 'Tadotsu', + 'Sindhnur', + 'Ouda-daito', + 'Harmah', + 'Nordhausen', + 'Had Laaounate', + 'Dolny Kubin', + 'Penarth', + 'Androka', + 'Cajibio', + 'Molesey', + 'Zhanjia', + 'Abuzar-e Ghaffari', + 'Sayaxche', + 'Austin', + 'Pipalrawan', + 'Chillan', + 'Baena', + 'Langenberg', + 'Mykolaivka', + 'Ferentino', + 'Biddulph', + 'Harnosand', + 'Koekelberg', + 'Little Hulton', + 'Al Qitena', + 'Lewisville', + 'Turaiyur', + 'Chakkuvarakal', + 'Tres Rios', + 'Banabuiu', + 'Muzaffargarh', + 'Erragondapalem', + 'Ghambiraopet', + 'Okara', + 'Ilioupoli', + 'Paddhari', + 'Nariar', + 'Longjing', + "M'Sila", + 'Bongor', + 'Viterbo', + 'Vlasenica', + 'Montalvania', + 'Parali', + 'Spa', + 'Westford', + 'Muttam', + 'Raamsdonksveer', + 'Garoua Boulai', + 'Batarasa', + 'Vasylkivka', + 'Estevan', + 'Luanda', + 'MacArthur', + 'Zulpich', + 'Phulbani', + 'Ifarantsa', + 'Umbita', + 'Korogwe', + 'Ilhabela', + 'Baronissi', + 'Kalyvia Thorikou', + 'Lahfayr', + 'Ba Ria', + 'Bronderslev', + 'Kaynasli', + 'Usmate Velate', + 'Hirosaki', + 'Quedgeley', + 'Zawyat ech Cheikh', + 'Soledad Atzompa', + 'Ambohitromby', + 'Joao Teves', + 'Siddipet', + 'Bitkine', + 'Bellshill', + 'Feke', + 'Grass Valley', + 'Ashton in Makerfield', + 'Punjai Puliyampatti', + 'Lobito', + 'Bernalda', + 'Loudonville', + 'Asbury Lake', + 'Gyoda', + 'Artik', + 'Rio Rancho', + 'Roissy-en-Brie', + 'Garches', + 'Chemini', +]; + +export const maxStringLength = 49; diff --git a/drizzle-seed/src/datasets/companyNameSuffixes.ts b/drizzle-seed/src/datasets/companyNameSuffixes.ts new file mode 100644 index 000000000..1ce31a9c3 --- /dev/null +++ b/drizzle-seed/src/datasets/companyNameSuffixes.ts @@ -0,0 +1,29 @@ +export default [ + 'LLC', + 'Ltd', + 'Inc.', + 'Corp.', + 'PLC', + 'GmbH', + 'AG', + 'S.A.', + 'S.p.A.', + 'SARL', + 'B.V.', + 'N.V.', + 'Oy', + 'AB', + 'AS', + 'Pty Ltd', + 'K.K.', + 'JSC', + 'Ltda.', + 'Pvt Ltd', + 'Sdn Bhd', + 'A/S', + 'SAOG', + 'Co.', + 'SCC', +]; + +export const maxStringLength = 7; diff --git a/drizzle-seed/src/datasets/countries.ts b/drizzle-seed/src/datasets/countries.ts new file mode 100644 index 000000000..eb1c001d0 --- /dev/null +++ b/drizzle-seed/src/datasets/countries.ts @@ -0,0 +1,173 @@ +/** + * The original source for countries data was taken from https://www.kaggle.com/datasets/manusmitajha/countrydatacsv + * We've excluded a few countries and their cities from this list because we don't think they should ever appear in any list + */ +export default [ + 'Afghanistan', + 'Albania', + 'Algeria', + 'Angola', + 'Antigua and Barbuda', + 'Argentina', + 'Armenia', + 'Australia', + 'Austria', + 'Azerbaijan', + 'Bahamas', + 'Bahrain', + 'Bangladesh', + 'Barbados', + 'Belgium', + 'Belize', + 'Benin', + 'Bhutan', + 'Bolivia', + 'Bosnia and Herzegovina', + 'Botswana', + 'Brazil', + 'Brunei', + 'Bulgaria', + 'Burkina Faso', + 'Burundi', + 'Cambodia', + 'Cameroon', + 'Canada', + 'Cape Verde', + 'Central African Republic', + 'Chad', + 'Chile', + 'China', + 'Colombia', + 'Comoros', + 'Congo, Dem. Rep.', + 'Congo, Rep.', + 'Costa Rica', + "Cote d'Ivoire", + 'Croatia', + 'Cyprus', + 'Czech Republic', + 'Denmark', + 'Dominican Republic', + 'Ecuador', + 'Egypt', + 'El Salvador', + 'Equatorial Guinea', + 'Eritrea', + 'Estonia', + 'Fiji', + 'Finland', + 'France', + 'Gabon', + 'Gambia', + 'Georgia', + 'Germany', + 'Ghana', + 'Greece', + 'Grenada', + 'Guatemala', + 'Guinea', + 'Guinea-Bissau', + 'Guyana', + 'Haiti', + 'Hungary', + 'Iceland', + 'India', + 'Indonesia', + 'Iran', + 'Iraq', + 'Ireland', + 'Israel', + 'Italy', + 'Jamaica', + 'Japan', + 'Jordan', + 'Kazakhstan', + 'Kenya', + 'Kiribati', + 'Kuwait', + 'Kyrgyz Republic', + 'Lao', + 'Latvia', + 'Lebanon', + 'Lesotho', + 'Liberia', + 'Libya', + 'Lithuania', + 'Luxembourg', + 'Macedonia, FYR', + 'Madagascar', + 'Malawi', + 'Malaysia', + 'Maldives', + 'Mali', + 'Malta', + 'Mauritania', + 'Mauritius', + 'Micronesia, Fed. Sts.', + 'Moldova', + 'Mongolia', + 'Montenegro', + 'Morocco', + 'Mozambique', + 'Myanmar', + 'Namibia', + 'Nepal', + 'Netherlands', + 'New Zealand', + 'Niger', + 'Nigeria', + 'Norway', + 'Oman', + 'Pakistan', + 'Panama', + 'Paraguay', + 'Peru', + 'Philippines', + 'Poland', + 'Portugal', + 'Qatar', + 'Romania', + 'Rwanda', + 'Samoa', + 'Saudi Arabia', + 'Senegal', + 'Serbia', + 'Seychelles', + 'Sierra Leone', + 'Singapore', + 'Slovak Republic', + 'Slovenia', + 'Solomon Islands', + 'South Africa', + 'South Korea', + 'Spain', + 'Sri Lanka', + 'St. Vincent and the Grenadines', + 'Sudan', + 'Suriname', + 'Sweden', + 'Switzerland', + 'Tajikistan', + 'Tanzania', + 'Thailand', + 'Timor-Leste', + 'Togo', + 'Tonga', + 'Tunisia', + 'Turkey', + 'Turkmenistan', + 'Uganda', + 'Ukraine', + 'United Arab Emirates', + 'United Kingdom', + 'United States', + 'Uruguay', + 'Uzbekistan', + 'Vanuatu', + 'Venezuela', + 'Vietnam', + 'Yemen', + 'Zambia', +]; + +export const maxStringLength = 30; diff --git a/drizzle-seed/src/datasets/emailDomains.ts b/drizzle-seed/src/datasets/emailDomains.ts new file mode 100644 index 000000000..ea323ed41 --- /dev/null +++ b/drizzle-seed/src/datasets/emailDomains.ts @@ -0,0 +1,26 @@ +export default [ + 'gmail.com', + 'yahoo.com', + 'outlook.com', + 'msn.com', + 'hotmail.com', + 'aol.com', + 'hotmail.co.uk', + 'hotmail.fr', + 'yahoo.fr', + 'wanadoo.fr', + 'orange.fr', + 'comcast.net', + 'yahoo.co.uk', + 'yahoo.com.br', + 'yahoo.co.in', + 'live.com', + 'rediffmail.com', + 'free.fr', + 'gmx.de', + 'web.de', + 'ymail.com', + 'libero.it', +]; + +export const maxStringLength = 14; diff --git a/drizzle-seed/src/datasets/firstNames.ts b/drizzle-seed/src/datasets/firstNames.ts new file mode 100644 index 000000000..d719aa4e2 --- /dev/null +++ b/drizzle-seed/src/datasets/firstNames.ts @@ -0,0 +1,30281 @@ +/** + * The original source for first names data was taken from https://www.kaggle.com/datasets/kaggle/us-baby-names?select=StateNames.csv + */ +export default [ + 'Robert', + 'John', + 'Michael', + 'David', + 'James', + 'Richard', + 'Christopher', + 'William', + 'Daniel', + 'Mark', + 'Thomas', + 'Jose', + 'Joseph', + 'Matthew', + 'Jason', + 'Andrew', + 'Joshua', + 'Steven', + 'Anthony', + 'Jonathan', + 'Angel', + 'Ryan', + 'Kevin', + 'Jacob', + 'Nicholas', + 'Brandon', + 'Justin', + 'Charles', + 'Gary', + 'Paul', + 'Scott', + 'George', + 'Christian', + 'Eric', + 'Brian', + 'Alexander', + 'Ronald', + 'Jayden', + 'Juan', + 'Edward', + 'Noah', + 'Diego', + 'Donald', + 'Ethan', + 'Kyle', + 'Peter', + 'Jeffrey', + 'Luis', + 'Timothy', + 'Nathan', + 'Tyler', + 'Frank', + 'Stephen', + 'Dennis', + 'Larry', + 'Jesus', + 'Kenneth', + 'Austin', + 'Adrian', + 'Adam', + 'Sebastian', + 'Gregory', + 'Carlos', + 'Aiden', + 'Gabriel', + 'Isaac', + 'Zachary', + 'Julian', + 'Benjamin', + 'Liam', + 'Billy', + 'Miguel', + 'Mason', + 'Aaron', + 'Mike', + 'Dylan', + 'Sean', + 'Alejandro', + 'Bryan', + 'Jordan', + 'Cody', + 'Jeremy', + 'Samuel', + 'Harry', + 'Victor', + 'Joe', + 'Eduardo', + 'Isaiah', + 'Jorge', + 'Logan', + 'Elijah', + 'Bruce', + 'Patrick', + 'Jerry', + 'Jesse', + 'Lawrence', + 'Steve', + 'Walter', + 'Harold', + 'Arthur', + 'Lucas', + 'Francisco', + 'Douglas', + 'Oscar', + 'Craig', + 'Alexis', + 'Todd', + 'Randy', + 'Alan', + 'Raymond', + 'Damian', + 'Willie', + 'Albert', + 'Ricardo', + 'Louis', + 'Luke', + 'Edgar', + 'Travis', + 'Evan', + 'Ricky', + 'Aidan', + 'Jack', + 'Jeff', + 'Jimmy', + 'Manuel', + 'Oliver', + 'Mateo', + 'Johnny', + 'Henry', + 'Cristian', + 'Terry', + 'Dominic', + 'Cameron', + 'Gerald', + 'Caleb', + 'Christop', + 'Bobby', + 'Alex', + 'Gavin', + 'Shawn', + 'Jackson', + 'Ivan', + 'Keith', + 'Antonio', + 'Vincent', + 'Philip', + 'Chad', + 'Alfred', + 'Eugene', + 'Erik', + 'Martin', + 'Omar', + 'Chris', + 'Stanley', + 'Sergio', + 'Francis', + 'Mario', + 'Fernando', + 'Taylor', + 'Herbert', + 'Santiago', + 'Nathaniel', + 'Cesar', + 'Barry', + 'Trevor', + 'Dustin', + 'Hunter', + 'Roger', + 'Andres', + 'Javier', + 'Bernard', + 'Jim', + 'Ian', + 'Wayne', + 'Leonardo', + 'Giovanni', + 'Josiah', + 'Jeremiah', + 'Glenn', + 'Hector', + 'Roberto', + 'Rodney', + 'Howard', + 'Eli', + 'Xavier', + 'Jaxon', + 'Levi', + 'Derek', + 'Danny', + 'Jared', + 'Landon', + 'Ralph', + 'Ruben', + 'Gael', + 'Connor', + 'Tommy', + 'Tony', + 'Marc', + 'Wyatt', + 'Rick', + 'Carter', + 'Ayden', + 'Tim', + 'Roy', + 'Owen', + 'Greg', + 'Joel', + 'Leonard', + 'Frederick', + 'Russell', + 'Jon', + 'Jaden', + 'Jeffery', + 'Irving', + 'Erick', + 'Darren', + 'Dale', + 'Carl', + 'Brayden', + 'Ronnie', + 'Gerardo', + 'Pedro', + 'Raul', + 'Elias', + 'Chase', + 'Alberto', + 'Troy', + 'Tom', + 'Axel', + 'Julio', + 'Emmanuel', + 'Edwin', + 'Norman', + 'Marcus', + 'Fred', + 'Bill', + 'Jake', + 'Marco', + 'Leo', + 'Rafael', + 'Armando', + 'Jace', + 'Garrett', + 'Jaime', + 'Earl', + 'Shane', + 'Cole', + 'Phillip', + 'Seth', + 'Corey', + 'Nicolas', + 'Randall', + 'Hayden', + 'Abraham', + 'Grayson', + 'Tristan', + 'Cory', + 'Josue', + 'Andy', + 'Warren', + 'Roman', + 'Devin', + 'Salvador', + 'Shaun', + 'Spencer', + 'Infant', + 'Ryder', + 'Dillon', + 'Max', + 'Salvatore', + 'Bradley', + 'Seymour', + 'Arturo', + 'Iker', + 'Dean', + 'Milton', + 'Sidney', + 'Gustavo', + 'Alfredo', + 'Blake', + 'Clarence', + 'Brody', + 'Enrique', + 'Brett', + 'Colton', + 'Dan', + 'Brendan', + 'Charlie', + 'Darrell', + 'Hudson', + 'Ezra', + 'Emiliano', + 'Ashton', + 'Darryl', + 'Dave', + 'Nolan', + 'Theodore', + 'Casey', + 'Colin', + 'Easton', + 'Caden', + 'Marcos', + 'Cooper', + 'Mitchell', + 'Morris', + 'Don', + 'Eddie', + 'Jay', + 'Marvin', + 'Kaden', + 'Curtis', + 'Lance', + 'Gerard', + 'Israel', + 'Ramon', + 'Rickey', + 'Alec', + 'Carson', + 'Ernesto', + 'Riley', + 'Kai', + 'Ezekiel', + 'Yahir', + 'Dakota', + 'Ron', + 'Bob', + 'Saul', + 'Kayden', + 'Adan', + 'Fabian', + 'Maxwell', + 'Allen', + 'Micheal', + 'Parker', + 'Micah', + 'Miles', + 'Gilbert', + 'Grant', + 'Malik', + 'Abel', + 'Darrin', + 'Johnathan', + 'Jase', + 'Kaleb', + 'Ray', + 'Jaxson', + 'Brent', + 'Wesley', + 'Tanner', + 'Chester', + 'Bryce', + 'Lincoln', + 'Preston', + 'Maximiliano', + 'Jerome', + 'Sam', + 'Ernest', + 'Bentley', + 'Colby', + 'Elmer', + 'Moises', + 'Joaquin', + 'Arnold', + 'Stuart', + 'Murray', + 'Asher', + 'Andre', + 'Neil', + 'Allan', + 'Brady', + 'Brad', + 'Maximus', + 'Dalton', + 'Jonah', + 'Kim', + 'Kirk', + 'Bryson', + 'Kurt', + 'Angelo', + 'Rene', + 'Jimmie', + 'Emilio', + 'Damien', + 'Harvey', + 'Moshe', + 'Derrick', + 'Kelly', + 'Franklin', + 'Rodrigo', + 'Woodrow', + 'Leon', + 'Esteban', + 'Hugo', + 'Clayton', + 'Guadalupe', + 'Darin', + 'Pablo', + 'Luca', + 'Ken', + 'Ismael', + 'Leroy', + 'Guillermo', + 'Tracy', + 'Melvin', + 'Lorenzo', + 'Clifford', + 'Hugh', + 'Mathew', + 'Jameson', + 'Billie', + 'Nelson', + 'Herman', + 'Ira', + 'Jamie', + 'Alexande', + 'Lester', + 'Glen', + 'Damon', + 'Emanuel', + 'Maverick', + 'Braxton', + 'Zayden', + 'Dominick', + 'Irwin', + 'Rudy', + 'Calvin', + 'Julius', + 'Jermaine', + 'Jakob', + 'Donovan', + 'Lee', + 'Shaquille', + 'Gordon', + 'Peyton', + 'Duane', + 'Declan', + 'Jalen', + 'Jude', + 'Tyrone', + 'Bret', + 'Gene', + 'Felix', + 'Guy', + 'Devon', + 'Cruz', + 'Rylan', + 'Clinton', + 'Jonathon', + 'Kaiden', + 'Kingston', + 'Kristopher', + 'Felipe', + 'Collin', + 'Alfonso', + 'Rodolfo', + 'King', + 'Everett', + 'Chance', + 'Johnnie', + 'Clyde', + 'Weston', + 'Karl', + 'Camden', + 'Maddox', + 'Bryant', + 'Gage', + 'Dwayne', + 'Shannon', + 'Gilberto', + 'Braden', + 'Lewis', + 'Greyson', + 'Rudolph', + 'Floyd', + 'Jayce', + 'Harrison', + 'Brayan', + 'Cayden', + 'Reginald', + 'Jaiden', + 'Brantley', + 'Hyman', + 'Perry', + 'Kent', + 'Alvin', + 'Cade', + 'Doug', + 'Romeo', + 'Jax', + 'Silas', + 'Ty', + 'Emmett', + 'Jackie', + 'Leslie', + 'Vernon', + 'Jessie', + 'Lloyd', + 'Cecil', + 'Roland', + 'Ted', + 'Amir', + 'Cash', + 'Gregg', + 'Uriel', + 'Donnie', + 'Noel', + 'Mauricio', + 'Dana', + 'Osvaldo', + 'Sawyer', + 'Rogelio', + 'Terrence', + 'Conner', + 'Darius', + 'Chaim', + 'Maurice', + 'Male', + 'Malachi', + 'Issac', + 'Ramiro', + 'Zane', + 'Jaylen', + 'Dawson', + 'Willard', + 'Randolph', + 'Wilbur', + 'Noe', + 'Huey', + 'Sammy', + 'Lonnie', + 'Morton', + 'Chandler', + 'Elliot', + 'Geoffrey', + 'Robin', + 'Muhammad', + 'Wallace', + 'Matt', + 'Drew', + 'Bailey', + 'Orlando', + 'Jasper', + 'Tyrese', + 'Matteo', + 'Leonel', + 'Simon', + 'Braylon', + 'Corbin', + 'Jayceon', + 'Gunner', + 'Dante', + 'Daryl', + 'Bennett', + 'Ulises', + 'Efrain', + 'Drake', + 'Rolando', + 'Lukas', + 'Arian', + 'Trenton', + 'Humberto', + 'Ryker', + 'Aldo', + 'Landen', + 'Xander', + 'Dwight', + 'Alvaro', + 'Sheldon', + 'Freddie', + 'Vicente', + 'Avery', + 'Marty', + 'Irvin', + 'Ariel', + 'Lane', + 'Nestor', + 'Chuck', + 'Dominique', + 'Baby', + 'Kerry', + 'Enzo', + 'Nick', + 'Yosef', + 'Edmund', + 'Oswaldo', + 'Kobe', + 'Aden', + 'Clair', + 'Freddy', + 'Karter', + 'Stacy', + 'Byron', + 'Roosevelt', + 'Claude', + 'Marion', + 'Thiago', + 'Colt', + 'Sol', + 'Lamont', + 'Neal', + 'August', + 'Cason', + 'Kason', + 'Reynaldo', + 'Malcolm', + 'Beau', + 'Ignacio', + 'Kareem', + 'Laurence', + 'Finn', + 'Domingo', + 'Rigoberto', + 'Solomon', + 'Aaden', + 'Case', + 'Horace', + 'Griffin', + 'Rocco', + 'Pete', + 'Ross', + 'Skyler', + 'Kenny', + 'Tucker', + 'Morgan', + 'Forrest', + 'Timmy', + 'Clint', + 'Garry', + 'Elwood', + 'Knox', + 'Elian', + 'Zion', + 'Trey', + 'Vito', + 'Jamel', + 'Junior', + 'Roderick', + 'Brooks', + 'Isidore', + 'Kelvin', + 'Ali', + 'Octavio', + 'Luther', + 'Milo', + 'Jett', + 'Unknown', + 'Milan', + 'Nickolas', + 'German', + 'Terence', + 'Virgil', + 'Conor', + 'Isaias', + 'Cristopher', + 'Jayson', + 'Brenden', + 'Joey', + 'Tevin', + 'Branden', + 'Arjun', + 'Carmine', + 'Wendell', + 'Judah', + 'Nikolas', + 'Izaiah', + 'Dick', + 'Jairo', + 'Giovani', + 'Ervin', + 'Graham', + 'Trent', + 'Tyson', + 'Cedric', + 'Elliott', + 'Myles', + 'Kameron', + 'Jaylon', + 'Hubert', + 'Grady', + 'Homer', + 'Quinn', + 'Payton', + 'Bennie', + 'River', + 'Dexter', + 'Emil', + 'Jamal', + 'Orion', + 'Alonzo', + 'Paxton', + 'Ashley', + 'Desmond', + 'Waylon', + 'Patsy', + 'Agustin', + 'Shimon', + 'Jarrod', + 'Rex', + 'Pat', + 'Rhett', + 'Benny', + 'Adriel', + 'Moses', + 'Daquan', + 'Johan', + 'Adolfo', + 'Otis', + 'Kadeem', + 'Jody', + 'Wilson', + 'Pasquale', + 'Kendrick', + 'Alonso', + 'Ben', + 'Ezequiel', + 'Jair', + 'Tomas', + 'Zackary', + 'Dane', + 'Nasir', + 'Alton', + 'Tristen', + 'Wilfredo', + 'Lyle', + 'Rowan', + 'Deandre', + 'Mordechai', + 'Mohamed', + 'Khalil', + 'Maximilian', + 'Devante', + 'Wade', + 'Norbert', + 'Yehuda', + 'Dallas', + 'Menachem', + 'Anderson', + 'Jonas', + 'Zachery', + 'Zaiden', + 'Giovanny', + 'Clifton', + 'Tommie', + 'Ronaldo', + 'Major', + 'Barrett', + 'Darnell', + 'Keegan', + 'Randal', + 'Aarav', + 'Burton', + 'Terrance', + 'Reid', + 'Fredrick', + 'Bobbie', + 'Ace', + 'Kyler', + 'Yoel', + 'Earnest', + 'Toby', + 'Merle', + 'Archer', + 'Santos', + 'Nico', + 'Beckett', + 'Yisroel', + 'Nehemiah', + 'Lynn', + 'Holden', + 'Matias', + 'Rufus', + 'Mohammed', + 'Hayes', + 'Marshall', + 'Trinidad', + 'Valentin', + 'Heath', + 'Weldon', + 'Ed', + 'Lionel', + 'Jaret', + 'Aron', + 'Bernardo', + 'Zander', + 'Devonte', + 'Meyer', + 'Ulysses', + 'Myron', + 'Lowell', + 'Linwood', + 'Rocky', + 'Phoenix', + 'Antoine', + 'Cyrus', + 'Demarcus', + 'Bruno', + 'Titus', + 'Madison', + 'Jarod', + 'Caiden', + 'Kash', + 'Jarvis', + 'Clay', + 'Notnamed', + 'Doyle', + 'Dallin', + 'Atticus', + 'Orville', + 'Nixon', + 'Loren', + 'Wilbert', + 'Karson', + 'Brennan', + 'Brittany', + 'Marlon', + 'Gonzalo', + 'Carlton', + 'Cary', + 'Marquis', + 'Amari', + 'Rohan', + 'Terrell', + 'Gianni', + 'Johnathon', + 'Jan', + 'Boston', + 'Ibrahim', + 'Yitzchok', + 'Jean', + 'Camron', + 'Ronny', + 'Porter', + 'Adonis', + 'Alessandro', + 'Stefan', + 'Giancarlo', + 'Clark', + 'Lupe', + 'Edgardo', + 'Scotty', + 'Messiah', + 'Benito', + 'Zachariah', + 'Kristian', + 'Bodhi', + 'Ronan', + 'Emerson', + 'Wilfred', + 'Heriberto', + 'Davis', + 'Stewart', + 'Efren', + 'Brock', + 'Christophe', + 'Sammie', + 'Kade', + 'Denis', + 'Ernie', + 'Kayson', + 'Quincy', + 'Abe', + 'Estevan', + 'Jamari', + 'Mohammad', + 'Kendall', + 'Demetrius', + 'Walker', + 'Shlomo', + 'Louie', + 'Kody', + 'Valentino', + 'Jaheim', + 'Vince', + 'Frankie', + 'Aubrey', + 'Quinton', + 'Royce', + 'Ari', + 'Abram', + 'Jessica', + 'Curt', + 'Bart', + 'Daren', + 'Braylen', + 'Alexandro', + 'Lamar', + 'Kasen', + 'Willis', + 'Vihaan', + 'Delbert', + 'Triston', + 'Yakov', + 'Courtney', + 'Niko', + 'Pierre', + 'Jaquan', + 'Braulio', + 'Santino', + 'Quentin', + 'Dario', + 'Dusty', + 'Neymar', + 'Bridger', + 'Tyrell', + 'Bertram', + 'Raymundo', + 'Isiah', + 'Reed', + 'Archie', + 'Prince', + 'Rory', + 'Davon', + 'Stacey', + 'Bradford', + 'Nikolai', + 'Kian', + 'Kase', + 'Casen', + 'Dion', + 'Isai', + 'Armand', + 'Percy', + 'Emily', + 'Leland', + 'Sterling', + 'Yandel', + 'Olin', + 'Sanford', + 'Marlin', + 'Denzel', + 'Mekhi', + 'Elbert', + 'Braydon', + 'Dewey', + 'Dudley', + 'Shmuel', + 'Jadon', + 'Braeden', + 'Yair', + 'Rob', + 'Mickey', + 'Monty', + 'Hannah', + 'Luciano', + 'Remington', + 'Akeem', + 'Julien', + 'Carmen', + 'Jensen', + 'Johnie', + 'Mack', + 'Rickie', + 'Javon', + 'Misael', + 'Elvis', + 'Eden', + 'Jess', + 'Phil', + 'Malakai', + 'Melvyn', + 'Rod', + 'Arnulfo', + 'Cohen', + 'Fidel', + 'Levar', + 'Dominik', + 'Grover', + 'Yaakov', + 'Landyn', + 'Colten', + 'Dorian', + 'Keaton', + 'Loyd', + 'Brodie', + 'Otto', + 'Eliezer', + 'Ahmed', + 'Shelby', + 'Hernan', + 'Odin', + 'Regis', + 'Jaydon', + 'Uriah', + 'Remy', + 'Tariq', + 'Sonny', + 'Carroll', + 'Xavi', + 'Christia', + 'Marcel', + 'Brendon', + 'Kellan', + 'Bowen', + 'Unnamed', + 'Scottie', + 'Justice', + 'Kurtis', + 'Stephan', + 'Daxton', + 'Coby', + 'Jadiel', + 'Dashawn', + 'Amare', + 'Cannon', + 'Blaine', + 'Tate', + 'Talmadge', + 'Nathanael', + 'Adolph', + 'Talan', + 'Tobias', + 'Sylvester', + 'Tadeo', + 'Darrel', + 'Winston', + 'Garland', + 'Meir', + 'Kory', + 'Joseluis', + 'Wilburn', + 'Rusty', + 'Avraham', + 'Ayaan', + 'Theo', + 'Mathias', + 'Marcelo', + 'Dino', + 'Kolby', + 'Cael', + 'Tzvi', + 'Davion', + 'Aryan', + 'Rhys', + 'Cain', + 'Duke', + 'Pierce', + 'Landry', + 'Tristin', + 'Emma', + 'Zackery', + 'Antone', + 'Rayan', + 'Hendrix', + 'Lucca', + 'Luka', + 'Jarrett', + 'Miguelangel', + 'Rodger', + 'Kevon', + 'Jacoby', + 'Damion', + 'Maximo', + 'Robbie', + 'Jovanny', + 'Trace', + 'Gunnar', + 'Kieran', + 'Cristobal', + 'Kris', + 'Ellis', + 'Matthias', + 'Eloy', + 'Sarah', + 'Donny', + 'Donte', + 'Ronin', + 'Reece', + 'Alijah', + 'Zayne', + 'Jamarion', + 'Laverne', + 'Gregorio', + 'Kellen', + 'Nathen', + 'Gideon', + 'Rosario', + 'Erwin', + 'Jakub', + 'Normand', + 'Rey', + 'Trevon', + 'Stetson', + 'Carmelo', + 'Rashad', + 'Tod', + 'Elizabeth', + 'Harley', + 'Darian', + 'Scot', + 'Tavon', + 'Keven', + 'Merlin', + 'Nash', + 'Deangelo', + 'Raiden', + 'Jahir', + 'Isidro', + 'Davian', + 'Raekwon', + 'Alphonse', + 'Reese', + 'Abigail', + 'Deshawn', + 'Ahmad', + 'Conrad', + 'Truman', + 'Kolton', + 'Ryland', + 'Jamaal', + 'Abdiel', + 'Aditya', + 'Keenan', + 'Brycen', + 'Thaddeus', + 'Austen', + 'Leonidas', + 'Raphael', + 'Jovani', + 'Brenton', + 'Jasmine', + 'Thurman', + 'Russel', + 'Emory', + 'Cornelius', + 'Roel', + 'Xzavier', + 'Jovanni', + 'Zev', + 'Eldon', + 'Deven', + 'Kamden', + 'Eliseo', + 'Franco', + 'Duncan', + 'Anton', + 'Amarion', + 'Deron', + 'Sage', + 'Babyboy', + 'Fredy', + 'Russ', + 'Omarion', + 'Ryne', + 'Jovany', + 'Camilo', + 'Stan', + 'Cullen', + 'Armani', + 'Adrien', + 'Royal', + 'Kane', + 'Ishaan', + 'Spenser', + 'Antwan', + 'Stephon', + 'Juanpablo', + 'Tiffany', + 'Garret', + 'Jagger', + 'Will', + 'Nigel', + 'Chadwick', + 'Casimir', + 'Abdirahman', + 'Odell', + 'Keanu', + 'Josh', + 'Mortimer', + 'Raheem', + 'Jordon', + 'Nery', + 'Monte', + 'Jaxton', + 'Deacon', + 'Reuben', + 'Carlo', + 'Skylar', + 'Jamarcus', + 'Robby', + 'Jaycob', + 'Kristofer', + 'Buddy', + 'Korbin', + 'Arlo', + 'Jennifer', + 'Rodrick', + 'Juwan', + 'Latrell', + 'Chaz', + 'Lawson', + 'Mendel', + 'Jordy', + 'Dirk', + 'Finnegan', + 'Eason', + 'Atlas', + 'Eddy', + 'Mitch', + 'Reagan', + 'Clement', + 'Jamar', + 'Kamari', + 'Jarred', + 'Lauren', + 'Roscoe', + 'Jefferson', + 'Devan', + 'Elton', + 'Cortez', + 'Leandro', + 'Finley', + 'Cordero', + 'Dov', + 'Eliyahu', + 'Princeton', + 'Avrohom', + 'Hassan', + 'Dangelo', + 'Shamar', + 'Gino', + 'Yusuf', + 'Jaylin', + 'Martez', + 'Shad', + 'Keyshawn', + 'Nikhil', + 'Yael', + 'Harlan', + 'Jeffry', + 'Frederic', + 'Derick', + 'Dondre', + 'Vance', + 'Markus', + 'London', + 'Arman', + 'Marley', + 'Van', + 'Jaeden', + 'Krish', + 'Benson', + 'Marquise', + 'Cristofer', + 'Dewayne', + 'Gannon', + 'Genaro', + 'Crew', + 'Rashawn', + 'Rayden', + 'Raylan', + 'Jaxen', + 'Fredric', + 'Beckham', + 'Tripp', + 'Mckay', + 'Deonte', + 'Johann', + 'Johnpaul', + 'Santo', + 'Hakeem', + 'Federico', + 'Bert', + 'Flynn', + 'Edison', + 'Enoch', + 'Shulem', + 'Jovan', + 'Art', + 'Isadore', + 'Hal', + 'Cristiano', + 'Urijah', + 'Dilan', + 'Benicio', + 'Kingsley', + 'Aydan', + 'Syed', + 'Nicole', + 'Rachel', + 'Tyree', + 'Maximillian', + 'Branson', + 'Davin', + 'Layton', + 'Joan', + 'Darien', + 'Deion', + 'Augustus', + 'Dariel', + 'Oziel', + 'Juancarlos', + 'Pranav', + 'Danielle', + 'Rubin', + 'Jerald', + 'Wilmer', + 'Deegan', + 'Teddy', + 'Mariano', + 'Nathanie', + 'Stevie', + 'Dorsey', + 'Maxim', + 'Jaron', + 'Coty', + 'Damarion', + 'Semaj', + 'Maria', + 'Jamison', + 'Domenick', + 'Emile', + 'Armaan', + 'Arnav', + 'Mackenzie', + 'Everardo', + 'Aurelio', + 'Cayson', + 'Edwardo', + 'Charley', + 'Geovanni', + 'Vincenzo', + 'Yadiel', + 'Francesco', + 'Koby', + 'Joziah', + 'Jasiah', + 'Makai', + 'Long', + 'Cassius', + 'Omari', + 'Ferdinand', + 'Samir', + 'Cleveland', + 'Olivia', + 'Lanny', + 'Sincere', + 'Hyrum', + 'Christina', + 'Lucian', + 'Margarito', + 'Osiel', + 'Kinsler', + 'Sydney', + 'Slade', + 'Lazaro', + 'Sal', + 'Lipa', + 'Hobert', + 'Coy', + 'Elroy', + 'Tatum', + 'Katherine', + 'Chloe', + 'Kyrie', + 'Amanda', + 'Buford', + 'Kymani', + 'Kacper', + 'Elmo', + 'Alphonso', + 'Ramses', + 'Homero', + 'Sherman', + 'Reinaldo', + 'Yechiel', + 'Jonatan', + 'Mychal', + 'Gustave', + 'Paris', + 'Zain', + 'Markanthony', + 'Dimitri', + 'Mamadou', + 'Apollo', + 'Bronson', + 'Hamza', + 'Samson', + 'Madden', + 'Tylor', + 'Jacquez', + 'Garth', + 'Giuseppe', + 'Stephanie', + 'Darion', + 'Yurem', + 'Antony', + 'Rico', + 'Rich', + 'Dontavious', + 'Erin', + 'Kannon', + 'Isaak', + 'Dovid', + 'Coleman', + 'Monroe', + 'Bryon', + 'Asa', + 'Patricio', + 'Arnoldo', + 'Alexandra', + 'Jessy', + 'Jules', + 'Alexzander', + 'Jerrod', + 'Talon', + 'Elvin', + 'Chace', + 'Amos', + 'Galen', + 'Kenji', + 'Rahul', + 'Delmar', + 'Nakia', + 'Abdullah', + 'Deon', + 'Brice', + 'Osbaldo', + 'Favian', + 'Mauro', + 'Tristian', + 'Leopoldo', + 'Hans', + 'Hank', + 'Tou', + 'Demond', + 'Jemal', + 'Ladarius', + 'Kylan', + 'Braiden', + 'Darwin', + 'Kamron', + 'Millard', + 'Dax', + 'Shaquan', + 'Aloysius', + 'Tyshawn', + 'Westley', + 'Marquez', + 'Shayne', + 'Kasey', + 'Usher', + 'Ares', + 'Killian', + 'Maynard', + 'Jeshua', + 'Vaughn', + 'Shia', + 'Naftali', + 'Zaire', + 'Taj', + 'Edmond', + 'Zechariah', + 'Ollie', + 'Hoyt', + 'Donnell', + 'Soren', + 'Isac', + 'Tyquan', + 'Legend', + 'Devyn', + 'Shon', + 'Gerry', + 'Ellsworth', + 'Naftuli', + 'Johnson', + 'Haywood', + 'Aydin', + 'Junius', + 'Wiley', + 'Lennox', + 'Siddharth', + 'Odis', + 'Zaid', + 'Lacy', + 'Hussein', + 'Nicklas', + 'Callen', + 'Izayah', + 'Jaziel', + 'Claud', + 'Horacio', + 'Cyril', + 'Jariel', + 'Shemar', + 'Rebecca', + 'Reyes', + 'Denny', + 'Dereck', + 'Marcelino', + 'Najee', + 'Mac', + 'Hollis', + 'Korey', + 'Addison', + 'Jordi', + 'Eleazar', + 'Lisandro', + 'Dayton', + 'Ammon', + 'Reymundo', + 'Erich', + 'Tenzin', + 'Mitchel', + 'Kristoffer', + 'Jerrold', + 'Kristoph', + 'Refugio', + 'Erasmo', + 'Samantha', + 'Simcha', + 'Abdullahi', + 'Booker', + 'Quadir', + 'Kyson', + 'Hoover', + 'Gus', + 'Azael', + 'Mervin', + 'Yoshio', + 'Jorje', + 'Jesiah', + 'Shirley', + 'Brigham', + 'Memphis', + 'Reyansh', + 'Flavio', + 'Lavern', + 'Rosendo', + 'Dantrell', + 'Devonta', + 'Forest', + 'Alden', + 'Lyndon', + 'Luiz', + 'Elisha', + 'Al', + 'Bentlee', + 'Eriberto', + 'Marques', + 'Alexandre', + 'Fidencio', + 'Jabari', + 'Arsenio', + 'Kaysen', + 'Ethen', + 'Cleo', + 'Blaze', + 'Aryeh', + 'Dequan', + 'Denver', + 'Luc', + 'Delmas', + 'Javion', + 'Gauge', + 'Martell', + 'Ever', + 'Gavyn', + 'Aldair', + 'Okey', + 'Carey', + 'Geovanny', + 'Kalel', + 'Layne', + 'Hiroshi', + 'Ayan', + 'Akiva', + 'Clare', + 'Sigmund', + 'Furman', + 'Claudio', + 'Garrison', + 'Draven', + 'Aidyn', + 'Vern', + 'Andreas', + 'Kwame', + 'Imanol', + 'Jorden', + 'Glynn', + 'Adalberto', + 'Varun', + 'Dashiell', + 'Baron', + 'Jasen', + 'Child', + 'Earle', + 'Izaac', + 'Vivaan', + 'Koa', + 'Lennon', + 'Marcoantonio', + 'Gaetano', + 'Sumner', + 'Barney', + 'Demarion', + 'Abner', + 'Delonte', + 'Val', + 'Jacky', + 'Demario', + 'Zavier', + 'Kale', + 'Wilton', + 'Jordyn', + 'Tatsuo', + 'Boyd', + 'Zayn', + 'Darron', + 'Moe', + 'Dillan', + 'Naquan', + 'Ned', + 'Kaylee', + 'Kelton', + 'Sahil', + 'Kermit', + 'Abelardo', + 'Sullivan', + 'Crosby', + 'Hagen', + 'Tyreek', + 'Jaquez', + 'Andrea', + 'Kyan', + 'Jeremias', + 'Tracey', + 'Ward', + 'Brixton', + 'Seamus', + 'Cedrick', + 'Enrico', + 'Emmitt', + 'Ford', + 'Travon', + 'Felton', + 'Blair', + 'Rio', + 'Dandre', + 'Kaeden', + 'Tiger', + 'Orval', + 'Castiel', + 'Yousef', + 'Anson', + 'Callan', + 'Jamey', + 'Darrius', + 'Tre', + 'Michel', + 'Mcarthur', + 'Rasheed', + 'Jamir', + 'Herschel', + 'Anibal', + 'Kinnick', + 'Hilario', + 'Shea', + 'Jencarlos', + 'Darrick', + 'Rishi', + 'Shaya', + 'Haden', + 'Ean', + 'Jaylan', + 'Rolland', + 'Leobardo', + 'Fermin', + 'Keon', + 'Lucio', + 'Keagan', + 'Savion', + 'Masao', + 'Damari', + 'Aarush', + 'Nunzio', + 'Anakin', + 'Mayson', + 'Westin', + 'Norberto', + 'Tavares', + 'Gorge', + 'Tavaris', + 'Joesph', + 'Sylas', + 'Huy', + 'Gerson', + 'Augustine', + 'Buster', + 'Jelani', + 'Haley', + 'Filip', + 'Shmiel', + 'Lucius', + 'Rojelio', + 'Gale', + 'Quintin', + 'Channing', + 'Brayton', + 'Keshawn', + 'Osmar', + 'Otha', + 'Eder', + 'Mary', + 'Eusebio', + 'Matheus', + 'Randell', + 'Brennen', + 'Trae', + 'Paolo', + 'Caesar', + 'Estill', + 'Camren', + 'Dhruv', + 'Cutter', + 'Rayyan', + 'Jeramiah', + 'Anish', + 'Donavan', + 'Sunny', + 'Hershel', + 'Salvator', + 'Jedidiah', + 'Romario', + 'Hershy', + 'Anders', + 'Trevion', + 'Murphy', + 'Kanye', + 'Jionni', + 'Bradyn', + 'Cordell', + 'Alek', + 'Luisangel', + 'Norris', + 'Nevin', + 'Jaleel', + 'Lenny', + 'Judson', + 'Tayshaun', + 'Aedan', + 'Rhyder', + 'Domenic', + 'Santana', + 'Rahsaan', + 'Sebastien', + 'Corban', + 'Rowdy', + 'Kiyoshi', + 'Armen', + 'Efraim', + 'Vladimir', + 'Callum', + 'Abdul', + 'Gianluca', + 'Mayer', + 'Mustafa', + 'Demarco', + 'Neyland', + 'Vidal', + 'Marshawn', + 'Rudolfo', + 'Nazir', + 'Azariah', + 'Shoji', + 'Worth', + 'Levern', + 'Jai', + 'Antione', + 'Dickie', + 'Yehoshua', + 'Cliff', + 'Kaison', + 'Kye', + 'Jaren', + 'Emir', + 'Henrik', + 'Maxx', + 'Kainoa', + 'Athan', + 'Cletus', + 'Jasir', + 'Dejon', + 'Jadyn', + 'Houston', + 'Kadin', + 'Erubiel', + 'Hadi', + 'Jaydin', + 'Brianna', + 'Alyssa', + 'Marcello', + 'Omer', + 'Ikaika', + 'Ramel', + 'Arron', + 'Bently', + 'Daron', + 'Avi', + 'Jerod', + 'Shelton', + 'Winfred', + 'Mendy', + 'Ryu', + 'Nikko', + 'Arley', + 'Kamdyn', + 'Bo', + 'Erica', + 'Faustino', + 'Fletcher', + 'Dionte', + 'Boyce', + 'Kennedy', + 'Reyli', + 'Paulo', + 'Baruch', + 'Bernie', + 'Mohamad', + 'Kahlil', + 'Kong', + 'Baldemar', + 'Murry', + 'Rogers', + 'Sandy', + 'Bodie', + 'Ivory', + 'Youssef', + 'Kee', + 'Jahiem', + 'Isabella', + 'Keoni', + 'Michelle', + 'Luigi', + 'Marcanthony', + 'Jericho', + 'Achilles', + 'Everette', + 'Americo', + 'Edson', + 'Hiram', + 'Jeramy', + 'Metro', + 'Davi', + 'Hezekiah', + 'Harper', + 'Kiel', + 'Brandan', + 'Said', + 'Noam', + 'Tarik', + 'Raquan', + 'Zeb', + 'Broderick', + 'Arath', + 'Emery', + 'Kip', + 'Tymir', + 'Garrick', + 'Anfernee', + 'Khalid', + 'Jamil', + 'Demian', + 'Amador', + 'Oran', + 'Franklyn', + 'Porfirio', + 'Delano', + 'Justyn', + 'Aharon', + 'Karol', + 'Alva', + 'Nicky', + 'Zack', + 'Jerimiah', + 'Josef', + 'Errol', + 'Hideo', + 'Tahj', + 'Ilan', + 'Kennith', + 'Nathanial', + 'Kyron', + 'Merton', + 'Danial', + 'Tuan', + 'Hung', + 'Massimo', + 'Krew', + 'Arya', + 'Jedediah', + 'Nosson', + 'Jakobe', + 'Eitan', + 'Edmundo', + 'Olen', + 'Benedict', + 'Quintavious', + 'Shalom', + 'Akash', + 'Maxton', + 'Anna', + 'Julia', + 'Melissa', + 'Victoria', + 'Kekoa', + 'Konner', + 'Kirby', + 'Heyward', + 'Davonte', + 'Magnus', + 'Zeus', + 'Neel', + 'Franky', + 'Isael', + 'Gaylon', + 'Kole', + 'Axton', + 'Brando', + 'Mateusz', + 'Lucien', + 'Marquavious', + 'Lon', + 'Gian', + 'Savannah', + 'Trinity', + 'Harris', + 'Kamarion', + 'Aydenn', + 'Cale', + 'Neo', + 'Justus', + 'Mose', + 'Tiago', + 'Saverio', + 'Eligh', + 'Mikel', + 'Eliot', + 'Alvis', + 'Argenis', + 'Musa', + 'Lonny', + 'Thad', + 'Guido', + 'Ceasar', + 'Obed', + 'Pinchas', + 'Barton', + 'Durell', + 'Johnatha', + 'Aric', + 'Geovany', + 'Fransisco', + 'Jaheem', + 'Jarett', + 'Yeshua', + 'Karim', + 'Aayden', + 'Merrill', + 'Michele', + 'Jaydan', + 'Octavius', + 'Jermiah', + 'Alexavier', + 'Brandyn', + 'Arvid', + 'Brentley', + 'Sutton', + 'Coen', + 'Ameer', + 'Giovany', + 'Ishan', + 'Blaise', + 'Bayron', + 'Kamil', + 'Brooklyn', + 'Catherine', + 'Akira', + 'Briggs', + 'Damani', + 'Rasheen', + 'Rayford', + 'Moishe', + 'Ephraim', + 'Esequiel', + 'Kenyon', + 'Constantine', + 'Silvio', + 'Brain', + 'Daylon', + 'Raymon', + 'Ayush', + 'Lazer', + 'Telly', + 'Elan', + 'Stone', + 'Marland', + 'Donn', + 'Shamel', + 'Silvestre', + 'Zephyr', + 'Merrick', + 'Fausto', + 'Dedrick', + 'Cornell', + 'Whitney', + 'Derrell', + 'Mitsuo', + 'Lucious', + 'Tad', + 'Lyric', + 'Darrion', + 'Dannie', + 'Gayle', + 'Burl', + 'Jayquan', + 'Carrol', + 'Laquan', + 'Tyrek', + 'Natividad', + 'Casimer', + 'Jael', + 'Aven', + 'Arnaldo', + 'Yovani', + 'Laura', + 'Dejuan', + 'Dimitrios', + 'Yash', + 'Esai', + 'Zavion', + 'Ora', + 'Durward', + 'Bradly', + 'Hazel', + 'Che', + 'Richie', + 'Diana', + 'Alois', + 'Lynwood', + 'Luverne', + 'Zeke', + 'Dash', + 'Cairo', + 'Delvin', + 'Kawika', + 'Josemanuel', + 'Devean', + 'Sameer', + 'Seneca', + 'Presley', + 'Jed', + 'Malaki', + 'Dominque', + 'Dontae', + 'Dev', + 'Darey', + 'Reggie', + 'Izaak', + 'Manny', + 'Jere', + 'Minh', + 'Ryden', + 'Montana', + 'Kaleo', + 'Jacorey', + 'Ignatius', + 'Filiberto', + 'Cache', + 'Yitzchak', + 'Yaseen', + 'Kentrell', + 'Basil', + 'Ivy', + 'Migel', + 'Jalon', + 'Lenwood', + 'Ellwood', + 'Zakary', + 'Haiden', + 'Dontrell', + 'Braedon', + 'Lorne', + 'Trever', + 'Mikael', + 'Kenzo', + 'Javaris', + 'Ambrose', + 'Alain', + 'Columbus', + 'Leif', + 'Jerold', + 'Anwar', + 'Gabino', + 'Dillion', + 'Kelby', + 'Denzil', + 'Ulisses', + 'Sami', + 'Jahmir', + 'Elimelech', + 'Dock', + 'Zahir', + 'Hardy', + 'Florian', + 'Jewel', + 'Tobin', + 'Curley', + 'Mahdi', + 'Mccoy', + 'Jaquavious', + 'Justen', + 'Lino', + 'Teodoro', + 'Kazuo', + 'Lenard', + 'Robb', + 'Takashi', + 'Maison', + 'Merlyn', + 'Brecken', + 'Ricki', + 'Jet', + 'Lars', + 'Ulices', + 'Dereon', + 'Fox', + 'Ajay', + 'Geraldo', + 'Maksim', + 'Jullian', + 'Kalani', + 'Andrei', + 'Jaidyn', + 'Maxie', + 'Javen', + 'Gail', + 'Ely', + 'Caroline', + 'Amber', + 'Crystal', + 'Kiara', + 'Megan', + 'Reilly', + 'Eugenio', + 'Fisher', + 'Langston', + 'Gavriel', + 'Abhinav', + 'Dee', + 'Kace', + 'Axl', + 'Isabel', + 'Uziel', + 'Sabastian', + 'Rylee', + 'Eliazar', + 'Renato', + 'Harland', + 'Lavar', + 'Stefano', + 'Mayra', + 'Valentine', + 'Bud', + 'Hasan', + 'Zaden', + 'Truett', + 'Korbyn', + 'Toshio', + 'Stockton', + 'Edd', + 'Trystan', + 'Daylan', + 'Jayven', + 'Dewitt', + 'Kraig', + 'Wilford', + 'Celestino', + 'Jacobo', + 'Patryk', + 'Hailey', + 'Nainoa', + 'Haskell', + 'Sharif', + 'Jerad', + 'Raynaldo', + 'Jacques', + 'Jessi', + 'Geary', + 'Gaige', + 'Garnett', + 'Jakari', + 'Yonatan', + 'Eino', + 'Phong', + 'Jerel', + 'Benzion', + 'Quinten', + 'Amado', + 'Blas', + 'Kimberly', + 'Cuauhtemoc', + 'Aayan', + 'Catarino', + 'Jeromy', + 'Kyree', + 'Apolonio', + 'Boy', + 'Antwon', + 'Hakim', + 'Creed', + 'Shiloh', + 'Shepherd', + 'Garett', + 'Oakley', + 'Miller', + 'Dajuan', + 'Mattias', + 'Titan', + 'Immanuel', + 'Lamarcus', + 'Devontae', + 'Reef', + 'Brayson', + 'Grey', + 'Deante', + 'Yariel', + 'Makhi', + 'Jayse', + 'Corbyn', + 'Domenico', + 'Sedrick', + 'Deontae', + 'Kou', + 'Shant', + 'Willy', + 'Austyn', + 'Shloime', + 'Masen', + 'Linus', + 'Florentino', + 'Gionni', + 'Boden', + 'Torrey', + 'Minoru', + 'Daulton', + 'Kolten', + 'Jennings', + 'Noble', + 'Hersh', + 'Kelsey', + 'Nicholaus', + 'Florencio', + 'Nam', + 'Juelz', + 'Kainalu', + 'Destin', + 'Damarcus', + 'Jacolby', + 'Nikita', + 'Artis', + 'Bilal', + 'Kendell', + 'Alexsander', + 'Parth', + 'Esau', + 'Glennon', + 'Kohen', + 'Isacc', + 'Aleksander', + 'Vinh', + 'Trenten', + 'Koen', + 'Candelario', + 'Connie', + 'Aram', + 'Wolfgang', + 'Amit', + 'Om', + 'Shyheim', + 'Raven', + 'Kendra', + 'Eliel', + 'Viet', + 'Kenyatta', + 'Sky', + 'Binyomin', + 'Deanthony', + 'Lachlan', + 'Tory', + 'Kenton', + 'Tamir', + 'Kramer', + 'Deshaun', + 'Javian', + 'Haruo', + 'Rupert', + 'Jevon', + 'Shlome', + 'Danilo', + 'Vanessa', + 'Fernand', + 'Daveon', + 'Les', + 'Marko', + 'Delmer', + 'Marlyn', + 'Winfield', + 'Wes', + 'Rosevelt', + 'Rayshawn', + 'Tai', + 'Kalvin', + 'Jerardo', + 'Sarkis', + 'Bertrand', + 'Kaimana', + 'Kaitlyn', + 'Summer', + 'Veer', + 'Waymon', + 'Evin', + 'Andrey', + 'Iain', + 'Kimi', + 'Foster', + 'Servando', + 'Mychael', + 'Derik', + 'Ryon', + 'Rowen', + 'Mel', + 'Ibn', + 'Werner', + 'Jameel', + 'Avrum', + 'Nachman', + 'Jomar', + 'Rudolf', + 'Tyrique', + 'Rayburn', + 'Khalif', + 'Rondal', + 'Bijan', + 'Rohit', + 'Jeremie', + 'Kain', + 'Nicola', + 'Bode', + 'Brogan', + 'Trayvon', + 'Turner', + 'Dwain', + 'Konnor', + 'Lev', + 'Zayd', + 'Finnley', + 'Brantlee', + 'Deonta', + 'Demetrio', + 'Ajani', + 'Arther', + 'Bianca', + 'Takeo', + 'Harding', + 'Jareth', + 'Rigo', + 'Epifanio', + 'Nahum', + 'Carleton', + 'Cosmo', + 'Shigeru', + 'Josias', + 'Takeshi', + 'Jacobi', + 'Michal', + 'Dorris', + 'Treveon', + 'Jaxx', + 'Aren', + 'Tejas', + 'Beverly', + 'Geoff', + 'Maddux', + 'Camryn', + 'Burt', + 'Norwood', + 'Sholom', + 'Ahron', + 'Macario', + 'Carol', + 'Camdyn', + 'Gennaro', + 'Leeroy', + 'Pinchus', + 'Kaito', + 'Burnell', + 'Frantz', + 'Laron', + 'Clemente', + 'Chasen', + 'Neri', + 'Jerrell', + 'Kashawn', + 'Keola', + 'Alvan', + 'Amar', + 'Ubaldo', + 'Roque', + 'Zalmen', + 'Daylen', + 'Kadyn', + 'Gil', + 'Bernice', + 'Yosgart', + 'Shaan', + 'Yahel', + 'Elon', + 'Levon', + 'Kit', + 'Brodrick', + 'Gaven', + 'Kaidyn', + 'Ansel', + 'Jewell', + 'Mikhail', + 'Derian', + 'Elam', + 'Tye', + 'Leigh', + 'Wayde', + 'Rian', + 'Artemio', + 'Ibrahima', + 'Noa', + 'Autumn', + 'Kylie', + 'Pernell', + 'Britton', + 'Deondre', + 'Arlen', + 'Aman', + 'Kelley', + 'Eliud', + 'Dijon', + 'Imran', + 'Eulalio', + 'Juvenal', + 'Agapito', + 'Brant', + 'Nima', + 'Yisrael', + 'Yerik', + 'Ewan', + 'Lathan', + 'Adair', + 'Gentry', + 'Kyren', + 'Lian', + 'Tayshawn', + 'Alejandra', + 'Jeancarlos', + 'Keyon', + 'Jade', + 'Shayan', + 'June', + 'Christos', + 'Adrain', + 'Jarom', + 'Kathryn', + 'Thor', + 'Haven', + 'Duy', + 'Enmanuel', + 'Montavious', + 'Cortney', + 'Teagan', + 'Blayne', + 'Anselmo', + 'Leyton', + 'Jonny', + 'Braylin', + 'Albaro', + 'Pascual', + 'Gasper', + 'Waldo', + 'Tyreke', + 'Dylon', + 'Narciso', + 'Ebony', + 'Hilton', + 'Margaret', + 'Brighton', + 'Martavious', + 'Demetrios', + 'Kishan', + 'Ansh', + 'Treyton', + 'Albin', + 'Rashon', + 'Rony', + 'Krystian', + 'Amrom', + 'Korver', + 'Richardo', + 'Kayla', + 'Katelyn', + 'Milford', + 'Bishop', + 'Ottis', + 'Emmet', + 'Codey', + 'Ayub', + 'Isreal', + 'Karas', + 'Kendarius', + 'Isamu', + 'Kunta', + 'Jermey', + 'Arvin', + 'Kayleb', + 'Sione', + 'Taurean', + 'Tyron', + 'Mihir', + 'Rami', + 'Vincente', + 'Zayan', + 'Mahlon', + 'Clovis', + 'Kirt', + 'Dyllan', + 'Ramsey', + 'Jeramie', + 'Nikolaus', + 'Edsel', + 'Asael', + 'Andrik', + 'Lisa', + 'Sandro', + 'Desean', + 'Narek', + 'Kiran', + 'Elzie', + 'Jered', + 'Arlie', + 'Yahya', + 'Lizandro', + 'Rollin', + 'Khiry', + 'Yuvraj', + 'Jeancarlo', + 'Anay', + 'Freeman', + 'Stevan', + 'Keller', + 'Ledger', + 'Jasiel', + 'Jacinto', + 'Sherwin', + 'Beaux', + 'Campbell', + 'Sherwood', + 'Torrence', + 'Daryle', + 'Chevy', + 'Adiel', + 'Patricia', + 'Jameer', + 'Bilbo', + 'Jayvon', + 'Early', + 'Boruch', + 'Jadarius', + 'Alpha', + 'Amadou', + 'Reino', + 'Betty', + 'Moussa', + 'Wolf', + 'Jenna', + 'Grace', + 'Natalie', + 'Javonte', + 'Crawford', + 'Damir', + 'Mckinley', + 'Elden', + 'Jhon', + 'Lemuel', + 'Colston', + 'Donta', + 'Pearl', + 'Taquan', + 'Salman', + 'Palmer', + 'Muhammed', + 'Brennon', + 'Cashton', + 'Ysidro', + 'Salomon', + 'Ocean', + 'Anirudh', + 'Aksel', + 'Cal', + 'Ishmael', + 'Brenda', + 'Abran', + 'Rome', + 'Leighton', + 'Canyon', + 'Kael', + 'Amin', + 'Antoni', + 'Tiara', + 'Heather', + 'Christine', + 'Brittney', + 'Angela', + 'Johathan', + 'Cipriano', + 'Coltin', + 'Verne', + 'Darrien', + 'Eamon', + 'Oskar', + 'Mikah', + 'Matix', + 'Kooper', + 'Antonino', + 'Duwayne', + 'Dagoberto', + 'Kolt', + 'Sanjay', + 'Tayden', + 'Waverly', + 'Abrahan', + 'Diamond', + 'West', + 'Jefferey', + 'Shigeo', + 'Kabir', + 'Jamell', + 'Jaedyn', + 'Malcom', + 'Gadiel', + 'Manav', + 'Audie', + 'Hipolito', + 'Theron', + 'Codie', + 'General', + 'Lindy', + 'Carver', + 'Nat', + 'Jacari', + 'Khamari', + 'Wally', + 'Kay', + 'Anastacio', + 'Jaymes', + 'Skip', + 'Cheyne', + 'Dameon', + 'Geronimo', + 'Kevyn', + 'Toney', + 'Arden', + 'Dontavius', + 'Rasheem', + 'Geovani', + 'Gaspar', + 'Baltazar', + 'Bladimir', + 'Rashan', + 'Rulon', + 'Karan', + 'Jory', + 'Chet', + 'Abiel', + 'Lazarus', + 'Britt', + 'Rodriquez', + 'Akil', + 'Zuriel', + 'Rylen', + 'Aston', + 'Graysen', + 'Jaysen', + 'Hillel', + 'Alford', + 'Tyriq', + 'Cassidy', + 'Rahiem', + 'Juanmanuel', + 'Demetri', + 'Jayton', + 'Timoteo', + 'Infantof', + 'Braedyn', + 'Corde', + 'Bee', + 'Valente', + 'Gildardo', + 'Feliciano', + 'Dalvin', + 'Tadashi', + 'Claudie', + 'Teng', + 'Genesis', + 'Tayler', + 'Joeangel', + 'Teruo', + 'Tylan', + 'Markel', + 'Linda', + 'Taven', + 'Pierson', + 'Newton', + 'Keandre', + 'Jayvion', + 'Donavon', + 'Encarnacion', + 'Melton', + 'Ritchie', + 'Erika', + 'Edgard', + 'Christoper', + 'Rocio', + 'Alvie', + 'Josedejesus', + 'Dashaun', + 'Travion', + 'Johny', + 'Marcell', + 'Monique', + 'Caitlin', + 'Durwood', + 'Gustav', + 'Rosalio', + 'Farhan', + 'Benuel', + 'Lashawn', + 'Shakeem', + 'Ocie', + 'Yasir', + 'Szymon', + 'Aaryan', + 'Hansel', + 'Slater', + 'Samarth', + 'Kiyan', + 'Storm', + 'Ava', + 'Yassin', + 'Dayquan', + 'Sherrill', + 'Khari', + 'Anas', + 'Cheskel', + 'Kamryn', + 'Zyaire', + 'Cristo', + 'Christofer', + 'Akhil', + 'Shreyas', + 'Ryley', + 'Gibson', + 'Haziel', + 'Talen', + 'Bracken', + 'Dallen', + 'Rashard', + 'Rockwell', + 'Colie', + 'Del', + 'Jihad', + 'Simeon', + 'Jahmari', + 'Ashwin', + 'Shraga', + 'Cian', + 'Alistair', + 'Cartier', + 'Stoney', + 'Verlyn', + 'Kavon', + 'Konrad', + 'Conrado', + 'Colon', + 'Randel', + 'Christ', + 'Jeremey', + 'Raleigh', + 'Lauro', + 'Dionicio', + 'Kauan', + 'Piotr', + 'Cleon', + 'Malique', + 'Rand', + 'Fritz', + 'Cordaro', + 'Pietro', + 'Faris', + 'Ezio', + 'Atharv', + 'Karthik', + 'Jahsir', + 'Saleem', + 'Abdoulaye', + 'Jiovanni', + 'Ezrah', + 'Everest', + 'Bronx', + 'Kruz', + 'Viktor', + 'Yasiel', + 'Thatcher', + 'Michelangelo', + 'Alaric', + 'Oneal', + 'Sahib', + 'Osiris', + 'Teo', + 'Joseangel', + 'Nate', + 'Walton', + 'Yousif', + 'Ezzard', + 'Yamil', + 'Angus', + 'Jhonny', + 'Fabio', + 'Darold', + 'Junious', + 'Atreyu', + 'Beck', + 'Adriano', + 'Amani', + 'Trevin', + 'Rudra', + 'Parsa', + 'Breon', + 'Umar', + 'Taha', + 'Cormac', + 'Yossi', + 'Jaison', + 'Saad', + 'Shloimy', + 'Chesky', + 'Ayman', + 'Alicia', + 'Chadd', + 'Broc', + 'Cynthia', + 'Reynold', + 'Ismail', + 'Gaylord', + 'Saburo', + 'Kao', + 'Masato', + 'Alfonzo', + 'Joshue', + 'Earvin', + 'Patric', + 'Robinson', + 'Serjio', + 'Gavino', + 'Stanford', + 'Thanh', + 'Kamren', + 'Vikram', + 'Roan', + 'Jeronimo', + 'Zahid', + 'Anjel', + 'Jayro', + 'Skye', + 'Baylor', + 'Drayden', + 'Pheng', + 'Yeng', + 'Wilber', + 'Meng', + 'Arik', + 'Jamarius', + 'Avigdor', + 'Ladarrius', + 'Nicklaus', + 'Gatlin', + 'Boone', + 'Jacen', + 'Antonia', + 'Kyran', + 'Quintavius', + 'Estil', + 'Casimiro', + 'Prentice', + 'Jodie', + 'Rashaad', + 'Konstantinos', + 'Allison', + 'Sophia', + 'Makayla', + 'Lillian', + 'Zymir', + 'Canaan', + 'Delfino', + 'Benton', + 'Apolinar', + 'Winford', + 'Dayne', + 'Shivam', + 'Fredi', + 'Yves', + 'Jarrell', + 'Ignazio', + 'Gamaliel', + 'Young', + 'Kiefer', + 'Juanjose', + 'Rehan', + 'Kegan', + 'Davante', + 'Naim', + 'Lyman', + 'Erskine', + 'Toivo', + 'Darrian', + 'Jad', + 'Ender', + 'Remi', + 'Rishaan', + 'Shaurya', + 'Viaan', + 'Chelsea', + 'Molly', + 'Sara', + 'Leib', + 'Azriel', + 'Howell', + 'Briar', + 'Korben', + 'Manning', + 'Job', + 'Brandt', + 'Jaedon', + 'Ozzy', + 'Cordarius', + 'Lannie', + 'Stanton', + 'Radames', + 'Blease', + 'Zyon', + 'Chadrick', + 'Watson', + 'Kentavious', + 'Taurus', + 'Adin', + 'Jordin', + 'Bryden', + 'Susumu', + 'Tamotsu', + 'Yukio', + 'Granville', + 'Ashby', + 'Tristyn', + 'Devaughn', + 'Deric', + 'Cecilio', + 'Pershing', + 'Noboru', + 'Rashaun', + 'Masaichi', + 'Juventino', + 'Norton', + 'Serafin', + 'Windell', + 'Cris', + 'Curtiss', + 'Boris', + 'Elio', + 'Williams', + 'Trung', + 'Torao', + 'Karon', + 'Canon', + 'Tyrik', + 'Naythan', + 'Michaelangelo', + 'Kavin', + 'Akshay', + 'Broden', + 'Quran', + 'Rishabh', + 'Hilbert', + 'Abbas', + 'Damoni', + 'Dillard', + 'Tigran', + 'Romel', + 'Chip', + 'Aeden', + 'Deagan', + 'Treyson', + 'Brannon', + 'Tremaine', + 'Fay', + 'Bryton', + 'Lucky', + 'Izak', + 'Edan', + 'Casper', + 'Koda', + 'Saquan', + 'Alcide', + 'Quinlan', + 'Maddex', + 'Hoyle', + 'Sandra', + 'Joshuah', + 'Lindsay', + 'Donato', + 'Jancarlos', + 'Kalin', + 'Zigmund', + 'Kalen', + 'Jalil', + 'Bonifacio', + 'Gabrielle', + 'Destiny', + 'Cheyenne', + 'Ulyses', + 'Rueben', + 'Markell', + 'Jermel', + 'Corwin', + 'Justine', + 'Idris', + 'Pilar', + 'Torrance', + 'Raeford', + 'Olan', + 'Octavious', + 'Quantavious', + 'Modesto', + 'Kashton', + 'Librado', + 'Bonnie', + 'Lois', + 'Justo', + 'Mahmoud', + 'Divine', + 'Baylen', + 'Rakeem', + 'Diesel', + 'Kyng', + 'Daisy', + 'Armon', + 'Joseantonio', + 'Montel', + 'Gearld', + 'Cloyd', + 'Lindell', + 'Nile', + 'Kashif', + 'Johnmichael', + 'Aramis', + 'Leopold', + 'Kamal', + 'Jerrad', + 'Jadin', + 'Mykel', + 'Jahlil', + 'Cheng', + 'Ezriel', + 'Aria', + 'Dajon', + 'Holt', + 'Chauncey', + 'Karsen', + 'Stryker', + 'Olaf', + 'Reno', + 'Colter', + 'Schuyler', + 'Orvil', + 'Auden', + 'Eyan', + 'Tyce', + 'Barbara', + 'Zamir', + 'Alexi', + 'Braelyn', + 'Brook', + 'Marchello', + 'Tyrel', + 'Oracio', + 'Jalin', + 'Verlon', + 'Raj', + 'Lindsey', + 'Andon', + 'Devlin', + 'Brysen', + 'Harman', + 'Treyvon', + 'Foy', + 'Arash', + 'Cuong', + 'Torin', + 'Rommel', + 'Lorenza', + 'Vishal', + 'Kenya', + 'Heber', + 'Victoriano', + 'Shay', + 'Tremayne', + 'Natanael', + 'Zachry', + 'Eros', + 'Veronica', + 'Wayland', + 'Rayquan', + 'Ana', + 'Jaceon', + 'Yida', + 'Rahmel', + 'Alter', + 'Lamarion', + 'Tavion', + 'Javin', + 'Lawerence', + 'Alessio', + 'Kristen', + 'Jacqueline', + 'Oren', + 'Aahil', + 'Adyan', + 'Augustin', + 'Coleton', + 'Wilfrid', + 'Dezmond', + 'Keelan', + 'Ike', + 'Kanoa', + 'Kedrick', + 'Chue', + 'Danniel', + 'Jowell', + 'Micahel', + 'Yonathan', + 'Finnian', + 'Garfield', + 'Joao', + 'Ezell', + 'Masaru', + 'Yoshito', + 'Pasco', + 'Yechezkel', + 'Shloma', + 'Adnan', + 'Jaythan', + 'Laith', + 'Greysen', + 'Maddix', + 'Alfonse', + 'Ernst', + 'Hobart', + 'Tavin', + 'Dajour', + 'Cy', + 'Estel', + 'Osman', + 'Vedant', + 'Rolf', + 'Ova', + 'Colson', + 'Kelan', + 'Oumar', + 'Olivier', + 'Seichi', + 'Tayson', + 'Roshan', + 'Blane', + 'Baxter', + 'Vu', + 'Tam', + 'Pao', + 'Wardell', + 'Davonta', + 'Montrell', + 'Ravi', + 'Durrell', + 'Bastian', + 'Aj', + 'Ren', + 'Loki', + 'Kairo', + 'Rock', + 'Mylo', + 'Lavell', + 'Bjorn', + 'Arvil', + 'Reinhold', + 'Yesenia', + 'Carsen', + 'Zephaniah', + 'Renzo', + 'Willem', + 'Unique', + 'Elmore', + 'Kalob', + 'Payne', + 'Leeland', + 'Naseem', + 'Yusef', + 'Aboubacar', + 'Ioannis', + 'Bohdan', + 'Javien', + 'Jakobi', + 'Dempsey', + 'Xavian', + 'Antavious', + 'Jc', + 'Dara', + 'Obie', + 'Celso', + 'Tyrin', + 'Eian', + 'Elgin', + 'Jaylyn', + 'Brandin', + 'Adyn', + 'Gabriela', + 'Jaidon', + 'Zavian', + 'Lonzo', + 'Elwin', + 'Tsutomu', + 'Jeanluc', + 'Caeden', + 'Auston', + 'Jasson', + 'Omid', + 'Gray', + 'Vang', + 'Nancy', + 'Nader', + 'Kylen', + 'Jarell', + 'Prentiss', + 'Tahir', + 'Ahmir', + 'Terell', + 'Ludwig', + 'Biagio', + 'Douglass', + 'Nafis', + 'Harlem', + 'Phineas', + 'Lochlan', + 'Hermon', + 'Wilder', + 'Aniello', + 'Attilio', + 'Shiv', + 'Montgomery', + 'Bowie', + 'Aries', + 'Itzae', + 'Isa', + 'Huxley', + 'Elwyn', + 'Advik', + 'Mahamadou', + 'Grayden', + 'Landin', + 'Decker', + 'Dakotah', + 'Ella', + 'Md', + 'Shayaan', + 'Isidor', + 'Joahan', + 'Tillman', + 'Jafet', + 'Panagiotis', + 'Jajuan', + 'Cristhian', + 'Demetric', + 'Zaylen', + 'Kacen', + 'Sloan', + 'Shedrick', + 'Denilson', + 'Buck', + 'Dyland', + 'Aris', + 'Demonte', + 'Telvin', + 'Raynard', + 'Quantavius', + 'Neftali', + 'Alma', + 'Kadarius', + 'Philippe', + 'Laurel', + 'Vadhir', + 'Juandiego', + 'Alekzander', + 'Napoleon', + 'Fabrizio', + 'Abisai', + 'Yasin', + 'Kamran', + 'Ole', + 'Nicolai', + 'Erling', + 'Jathan', + 'Zen', + 'Shiven', + 'Keshaun', + 'Nikola', + 'Loy', + 'Usman', + 'Concepcion', + 'Verlin', + 'Dedric', + 'Derwin', + 'Graig', + 'Serge', + 'Merritt', + 'Kervin', + 'Maleek', + 'Baldomero', + 'Germaine', + 'Hampton', + 'Shan', + 'Alvino', + 'Davy', + 'Arlington', + 'Brandy', + 'Timmie', + 'Andrae', + 'Terrion', + 'Quang', + 'Jeb', + 'Clem', + 'Judd', + 'Severo', + 'Woody', + 'Toan', + 'Alonza', + 'Gardner', + 'Delton', + 'Vinny', + 'Vilas', + 'Welton', + 'Sabian', + 'Dell', + 'Randolf', + 'Tyren', + 'Glenwood', + 'Antwain', + 'Savon', + 'Lesley', + 'Rashid', + 'Tavian', + 'Marvens', + 'Aleksandr', + 'Vivek', + 'Maximino', + 'Pavel', + 'Renee', + 'Charly', + 'Donell', + 'Shariff', + 'Ennis', + 'Menashe', + 'Ygnacio', + 'Hoke', + 'Lebron', + 'Hillard', + 'Xavion', + 'Nicolaus', + 'Kemari', + 'Sammuel', + 'Jessiah', + 'Virgle', + 'Niklas', + 'Allante', + 'Keenen', + 'Albino', + 'Rivaldo', + 'Jospeh', + 'Broadus', + 'Trequan', + 'Finis', + 'Sabas', + 'Abdoul', + 'Tyronne', + 'Tyreik', + 'Tyriek', + 'Linton', + 'Jashawn', + 'Ivey', + 'Janiel', + 'Jayme', + 'Lamarr', + 'Tiernan', + 'Meilech', + 'Fitzgerald', + 'Jonnathan', + 'Tashawn', + 'Verl', + 'Nichoals', + 'Urban', + 'Marquan', + 'Montez', + 'Akshaj', + 'Syrus', + 'Nehemias', + 'Nova', + 'Makaio', + 'Joselito', + 'Armin', + 'Monica', + 'Natasha', + 'Leonce', + 'Corby', + 'Doris', + 'Chancellor', + 'Yonah', + 'Gaston', + 'Alston', + 'Tyreese', + 'Gaither', + 'Donna', + 'Graeme', + 'Frances', + 'Earlie', + 'Oral', + 'Ruby', + 'Krishna', + 'Berkley', + 'Viraj', + 'Jame', + 'Judge', + 'Denim', + 'Guilherme', + 'Salim', + 'Rondell', + 'Marek', + 'Zac', + 'Seven', + 'Stellan', + 'Calder', + 'Eithan', + 'Eliam', + 'Gareth', + 'Auther', + 'Theodis', + 'Denzell', + 'Octave', + 'Destry', + 'Bartholomew', + 'Rajiv', + 'Jaxxon', + 'Maxson', + 'Adler', + 'Tyran', + 'Carnell', + 'Alben', + 'Saif', + 'Merwin', + 'Binyamin', + 'Hayward', + 'Arav', + 'Berry', + 'Daunte', + 'Arvo', + 'Gerhard', + 'Selmer', + 'Davie', + 'Courtland', + 'Athanasios', + 'Ori', + 'Aadi', + 'Kamar', + 'Jeremih', + 'Jayvian', + 'Doyne', + 'Macarthur', + 'Elza', + 'Harden', + 'Soham', + 'Alder', + 'Josemaria', + 'Iziah', + 'Jin', + 'Woodie', + 'Alfie', + 'Stefon', + 'Oswald', + 'Talmage', + 'Leander', + 'Jancarlo', + 'Sasha', + 'Lorin', + 'Roby', + 'Juanmiguel', + 'Johannes', + 'Allie', + 'Demetris', + 'Sharod', + 'Mynor', + 'Lex', + 'Tito', + 'Domonique', + 'Seferino', + 'Jourdan', + 'Marcial', + 'Herminio', + 'Mikal', + 'Alegandro', + 'Makana', + 'Bb', + 'Jarret', + 'Jemel', + 'Kareen', + 'Sierra', + 'Michale', + 'Jalyn', + 'Meredith', + 'Gracie', + 'Dawud', + 'Raylon', + 'Avan', + 'Dayshawn', + 'Livan', + 'Kendal', + 'Otho', + 'Dung', + 'Reuven', + 'Karmelo', + 'Myer', + 'Tadao', + 'Bentzion', + 'Tex', + 'Jamin', + 'Clois', + 'Sadao', + 'Tetsuo', + 'Izrael', + 'Avion', + 'Katsumi', + 'Gerrit', + 'Jamauri', + 'Kunal', + 'Nickolaus', + 'Hoang', + 'Bernabe', + 'Khristian', + 'Arne', + 'Javeon', + 'Vasilios', + 'Noach', + 'Ruger', + 'Kutter', + 'Kyden', + 'Marshal', + 'Jaelon', + 'Raffi', + 'Rito', + 'Parrish', + 'Duvid', + 'Jamario', + 'Verle', + 'Harmon', + 'Thai', + 'Claire', + 'Daiquan', + 'Didier', + 'Jonnie', + 'Arlan', + 'Taggart', + 'Henri', + 'Rogan', + 'Woodford', + 'Maceo', + 'Nyjah', + 'Smith', + 'Syncere', + 'Ballard', + 'Kenichi', + 'Khaled', + 'Dwaine', + 'Mathieu', + 'Ousmane', + 'Emmit', + 'Aayush', + 'Elyas', + 'Taysom', + 'Azaiah', + 'Axle', + 'Ander', + 'Azaan', + 'Vic', + 'Terrel', + 'Alen', + 'Fabricio', + 'Yeshaya', + 'Greggory', + 'Derrik', + 'Esgar', + 'Selwyn', + 'Binh', + 'Tarun', + 'Quoc', + 'Corry', + 'Wylie', + 'Jadan', + 'Aamir', + 'Barron', + 'Ciaran', + 'Melville', + 'Bronislaus', + 'Fong', + 'Hakop', + 'Jashua', + 'Stanislaus', + 'Keion', + 'Timmothy', + 'Kenan', + 'Banks', + 'Ammar', + 'Maxfield', + 'Tyre', + 'Chistian', + 'Son', + 'Shaka', + 'Jahmal', + 'Jerell', + 'Beckam', + 'Zakariya', + 'Jayshawn', + 'Orvel', + 'Yona', + 'Derrek', + 'Warner', + 'Rollie', + 'Adelbert', + 'Von', + 'Kathleen', + 'April', + 'Nikolaos', + 'Alika', + 'Barrington', + 'Inez', + 'Len', + 'Arsh', + 'Elyjah', + 'Eshaan', + 'Shayden', + 'Jaykob', + 'Raziel', + 'Makoa', + 'Cornelio', + 'Rufino', + 'Leamon', + 'Terrill', + 'Hai', + 'Jonerik', + 'Hamilton', + 'Lindbergh', + 'Enos', + 'Sabino', + 'Ara', + 'Raudel', + 'Jones', + 'Cedar', + 'Yohan', + 'Janet', + 'Archibald', + 'Boaz', + 'Cleotha', + 'Dontez', + 'Eldridge', + 'Abhay', + 'Butch', + 'Jayvien', + 'Rowland', + 'Kimo', + 'Gurney', + 'Virgilio', + 'Alfonza', + 'Perley', + 'Silverio', + 'Amilcar', + 'Kapena', + 'Issak', + 'Josemiguel', + 'Mikey', + 'Camille', + 'Gershon', + 'Mehki', + 'Carsten', + 'Lavelle', + 'Jamere', + 'Natale', + 'Elya', + 'Antwone', + 'Pedrohenrique', + 'Kyjuan', + 'Shakim', + 'Evaristo', + 'Lionell', + 'Helen', + 'Aariz', + 'Paige', + 'Jaquavius', + 'Adolphus', + 'Faith', + 'Breanna', + 'Martavius', + 'Armondo', + 'Yobani', + 'Missael', + 'Marcellus', + 'Rishab', + 'Jaxsen', + 'Jahleel', + 'Bernell', + 'Woodroe', + 'Breck', + 'Paden', + 'Trumaine', + 'Rogerio', + 'Cleve', + 'Ameen', + 'Jermain', + 'Shakir', + 'Berl', + 'Conley', + 'Vinson', + 'Andru', + 'Andrue', + 'Suraj', + 'Ruvim', + 'Rodriguez', + 'Benji', + 'Kylon', + 'Matheo', + 'Kellin', + 'Karsyn', + 'Izan', + 'Caysen', + 'Caison', + 'Witten', + 'Issa', + 'Audrey', + 'Sekou', + 'Januel', + 'Christpher', + 'Octaviano', + 'Jereme', + 'Basilio', + 'Kaine', + 'Jayvyn', + 'Vishnu', + 'Umberto', + 'Keondre', + 'Delroy', + 'Herve', + 'Rakim', + 'Denton', + 'Donavin', + 'Elder', + 'Ger', + 'Jazmin', + 'Schneider', + 'Ethyn', + 'Davien', + 'Cross', + 'Reginal', + 'Maksymilian', + 'Rahim', + 'Ridge', + 'Ved', + 'Bartosz', + 'Kaye', + 'Quamir', + 'Jasmin', + 'Diante', + 'Codi', + 'Khamani', + 'Juliocesar', + 'Lydell', + 'Dakari', + 'Eluzer', + 'Daniyal', + 'Isidoro', + 'Yousuf', + 'Rider', + 'Winthrop', + 'Diogo', + 'Kejuan', + 'Micaiah', + 'Ransom', + 'Rolla', + 'Leibish', + 'Ilyas', + 'Arham', + 'Adham', + 'Abdulrahman', + 'Lateef', + 'Rahmir', + 'Kollin', + 'Jamaine', + 'Khary', + 'De', + 'Jabbar', + 'Hardin', + 'Deryl', + 'Yanky', + 'Aviel', + 'Boubacar', + 'Eshan', + 'Hanley', + 'Hussain', + 'Tylon', + 'Leldon', + 'Raoul', + 'Braheem', + 'Kaseem', + 'Tyshaun', + 'Rashaan', + 'Kordell', + 'Anil', + 'Devion', + 'Mervyn', + 'Shaquil', + 'Shaquill', + 'Shaul', + 'Musab', + 'Muad', + 'Tomasz', + 'Madeline', + 'Delante', + 'Jahari', + 'Leah', + 'Tamika', + 'Britney', + 'Jeriel', + 'Yidel', + 'Jarad', + 'Oneil', + 'Fransico', + 'Shamir', + 'Carmello', + 'Abdulahi', + 'Shneur', + 'Yehudah', + 'Brown', + 'Sylvan', + 'Dontay', + 'French', + 'Griffen', + 'Faisal', + 'Dru', + 'Demitri', + 'Faron', + 'Deloy', + 'Juston', + 'Charleston', + 'Farrell', + 'Tab', + 'Donaciano', + 'Candido', + 'Joyce', + 'Marquel', + 'Lamonte', + 'Raheen', + 'Dashon', + 'Hieu', + 'Tyus', + 'Ciro', + 'Naeem', + 'Rush', + 'Keifer', + 'Christion', + 'Bladen', + 'Kobie', + 'Darell', + 'Mouhamed', + 'Jia', + 'Shepard', + 'Price', + 'Kasyn', + 'Truitt', + 'Jenson', + 'Aizen', + 'Markeith', + 'Braylan', + 'Jonmichael', + 'Damond', + 'Jaycion', + 'Platon', + 'Amaury', + 'Amaan', + 'Daven', + 'Tobey', + 'Hymen', + 'Altariq', + 'Jacory', + 'Ashtin', + 'Domonic', + 'Demari', + 'Denise', + 'Abimael', + 'Izaya', + 'Jovon', + 'Harout', + 'Caelan', + 'Donal', + 'Martel', + 'Jaskaran', + 'Alante', + 'Bradon', + 'Deborah', + 'Harrell', + 'Kaipo', + 'Klayton', + 'Danthony', + 'Justino', + 'Kamuela', + 'Barrie', + 'Argelis', + 'Dolores', + 'Jahaziel', + 'Iram', + 'Adian', + 'Rance', + 'Karsten', + 'Christain', + 'Jamarian', + 'Yee', + 'Adriana', + 'Jamichael', + 'Waino', + 'Anh', + 'Casmer', + 'Ronnell', + 'Tong', + 'Vicent', + 'Jarius', + 'Tiburcio', + 'Burdette', + 'Amadeo', + 'Kevan', + 'Arlyn', + 'Derald', + 'Waleed', + 'Jabez', + 'Khoa', + 'Neville', + 'Susan', + 'Leandre', + 'Jorgeluis', + 'Angelica', + 'Regan', + 'Froylan', + 'Tevita', + 'Sagar', + 'Drayton', + 'Zade', + 'Karriem', + 'Townes', + 'Ram', + 'Jaceyon', + 'Keng', + 'Isao', + 'Unkown', + 'Vivian', + 'Mamoru', + 'Dyllon', + 'Hagop', + 'Masami', + 'Shoichi', + 'Landan', + 'Cadence', + 'Yanixan', + 'Xzavion', + 'Javan', + 'Avian', + 'Cadyn', + 'Collier', + 'Clarance', + 'Karen', + 'Christy', + 'Toriano', + 'Diallo', + 'Mateus', + 'Caio', + 'Larue', + 'Gilmer', + 'Rhyan', + 'Elijiah', + 'Curren', + 'Souleymane', + 'Deklan', + 'Zakaria', + 'Hayk', + 'Ric', + 'Briley', + 'Oval', + 'Lovell', + 'Daryn', + 'Franz', + 'Spurgeon', + 'Giacomo', + 'Orrin', + 'Vester', + 'Taran', + 'Salem', + 'Naveen', + 'Linkin', + 'Kallen', + 'Kongmeng', + 'Patrice', + 'Bibb', + 'Arjan', + 'Fateh', + 'Clive', + 'Pharaoh', + 'Subhan', + 'Rayaan', + 'Zebulon', + 'Webster', + 'Raghav', + 'Zakai', + 'Ekam', + 'Caspian', + 'Atom', + 'Athen', + 'Esdras', + 'Vihan', + 'Ronav', + 'Arrow', + 'Izek', + 'Gaines', + 'Trajan', + 'Onofrio', + 'Romello', + 'Ramone', + 'Symir', + 'Kanyon', + 'Shomari', + 'Christo', + 'Anthoney', + 'Giovonni', + 'Gurshan', + 'Nathon', + 'Zach', + 'Jhonatan', + 'Shakur', + 'Favio', + 'Imani', + 'Asad', + 'Brien', + 'Aureliano', + 'Fischer', + 'Yadier', + 'Marino', + 'Kimball', + 'Saleh', + 'Greco', + 'Helmer', + 'Sai', + 'Khai', + 'Marius', + 'Joy', + 'Amauri', + 'Tegan', + 'Darl', + 'Cosimo', + 'Armond', + 'Yecheskel', + 'Natan', + 'Shabazz', + 'Devine', + 'Fabrice', + 'Tarek', + 'Renaldo', + 'Jarrel', + 'Gamal', + 'Rajesh', + 'Lavon', + 'Ahnaf', + 'Cono', + 'Gaspare', + 'Chas', + 'Jaspreet', + 'Tevon', + 'Kush', + 'Nuchem', + 'Jostin', + 'Wm', + 'Darnel', + 'Thurston', + 'Maliek', + 'Shakeel', + 'Coolidge', + 'Shaheed', + 'Anastasios', + 'Wesson', + 'Humza', + 'Kofi', + 'Jamelle', + 'Davey', + 'Llewellyn', + 'Nashawn', + 'Odie', + 'Jun', + 'Jahmere', + 'Bienvenido', + 'Safwan', + 'Mordche', + 'Demarius', + 'Cillian', + 'Alexandros', + 'Nochum', + 'Shareef', + 'Pawel', + 'Theadore', + 'Dorothy', + 'Geno', + 'Haris', + 'Dayvon', + 'Lemarcus', + 'Rayvon', + 'Laird', + 'Zayvion', + 'Dennie', + 'Dwane', + 'Orvis', + 'Chalmer', + 'Adil', + 'Zamari', + 'Kodi', + 'Braxtyn', + 'Fahim', + 'Merl', + 'Name', + 'Aaiden', + 'Dyson', + 'Westyn', + 'Wells', + 'Niles', + 'Nabil', + 'Kaelan', + 'Dmitri', + 'Demitrius', + 'Arlis', + 'Reco', + 'Glendon', + 'Abhishek', + 'Jammie', + 'Grabiel', + 'Jerson', + 'Gerhardt', + 'Kyrin', + 'Kipton', + 'Bear', + 'Jaciel', + 'Dakoda', + 'Kaelin', + 'Keilan', + 'Brendyn', + 'Fortino', + 'Diondre', + 'Arin', + 'Cleophus', + 'Dimas', + 'Caine', + 'Jakoby', + 'Hagan', + 'Layden', + 'Calen', + 'Nils', + 'Cisco', + 'Jerrick', + 'Gevork', + 'Mckenzie', + 'Justis', + 'Coltyn', + 'Brazos', + 'Jaycen', + 'Kemauri', + 'Tyrus', + 'Zaidyn', + 'Lenin', + 'Karlos', + 'Shrey', + 'Edric', + 'Tino', + 'Macklin', + 'Nevan', + 'Lawrance', + 'Arno', + 'Irby', + 'Namir', + 'Chayse', + 'Ronit', + 'Clemens', + 'Giorgio', + 'Khriz', + 'Khang', + 'Zidane', + 'Nomar', + 'Glade', + 'Doyce', + 'Kaya', + 'Surya', + 'Jaelen', + 'Vernell', + 'Issiah', + 'Henderson', + 'Jessejames', + 'Gaylen', + 'Aldahir', + 'An', + 'Asencion', + 'Garner', + 'Treston', + 'Evans', + 'Salome', + 'Cyle', + 'Sang', + 'Isaih', + 'Kirkland', + 'Loyal', + 'Jonpaul', + 'Cindy', + 'Bao', + 'Laurie', + 'Monico', + 'Kiptyn', + 'Toribio', + 'Cresencio', + 'Ruperto', + 'Dat', + 'Rustin', + 'Kendric', + 'Miquel', + 'Hasani', + 'Caron', + 'Jarron', + 'Enrigue', + 'Evelyn', + 'Paulino', + 'Eligio', + 'Melchor', + 'Deshon', + 'Johndavid', + 'Cliffton', + 'Ovidio', + 'Jacorian', + 'Laken', + 'Aedyn', + 'Ichiro', + 'Derion', + 'Sharon', + 'Yasuo', + 'Masayuki', + 'Andrez', + 'Dustyn', + 'Toua', + 'Jossue', + 'Zakkary', + 'Bernardino', + 'Deward', + 'Joanthan', + 'Sandeep', + 'Hercules', + 'Claudia', + 'Sampson', + 'Jacobe', + 'Hulon', + 'Ventura', + 'Blade', + 'Jayzen', + 'Jarren', + 'Nakoa', + 'Chan', + 'Jerrel', + 'Isamar', + 'Artie', + 'Amy', + 'Meghan', + 'Rockey', + 'Sixto', + 'Ascencion', + 'Damonte', + 'Golden', + 'Bubba', + 'Randle', + 'Adelard', + 'Rumaldo', + 'Nieves', + 'Marshaun', + 'Kavion', + 'Mikolaj', + 'Brees', + 'Gayland', + 'Herb', + 'Quenton', + 'Flint', + 'Lennie', + 'Tramaine', + 'Nadir', + 'Timur', + 'Keshav', + 'Malek', + 'Ozzie', + 'Dresden', + 'Eliah', + 'Benaiah', + 'Muhsin', + 'Walt', + 'Damen', + 'Enoc', + 'Giancarlos', + 'Darsh', + 'Maximilliano', + 'Yaniel', + 'Jeevan', + 'Malakhi', + 'Viggo', + 'Karlo', + 'Yosgar', + 'Xavior', + 'Frazier', + 'Orin', + 'Payson', + 'Tonatiuh', + 'Amando', + 'Angad', + 'Gibran', + 'Eben', + 'Deaundre', + 'Rajon', + 'Anand', + 'Andree', + 'Dany', + 'Kayvon', + 'Joell', + 'Jahsiah', + 'Rosaire', + 'Kc', + 'Page', + 'Salvadore', + 'Arjen', + 'Torey', + 'Manraj', + 'Lyam', + 'Mazen', + 'Autry', + 'Coopar', + 'Ranveer', + 'Santhiago', + 'Ronen', + 'Remmy', + 'Kamauri', + 'Andra', + 'Sohan', + 'Cayetano', + 'Jarrad', + 'Fortunato', + 'Magdaleno', + 'Dorman', + 'Cesario', + 'Doroteo', + 'Roddy', + 'Matilde', + 'Lafayette', + 'Edelmiro', + 'Higinio', + 'Yancy', + 'Zvi', + 'Pascal', + 'Timm', + 'Dickey', + 'Spiros', + 'Georgios', + 'Jarid', + 'Johnatho', + 'Nachum', + 'Efrem', + 'Stafford', + 'Pajtim', + 'Amelia', + 'Jada', + 'Lily', + 'Lydia', + 'Sherrod', + 'Stedman', + 'Ardis', + 'Levy', + 'Ulysse', + 'Zalman', + 'Marquette', + 'Gabe', + 'Blaize', + 'Ashanti', + 'Shaheem', + 'Hervey', + 'Abbott', + 'Boleslaw', + 'Tyshon', + 'Kimani', + 'Beecher', + 'Diquan', + 'Eulogio', + 'Arvel', + 'Kennth', + 'Benigno', + 'Luz', + 'Dionisio', + 'Eustacio', + 'Trino', + 'Eldred', + 'Primitivo', + 'Perfecto', + 'Delma', + 'Cosme', + 'Milburn', + 'Shameek', + 'Quayshaun', + 'Evert', + 'Green', + 'Brylan', + 'Crit', + 'Haskel', + 'Ancil', + 'Rayhan', + 'Rose', + 'Gianfranco', + 'Matan', + 'Derin', + 'Artem', + 'Abhiram', + 'Yovanni', + 'Stevenson', + 'Crue', + 'Krue', + 'Jethro', + 'Jakai', + 'Mattix', + 'Daxon', + 'Dallan', + 'Murl', + 'Harsh', + 'Uzziel', + 'Kemarion', + 'Jashaun', + 'Rodman', + 'Elie', + 'Desi', + 'Malikai', + 'Angello', + 'Amogh', + 'Advaith', + 'Adryan', + 'Nazareth', + 'Adolf', + 'Bosco', + 'Arshan', + 'Abdulaziz', + 'Theseus', + 'Riaan', + 'Reza', + 'Radley', + 'Mars', + 'Kirin', + 'Kiaan', + 'Evander', + 'Indiana', + 'Hanson', + 'Viliami', + 'Jaydenn', + 'Ilya', + 'Draco', + 'Riyan', + 'Onyx', + 'Xian', + 'Khristopher', + 'Ayrton', + 'Aurelius', + 'Crosley', + 'Obadiah', + 'Nihal', + 'Rithvik', + 'Constantino', + 'Jeyden', + 'Jaycee', + 'Bane', + 'Aakash', + 'Aniket', + 'Mathis', + 'Maximos', + 'Kohl', + 'Fuquan', + 'Rahman', + 'Aziel', + 'Alexys', + 'Iverson', + 'Marck', + 'Criss', + 'Arsen', + 'Angelgabriel', + 'Ronak', + 'Selvin', + 'Ibraheem', + 'Yordi', + 'Taylen', + 'Javari', + 'Jairus', + 'Hamzah', + 'Sacha', + 'Nayan', + 'Marciano', + 'Aneesh', + 'Manfred', + 'Adal', + 'Bernhard', + 'Jeovanny', + 'Satvik', + 'Nicolo', + 'Julious', + 'Weyman', + 'Roswell', + 'Brevin', + 'Amedeo', + 'Deforest', + 'Barnett', + 'Braydin', + 'Italo', + 'Adrienne', + 'Anne', + 'Jr', + 'Krystal', + 'Brion', + 'Wilberto', + 'Detrick', + 'Bucky', + 'Kristin', + 'Christohper', + 'Laddie', + 'Creighton', + 'Gust', + 'Darby', + 'Shanon', + 'Darious', + 'Josua', + 'Thang', + 'Demarkus', + 'Chistopher', + 'Ehren', + 'Marlo', + 'Matas', + 'Augusto', + 'Diamonte', + 'Maciej', + 'Jamon', + 'Marcin', + 'Valdemar', + 'Nickey', + 'Niam', + 'Ambrosio', + 'Crispin', + 'Lukasz', + 'Yazan', + 'Romell', + 'Darryle', + 'Renard', + 'Ewald', + 'Quint', + 'Andrzej', + 'Vittorio', + 'Keonte', + 'Lavonte', + 'Cordale', + 'Darvin', + 'Marvell', + 'Krzysztof', + 'Corben', + 'Keylan', + 'Haydon', + 'Ociel', + 'Zeth', + 'Ahmari', + 'Texas', + 'Yutaka', + 'Isami', + 'Adarius', + 'Juaquin', + 'Jaydn', + 'Jaidan', + 'Exavier', + 'Steffan', + 'Vahe', + 'Crystian', + 'Edilberto', + 'Jaquavion', + 'Xavien', + 'Delvon', + 'Otoniel', + 'Demontae', + 'Collins', + 'Keoki', + 'Nolberto', + 'Leng', + 'Karina', + 'Grigor', + 'Isrrael', + 'Kaoru', + 'Hisao', + 'Masayoshi', + 'Satoru', + 'Satoshi', + 'Nobuo', + 'Michaelanthony', + 'Lucero', + 'Jocelyn', + 'Yovany', + 'Joangel', + 'Jaelyn', + 'Caedmon', + 'Granger', + 'Heston', + 'Rhodes', + 'Kanon', + 'Judith', + 'Montavius', + 'Antron', + 'Xaiden', + 'Burhanuddin', + 'Stratton', + 'Kadence', + 'Jhett', + 'Jacion', + 'Aiyden', + 'Journey', + 'Jaziah', + 'Thien', + 'Travious', + 'Carsyn', + 'Quindarius', + 'Masyn', + 'Jalan', + 'Jaelin', + 'Dorien', + 'Aarron', + 'Dmarcus', + 'Ramin', + 'Christan', + 'Blain', + 'Rosa', + 'Christoher', + 'Vadim', + 'Martha', + 'Osher', + 'Laakea', + 'Chayton', + 'Keahi', + 'Johnatan', + 'Juanantonio', + 'Kahiau', + 'Sheridan', + 'Samual', + 'Luisalberto', + 'Zacharias', + 'Phi', + 'Marquice', + 'Chong', + 'Harpreet', + 'Fue', + 'Derrion', + 'Eber', + 'Kevion', + 'Beryl', + 'Gavan', + 'Liliana', + 'Fernie', + 'Sulo', + 'Jayren', + 'Lior', + 'Ruth', + 'Carlie', + 'Thierno', + 'Davontae', + 'Jamier', + 'Arye', + 'Kiernan', + 'Hanad', + 'Huston', + 'Winson', + 'Hobson', + 'Yates', + 'Kaua', + 'Einar', + 'Berish', + 'Annie', + 'Mahir', + 'Amr', + 'Sabir', + 'Ewell', + 'Orland', + 'Dujuan', + 'Harvie', + 'Dahmir', + 'Hosea', + 'Haneef', + 'Wei', + 'Nello', + 'Fishel', + 'Amere', + 'Rafi', + 'Charlton', + 'Colden', + 'Hughes', + 'Laurier', + 'Blong', + 'Shimshon', + 'Jahmel', + 'Steward', + 'Milbert', + 'Buel', + 'Hallie', + 'Comer', + 'Tafari', + 'Iver', + 'Evangelos', + 'Jaquarius', + 'Azan', + 'Braedan', + 'Jadarrius', + 'Vernie', + 'Andi', + 'Darry', + 'Jawad', + 'Uri', + 'Kennard', + 'Yishai', + 'Kijana', + 'Brekken', + 'Rajan', + 'Stevens', + 'Sunil', + 'Siddhant', + 'Sir', + 'Sire', + 'Jansen', + 'Theodor', + 'Kaedyn', + 'Tymere', + 'Zyair', + 'Tron', + 'Sanchez', + 'Amaru', + 'Anastasio', + 'Agastya', + 'Hawk', + 'Honor', + 'Sotero', + 'Saeed', + 'Ziggy', + 'Conan', + 'Arie', + 'Gloria', + 'Onesimo', + 'Wellington', + 'Alexei', + 'Tavarus', + 'Cayleb', + 'Arion', + 'Amadeus', + 'Bryer', + 'Jeter', + 'Merced', + 'Kaylon', + 'Lakendrick', + 'Nolen', + 'Niccolo', + 'Halston', + 'Deontre', + 'Ash', + 'Arush', + 'Artur', + 'Bidwell', + 'Tomie', + 'Author', + 'Izik', + 'Jeriah', + 'Edwyn', + 'Zhi', + 'Gilman', + 'Jawan', + 'Bryar', + 'Giles', + 'Talha', + 'Gill', + 'Abelino', + 'Kwasi', + 'Stavros', + 'Juanita', + 'Tri', + 'Consuelo', + 'Khambrel', + 'Peterson', + 'Brantly', + 'Brently', + 'Vitaliy', + 'Hashim', + 'Rain', + 'Quintus', + 'Matthieu', + 'Kayne', + 'Icker', + 'Valen', + 'Nels', + 'Josephus', + 'Nasario', + 'Romulo', + 'Kaisen', + 'Sulaiman', + 'Selim', + 'Mahad', + 'Steele', + 'Stryder', + 'Cristina', + 'Thornton', + 'Girard', + 'Prudencio', + 'Ethaniel', + 'Laurent', + 'Jayvin', + 'Jayveon', + 'Eladio', + 'Ellison', + 'Caius', + 'Christiano', + 'Navid', + 'Gerold', + 'Sven', + 'Advay', + 'Cabell', + 'Marcio', + 'Luisalfredo', + 'Ryatt', + 'Elijio', + 'Pax', + 'Neev', + 'Mehtab', + 'Eluterio', + 'Tahmir', + 'Davit', + 'Eliott', + 'Keane', + 'Kysen', + 'Rafe', + 'Legacy', + 'Erie', + 'Orlin', + 'Dawn', + 'Calum', + 'Adithya', + 'Adarsh', + 'Ulysee', + 'Thurmond', + 'Christen', + 'Thayne', + 'Sriram', + 'Yoav', + 'Lawton', + 'Kemar', + 'Duston', + 'Jatavious', + 'Luisfernando', + 'Maxime', + 'Rithik', + 'Dior', + 'Phuong', + 'Roni', + 'Manu', + 'Esteven', + 'Hazen', + 'Farris', + 'Leverne', + 'Ryen', + 'Tanay', + 'Seaborn', + 'Cicero', + 'Gianmarco', + 'Isak', + 'Lige', + 'Burke', + 'Authur', + 'Javarius', + 'Jeromie', + 'Jerred', + 'Silvano', + 'Keyan', + 'Briant', + 'Arun', + 'Jeremi', + 'Decarlos', + 'Jeanpierre', + 'Haydn', + 'Ab', + 'Anmol', + 'Shaye', + 'Nana', + 'Mateen', + 'Maurisio', + 'Nitin', + 'Dustan', + 'Srikar', + 'Arlin', + 'Burnett', + 'Johnathen', + 'Wyman', + 'Aleksandar', + 'Agustine', + 'Ronney', + 'Marisol', + 'Dmarion', + 'Keir', + 'Demetrice', + 'Jawon', + 'Ricci', + 'Javontae', + 'Armoni', + 'Alto', + 'Dawid', + 'Zakir', + 'Jarek', + 'Lary', + 'Dez', + 'Kaydon', + 'Henley', + 'Adonai', + 'Zahmir', + 'Youssouf', + 'Oisin', + 'Deniz', + 'Antonios', + 'Netanel', + 'Shlok', + 'Ranger', + 'Uzziah', + 'Eryk', + 'Sid', + 'Andersen', + 'Daylin', + 'Naftoli', + 'Lyn', + 'Orvin', + 'Kesean', + 'Hanif', + 'Adael', + 'Maury', + 'Ronn', + 'Carlyle', + 'Ankur', + 'Takumi', + 'Piero', + 'Jeanpaul', + 'Hoa', + 'Jacarri', + 'Jakhi', + 'Zyion', + 'Jeovany', + 'Eoin', + 'Etienne', + 'Amrit', + 'Dang', + 'Juliano', + 'Blakely', + 'Tauno', + 'Edin', + 'Dmitriy', + 'Lambert', + 'Roderic', + 'Felice', + 'Zaki', + 'Debra', + 'Teegan', + 'Tosh', + 'Nicholai', + 'Erickson', + 'Atharva', + 'Aaditya', + 'Anuj', + 'Diane', + 'Sachin', + 'Elazar', + 'Torian', + 'Tan', + 'Cristoval', + 'Jonathen', + 'Kobi', + 'Yuki', + 'Jacori', + 'Eduard', + 'Keron', + 'Tysean', + 'Deshun', + 'Hewitt', + 'Kaulana', + 'Jaydyn', + 'Sebastia', + 'Shamell', + 'Trysten', + 'Treshawn', + 'Samer', + 'Burnice', + 'Da', + 'Parris', + 'Royer', + 'Tien', + 'Tj', + 'Andranik', + 'Nino', + 'Luisenrique', + 'Andrick', + 'Graydon', + 'Pookela', + 'Nevaeh', + 'Zoe', + 'Hanna', + 'Joniel', + 'Jamarious', + 'Hurley', + 'Avante', + 'Iban', + 'Isaiha', + 'Chee', + 'Kealii', + 'Irbin', + 'Maynor', + 'Wendy', + 'Germain', + 'Shamus', + 'Zygmunt', + 'Garnet', + 'Lopaka', + 'Damar', + 'Ramy', + 'Everton', + 'Raylen', + 'Tryston', + 'Kullen', + 'Therman', + 'Khaliq', + 'Alon', + 'Arch', + 'Tylen', + 'Kalan', + 'Zacharia', + 'Dalen', + 'Bedford', + 'Lou', + 'Tsuneo', + 'Kalub', + 'Dadrian', + 'Jiro', + 'Fahad', + 'Quashawn', + 'Hisashi', + 'Fumio', + 'Carlito', + 'Ewing', + 'Zarek', + 'Leron', + 'Cardell', + 'Westen', + 'Hogan', + 'Payden', + 'Chazz', + 'Jarryd', + 'Sedric', + 'Homar', + 'Tylar', + 'Keone', + 'Dasean', + 'Lake', + 'Joeanthony', + 'Haroon', + 'Adonys', + 'Grayling', + 'Braelon', + 'Loras', + 'Jontavious', + 'Nesanel', + 'Carlisle', + 'Camillo', + 'Mandeep', + 'Yang', + 'Blayden', + 'Niall', + 'Evelio', + 'Zaragoza', + 'Shlomie', + 'Percell', + 'Baylee', + 'Garold', + 'Eriq', + 'Ozell', + 'Benjiman', + 'Wayman', + 'Saturnino', + 'Moody', + 'Deandra', + 'Estanislado', + 'Curvin', + 'Demonta', + 'Crimson', + 'Scout', + 'Daequan', + 'Izael', + 'Trine', + 'Demontre', + 'Rexford', + 'Fenix', + 'Raheim', + 'Rivers', + 'Cobe', + 'Jeron', + 'Yanuel', + 'Naftula', + 'Dwan', + 'Kanai', + 'Nicco', + 'Kaeson', + 'Shadman', + 'Cobi', + 'Raequan', + 'Shae', + 'Osama', + 'Ernan', + 'Dennys', + 'Aquil', + 'Tierra', + 'Sabrina', + 'Mia', + 'Melanie', + 'Marissa', + 'Carolyn', + 'Arielle', + 'Zaine', + 'Macen', + 'Shahin', + 'Casyn', + 'Osmin', + 'Alphonsus', + 'Carrington', + 'Chayce', + 'Opal', + 'Taylon', + 'Koy', + 'Ebenezer', + 'Amarii', + 'Keshun', + 'Kolin', + 'Aspen', + 'Cort', + 'Zaylon', + 'Zaedyn', + 'Zaydyn', + 'Tuff', + 'Holton', + 'Ashtyn', + 'Lathen', + 'Hershell', + 'Jerre', + 'Tsugio', + 'Josealberto', + 'Adien', + 'Acen', + 'Maurilio', + 'Ashten', + 'Wataru', + 'Keontae', + 'Donaven', + 'Javonta', + 'Jacobie', + 'Peng', + 'Ector', + 'Ankit', + 'Ann', + 'Kasim', + 'Parley', + 'Mizael', + 'Maxon', + 'Kylar', + 'Jjesus', + 'Kaven', + 'Curran', + 'Edvin', + 'Enrrique', + 'Donovin', + 'Godfrey', + 'Xayden', + 'Xzavian', + 'Carlosmanuel', + 'Ladainian', + 'Keithan', + 'Azrael', + 'Jae', + 'Marlow', + 'Aviv', + 'Orson', + 'Zamarion', + 'Chason', + 'Henrry', + 'Gevorg', + 'Dartagnan', + 'Zakee', + 'Giovannie', + 'Halen', + 'Vinay', + 'Wilfrido', + 'Winton', + 'Garet', + 'Josafat', + 'Manjot', + 'Juandaniel', + 'Manley', + 'Oshea', + 'Wali', + 'Reymond', + 'Harjot', + 'Sidharth', + 'Amer', + 'Camari', + 'Quincey', + 'Dawan', + 'Newell', + 'Sigurd', + 'Logen', + 'Rafiq', + 'Delonta', + 'Katrina', + 'Kristina', + 'Octavia', + 'Sade', + 'Ziyad', + 'Tovia', + 'Malachai', + 'Briana', + 'Alison', + 'Ashleigh', + 'Jerick', + 'Benedetto', + 'Fiore', + 'Mikail', + 'Qasim', + 'Yochanan', + 'Ettore', + 'Ferris', + 'Aziz', + 'Naseer', + 'Jabril', + 'Brodey', + 'Alvah', + 'Kalman', + 'Ziyon', + 'Zakery', + 'Sedale', + 'Jevin', + 'Kalmen', + 'Moishy', + 'Shai', + 'Zakari', + 'Bradlee', + 'Kenley', + 'Pratham', + 'Izeah', + 'Ilias', + 'Emari', + 'Race', + 'Zacarias', + 'Yuri', + 'Kleber', + 'Kailer', + 'Jhovany', + 'Iven', + 'Issaiah', + 'Hosie', + 'Dixon', + 'Massiah', + 'Remo', + 'Pinchos', + 'Mahki', + 'Gunther', + 'Irene', + 'Jamarie', + 'Kaan', + 'Jayon', + 'Moroni', + 'Jkwon', + 'Barack', + 'Alastair', + 'Fares', + 'Zackariah', + 'Yoshua', + 'Tanish', + 'Iann', + 'Linden', + 'Avinash', + 'Willam', + 'Iman', + 'Domanic', + 'Lenton', + 'Samad', + 'Aimar', + 'Buddie', + 'Jozef', + 'Josmar', + 'Mercer', + 'Collie', + 'Nephi', + 'Kenai', + 'Alquan', + 'Cezar', + 'Verbon', + 'Aeneas', + 'Jeremyah', + 'Eren', + 'Tej', + 'Jahad', + 'Deep', + 'Augusta', + 'Yaqub', + 'Yahye', + 'Vashon', + 'Kristoff', + 'Penn', + 'Loukas', + 'Kaydin', + 'Kaius', + 'Perseus', + 'Mykah', + 'Joab', + 'Cylus', + 'Emrys', + 'Mikko', + 'Jaxsyn', + 'Sudais', + 'Tiberius', + 'Rooney', + 'Yuvan', + 'Cletis', + 'Liev', + 'Ester', + 'Harlow', + 'Shreyan', + 'Samar', + 'Saharsh', + 'Ruhaan', + 'Zyler', + 'Yuma', + 'Dwyane', + 'Yanni', + 'Dutch', + 'Rajveer', + 'Tayton', + 'Kasir', + 'Luster', + 'Tage', + 'Damarius', + 'Elihu', + 'Heinz', + 'Manolo', + 'Makhai', + 'Madhav', + 'Sohum', + 'Omri', + 'Egbert', + 'Marie', + 'Keshon', + 'Jahmier', + 'Nachmen', + 'Mckade', + 'Moise', + 'Ames', + 'Iden', + 'Benard', + 'Yannick', + 'Pasha', + 'Sherrick', + 'Jordany', + 'Fenton', + 'Tytan', + 'Dashel', + 'Daksh', + 'Juliani', + 'Jhonathan', + 'Broxton', + 'Essie', + 'Devontay', + 'Maksym', + 'Park', + 'Dasani', + 'Severiano', + 'Kamel', + 'Chayanne', + 'Jarel', + 'Yolanda', + 'Tylik', + 'Marquell', + 'Jamarr', + 'Micky', + 'Socorro', + 'Waymond', + 'Michial', + 'Yoseph', + 'Lumir', + 'Placido', + 'Asif', + 'Needham', + 'Claiborne', + 'Tennis', + 'Burley', + 'Raffaele', + 'Shavar', + 'Atanacio', + 'Jahmar', + 'Arben', + 'Nabeel', + 'Cordarryl', + 'Danyal', + 'Bryston', + 'Lemont', + 'Elston', + 'Kerwin', + 'Riccardo', + 'Danzel', + 'Waldemar', + 'Ledarius', + 'Omarr', + 'Wilho', + 'Alger', + 'Raymie', + 'Kenney', + 'Abdallah', + 'Aristides', + 'Avram', + 'Tayvion', + 'Urbano', + 'Deontay', + 'Darcy', + 'Robbin', + 'Bartlomiej', + 'Dann', + 'Tyjuan', + 'Khaleel', + 'Winifred', + 'Claron', + 'Linford', + 'Hilliard', + 'Arlon', + 'Yong', + 'Malvin', + 'Zymere', + 'Newborn', + 'Eleuterio', + 'Glyn', + 'Koltyn', + 'Serapio', + 'Pius', + 'Ines', + 'Harrold', + 'Caitlyn', + 'Rajeev', + 'Constantinos', + 'Abid', + 'Calvert', + 'Parnell', + 'Aubry', + 'Damone', + 'Akim', + 'Adem', + 'Othel', + 'Joaopedro', + 'Tomer', + 'Brentlee', + 'Melquan', + 'Elpidio', + 'Jenny', + 'Alejos', + 'Romie', + 'Ardell', + 'Doctor', + 'Virginia', + 'Makenzie', + 'Maggie', + 'Tywan', + 'Elisaul', + 'Luby', + 'Teofilo', + 'Jermell', + 'Gumesindo', + 'Harless', + 'Croix', + 'Obinna', + 'Traveon', + 'Coley', + 'Tu', + 'Brylon', + 'Carlin', + 'Daneil', + 'Garen', + 'Ronell', + 'Chesley', + 'Tyrece', + 'Arville', + 'Eamonn', + 'Kayshawn', + 'Wilkie', + 'Zacchaeus', + 'Rapheal', + 'Cordaryl', + 'Quan', + 'Nhan', + 'Vann', + 'Franciscojavier', + 'Kinte', + 'Rui', + 'Chuong', + 'Chao', + 'Chai', + 'Linh', + 'Cirilo', + 'Ky', + 'Gwyn', + 'Hearl', + 'Tray', + 'Carmon', + 'Phuc', + 'Neiman', + 'Ladon', + 'Moua', + 'Eulises', + 'Jonte', + 'Yusuke', + 'Vinnie', + 'Seanpatrick', + 'Pearson', + 'Daemon', + 'Reyn', + 'Daekwon', + 'Garron', + 'Sequan', + 'Zavien', + 'Geovanie', + 'Jessee', + 'Richmond', + 'Osualdo', + 'Artin', + 'Devone', + 'Makoto', + 'Hitoshi', + 'Shinichi', + 'Samari', + 'Saxon', + 'Glennis', + 'Fadi', + 'Bronislaw', + 'Estuardo', + 'Shaheen', + 'Saman', + 'Lue', + 'Djuan', + 'Cord', + 'Linville', + 'Landis', + 'Cameren', + 'Herson', + 'Ellie', + 'Seanmichael', + 'Froilan', + 'Delon', + 'Jestin', + 'Mattew', + 'Toni', + 'Kelii', + 'Maribel', + 'Jadrian', + 'Traylon', + 'Kaiea', + 'Kaeo', + 'Taft', + 'Dameion', + 'Darryn', + 'Dondi', + 'Clell', + 'Corbett', + 'Lyndell', + 'Avenir', + 'Seldon', + 'Jakwon', + 'Jacque', + 'Deane', + 'Cheikh', + 'Carmel', + 'Kieth', + 'Tahmid', + 'Lillard', + 'Tasheem', + 'Jens', + 'Christobal', + 'Delos', + 'Lashon', + 'Jaimie', + 'Kary', + 'Kendarious', + 'Johnell', + 'Harlen', + 'Terron', + 'Corliss', + 'Liston', + 'Seng', + 'Phu', + 'Rasean', + 'Sung', + 'San', + 'Babak', + 'Adel', + 'Gillermo', + 'Avon', + 'Harlon', + 'Allyn', + 'Clary', + 'Orry', + 'Nazario', + 'Jamail', + 'Daeshawn', + 'Tal', + 'Moustafa', + 'Tarell', + 'Jahquan', + 'Jian', + 'Lazar', + 'Adama', + 'Benyamin', + 'Tayvon', + 'Lamel', + 'Davonne', + 'Tayquan', + 'Jusitn', + 'Shjon', + 'Leotis', + 'Kasheem', + 'Ilir', + 'Ravon', + 'Parish', + 'Ehan', + 'Daishawn', + 'Islam', + 'Pinches', + 'Ovadia', + 'Mechel', + 'Berlin', + 'Deryk', + 'Tymel', + 'Vijay', + 'Dyquan', + 'Agron', + 'Tarrell', + 'Itamar', + 'Mordcha', + 'Chrisotpher', + 'Alban', + 'Stephane', + 'Tanvir', + 'Demetriu', + 'Yan', + 'Asim', + 'Ahsan', + 'Mackenzi', + 'Kristofe', + 'Kenrick', + 'Cuahutemoc', + 'Tavis', + 'Audric', + 'Deaven', + 'Nicanor', + 'Mick', + 'Geoffery', + 'Timofey', + 'Dolphus', + 'Franciso', + 'Gorje', + 'Jobany', + 'Abdelrahman', + 'Clenton', + 'Yohance', + 'Milad', + 'Juanluis', + 'Luismario', + 'Marvyn', + 'Rushil', + 'Tenoch', + 'Trentin', + 'Fardeen', + 'Shashank', + 'Yuta', + 'Ritvik', + 'Akili', + 'Aleksei', + 'Gaurav', + 'Iran', + 'Caillou', + 'Borach', + 'Keisuke', + 'Kaushik', + 'Hari', + 'Izac', + 'Josejulian', + 'Juanangel', + 'Kasra', + 'Anthonie', + 'Daivd', + 'Dain', + 'Toren', + 'Sesar', + 'Eldor', + 'Pieter', + 'Yu', + 'Cloyce', + 'Dusten', + 'Aquiles', + 'Aslan', + 'Sevastian', + 'Siddarth', + 'Tysen', + 'Johncarlo', + 'Idan', + 'Daymian', + 'Domanick', + 'Arnie', + 'Bartley', + 'Newman', + 'Akram', + 'Abdulla', + 'Lew', + 'Geremy', + 'Jehu', + 'Josejuan', + 'Jailen', + 'Etai', + 'Fabien', + 'Victormanuel', + 'Ossie', + 'Egan', + 'Eldin', + 'Shamari', + 'Nussen', + 'Arda', + 'Sina', + 'Tytus', + 'Pranay', + 'Dylen', + 'Juandavid', + 'Kalil', + 'Kushal', + 'Hazael', + 'Lecil', + 'Belton', + 'Aleczander', + 'Terance', + 'Faizan', + 'Naithan', + 'Koji', + 'Akshat', + 'Andruw', + 'Bram', + 'Dieter', + 'Saahil', + 'Saulo', + 'Arnel', + 'Demarea', + 'Farhad', + 'Joeseph', + 'Alondra', + 'Belal', + 'Antoniodejesus', + 'Anival', + 'Choua', + 'Cha', + 'Bryn', + 'Xiong', + 'Aristeo', + 'Mehmet', + 'Moustapha', + 'Jandel', + 'Asante', + 'Yunus', + 'Schneur', + 'Steffen', + 'Leovardo', + 'Kacey', + 'Payam', + 'Salbador', + 'Nicholes', + 'Neema', + 'Clarke', + 'Marqus', + 'Araceli', + 'Jerman', + 'Marioalberto', + 'Joseguadalupe', + 'Emigdio', + 'Krishan', + 'Jessey', + 'Arcadio', + 'Zong', + 'Yoni', + 'Tirso', + 'Thompson', + 'Damarea', + 'Everado', + 'Edy', + 'Edder', + 'Nikki', + 'Clemmie', + 'Willian', + 'Marquese', + 'Perris', + 'Miriam', + 'Shelly', + 'Bulmaro', + 'Jasdeep', + 'Irvine', + 'Hue', + 'Gurpreet', + 'Donaldo', + 'Jonthan', + 'Geroge', + 'Francois', + 'Duc', + 'Jerico', + 'Avedis', + 'Chang', + 'Damario', + 'Kenta', + 'Nikkolas', + 'Khoi', + 'Garren', + 'Norma', + 'My', + 'Lam', + 'Sahir', + 'Yer', + 'Jaskarn', + 'Jeric', + 'Maximillion', + 'Elson', + 'Marin', + 'Loc', + 'Lemar', + 'Kristofor', + 'Nai', + 'Takoda', + 'Tung', + 'Thong', + 'Rayshaun', + 'Derreck', + 'Regino', + 'Nadav', + 'Luismiguel', + 'Josede', + 'Hao', + 'Rayce', + 'Zacary', + 'Nareg', + 'Khyree', + 'Chi', + 'Joanna', + 'Sevag', + 'Garin', + 'Juluis', + 'Petros', + 'Berel', + 'Abubakar', + 'Jorel', + 'Kazi', + 'Jaiceon', + 'Haider', + 'Feynman', + 'Muhammadali', + 'Jassiel', + 'Morrison', + 'Nakai', + 'Oden', + 'Odysseus', + 'Quest', + 'Kaidan', + 'Kilian', + 'Kirill', + 'Thorin', + 'Tru', + 'Xzander', + 'Taniela', + 'Roen', + 'Sho', + 'Aarin', + 'Gracen', + 'Gurfateh', + 'Gurman', + 'Hiro', + 'Edrick', + 'Esaias', + 'Johncarlos', + 'Sidi', + 'Cataldo', + 'Noor', + 'Philbert', + 'Eyad', + 'Arber', + 'Abrar', + 'Ladislaus', + 'Serafino', + 'Mannie', + 'Daevon', + 'Haseeb', + 'Yale', + 'Spiro', + 'Emre', + 'Daryan', + 'Camrin', + 'Kavi', + 'Doran', + 'Vaibhav', + 'Rayne', + 'Derric', + 'Orbie', + 'Reily', + 'Gio', + 'Gurnoor', + 'Jaasiel', + 'Naman', + 'Josaiah', + 'Josiyah', + 'Kasper', + 'Filippo', + 'Sigfredo', + 'Joesiah', + 'Rei', + 'Nahom', + 'Ojas', + 'Vladislav', + 'Hilary', + 'Rinaldo', + 'Even', + 'Gautam', + 'Cornel', + 'Julyan', + 'Inaki', + 'Iseah', + 'Itai', + 'Laurance', + 'Garey', + 'Lawerance', + 'Quindon', + 'Levin', + 'Leviticus', + 'Link', + 'Glenford', + 'Avyan', + 'Dmitry', + 'Eiden', + 'Advait', + 'Ahaan', + 'Arhaan', + 'Kassius', + 'Hendrick', + 'Jaiveer', + 'Nirvaan', + 'Reeve', + 'Torsten', + 'True', + 'Iwao', + 'Jahvon', + 'Paxson', + 'Kali', + 'Kwesi', + 'Yaron', + 'Ami', + 'Dashiel', + 'Meliton', + 'Sylus', + 'Mika', + 'Jireh', + 'Selig', + 'Adi', + 'Brenner', + 'Breyden', + 'Mitsuru', + 'Farley', + 'Montrel', + 'Kyland', + 'Jadakiss', + 'Tadarius', + 'Brooke', + 'Alexandria', + 'Alexa', + 'Abby', + 'Hayley', + 'Mallory', + 'Madelyn', + 'Layla', + 'Kirsten', + 'Quayshawn', + 'Deadrick', + 'Hobby', + 'Eunice', + 'Macon', + 'Ysabel', + 'Secundino', + 'Hulen', + 'Estle', + 'Tolbert', + 'Baker', + 'Tilford', + 'Shyheem', + 'Orbin', + 'Ruel', + 'Hurshel', + 'Jailyn', + 'Dequincy', + 'Jamall', + 'Draper', + 'Kenric', + 'Aime', + 'Cam', + 'Connell', + 'Treylon', + 'Bethel', + 'Rommie', + 'Alphonza', + 'Gussie', + 'Elridge', + 'Hillery', + 'Ruffin', + 'Farrel', + 'Wendall', + 'Gerome', + 'Ferrell', + 'Uvaldo', + 'Marshon', + 'Jawaun', + 'Trevell', + 'Tyvon', + 'Telesforo', + 'Ellery', + 'Cordae', + 'Loran', + 'Travell', + 'Lamari', + 'Errick', + 'Antwoine', + 'Starsky', + 'Chirag', + 'Donzell', + 'Tierre', + 'Ketan', + 'Crespin', + 'Orris', + 'Bawi', + 'Wanda', + 'Canuto', + 'Aniceto', + 'Braxten', + 'Audry', + 'Bartolo', + 'Brigido', + 'Garvin', + 'Vergil', + 'Olegario', + 'Thelma', + 'Crecencio', + 'Eleno', + 'Wright', + 'Burtis', + 'Dicky', + 'Avelino', + 'Norval', + 'Cirildo', + 'Darwyn', + 'Delwin', + 'Henery', + 'Beauford', + 'Little', + 'Ameir', + 'Arland', + 'Verner', + 'Taron', + 'Undra', + 'Khasir', + 'Kymir', + 'Aleem', + 'Ordean', + 'Carmino', + 'Lucus', + 'Jodeci', + 'Linn', + 'Sinclair', + 'Delorean', + 'Chalmers', + 'Kentavius', + 'Jarious', + 'Lajuan', + 'Narada', + 'Hussien', + 'Alonte', + 'Damarco', + 'Benjamen', + 'Randon', + 'Jabree', + 'Lawyer', + 'Wanya', + 'Samie', + 'Sim', + 'Washington', + 'Isom', + 'Keyton', + 'Quin', + 'Mahamed', + 'Liban', + 'Ramir', + 'Samaj', + 'Kipp', + 'Prentis', + 'Jibril', + 'Kyaire', + 'Buell', + 'Nasim', + 'Adell', + 'Mohamedamin', + 'Abdiaziz', + 'Harun', + 'Amire', + 'Eligah', + 'Parks', + 'Colonel', + 'Joaovictor', + 'Vinicius', + 'Mcdonald', + 'Manly', + 'Phares', + 'Geza', + 'Kemp', + 'Alphonzo', + 'Loring', + 'Haig', + 'Joaquim', + 'Craven', + 'Bynum', + 'Parke', + 'Ignatz', + 'Hebert', + 'Berton', + 'Ayomide', + 'Kidus', + 'Ayven', + 'Aziah', + 'Banner', + 'Barret', + 'Blayze', + 'Braddock', + 'Javoris', + 'Cortland', + 'Antavius', + 'Amaziah', + 'Santonio', + 'Slate', + 'Sylis', + 'Thierry', + 'Joanthony', + 'Rhylan', + 'Pryce', + 'Riggin', + 'Dequavious', + 'Bakari', + 'Marquavius', + 'Artavious', + 'Desmon', + 'Rajohn', + 'Faheem', + 'Kage', + 'Arkeem', + 'Jaquon', + 'Dontavis', + 'Quentavious', + 'Braysen', + 'Bricen', + 'Traevon', + 'Caidyn', + 'Collyn', + 'Joah', + 'Patton', + 'Coleson', + 'Eythan', + 'Hadley', + 'Jaaziel', + 'Johntavious', + 'Quadarius', + 'Rafeal', + 'Karam', + 'Krishiv', + 'Majd', + 'Yeray', + 'Whitten', + 'Johnluke', + 'Demani', + 'Easten', + 'Ediel', + 'Tellis', + 'Delvecchio', + 'Aleks', + 'Rylie', + 'Osmel', + 'Lelan', + 'Tamarion', + 'Cayman', + 'Hajime', + 'Akio', + 'Takao', + 'Seiji', + 'Ah', + 'Mitsugi', + 'Koichi', + 'Ikenna', + 'Tyquavious', + 'Brannen', + 'Slayde', + 'Sultan', + 'Cage', + 'Jillian', + 'Kara', + 'Simone', + 'Theresa', + 'Julie', + 'Alisha', + 'Candace', + 'Candice', + 'Jazmine', + 'Domani', + 'Tiana', + 'Jeovanni', + 'Khaleb', + 'Copeland', + 'Dathan', + 'Deleon', + 'Jakori', + 'Jayke', + 'Kadon', + 'Camdon', + 'Shandon', + 'Mylan', + 'Jaxin', + 'Beverley', + 'Dallon', + 'Jakeem', + 'Tallon', + 'Vraj', + 'Welford', + 'Jadarian', + 'Yancarlos', + 'Omkar', + 'Jamaree', + 'Alix', + 'Trevyn', + 'Orestes', + 'Trevis', + 'Refoel', + 'Roddrick', + 'Tarvis', + 'Tamarick', + 'Denard', + 'Kerem', + 'Treyden', + 'Stephano', + 'Shubh', + 'Carston', + 'Utah', + 'Treven', + 'Reshard', + 'Yerachmiel', + 'Osmany', + 'Vansh', + 'Samaad', + 'Shakil', + 'Saford', + 'Doyal', + 'Cai', + 'Alexey', + 'Cruze', + 'Masiah', + 'Kitai', + 'Fedor', + 'Algie', + 'Worley', + 'Jakhari', + 'Brison', + 'Lanier', + 'Eston', + 'Qadir', + 'Lonzie', + 'Rayfield', + 'Chirstopher', + 'Eron', + 'Deontray', + 'Zoltan', + 'Christon', + 'Byford', + 'Mikeal', + 'Talyn', + 'Stormy', + 'Laramie', + 'Chrisopher', + 'Breckin', + 'Kennon', + 'Json', + 'Deiondre', + 'Heron', + 'Mykal', + 'Kalai', + 'Ervey', + 'Brayam', + 'Alakai', + 'Maika', + 'Kelson', + 'Trevaughn', + 'Aundre', + 'Eathan', + 'Keylon', + 'Kolbe', + 'Sebastion', + 'Kalib', + 'Jermy', + 'Jarrid', + 'Gumaro', + 'Maliq', + 'Armstead', + 'Stephone', + 'Oris', + 'Hassel', + 'Antwine', + 'Lorraine', + 'Budd', + 'Irfan', + 'Kamrin', + 'Araf', + 'Affan', + 'Leiby', + 'Sruly', + 'Peretz', + 'Mildred', + 'Louise', + 'Ryken', + 'Ryler', + 'Tayven', + 'Taysen', + 'Brexton', + 'Zayaan', + 'Oronde', + 'Firman', + 'Collen', + 'Letcher', + 'Clearence', + 'Braydan', + 'Yasser', + 'Jeferson', + 'Yahsir', + 'Cavan', + 'Ivor', + 'Hasker', + 'Kodie', + 'Lori', + 'Jaysean', + 'Cadin', + 'Breydon', + 'Amaree', + 'Nyeem', + 'Menno', + 'Orlo', + 'Nassir', + 'Sylar', + 'Drevon', + 'Burech', + 'Lenox', + 'Shloima', + 'Daris', + 'Diontae', + 'Aidin', + 'Brydon', + 'Jasean', + 'Nasier', + 'Johney', + 'Gabrial', + 'Fate', + 'Colyn', + 'Kaleem', + 'Capers', + 'Rembert', + 'Jquan', + 'Legrand', + 'Kirubel', + 'Gaberiel', + 'Thaddaeus', + 'Rece', + 'Dymir', + 'Tylil', + 'Remigio', + 'Ahad', + 'Melquiades', + 'Ethel', + 'Euel', + 'Harvy', + 'Margarita', + 'Jakeb', + 'Kagan', + 'Trinton', + 'Faiz', + 'Iliyan', + 'Emeterio', + 'Ferman', + 'Keeton', + 'Decorian', + 'Hadyn', + 'Rashaud', + 'Davontay', + 'Brallan', + 'Benancio', + 'Espiridion', + 'Seledonio', + 'Estefan', + 'Chanse', + 'Dade', + 'Sisto', + 'Herbie', + 'Janson', + 'Eusevio', + 'Loye', + 'Leocadio', + 'Kaelon', + 'Trevian', + 'Christien', + 'Chrystian', + 'Daegan', + 'Rosbel', + 'Romero', + 'Kylin', + 'Treyvion', + 'Ezekial', + 'Jaice', + 'Jantzen', + 'Aadyn', + 'Tennyson', + 'Kaedan', + 'Kaiser', + 'Kanin', + 'Jerron', + 'Jonaven', + 'Elija', + 'Amon', + 'Valton', + 'Derwood', + 'Atilano', + 'Jovanie', + 'Kaemon', + 'Oluwatobi', + 'Atlee', + 'Tadd', + 'Tammy', + 'Lem', + 'Hilmar', + 'Foch', + 'Clenard', + 'Jd', + 'Jiovanny', + 'Ladarion', + 'Lleyton', + 'Adrik', + 'Webb', + 'Toddrick', + 'Jerrett', + 'Omero', + 'Wendel', + 'Teresa', + 'Cass', + 'Kedric', + 'Heraclio', + 'Rainier', + 'Lakota', + 'Sanjuan', + 'Daymon', + 'Rodd', + 'Yancey', + 'Trampas', + 'Viviano', + 'Heith', + 'Bj', + 'Trevante', + 'Ildefonso', + 'Jaeger', + 'Jamarkus', + 'Remijio', + 'Desiderio', + 'Ausencio', + 'Alejo', + 'Keldrick', + 'Sigifredo', + 'Treavor', + 'Britain', + 'Macedonio', + 'Kourtney', + 'Gerrick', + 'Jousha', + 'Klinton', + 'Montreal', + 'Catlin', + 'Danner', + 'Eliberto', + 'Eliodoro', + 'Lonnell', + 'Michiel', + 'Hermilo', + 'Jackey', + 'Todrick', + 'Irineo', + 'Wenceslao', + 'Duaine', + 'Cleto', + 'Gaylan', + 'Derrel', + 'Nabor', + 'Huck', + 'Hoy', + 'Antwaun', + 'Hoyte', + 'Flournoy', + 'Mayford', + 'Harlie', + 'Hansford', + 'Cutler', + 'Amerigo', + 'Teague', + 'Griffith', + 'Emidio', + 'Kenna', + 'Cru', + 'Arnett', + 'Gay', + 'Dencil', + 'Carman', + 'Doy', + 'Trevan', + 'Jahziel', + 'Rodricus', + 'Copper', + 'Dael', + 'Aydon', + 'Ricco', + 'Judas', + 'Kessler', + 'Romelo', + 'Slayton', + 'Marico', + 'Leevi', + 'Xadrian', + 'Jceon', + 'Kross', + 'Chancey', + 'Bayne', + 'Brylen', + 'Eidan', + 'Olvin', + 'Pearce', + 'Zak', + 'Jaiven', + 'Dani', + 'Bairon', + 'Cordarious', + 'Jaxyn', + 'Rylin', + 'Avin', + 'Bransen', + 'Eastyn', + 'Eyden', + 'Brenham', + 'Chaston', + 'Horatio', + 'Dakarai', + 'Jencarlo', + 'Jevan', + 'Jhayden', + 'Tracen', + 'Peggy', + 'Wynn', + 'Bennet', + 'Milas', + 'Ronal', + 'Kadrian', + 'Jhase', + 'Callahan', + 'Hays', + 'Braidyn', + 'Ezana', + 'Chidubem', + 'Virat', + 'Maxemiliano', + 'Ozias', + 'Pace', + 'Mordecai', + 'Tabor', + 'Phillipe', + 'Maritza', + 'Ricahrd', + 'Jeanette', + 'Sundeep', + 'Tyric', + 'Mina', + 'Nasser', + 'Nhia', + 'Giuliano', + 'Farid', + 'Ryo', + 'Delmont', + 'Klaus', + 'Traquan', + 'Dawayne', + 'Broward', + 'Drequan', + 'Cagney', + 'Shellie', + 'Torre', + 'Deepak', + 'Janmichael', + 'Lan', + 'Quentavius', + 'Quantez', + 'Markevious', + 'Melbourne', + 'Melford', + 'Xue', + 'Samnang', + 'Jarquez', + 'Montrez', + 'Dao', + 'Luvern', + 'Vue', + 'Jenaro', + 'Wacey', + 'Lorena', + 'Ly', + 'Casmere', + 'Marsean', + 'Marinus', + 'Shiro', + 'Shizuo', + 'Knowledge', + 'Baudelio', + 'Cher', + 'Christiaan', + 'Adriane', + 'Wilgus', + 'Gustabo', + 'Barnet', + 'Xeng', + 'Priscilla', + 'Sou', + 'Sumeet', + 'Vartan', + 'Herschell', + 'Montell', + 'Illya', + 'Flem', + 'Marwan', + 'Johnrobert', + 'Boleslaus', + 'Christie', + 'Ericberto', + 'Esmeralda', + 'Cecilia', + 'Purvis', + 'Benjie', + 'Sutter', + 'Sufyan', + 'Viraaj', + 'Sathvik', + 'Quitman', + 'Liborio', + 'Humbert', + 'Zakariah', + 'Yichen', + 'Seward', + 'Alf', + 'Sebastiano', + 'Guiseppe', + 'Stanislaw', + 'Tyrice', + 'Lenell', + 'Kewon', + 'Bahe', + 'Recardo', + 'Paola', + 'Ronson', + 'Naveed', + 'Karla', + 'Lamberto', + 'Leoncio', + 'Sandor', + 'Diamante', + 'Woodson', + 'Hargis', + 'Kelcey', + 'Daquon', + 'Estell', + 'Christapher', + 'Jalal', + 'Tania', + 'Tramell', + 'Victoralfonso', + 'Kento', + 'Kiet', + 'Krystopher', + 'Shaine', + 'Bejamin', + 'Virgel', + 'Toxie', + 'Goebel', + 'Tyon', + 'Norvin', + 'Savalas', + 'Othmar', + 'Jakaiden', + 'Reis', + 'Pratik', + 'Ashish', + 'Hutson', + 'Karmello', + 'Dacari', + 'Katsuji', + 'Sadamu', + 'Masatoshi', + 'Kiyoto', + 'Carols', + 'Waylen', + 'Shain', + 'Alexandru', + 'Jomo', + 'Kalei', + 'Shyam', + 'Zyan', + 'Tamar', + 'Prem', + 'Jamiyl', + 'Remmel', + 'Harlin', + 'Novak', + 'Fynn', + 'Gonsalo', + 'Maliki', + 'Loghan', + 'Cauy', + 'Kassem', + 'Jitsuo', + 'Itsuo', + 'Atsushi', + 'Sunao', + 'Sueo', + 'Hiromu', + 'Toshiyuki', + 'Osamu', + 'Mena', + 'Aldin', + 'Leticia', + 'Darick', + 'Kawan', + 'Rajahn', + 'Asmar', + 'Emarion', + 'Hilmer', + 'Dameyune', + 'Rondarius', + 'Brinson', + 'Trason', + 'Chung', + 'Eran', + 'Khanh', + 'Javarious', + 'Makel', + 'Zyquan', + 'Quintarius', + 'Duran', + 'Veasna', + 'Thao', + 'Gracin', + 'Eberardo', + 'Ming', + 'Lusiano', + 'Kaveh', + 'Truong', + 'Ying', + 'Kentravious', + 'Dillen', + 'Jamonte', + 'Arthuro', + 'Camarion', + 'Avett', + 'Mehdi', + 'Nishant', + 'Bartek', + 'Aarnav', + 'Jeffory', + 'Deen', + 'Dayshaun', + 'Kemonte', + 'Petar', + 'Yug', + 'Donat', + 'Sylvio', + 'Magdiel', + 'Christianjames', + 'Lessie', + 'Sander', + 'Rajvir', + 'Nahuel', + 'Pearlie', + 'Aaren', + 'Dimitry', + 'Aravind', + 'Aristotle', + 'Jeury', + 'Naji', + 'Tysheem', + 'Alcee', + 'Gustaf', + 'Jamarrion', + 'Zollie', + 'Malick', + 'Navin', + 'Juwon', + 'Usama', + 'Walid', + 'Quamel', + 'Sadiq', + 'Tamarcus', + 'Merwyn', + 'Ferdie', + 'Kalif', + 'Latif', + 'Davidson', + 'Aahan', + 'Shahid', + 'Min', + 'Kieren', + 'Oz', + 'Oryan', + 'Madox', + 'Kota', + 'Gurshaan', + 'Gagik', + 'Finnigan', + 'Finlay', + 'Exodus', + 'Kaileb', + 'Jullien', + 'Jiovani', + 'Maryland', + 'Weaver', + 'Williard', + 'Keyondre', + 'Kailen', + 'Kanan', + 'Luisantonio', + 'Izack', + 'Daniela', + 'Colm', + 'Raja', + 'Keeshawn', + 'Adhemar', + 'Hillary', + 'Abdimalik', + 'Roark', + 'Kolston', + 'Cheryl', + 'Richardson', + 'Arif', + 'Jahkeem', + 'Kumar', + 'Raywood', + 'Jaiquan', + 'Earley', + 'Buren', + 'Rossie', + 'Jakayden', + 'Ruffus', + 'Zaquan', + 'Tamer', + 'Devonne', + 'Ikeem', + 'Dhruva', + 'Georges', + 'Kwabena', + 'Yeriel', + 'Glover', + 'Sanders', + 'Adonay', + 'Gillis', + 'Yomar', + 'Ediberto', + 'Antwane', + 'Isahi', + 'Haidyn', + 'Elizandro', + 'Markjoseph', + 'Jezreel', + 'Isayah', + 'Zedekiah', + 'Nikolay', + 'Jenner', + 'Uriyah', + 'Taiga', + 'Daniele', + 'Zacharie', + 'Joanne', + 'Manpreet', + 'Mohan', + 'Eliu', + 'Faraz', + 'Robah', + 'Isham', + 'Omarian', + 'Gagandeep', + 'Zeno', + 'Waddell', + 'Plato', + 'Quavon', + 'Talib', + 'Bascom', + 'Mayo', + 'Tequan', + 'Teron', + 'Anatole', + 'Tajh', + 'Algenis', + 'Liridon', + 'Kervens', + 'Yunior', + 'Kenson', + 'Wesly', + 'Antwann', + 'Zelig', + 'Demetrious', + 'Johnbenedict', + 'Josecarlos', + 'Kona', + 'Cj', + 'Atul', + 'Asaf', + 'Aleck', + 'Anthoni', + 'Anuar', + 'Gedalya', + 'Rafay', + 'Eyal', + 'Andry', + 'Natanel', + 'Nissim', + 'Jahdiel', + 'Jophy', + 'Rehaan', + 'Jhovani', + 'Maxximus', + 'Nain', + 'Yomtov', + 'Sheikh', + 'Demir', + 'Markos', + 'Mouhamadou', + 'Ousman', + 'Izreal', + 'Hadrian', + 'Aldrin', + 'Conlan', + 'Degan', + 'Toi', + 'Finneas', + 'Latroy', + 'Adon', + 'Antuan', + 'Elchonon', + 'Uzair', + 'Mohid', + 'Nazier', + 'Eliab', + 'Roc', + 'Pavan', + 'Yovanny', + 'Sinjin', + 'Tavoris', + 'Asiel', + 'Brayant', + 'Alexsandro', + 'Adrean', + 'Darel', + 'Olajuwon', + 'Corderro', + 'Tynan', + 'Xaiver', + 'Travaris', + 'Yonis', + 'Gerren', + 'Demon', + 'Furnell', + 'Juel', + 'Harish', + 'Raiyan', + 'Elia', + 'Elijha', + 'Gautham', + 'Arvind', + 'Audel', + 'Almer', + 'Djimon', + 'Jahi', + 'Gehrig', + 'Avant', + 'Arnell', + 'Eliaz', + 'Kaedon', + 'Jaedin', + 'Voshon', + 'Malachy', + 'Gilad', + 'Gabriele', + 'Riku', + 'Cameran', + 'Yoskar', + 'Jahfari', + 'Alexiz', + 'Javante', + 'Gregor', + 'Izel', + 'Donnovan', + 'Nikos', + 'Kodey', + 'Eytan', + 'Betzalel', + 'Dimitrius', + 'Chananya', + 'Graylin', + 'Samvel', + 'Yi', + 'Wassillie', + 'Kelechi', + 'Erroll', + 'Ardit', + 'Rahn', + 'Delaine', + 'Jule', + 'Idus', + 'Dessie', + 'Juda', + 'Levester', + 'Kiante', + 'Earnie', + 'Ihor', + 'Kapono', + 'Akoni', + 'Koamalu', + 'Sholem', + 'Howie', + 'Dariusz', + 'Hall', + 'Kekai', + 'Onix', + 'Ozie', + 'Liem', + 'Collis', + 'Lemon', + 'Hinton', + 'Guss', + 'Ronda', + 'Siddhartha', + 'Owyn', + 'Rye', + 'Riot', + 'Vander', + 'Selena', + 'Barnie', + 'Lewie', + 'Jaxiel', + 'Kaizen', + 'Haloa', + 'Dermot', + 'Misha', + 'Mister', + 'Nicholis', + 'Kevork', + 'Kia', + 'Houa', + 'Huriel', + 'Jesu', + 'Dionta', + 'Silvino', + 'Ivery', + 'Iokepa', + 'Geo', + 'Dex', + 'Izaan', + 'Jasraj', + 'Jakson', + 'Niel', + 'Avelardo', + 'Arjay', + 'Aran', + 'Alanzo', + 'Aidric', + 'Lomax', + 'Rawn', + 'Simmie', + 'Tonnie', + 'Yuto', + 'Mataio', + 'Nicodemus', + 'Maximilien', + 'Raider', + 'Ridley', + 'Orest', + 'Ramzi', + 'Kaikea', + 'Kamahao', + 'Kyrillos', + 'Mace', + 'Lyrik', + 'Lyon', + 'Lux', + 'Ashkan', + 'Jurgen', + 'Khachik', + 'Maher', + 'Jaccob', + 'Jagdeep', + 'Wash', + 'Simpson', + 'Macy', + 'Haylee', + 'Hope', + 'Katie', + 'Thurmon', + 'Savanna', + 'Zoey', + 'Atiba', + 'Dylann', + 'Kaylen', + 'Helio', + 'Geovannie', + 'Praneel', + 'Kamau', + 'Rhamel', + 'Knoah', + 'Harm', + 'Nyle', + 'Maveric', + 'Neithan', + 'Niklaus', + 'Lejon', + 'Wai', + 'Indigo', + 'Sayed', + 'Abdias', + 'Daniil', + 'Rashod', + 'Wren', + 'Chico', + 'Jamarri', + 'Leiland', + 'Ranvir', + 'Mavrick', + 'Matai', + 'Deveon', + 'Teyon', + 'Ramell', + 'Haik', + 'Dupree', + 'Emon', + 'Jermal', + 'Bayley', + 'Marshell', + 'Blouncie', + 'Larson', + 'Lorenz', + 'Jhovanny', + 'Jeffie', + 'Portia', + 'Adron', + 'Calogero', + 'Mathews', + 'Aundra', + 'Aariv', + 'Keniel', + 'Jameis', + 'Konstantin', + 'Khayden', + 'Manford', + 'Polo', + 'Chanel', + 'Brittani', + 'Kazuki', + 'Kaelen', + 'Alice', + 'Maya', + 'Madeleine', + 'Kiana', + 'Latasha', + 'Felicia', + 'Gabriella', + 'Bolivar', + 'Eileen', + 'Alister', + 'Aidenn', + 'Nina', + 'Ellington', + 'Alecsander', + 'Ja', + 'Jarmaine', + 'Kyriakos', + 'Apostolos', + 'Leshawn', + 'Shondell', + 'Matvey', + 'Savino', + 'Zakariye', + 'Dozier', + 'Holland', + 'Haruto', + 'Hendrik', + 'Allah', + 'Johnanthony', + 'Eliyah', + 'Champ', + 'Dastan', + 'Caliph', + 'Manish', + 'Agostino', + 'Kaio', + 'Avyaan', + 'Gerasimos', + 'Refujio', + 'Munir', + 'Abdurrahman', + 'Selso', + 'Epimenio', + 'Suhayb', + 'Jock', + 'Larwence', + 'Saadiq', + 'Lilburn', + 'Selestino', + 'Randi', + 'Nysir', + 'Harlyn', + 'Basir', + 'Kathy', + 'Teddie', + 'Luqman', + 'Tyhir', + 'Mubarak', + 'Ridwan', + 'Filemon', + 'Bergen', + 'Danney', + 'Eual', + 'Melburn', + 'Esiquio', + 'Cree', + 'Dorwin', + 'Naasir', + 'Ysmael', + 'Nirav', + 'Chuckie', + 'Lashaun', + 'Darris', + 'Blase', + 'Kiley', + 'Demarko', + 'Taiwan', + 'Lamon', + 'Corrie', + 'Feras', + 'Excell', + 'Cornelious', + 'Martinez', + 'Marvel', + 'Climmie', + 'Martrell', + 'Valley', + 'Lonie', + 'Jovante', + 'Lavante', + 'Lugene', + 'Cordarro', + 'Lacey', + 'Derrius', + 'Tedd', + 'Levell', + 'Linas', + 'Taras', + 'Toma', + 'Klint', + 'Gualberto', + 'Feliberto', + 'Tarrance', + 'Theran', + 'Lakeith', + 'Mearl', + 'Karry', + 'Denarius', + 'Dontarius', + 'Nikia', + 'Rakesh', + 'Not', + 'Darek', + 'Gery', + 'Ontario', + 'Jimi', + 'Shamarion', + 'Kedarius', + 'Jermarcus', + 'Amarie', + 'Kordae', + 'Montie', + 'Haleem', + 'Inocencio', + 'Brockton', + 'Yoshiaki', + 'Ponciano', + 'Silvester', + 'Derron', + 'Davaughn', + 'Urie', + 'Juanito', + 'Corky', + 'Pasqual', + 'Marilyn', + 'Morley', + 'Ayoub', + 'Eliasar', + 'Mickel', + 'Skylor', + 'Kewan', + 'Teon', + 'Rafal', + 'Devanta', + 'Rosco', + 'Tywon', + 'Evon', + 'Cleven', + 'Hardie', + 'Tori', + 'Trayvond', + 'Maaz', + 'Masashi', + 'Neno', + 'Kahari', + 'Terri', + 'Toru', + 'Jalynn', + 'Avonte', + 'Satchel', + 'Tanya', + 'Kalab', + 'Avetis', + 'Miko', + 'Kodiak', + 'Lang', + 'Leondre', + 'Purnell', + 'Harutyun', + 'Gorman', + 'Vong', + 'Shervin', + 'Soloman', + 'Sue', + 'Amandeep', + 'Amritpal', + 'Leonides', + 'Melecio', + 'Mikhael', + 'Estaban', + 'Arius', + 'Calix', + 'Gurtaj', + 'Dilraj', + 'Dillinger', + 'Aidden', + 'Shivansh', + 'Shravan', + 'Saud', + 'Yarel', + 'Riker', + 'Yareth', + 'Zeppelin', + 'Ladarious', + 'Lucan', + 'Terren', + 'Tustin', + 'Nicolaas', + 'Rakan', + 'Johnjoseph', + 'Hovanes', + 'Navjot', + 'Henrique', + 'Marsalis', + 'Karanveer', + 'Jeffren', + 'Khairi', + 'Haruki', + 'Jadden', + 'Iliya', + 'Hansen', + 'Srihan', + 'Sartaj', + 'Rishik', + 'Rishan', + 'Octavian', + 'Ranbir', + 'Padraic', + 'Tanush', + 'Tlaloc', + 'Cadarius', + 'Yared', + 'Vahan', + 'Lakai', + 'Fionn', + 'Eziah', + 'Emillio', + 'Hakob', + 'Gryphon', + 'Harsha', + 'Hiroto', + 'Nivaan', + 'Radin', + 'Nicasio', + 'Mael', + 'Lysander', + 'Rees', + 'Roemello', + 'Bretton', + 'Christoph', + 'Eliceo', + 'Armany', + 'Axell', + 'Bogdan', + 'Luan', + 'Aldon', + 'Aeson', + 'Adhvik', + 'Jese', + 'Blanca', + 'Crisanto', + 'Dietrich', + 'Tarin', + 'Yama', + 'Yia', + 'Omeed', + 'Arbie', + 'Shayn', + 'Ranferi', + 'Ricard', + 'Farmer', + 'Goble', + 'Herald', + 'Hager', + 'Elva', + 'Carlis', + 'Evertt', + 'Ledford', + 'Dequarius', + 'Hughie', + 'Burgess', + 'Kourosh', + 'Jaun', + 'Nicko', + 'Victorhugo', + 'Roverto', + 'Shadi', + 'Sopheak', + 'Acie', + 'Demar', + 'Carolina', + 'Vinal', + 'Earland', + 'Sergey', + 'Dayon', + 'Kwamaine', + 'Kerney', + 'Ola', + 'Welby', + 'Kyon', + 'Tyion', + 'Kiyon', + 'Neng', + 'Raquel', + 'Nadeem', + 'Terran', + 'Tin', + 'Rudi', + 'Murad', + 'Murrell', + 'Lenville', + 'Rondall', + 'Han', + 'Hovhannes', + 'Karapet', + 'Hamed', + 'Alasdair', + 'Agam', + 'Areg', + 'Ariston', + 'Askari', + 'Ayansh', + 'Byran', + 'Dolan', + 'Devonn', + 'Edith', + 'Christoffer', + 'Alaa', + 'Ashraf', + 'Rondle', + 'Tavarius', + 'Michaeljames', + 'Nichols', + 'Sonia', + 'Ryanchristopher', + 'Garo', + 'Hien', + 'Corin', + 'Dillin', + 'Jerid', + 'Jesusalberto', + 'Zeferino', + 'Gobel', + 'Tykeem', + 'Miking', + 'Juno', + 'Jiraiya', + 'Kailash', + 'Madix', + 'Lucciano', + 'Llewyn', + 'Leone', + 'Knight', + 'Dorse', + 'Oak', + 'Irie', + 'Brodi', + 'Hridhaan', + 'Coda', + 'Dekker', + 'Evren', + 'Eisen', + 'Eddison', + 'Donatello', + 'Happy', + 'Devron', + 'Suleiman', + 'Siddhanth', + 'Zorawar', + 'Zadkiel', + 'Waylan', + 'Valor', + 'Triton', + 'Govanni', + 'Angelus', + 'Ashvin', + 'Matthews', + 'Elver', + 'Brendin', + 'Rhea', + 'Jyron', + 'Matisse', + 'Karanvir', + 'Kenshin', + 'Saketh', + 'Trigo', + 'Wil', + 'Tyrick', + 'Trejon', + 'Manvir', + 'Sascha', + 'Samay', + 'Prabhjot', + 'Piers', + 'Arshia', + 'Karo', + 'Makani', + 'Ludwin', + 'Kean', + 'Nikoli', + 'Garlin', + 'Georgio', + 'Jyren', + 'Ledell', + 'Jayceion', + 'Wiltz', + 'Elgie', + 'Jediah', + 'Izzac', + 'Izeyah', + 'Jeyson', + 'Hamid', + 'Jalani', + 'Rohin', + 'Shiva', + 'Ramces', + 'Claudell', + 'Daymien', + 'Aeron', + 'Aadan', + 'Alesandro', + 'Aleksey', + 'Galileo', + 'Esvin', + 'Indy', + 'Graden', + 'Gor', + 'Vlad', + 'Kendrell', + 'Saket', + 'Asahel', + 'Blue', + 'Arshdeep', + 'Adain', + 'Keneth', + 'Jacy', + 'Dasan', + 'Haniel', + 'Ethin', + 'Ericson', + 'Izick', + 'Elisandro', + 'Coltrane', + 'Kemani', + 'Josearmando', + 'Josealfredo', + 'Alias', + 'Anurag', + 'Carlitos', + 'Ceaser', + 'Sukhraj', + 'Severin', + 'Nishanth', + 'Mattox', + 'Rhiley', + 'Dareon', + 'Danyel', + 'Calan', + 'Nithin', + 'Donivan', + 'Taye', + 'Trustin', + 'Igor', + 'Jayr', + 'Kayin', + 'Pleas', + 'Aadit', + 'Balam', + 'Jovannie', + 'Quintrell', + 'Japheth', + 'Hero', + 'Edu', + 'Duvan', + 'Anden', + 'Anshul', + 'Ailton', + 'Raybon', + 'Rabon', + 'Kendry', + 'Manases', + 'Damyan', + 'Braven', + 'Dhani', + 'Isaia', + 'Hovik', + 'Sonnie', + 'Wolfe', + 'Banyan', + 'Hiroki', + 'Matin', + 'Sequoia', + 'Acelin', + 'Aarya', + 'Arsalan', + 'Carlosdaniel', + 'Jaryd', + 'Ariana', + 'Kylee', + 'Mariah', + 'Serenity', + 'Kailey', + 'Delaney', + 'Emilee', + 'Isabelle', + 'Jayla', + 'Drue', + 'Emani', + 'Juandedios', + 'Kedar', + 'Baily', + 'Daijon', + 'Daman', + 'Kentaro', + 'Damaria', + 'Mareco', + 'Valmore', + 'Theophile', + 'Winslow', + 'Ugo', + 'Cainan', + 'Finian', + 'Keiji', + 'Issack', + 'Blanchard', + 'Domingos', + 'Jarin', + 'Giovan', + 'Ovila', + 'Lovelace', + 'Albion', + 'Curry', + 'Christophr', + 'Nolton', + 'Unborn', + 'Torry', + 'Yoshi', + 'Perrion', + 'Nathyn', + 'Syler', + 'Sheila', + 'Jaedan', + 'Cobey', + 'Bashar', + 'Ehsan', + 'Daryll', + 'Seann', + 'Niels', + 'Nazar', + 'Frederico', + 'Esther', + 'Bobie', + 'Loyce', + 'Heberto', + 'Bentura', + 'Jafar', + 'Keigan', + 'Bertil', + 'Aloys', + 'Janie', + 'Paz', + 'Damacio', + 'Oiva', + 'Ingvald', + 'Walfred', + 'Jakeob', + 'Georgie', + 'Alcuin', + 'Raynold', + 'Josey', + 'Lasaro', + 'Jo', + 'Hjalmer', + 'Philemon', + 'Paula', + 'Christophor', + 'Estanislao', + 'Angelita', + 'Anacleto', + 'Alfons', + 'Lawayne', + 'Delrico', + 'Clemson', + 'Jaleen', + 'Jerimy', + 'Javaughn', + 'Tiofilo', + 'Hubbard', + 'Abundio', + 'Derl', + 'Keagen', + 'Aymen', + 'Freedom', + 'Venancio', + 'Pauline', + 'Gorden', + 'Hani', + 'Pharrell', + 'Jager', + 'Nyair', + 'Azeem', + 'Khyir', + 'Jabriel', + 'Yandiel', + 'Zaahir', + 'Laine', + 'Xai', + 'Vernard', + 'Augie', + 'Sostenes', + 'Darryll', + 'Asir', + 'Lindon', + 'Jearl', + 'Peder', + 'Rudolpho', + 'Clancy', + 'Yue', + 'Ronnald', + 'Onofre', + 'Kysir', + 'Helmuth', + 'Marlowe', + 'Derk', + 'Demetrick', + 'Jefrey', + 'Burrell', + 'Robie', + 'Marlan', + 'Thane', + 'Jamire', + 'Donnel', + 'Syaire', + 'York', + 'Asaad', + 'Kyair', + 'Devere', + 'Wing', + 'Yaniv', + 'Mathhew', + 'Silvia', + 'Chia', + 'Bren', + 'Cavin', + 'Aldrich', + 'Judy', + 'Erron', + 'Butler', + 'Carole', + 'Almon', + 'Gilles', + 'Christin', + 'Renald', + 'Sony', + 'Chavis', + 'Nghia', + 'Mercedes', + 'Real', + 'Josejesus', + 'Ryman', + 'Kori', + 'Ichael', + 'Jabier', + 'Nguyen', + 'Angeldejesus', + 'Bobak', + 'Brittan', + 'Shaunt', + 'Karlton', + 'Jerin', + 'Gerado', + 'Raymund', + 'Kerolos', + 'Rolan', + 'Wilbern', + 'Sipriano', + 'Hermes', + 'Robyn', + 'Ynes', + 'Vernice', + 'Pink', + 'Jevonte', + 'Jerame', + 'Tajuan', + 'Mingo', + 'Jeremia', + 'Edmon', + 'Castulo', + 'Cleofas', + 'Arlee', + 'Oather', + 'Larkin', + 'Mcarther', + 'Ryann', + 'Hong', + 'Jamieson', + 'Enedino', + 'Gerad', + 'Lenord', + 'Alireza', + 'Hollie', + 'Gilford', + 'Lajuane', + 'Izell', + 'Trenidad', + 'Shelley', + 'Ulysees', + 'Juana', + 'Coalton', + 'Remer', + 'Raiford', + 'Caydon', + 'Dalyn', + 'Wilhelm', + 'Lenzy', + 'Bartow', + 'Tibor', + 'Cebert', + 'Elizar', + 'Ellen', + 'Uchenna', + 'Toy', + 'Curlee', + 'Ralf', + 'Giulio', + 'Conway', + 'Ngai', + 'Chaka', + 'Engelbert', + 'Auburn', + 'Socrates', + 'Kostas', + 'Kamalei', + 'Kupono', + 'Carrell', + 'Lister', + 'Mattie', + 'Thermon', + 'Tina', + 'Kennan', + 'Adison', + 'Dalon', + 'Ephram', + 'Jaylynn', + 'Zabdiel', + 'Kaidon', + 'Juvencio', + 'Havis', + 'Dagan', + 'Dacorian', + 'Donavyn', + 'Evyn', + 'Issai', + 'Zenon', + 'Inman', + 'Hason', + 'Lehman', + 'Afton', + 'Clayborn', + 'Abrahm', + 'Neill', + 'Conard', + 'Mutsuo', + 'Seikichi', + 'Wetzel', + 'Masaji', + 'Masanobu', + 'Shigeto', + 'Edgel', + 'Goro', + 'Lovett', + 'Seiko', + 'Sakae', + 'Roshawn', + 'Antjuan', + 'Erby', + 'Jobe', + 'Ladarian', + 'Cyler', + 'Edel', + 'Hartsel', + 'Jill', + 'Jami', + 'Rabun', + 'Fulton', + 'Dreddy', + 'Corrado', + 'Harald', + 'Alterick', + 'Hala', + 'Powell', + 'Lesly', + 'Kalon', + 'Theodoros', + 'Etan', + 'Trev', + 'Javiel', + 'Jusiah', + 'Joncarlos', + 'Jhamari', + 'Rasheim', + 'Raysean', + 'Kreg', + 'Rahmell', + 'Kerby', + 'Eliga', + 'Clemon', + 'Aneudy', + 'Keiran', + 'Kensley', + 'Ludie', + 'Jorell', + 'Can', + 'Demondre', + 'Cierra', + 'Maurizio', + 'Tacuma', + 'Ryzen', + 'Jabar', + 'Tara', + 'Reign', + 'Jashon', + 'Lasean', + 'Artavius', + 'Akbar', + 'Un', + 'Kaikane', + 'Tanisha', + 'Elena', + 'Bridget', + 'Asia', + 'Latisha', + 'Rachael', + 'Latoya', + 'Elisabeth', + 'Janelle', + 'Ikea', + 'Kobey', + 'Kamaehu', + 'Keona', + 'Calixto', + 'Theotis', + 'Worthy', + 'Galo', + 'Holly', + 'Sevyn', + 'Petr', + 'Cerrone', + 'Tedrick', + 'Kymari', + 'Gerrard', + 'Eldo', + 'Alcides', + 'Derrian', + 'Eulas', + 'Leodis', + 'Akai', + 'Dalonte', + 'Pantelis', + 'Sheron', + 'Tommaso', + 'Treg', + 'Shirl', + 'Abrian', + 'Brewer', + 'Yamir', + 'Zadok', + 'Holdyn', + 'Jayanthony', + 'Eh', + 'Dayson', + 'Khaden', + 'Quintez', + 'Rontavious', + 'Markese', + 'Quintavis', + 'Daveion', + 'Tonny', + 'Jaevon', + 'Ahkeem', + 'Hy', + 'Adams', + 'Marian', + 'Huner', + 'Jarmarcus', + 'Treyon', + 'Tullio', + 'Oreste', + 'Oleg', + 'Xzavien', + 'Atzel', + 'Brenan', + 'Abriel', + 'Braylyn', + 'Chidera', + 'Lebaron', + 'Jameir', + 'Kameryn', + 'Shade', + 'Koltin', + 'Cordarrius', + 'Amelio', + 'Demarquez', + 'Tarus', + 'Calob', + 'Dmarco', + 'Creek', + 'Amen', + 'Cylas', + 'Davyn', + 'Haygen', + 'Godric', + 'Garn', + 'Renardo', + 'Locke', + 'Lexington', + 'Mazin', + 'Othniel', + 'Kruze', + 'Jaxston', + 'Jaxten', + 'Jeziah', + 'Jettson', + 'Zebastian', + 'Sarim', + 'Jawuan', + 'Tremain', + 'Hassell', + 'Quartez', + 'Hawkins', + 'Riggs', + 'Rebel', + 'Nael', + 'Kaycen', + 'Kamsiyochukwu', + 'Kagen', + 'Jrue', + 'Jaydeen', + 'Azazel', + 'Ayson', + 'Cheston', + 'Aarian', + 'Chavez', + 'Void', + 'Zacariah', + 'Keena', + 'Antwuan', + 'Labarron', + 'Quamere', + 'Mikell', + 'Prestyn', + 'Savian', + 'Dayden', + 'Jaivion', + 'Geremiah', + 'Aidon', + 'Bralyn', + 'Gianncarlo', + 'Jarquavious', + 'Muriel', + 'Akshar', + 'Kadir', + 'Najir', + 'Neko', + 'Jahaad', + 'Jdyn', + 'Kashon', + 'Jaquil', + 'Wah', + 'Delmos', + 'Masuo', + 'Nobuichi', + 'Kiichi', + 'Jerone', + 'Tatsumi', + 'Damarian', + 'Elier', + 'Lansing', + 'Heinrich', + 'Hasson', + 'Larrie', + 'Phyllis', + 'Jamoni', + 'Zylen', + 'Demoni', + 'Harrel', + 'Levie', + 'Zaryan', + 'Orazio', + 'Seymore', + 'Florence', + 'Kolter', + 'Kemper', + 'Daelyn', + 'Haddon', + 'Syon', + 'Sair', + 'Filadelfio', + 'Marquavion', + 'Breylon', + 'Filimon', + 'Abie', + 'Cortavious', + 'Achille', + 'Dontrez', + 'Matty', + 'Darshawn', + 'Overton', + 'Bashir', + 'Kavan', + 'Caidan', + 'Braelen', + 'Param', + 'Kani', + 'Percival', + 'Hartley', + 'Erminio', + 'Candler', + 'Ulyssee', + 'Damontae', + 'Ellijah', + 'Cesare', + 'Eleanor', + 'Eustace', + 'Joachim', + 'Tarique', + 'Altin', + 'Tyleek', + 'Posey', + 'Awais', + 'Daivon', + 'Zi', + 'Hammad', + 'Meshulem', + 'Nickie', + 'Brehon', + 'Dacoda', + 'Kwamane', + 'Rafuel', + 'Mikai', + 'Hensel', + 'Thelbert', + 'Valerio', + 'Trevonte', + 'Koran', + 'Cheick', + 'Shahzaib', + 'Tahsin', + 'Derry', + 'Mustapha', + 'Chucky', + 'Osborne', + 'Daquarius', + 'Marque', + 'Raquon', + 'Cherokee', + 'Keyshaun', + 'Mohamadou', + 'Keishawn', + 'Jahmeek', + 'Junaid', + 'Amil', + 'Mckoy', + 'Zackry', + 'Nyheim', + 'Nkosi', + 'Kweli', + 'Tydarius', + 'Umer', + 'Demorris', + 'Demarquis', + 'Hersch', + 'Luzer', + 'Li', + 'Aly', + 'Quade', + 'Quamaine', + 'Markee', + 'Jhoan', + 'Mert', + 'Supreme', + 'Tyheem', + 'Gomer', + 'Taseen', + 'Yousaf', + 'Yonason', + 'Gifford', + 'Ashar', + 'Sender', + 'Salah', + 'Saifan', + 'Raihan', + 'Nizar', + 'Abrahim', + 'Kunga', + 'Javis', + 'Evens', + 'Bayard', + 'Kaysan', + 'Padraig', + 'Ney', + 'Ahmet', + 'Misty', + 'Ayyan', + 'Saint', + 'Fern', + 'Wasil', + 'Nolie', + 'Zarif', + 'Younis', + 'Eesa', + 'Ketrick', + 'Veryl', + 'Refael', + 'Motty', + 'Naftuly', + 'Waseem', + 'Yusif', + 'Brigg', + 'Zaheer', + 'Shiya', + 'Karma', + 'Meilich', + 'Mihran', + 'Javares', + 'Efe', + 'Abubakr', + 'Avrumi', + 'Nayshawn', + 'Mostafa', + 'Hinckley', + 'Jahmeir', + 'Fintan', + 'Sollie', + 'Amiel', + 'Abu', + 'Yaro', + 'Josha', + 'Jermane', + 'Bertis', + 'Hernando', + 'Gerrod', + 'Haim', + 'Frandy', + 'Andrews', + 'Dayle', + 'Fleming', + 'Volvi', + 'Savior', + 'Shuford', + 'Plummer', + 'Ralston', + 'Dayvion', + 'Muhamed', + 'Naheem', + 'Nataniel', + 'Kaeleb', + 'Billyjoe', + 'Able', + 'Fareed', + 'Purcell', + 'Trayson', + 'Mackay', + 'Moyer', + 'Haynes', + 'Domnick', + 'Burnie', + 'Gleen', + 'Leavy', + 'Lennart', + 'Breken', + 'Arlind', + 'Clarnce', + 'Nissen', + 'Josiel', + 'Alvester', + 'Jaquay', + 'Nickolaos', + 'Ruddy', + 'Berkeley', + 'Flamur', + 'Sherif', + 'Shateek', + 'Ayodele', + 'Davone', + 'Meshach', + 'Kinston', + 'Algernon', + 'Dvonte', + 'Jawara', + 'Zamar', + 'Dayron', + 'Jaequan', + 'Tyrelle', + 'Jazion', + 'Tamel', + 'Torris', + 'Marguis', + 'Yuniel', + 'Saige', + 'Gottlieb', + 'Cori', + 'Dre', + 'Yordan', + 'Shaquell', + 'Jonel', + 'Kashaun', + 'Arjenis', + 'Tashan', + 'Fitzroy', + 'Francisc', + 'Kwaku', + 'Jakyri', + 'Trayton', + 'Jarrick', + 'Reginaldo', + 'Facundo', + 'Elma', + 'Dardan', + 'Dreshawn', + 'Demontray', + 'Chaddrick', + 'Roper', + 'Taariq', + 'Ausitn', + 'Jachai', + 'Duval', + 'Braun', + 'Taylan', + 'Dionis', + 'Samy', + 'Armistead', + 'Alize', + 'Tayshon', + 'Ainsley', + 'Kaheem', + 'Jaire', + 'Kyshawn', + 'Nahshon', + 'Aaliyah', + 'Shanard', + 'Azion', + 'Alana', + 'Alexia', + 'Breyon', + 'Trigg', + 'Wylder', + 'Zaydin', + 'Ziaire', + 'Zixuan', + 'Yanis', + 'Zair', + 'Zaven', + 'Alanmichael', + 'Viyan', + 'Vivan', + 'Klay', + 'Erico', + 'Tycho', + 'Waris', + 'Winter', + 'Aliyah', + 'Kamilo', + 'Kei', + 'Glendell', + 'Lilly', + 'Lauryn', + 'Jovian', + 'Shayla', + 'Tessa', + 'Jupiter', + 'Aaric', + 'Aadhav', + 'Jetson', + 'Abir', + 'Adhrit', + 'Alexandr', + 'Brooklynn', + 'Callie', + 'Ashlee', + 'Ashlyn', + 'Haleigh', + 'Heaven', + 'Jahkari', + 'Izaiyah', + 'Troyce', + 'Bige', + 'Hayze', + 'Neldon', + 'Marven', + 'Beckem', + 'Dvante', + 'Navarro', + 'Neiko', + 'Noeh', + 'Jen', + 'Torrian', + 'Helios', + 'Macallan', + 'Lio', + 'Wilkens', + 'Merrik', + 'Ameal', + 'Mujtaba', + 'Iktan', + 'Kavious', + 'Monterrio', + 'Hughey', + 'Calin', + 'Cali', + 'Jamaar', + 'Kenith', + 'Rihaan', + 'Deaglan', + 'Kelso', + 'Lavaris', + 'Ashot', + 'Marshun', + 'Rainer', + 'Rivan', + 'Talal', + 'Taiyo', + 'Minor', + 'Yvon', + 'Stonewall', + 'Merril', + 'Okie', + 'Trevino', + 'Imari', + 'Ithan', + 'Izmael', + 'Jayan', + 'Harut', + 'Harshaan', + 'Kainen', + 'Kalyan', + 'Kanishk', + 'Kotaro', + 'Josyah', + 'Vola', + 'Omarie', + 'Dmari', + 'Mannix', + 'Elad', + 'Shun', + 'Andriy', + 'Angelino', + 'Ary', + 'Axcel', + 'Becker', + 'Daxten', + 'Daemian', + 'Cypress', + 'Jakhai', + 'Warnie', + 'Maikel', + 'Davinci', + 'Calloway', + 'Vernal', + 'Tyrome', + 'Mont', + 'Ovie', + 'Hester', + 'Arvis', + 'Corbit', + 'Tarvaris', + 'Audra', + 'Cloud', + 'Taveon', + 'Balian', + 'Bodi', + 'Brodee', + 'Kainan', + 'Dezi', + 'Devesh', + 'Emad', + 'Esa', + 'Massie', + 'Moir', + 'Markavious', + 'Veachel', + 'Dalan', + 'Carles', + 'Antawn', + 'Jermichael', + 'Talin', + 'Sy', + 'Murrel', + 'Elster', + 'Kru', + 'Okley', + 'Maverik', + 'Diangelo', + 'Burns', + 'Jamaris', + 'Jayshaun', + 'Dantae', + 'Rahil', + 'Renny', + 'Rohith', + 'Strummer', + 'Birchel', + 'Astor', + 'Nolyn', + 'Neeko', + 'Reyan', + 'Kailan', + 'Jaideep', + 'Manveer', + 'Maeson', + 'Khris', + 'Lancelot', + 'Shaunak', + 'Shubham', + 'Siaosi', + 'Ruslan', + 'Sajan', + 'Renwick', + 'Yann', + 'Vitali', + 'Zealand', + 'Vyom', + 'Xabi', + 'Yazid', + 'Terrelle', + 'Oaks', + 'Kache', + 'Arjuna', + 'Cephas', + 'Holmes', + 'Rockie', + 'Elray', + 'Doc', + 'Mell', + 'Tyresse', + 'Maguire', + 'Sheddrick', + 'Loney', + 'Helaman', + 'Andrus', + 'Asberry', + 'Love', + 'Clebert', + 'Cashius', + 'Egypt', + 'Devansh', + 'Elige', + 'Tobe', + 'Taten', + 'Arias', + 'Leandrew', + 'Dekota', + 'Varian', + 'Lehi', + 'Colbert', + 'Ignace', + 'Suhas', + 'Syris', + 'Ahan', + 'Aithan', + 'Aiven', + 'Akshath', + 'Hamp', + 'Kato', + 'Leeon', + 'Reubin', + 'Lukah', + 'Wilmon', + 'Tait', + 'Theophilus', + 'Sion', + 'Maysen', + 'Nicoli', + 'Nason', + 'Mykell', + 'Montae', + 'Laszlo', + 'Lestat', + 'Prithvi', + 'Maxi', + 'Mekhai', + 'Hammond', + 'Atiksh', + 'Aldean', + 'Aldine', + 'Jedi', + 'Almond', + 'Edahi', + 'Hisham', + 'Clide', + 'Cosby', + 'Hayato', + 'Harnoor', + 'Gurjot', + 'Ethridge', + 'Dublin', + 'Daimian', + 'Derreon', + 'Hansell', + 'Mae', + 'Semisi', + 'Ulysess', + 'Render', + 'Eschol', + 'Rodell', + 'Atzin', + 'Alik', + 'Amiri', + 'Keyvon', + 'Noland', + 'Terius', + 'Isauro', + 'Harshith', + 'Pledger', + 'Tilman', + 'Lennis', + 'Jovin', + 'Jaymin', + 'Jaydee', + 'Asbury', + 'Lovie', + 'Mcclinton', + 'Grayton', + 'Cardin', + 'Jacey', + 'Gurveer', + 'Ethanmatthew', + 'Aaronjames', + 'Ascher', + 'Aarion', + 'Windle', + 'Jahan', + 'Jayen', + 'Jatin', + 'Jedrek', + 'Anthonyjr', + 'Dabney', + 'Galvin', + 'Ilijah', + 'Gohan', + 'Quaid', + 'Teancum', + 'Chael', + 'Chetan', + 'Cylis', + 'Manas', + 'Logun', + 'Karston', + 'Mickeal', + 'Iskander', + 'Isaah', + 'Aryaman', + 'Juvens', + 'Joncarlo', + 'Gurkirat', + 'Laymon', + 'Salesi', + 'Rion', + 'Tao', + 'Tadhg', + 'Stephens', + 'Terryl', + 'Jacquan', + 'Zubin', + 'Yul', + 'Yadriel', + 'Dolph', + 'Keiden', + 'Koston', + 'Demetre', + 'Kameren', + 'Kaedin', + 'Zebedee', + 'Tyrie', + 'Truth', + 'Lanorris', + 'Tilden', + 'Tidus', + 'Thelonious', + 'Tennessee', + 'Sirius', + 'Pervis', + 'Saatvik', + 'Adley', + 'Amarian', + 'Numa', + 'Bronco', + 'Zian', + 'Zephan', + 'Yaziel', + 'Ajit', + 'Arick', + 'Ollin', + 'Kort', + 'Tayvin', + 'Grason', + 'Leonid', + 'Nihaal', + 'Koah', + 'Southern', + 'Kavish', + 'Joeziah', + 'Javi', + 'Kaiyan', + 'Kyro', + 'Ziad', + 'Maxen', + 'Xion', + 'Mica', + 'Mansour', + 'Matteus', + 'Renner', + 'Jonan', + 'Shilo', + 'Josedaniel', + 'Kaj', + 'Robel', + 'Krithik', + 'Lautaro', + 'Evann', + 'Carden', + 'Nathaneal', + 'Wirt', + 'Kile', + 'Kevonte', + 'Jazz', + 'Vardan', + 'Tanav', + 'Tamim', + 'Ojani', + 'Raydel', + 'Rigel', + 'Sheamus', + 'Cameryn', + 'Jedd', + 'Dalessandro', + 'Daejon', + 'Zacheriah', + 'Jt', + 'Valeria', + 'Treshon', + 'Martynas', + 'Markeese', + 'Ladislado', + 'Fidensio', + 'Cincere', + 'Amonte', + 'Erion', + 'Emin', + 'Tayten', + 'Zachory', + 'Ysidoro', + 'Treshaun', + 'Franciszek', + 'Adit', + 'Neftaly', + 'Kaylan', + 'Dezmon', + 'Joby', + 'Terrick', + 'Irma', + 'Isiaha', + 'Micha', + 'Sylvia', + 'Dejan', + 'Kippy', + 'Tyreece', + 'Corie', + 'Martese', + 'Senovio', + 'Lexus', + 'Freddrick', + 'Jemarcus', + 'Kuba', + 'Corion', + 'Andrian', + 'Romualdo', + 'Lyndal', + 'Kalem', + 'Laderrick', + 'Jobin', + 'Chaise', + 'Naren', + 'Reynol', + 'Ohm', + 'Trashawn', + 'Danyell', + 'Diron', + 'Kameran', + 'Dunte', + 'Ikechukwu', + 'Trendon', + 'Visente', + 'Valeriano', + 'Dillian', + 'Chantz', + 'Bacilio', + 'Crescencio', + 'Policarpio', + 'Janice', + 'Kem', + 'Rutilio', + 'Jaqualin', + 'Kendon', + 'Keevin', + 'Adelaido', + 'Coltan', + 'Theodoro', + 'Devondre', + 'Dekendrick', + 'Deionte', + 'Taz', + 'Jimmey', + 'Cristan', + 'Chancelor', + 'Ascension', + 'Kemon', + 'Makari', + 'Cordel', + 'Colbey', + 'Ambrocio', + 'Marselino', + 'Dewain', + 'Graciano', + 'Gumecindo', + 'Lorenso', + 'Quaylon', + 'Halbert', + 'Celedonio', + 'Terrin', + 'Zuri', + 'Sherod', + 'Ermal', + 'Elisa', + 'Larnell', + 'Tully', + 'Wenceslaus', + 'Lashun', + 'Duan', + 'Correy', + 'Wilburt', + 'Antwoin', + 'Lynell', + 'Ramond', + 'Victorio', + 'Antion', + 'Dragan', + 'Priest', + 'Marice', + 'Laroy', + 'Ninos', + 'Byrl', + 'Ebert', + 'Keita', + 'Dimitris', + 'Zoran', + 'Khaalis', + 'Rollo', + 'Alwin', + 'Loraine', + 'Jerard', + 'Lyndle', + 'Quirino', + 'Ramey', + 'Jarian', + 'Marky', + 'Adlai', + 'Shamon', + 'Treyshawn', + 'Shaft', + 'Gumercindo', + 'Rita', + 'Derryl', + 'Chancy', + 'Kacy', + 'Jonothan', + 'Ruston', + 'Ranulfo', + 'Talik', + 'Johntae', + 'Kendale', + 'Diandre', + 'Reginold', + 'Tyris', + 'Davell', + 'Ladell', + 'Raymone', + 'Mariusz', + 'Edvardo', + 'Joh', + 'Lavontae', + 'Markie', + 'Laquinton', + 'Alexandar', + 'Divante', + 'Jabin', + 'Shawon', + 'Jawann', + 'Ladd', + 'Khali', + 'Gilmore', + 'Oliverio', + 'Thuan', + 'Daiel', + 'Kierre', + 'Javar', + 'Stevon', + 'Derius', + 'Chadley', + 'Manual', + 'Johnaton', + 'Lc', + 'Erek', + 'Jakaden', + 'Jden', + 'Drayke', + 'Dawsen', + 'Jadarion', + 'Shriyans', + 'Raylin', + 'Kaydan', + 'Lynden', + 'Duard', + 'Elo', + 'Amarius', + 'Cleburne', + 'Dailen', + 'Brance', + 'Braycen', + 'Daiden', + 'Cruzito', + 'Caedyn', + 'Aizik', + 'Abyan', + 'Felisiano', + 'Taevion', + 'Zaeden', + 'Zadrian', + 'Fredie', + 'Burnis', + 'Cleave', + 'Ki', + 'Quandarius', + 'Quavion', + 'Makell', + 'Myrl', + 'Tae', + 'Melik', + 'Samarion', + 'Branton', + 'Vollie', + 'Reynolds', + 'Draylon', + 'Keivon', + 'Kevontae', + 'Deundre', + 'Zaydrian', + 'Zaydan', + 'Jotham', + 'Janthony', + 'Sahid', + 'Keilon', + 'Lain', + 'Kenechukwu', + 'Kanaan', + 'Kamdon', + 'Ahmod', + 'Dong', + 'Nnamdi', + 'Jontavius', + 'Kelijah', + 'Searcy', + 'Wheeler', + 'Francisca', + 'Burrel', + 'Zyquavious', + 'Kortez', + 'Tres', + 'Tranquilino', + 'Guinn', + 'Hiawatha', + 'Jasiyah', + 'Arlos', + 'Celestine', + 'Deadrian', + 'Chinedu', + 'Cane', + 'Caedon', + 'Gabryel', + 'Garon', + 'Solon', + 'Udell', + 'Medardo', + 'Chon', + 'Zakk', + 'Trip', + 'Somtochukwu', + 'Shooter', + 'Frutoso', + 'Laurencio', + 'Izayiah', + 'Franko', + 'Izzak', + 'Braelan', + 'Dryden', + 'Wilborn', + 'Newt', + 'Petronilo', + 'Nathanel', + 'Jatavius', + 'Locadio', + 'Tyquez', + 'Laiden', + 'Allister', + 'Javarion', + 'Demarrio', + 'Shenouda', + 'Rodriques', + 'Jenard', + 'Azarias', + 'Axxel', + 'Ariyan', + 'Pate', + 'Raidyn', + 'Saylor', + 'Kreed', + 'Kayce', + 'Bray', + 'Zyren', + 'Zayvien', + 'Yeiden', + 'Kinta', + 'Trampus', + 'Lofton', + 'Zayvian', + 'Zaydon', + 'Zaidan', + 'Weslee', + 'Robben', + 'Rook', + 'Roston', + 'Trigger', + 'Steel', + 'Rustyn', + 'Jaeceon', + 'Hutton', + 'Hatcher', + 'Kartier', + 'Kallan', + 'Daxtyn', + 'Corvin', + 'Deklyn', + 'Kaveon', + 'Leviathan', + 'Leelan', + 'Lael', + 'Prynce', + 'Korban', + 'Khyren', + 'Omran', + 'Oluwademilade', + 'Orenthal', + 'Dequavius', + 'Quinterrius', + 'Quantavis', + 'Astin', + 'Asaiah', + 'Dace', + 'Brylee', + 'Kenyan', + 'Jeovani', + 'Kolson', + 'Dreyden', + 'Jujuan', + 'Gregoria', + 'Abdon', + 'Javious', + 'Latravious', + 'Nanya', + 'Kaleel', + 'Elsie', + 'Iris', + 'Javarus', + 'Hunner', + 'Ebubechukwu', + 'Ashaz', + 'Huntley', + 'Montravious', + 'Argelio', + 'Amaar', + 'Abdulmalik', + 'Deronte', + 'Ramirez', + 'Travius', + 'Xavius', + 'Rashamel', + 'Martice', + 'Oshay', + 'Jamerson', + 'Derico', + 'Benino', + 'Otilio', + 'Palani', + 'Trystin', + 'Domonick', + 'Jayron', + 'Josephine', + 'Dora', + 'Larence', + 'Feliz', + 'Tereso', + 'Natalio', + 'Olga', + 'Bralen', + 'Temple', + 'Keala', + 'Anita', + 'Eathen', + 'Lamond', + 'Jakobie', + 'Johnthan', + 'Elnathan', + 'Edris', + 'Alcario', + 'Cornie', + 'Ival', + 'Pantaleon', + 'Deavion', + 'Daevion', + 'Dorance', + 'Jailon', + 'Ragene', + 'Kaena', + 'Kaimipono', + 'Keaka', + 'Kiai', + 'Babygirl', + 'Aukai', + 'Kaitlin', + 'Kaikoa', + 'Jedadiah', + 'Pono', + 'Layth', + 'Kolbie', + 'Naaman', + 'Pacey', + 'Jearld', + 'Corinthian', + 'Bryceson', + 'Kayzen', + 'Mana', + 'Janee', + 'Janae', + 'Kelli', + 'Tamara', + 'Nora', + 'Sophie', + 'Rashida', + 'Princess', + 'Lakeisha', + 'Nadia', + 'Monet', + 'Meaghan', + 'Marquita', + 'Chiquita', + 'Charlotte', + 'Chantelle', + 'Cassandra', + 'Cara', + 'Brandi', + 'Asha', + 'Tatiana', + 'Haaheo', + 'Valerie', + 'Valencia', + 'Shoso', + 'Yoshimi', + 'Bristol', + 'Mikio', + 'Nobuyuki', + 'Tomio', + 'Kazumi', + 'Kunio', + 'Yoshiharu', + 'Balentin', + 'Paublo', + 'Nobuyoshi', + 'Toshiaki', + 'Matsuo', + 'Hachiro', + 'Tokio', + 'Eichi', + 'Manabu', + 'Masanori', + 'Yoshiyuki', + 'Tokuo', + 'Eustolio', + 'Petra', + 'Fukuichi', + 'Haruyoshi', + 'Eastin', + 'Keygan', + 'Kelin', + 'Lalo', + 'Ramona', + 'Felis', + 'Rodgers', + 'Deigo', + 'Guerin', + 'Arrington', + 'Bradin', + 'Aurora', + 'Calistro', + 'Ervie', + 'Velma', + 'Whit', + 'Adarian', + 'Jakevion', + 'Jadrien', + 'Calub', + 'Kaegan', + 'Jamorian', + 'Milam', + 'Usiel', + 'Drayven', + 'Orange', + 'Daelon', + 'Jatavion', + 'Vastine', + 'Preciliano', + 'Floyce', + 'Billye', + 'Burney', + 'Consepcion', + 'Dason', + 'Osie', + 'Tashaun', + 'Sajid', + 'Umair', + 'Tymarion', + 'Jakorian', + 'Ginobili', + 'Areeb', + 'Jonovan', + 'Jonavan', + 'Jaqualyn', + 'Billey', + 'Luisgustavo', + 'Lamario', + 'Telford', + 'Lekendrick', + 'Brinton', + 'Lebarron', + 'Marrio', + 'Tyshun', + 'Kendarrius', + 'Zylan', + 'Jarrius', + 'Kadarrius', + 'Marvis', + 'Orie', + 'Kimber', + 'Jakevious', + 'Shawndale', + 'Jakel', + 'Jaquarious', + 'Deakon', + 'Brevan', + 'Rochester', + 'Lemmie', + 'Athony', + 'Rosie', + 'Lillie', + 'Mozell', + 'Aubert', + 'Kimble', + 'Jaymon', + 'Gaza', + 'Lysle', + 'Wasco', + 'Zigmond', + 'Addie', + 'Erastus', + 'Claudius', + 'Audley', + 'Thadeus', + 'Exum', + 'Caldwell', + 'Emmert', + 'Teagen', + 'Royden', + 'Mykale', + 'Lindberg', + 'Elmon', + 'Norfleet', + 'Radford', + 'Romulus', + 'Thedore', + 'Cor', + 'Ledarrius', + 'Cyncere', + 'Hurbert', + 'Pearly', + 'Jobie', + 'Garvey', + 'Meade', + 'Casmir', + 'Bertie', + 'Belvin', + 'Lynford', + 'Verdun', + 'Junie', + 'Dover', + 'Harlee', + 'Romolo', + 'Sirr', + 'Bradey', + 'Kingsten', + 'Manuelito', + 'Leno', + 'Primo', + 'Antonie', + 'Jane', + 'Halsey', + 'Mujahid', + 'Quron', + 'Cleophas', + 'Amedio', + 'Gildo', + 'Norvel', + 'Livingston', + 'Norvell', + 'Fard', + 'Khaleef', + 'Dorr', + 'Chaquille', + 'Giro', + 'Verdell', + 'Concetto', + 'Taevon', + 'Amato', + 'Hasaan', + 'Burr', + 'Payten', + 'Baden', + 'Abdirizak', + 'Emran', + 'Abdurahman', + 'Greig', + 'Sabree', + 'Shymir', + 'Haakon', + 'Aasim', + 'Abdifatah', + 'Cheemeng', + 'Yuepheng', + 'Hamsa', + 'Abdalla', + 'Samatar', + 'Joshawa', + 'Leeman', + 'Hershal', + 'Fayette', + 'Patty', + 'Thom', + 'Yaaseen', + 'Alven', + 'Hillis', + 'Bard', + 'Nymir', + 'Imir', + 'Mohamud', + 'Muaad', + 'Mickael', + 'Hermann', + 'Varner', + 'Norm', + 'Suheyb', + 'Eivin', + 'Jamy', + 'Taro', + 'Caydin', + 'Masaharu', + 'Cassie', + 'Virgie', + 'Oddie', + 'Pamela', + 'Emmette', + 'Rayshon', + 'Vardaman', + 'Ruble', + 'Clance', + 'Rigdon', + 'Osborn', + 'Gina', + 'Rozell', + 'Marcy', + 'Farron', + 'Bartolomeo', + 'Dierre', + 'Demetrus', + 'Yoneo', + 'Blayke', + 'Decarlo', + 'Sebert', + 'Quayon', + 'Nihar', + 'Segundo', + 'Ritik', + 'Aljaquan', + 'Lealon', + 'Opie', + 'Darshan', + 'Trapper', + 'Ladarrion', + 'Thaine', + 'Abanoub', + 'Filipe', + 'Oley', + 'Zaylan', + 'Rushi', + 'Watie', + 'Cleatus', + 'Harshil', + 'Alferd', + 'Carthel', + 'Ogden', + 'Carmin', + 'Hiren', + 'Harl', + 'Drexel', + 'Shadeed', + 'Malvern', + 'Argus', + 'Sharief', + 'Almalik', + 'Audy', + 'Terral', + 'Nuno', + 'Verna', + 'Alim', + 'Sherron', + 'Terek', + 'Clardie', + 'Shadee', + 'Clendon', + 'Johnpatrick', + 'Chritopher', + 'Taheem', + 'Jahid', + 'Waitman', + 'Jabraylen', + 'Quasim', + 'Azim', + 'Eulis', + 'Wladyslaw', + 'Delmus', + 'Minter', + 'Kharter', + 'Zavhary', + 'Taji', + 'Hoskie', + 'Colsen', + 'Orlanda', + 'Shawntez', + 'Obryan', + 'Emanual', + 'Silviano', + 'Chrishawn', + 'Rayon', + 'Martino', + 'Fairley', + 'Lenward', + 'Autzen', + 'Selby', + 'Odus', + 'Redell', + 'Seavy', + 'Dennison', + 'Jamiere', + 'Rondy', + 'Donold', + 'Lindwood', + 'Laudie', + 'Obert', + 'Jahki', + 'Braidon', + 'Zalen', + 'Zymier', + 'Jahzir', + 'Nahsir', + 'Vikrant', + 'Shourya', + 'Eliyohu', + 'Tyheim', + 'Keyshon', + 'Kaydence', + 'Ekin', + 'Tresean', + 'Quendarius', + 'Shammond', + 'Malakye', + 'Findlay', + 'Ashrith', + 'Elfego', + 'Jalik', + 'Nyzir', + 'Boe', + 'Abdikadir', + 'Jameek', + 'Gyasi', + 'Khyri', + 'Mohit', + 'Shayquan', + 'Sivan', + 'Steffon', + 'Lord', + 'Leor', + 'Kujtim', + 'Haaris', + 'Rafid', + 'Nechemia', + 'Nyles', + 'Khalik', + 'Tysheen', + 'Shaheim', + 'Starling', + 'Taiquan', + 'Takeem', + 'Teshawn', + 'Tuvia', + 'Shu', + 'Schyler', + 'Indalecio', + 'Edouard', + 'Alverto', + 'Alexio', + 'Aurash', + 'Fabiola', + 'Firas', + 'Fredis', + 'Guthrie', + 'Babacar', + 'Ayinde', + 'Khallid', + 'Shadrach', + 'Rikki', + 'Prescott', + 'Saam', + 'Perla', + 'Michell', + 'Markis', + 'Nou', + 'Sher', + 'Tor', + 'Kyre', + 'Shykeem', + 'Jilberto', + 'Klye', + 'Jeramey', + 'Herber', + 'Kue', + 'Mainor', + 'Macaulay', + 'Jequan', + 'Bond', + 'Hykeem', + 'Husam', + 'Catalina', + 'Danh', + 'Aaronmichael', + 'Anthonyjames', + 'Jerrid', + 'Jobani', + 'Kenia', + 'Oshae', + 'Michaelvincent', + 'Mong', + 'Dawit', + 'Dabid', + 'Daisuke', + 'Geddy', + 'Ehab', + 'Jarmal', + 'Caelin', + 'Barak', + 'Gurtej', + 'Geordan', + 'Jacobb', + 'Estefani', + 'Esaul', + 'Karandeep', + 'Jevaughn', + 'Kassim', + 'Kion', + 'Vikas', + 'Infinite', + 'Yekusiel', + 'Zohaib', + 'Yaw', + 'Sakib', + 'Shah', + 'Zeshan', + 'Hassaan', + 'Masai', + 'Mattheus', + 'Jeniel', + 'Martine', + 'Maalik', + 'Jeanclaude', + 'Stirling', + 'Trayveon', + 'Paymon', + 'Ajai', + 'Habib', + 'Enis', + 'Grafton', + 'Nissan', + 'Oshane', + 'Mirza', + 'Malike', + 'Yianni', + 'Zachari', + 'Tadeh', + 'Patrik', + 'Richy', + 'Riki', + 'Yao', + 'Yadira', + 'Nylan', + 'Lennard', + 'Roldan', + 'Admir', + 'Oniel', + 'Addam', + 'Itzel', + 'Ivann', + 'Shabab', + 'Honorio', + 'Hrag', + 'Harutun', + 'Keano', + 'Kayvan', + 'Takahiro', + 'Juanfrancisco', + 'Eri', + 'Ermon', + 'Ramzy', + 'Selma', + 'Kasean', + 'Obrian', + 'Jonatha', + 'Jonahtan', + 'Davione', + 'Chandara', + 'Chantha', + 'Lo', + 'Loreto', + 'Derell', + 'Ganesh', + 'Janathan', + 'Alejandr', + 'Rodolphe', + 'Isaul', + 'Bejan', + 'Doron', + 'Yvette', + 'Erlon', + 'Erland', + 'Yuji', + 'Milagro', + 'Ndrew', + 'Pedram', + 'Thinh', + 'Vandy', + 'Vi', + 'Ryanjoseph', + 'Richar', + 'Hosey', + 'Adeel', + 'Nicholos', + 'Michaeljohn', + 'Philipe', + 'Bravlio', + 'Anup', + 'Davide', + 'Daquann', + 'Lequan', + 'Raymel', + 'Rahsean', + 'Woodley', + 'Jarmel', + 'Wiliam', + 'Joseh', + 'Somnang', + 'Colvin', + 'Jenkins', + 'Jaquawn', + 'Javonne', + 'Javed', + 'Joelle', + 'Lameek', + 'Kishawn', + 'Krikor', + 'Christipher', + 'Ghassan', + 'Essa', + 'Hovig', + 'Nayquan', + 'Shawndell', + 'Rawle', + 'Marwin', + 'Record', + 'Dmario', + 'Crist', + 'La', + 'Access', + 'Shaquel', + 'Tyrrell', + 'Tiquan', + 'Shavon', + 'Shatique', + 'Yochanon', + 'Keontay', + 'Shaquelle', + 'Kshawn', + 'Armend', + 'Eliazer', + 'Diony', + 'Saddam', + 'Takayuki', + 'Sukhdeep', + 'Shahan', + 'Valon', + 'Orel', + 'Tremell', + 'Chayim', + 'Jaquille', + 'Ayodeji', + 'Bekim', + 'Besnik', + 'Oluwanifemi', + 'Stalin', + 'Sadam', + 'Aniel', + 'Laureat', + 'Dyrell', + 'Jhony', + 'Barkim', + 'Ludger', + 'Mahendra', + 'Kadeen', + 'Jovaughn', + 'Khadeem', + 'Ardian', + 'Ravindra', + 'Harpal', + 'Jatinder', + 'Erving', + 'Gerrell', + 'Sylvestre', + 'Luismanuel', + 'Pharell', + 'Jahziah', + 'Salif', + 'Jakyrin', + 'Idrissa', + 'Daoud', + 'Swan', + 'Pryor', + 'Polk', + 'Rameses', + 'Prateek', + 'Lelon', + 'Ebrima', + 'Ezechiel', + 'Tevan', + 'Sohail', + 'Luiseduardo', + 'Clearance', + 'Brayn', + 'Alexsis', + 'Edwar', + 'Johnmark', + 'Hikaru', + 'Edon', + 'Chezkel', + 'Dinari', + 'Ahmadou', + 'Jadien', + 'Ismaeel', + 'Heshy', + 'Jhan', + 'Dejohn', + 'Ajdin', + 'Damier', + 'Cashmere', + 'Amitai', + 'Alp', + 'Avrahom', + 'Hooper', + 'Daichi', + 'Dariush', + 'Bryen', + 'Oseas', + 'Moyses', + 'Alderic', + 'Dickson', + 'Joon', + 'Justinkyle', + 'Jassiah', + 'Jaidin', + 'Lexie', + 'Mieczyslaw', + 'Joffre', + 'Augustino', + 'Adelino', + 'Tadeusz', + 'Humphrey', + 'Lonas', + 'Avry', + 'Tylin', + 'Dixie', + 'Goldman', + 'Yissachar', + 'Toure', + 'Yafet', + 'Siraj', + 'Nasiah', + 'Maor', + 'Roniel', + 'Kerim', + 'Danieljr', + 'Django', + 'Lion', + 'Baruc', + 'Cervando', + 'Akul', + 'Abdi', + 'Ameya', + 'Arhan', + 'Aliou', + 'Arcangel', + 'Avrumy', + 'Deandrea', + 'Dontreal', + 'Yossef', + 'Walden', + 'Tameem', + 'Kenderick', + 'Yassine', + 'Zeyad', + 'Riyad', + 'Kashmere', + 'Tevis', + 'Malichi', + 'Malakhai', + 'Yulian', + 'Clearnce', + 'Esco', + 'Fabrizzio', + 'Gianpaolo', + 'Jaskirat', + 'Termaine', + 'Daouda', + 'Abba', + 'Aaban', + 'Chanoch', + 'Raynell', + 'Ihsan', + 'Djibril', + 'Cassiel', + 'Ishaq', + 'Azlan', + 'Behruz', + 'Amirjon', + 'Anisjon', + 'Asadbek', + 'Dhilan', + 'Dream', + 'Daviel', + 'Mosha', + 'Rayane', + 'Shabsi', + 'Olie', + 'Vinicio', + 'Yuda', + 'Shohjahon', + 'Kylematthew', + 'Kien', + 'Matthewjames', + 'Giorgi', + 'Konstantine', + 'Jibreel', + 'Jadriel', + 'Lliam', + 'Travonte', + 'Taiki', + 'Rendell', + 'Wyland', + 'Arafat', + 'Tajon', + 'Loic', + 'Shaw', + 'Sukhman', + 'Randiel', + 'Stefanos', + 'Lukus', + 'Majesty', + 'Massimiliano', + 'Burach', + 'Jansel', + 'Ismaila', + 'Henoch', + 'Daelin', + 'Giordano', + 'Huber', + 'Rontrell', + 'Simran', + 'Majid', + 'Rayjon', + 'Pharoah', + 'Lamine', + 'Hanoch', + 'Chidi', + 'Jahmani', + 'Javid', + 'Kamani', + 'Endrit', + 'Endy', + 'Nasean', + 'Danyael', + 'Cinque', + 'Akaash', + 'Zeeshan', + 'Amel', + 'Adib', + 'Aboubakar', + 'Artan', + 'Burak', + 'Serigne', + 'Samin', + 'Hovsep', + 'Jomari', + 'Cesareo', + 'Dajohn', + 'Charbel', + 'Bakary', + 'Camerin', + 'Jaquel', + 'Pape', + 'Jahrel', + 'Jahrell', + 'Khadim', + 'Jeison', + 'Yobany', + 'Zaul', + 'Taryn', + 'Abou', + 'Besim', + 'Abdur', + 'Ebrahim', + 'Albi', + 'Haadi', + 'Saba', + 'Wen', + 'Felipedejesus', + 'Dragon', + 'Jamiel', + 'Alecxis', + 'Ashkon', + 'Tejon', + 'Meelad', + 'Renan', + 'Brailyn', + 'Harel', + 'Abdou', + 'Amier', + 'Jonathanjoseph', + 'Juanalberto', + 'Larenz', + 'Nerses', + 'Emmanuelle', + 'Jasmeet', + 'Jahred', + 'Elsworth', + 'Nyshawn', + 'Alexes', + 'Cranford', + 'Trenell', + 'Cephus', + 'Costas', + 'Rama', + 'Nickalas', + 'Moultrie', + 'Deklin', + 'Saafir', + 'Alexie', + 'Kajuan', + 'Jamahl', + 'Robet', + 'Antoin', + 'Turhan', + 'Mart', + 'Richrd', + 'Ante', + 'Bransyn', + 'Dargan', + 'Levan', + 'Milledge', + 'Ollis', + 'Morey', + 'Jeromey', + 'Ebon', + 'Nicholus', + 'Yvonne', + 'Gladstone', + 'Kwan', + 'Sherry', + 'Romney', + 'Nicolaos', + 'Oded', + 'Koty', + 'Mandy', + 'Adger', + 'Esaw', + 'Shaunte', + 'Nimesh', + 'Ahren', + 'Marcellino', + 'Attila', + 'Pinkney', + 'Reinhard', + 'Deanna', + 'Shanti', + 'Calmer', + 'Reda', + 'Darral', + 'Monserrate', + 'Levert', + 'Harce', + 'Ayham', + 'Breslin', + 'Dom', + 'Darrow', + 'Haidar', + 'Willaim', + 'Shann', + 'Regina', + 'Einer', + 'Zui', + 'Shonn', + 'Skipper', + 'Henning', + 'Jacek', + 'Wendelin', + 'Wilmar', + 'Algot', + 'Marlen', + 'Dquan', + 'Emanuele', + 'Erol', + 'Boby', + 'Elbin', + 'Londell', + 'Bradd', + 'Malo', + 'Mohamadali', + 'Toussaint', + 'Roald', + 'Trini', + 'Stace', + 'Erubey', + 'Labron', + 'Kyseem', + 'Duong', + 'Rande', + 'Siegfried', + 'Mamon', + 'Va', + 'Quy', + 'Raman', + 'Ramil', + 'Jasai', + 'Carla', + 'Belen', + 'Lawernce', + 'Jemar', + 'Markham', + 'Kym', + 'Jemaine', + 'Baldwin', + 'Damany', + 'Timonthy', + 'Tesfa', + 'Vinod', + 'Albertus', + 'Yupheng', + 'Danie', + 'Tashiem', + 'Uno', + 'Onnie', + 'Juliana', + 'Duff', + 'Doua', + 'Orman', + 'Kamaal', + 'Godwin', + 'Ulric', + 'Darrold', + 'Rennie', + 'Lory', + 'Jamile', + 'Terril', + 'Gable', + 'Hanh', + 'Grisel', + 'Jimmylee', + 'Mikkel', + 'Victorino', + 'Jaymere', + 'Rayn', + 'Duriel', + 'Ceferino', + 'Autrey', + 'Durant', + 'Kolsen', + 'Abayomi', + 'Azell', + 'Spyros', + 'Ato', + 'Damin', + 'Diogenes', + 'Barnaby', + 'Pinckney', + 'Keno', + 'Sherard', + 'Chukwuemeka', + 'Akin', + 'Harvel', + 'Marv', + 'Kenyetta', + 'Huel', + 'Royzell', + 'Luddie', + 'Olden', + 'Ardith', + 'Branch', + 'Bertha', + 'Hillman', + 'Namon', + 'Donnis', + 'Fitzhugh', + 'Lavaughn', + 'Lucille', + 'Amanuel', + 'Carvin', + 'Minnie', + 'Tivis', + 'Birt', + 'Bronner', + 'Vaden', + 'Joenathan', + 'Alphonsa', + 'Elvie', + 'Alpheus', + 'Clausell', + 'Clayburn', + 'Demetrias', + 'Avis', + 'Garlon', + 'Romaine', + 'Jamorris', + 'Swanson', + 'Perez', + 'Hurschel', + 'Virge', + 'Rutherford', + 'Lelton', + 'Tarris', + 'Denson', + 'Benjaman', + 'Rashun', + 'Keino', + 'Cedarius', + 'Keanthony', + 'Blakeley', + 'Burwell', + 'Kasai', + 'Euell', + 'Eldrick', + 'Ashford', + 'Demetruis', + 'Wood', + 'Blanton', + 'Daniell', + 'Robt', + 'Lamorris', + 'Waller', + 'Devoris', + 'Herley', + 'Jermery', + 'Jamicheal', + 'Horton', + 'Gradie', + 'Etheridge', + 'Millie', + 'Jammy', + 'Karey', + 'Rodregus', + 'Cordera', + 'Embry', + 'Forney', + 'Sims', + 'Gergory', + 'Rosser', + 'Benjamine', + 'Erskin', + 'Heflin', + 'Torrie', + 'Norville', + 'Arvie', + 'Bessie', + 'Keonta', + 'Tarrence', + 'Chapman', + 'Limmie', + 'Tavius', + 'Reynard', + 'Lonza', + 'Detroit', + 'Camauri', + 'Clanton', + 'Obbie', + 'Mizell', + 'Marshel', + 'Tollie', + 'Jondarius', + 'Therion', + 'Antoino', + 'Beatrice', + 'Keyonte', + 'Littleton', + 'Hozie', + 'Atwell', + 'Ottie', + 'Pelham', + 'Vickie', + 'Cederick', + 'Zaykeese', + 'Jadarious', + 'Shin', + 'Tizoc', + 'Mischa', + 'Tycen', + 'Jubal', + 'Kito', + 'Sabin', + 'Brannan', + 'Baltasar', + 'Hilda', + 'Orasio', + 'Bassel', + 'Ameet', + 'Talus', + 'Renne', + 'Reuel', + 'Saro', + 'Kam', + 'Heliodoro', + 'Hodari', + 'Mondo', + 'Damaso', + 'Damein', + 'Thunder', + 'Ravinder', + 'Remberto', + 'Rodel', + 'Yvan', + 'Marcelle', + 'Kiril', + 'Shem', + 'Bardo', + 'Carlson', + 'Jebediah', + 'Austreberto', + 'Hannibal', + 'Shawnn', + 'Kenyatte', + 'Geoffry', + 'Hadden', + 'Natnael', + 'Edurdo', + 'Errik', + 'Eva', + 'Gaelan', + 'Gilverto', + 'Antwaine', + 'Barclay', + 'Rithy', + 'Sarath', + 'Sasan', + 'Stefen', + 'Susana', + 'Le', + 'Mai', + 'Marquies', + 'Neeraj', + 'Galdino', + 'Cuitlahuac', + 'Griselda', + 'Jerret', + 'Filbert', + 'Travone', + 'Lizette', + 'Lourdes', + 'Ratana', + 'Sarith', + 'Ku', + 'Jocob', + 'Jushua', + 'Shaughn', + 'Sophal', + 'Sophana', + 'Stepan', + 'Tramel', + 'Veniamin', + 'Ha', + 'Halley', + 'Hiep', + 'Maclain', + 'Alberta', + 'Alejando', + 'Eliana', + 'Chay', + 'Esmond', + 'Frisco', + 'Dai', + 'Marta', + 'Man', + 'Kha', + 'Kin', + 'Sun', + 'Paulmichael', + 'Rj', + 'Jeoffrey', + 'Custodio', + 'Herberth', + 'Gerrad', + 'Seanpaul', + 'Sten', + 'Nereida', + 'Jasun', + 'Micharl', + 'Robbert', + 'Ronnel', + 'Rosio', + 'Othon', + 'Chau', + 'Hart', + 'Atthew', + 'Angelito', + 'Debbie', + 'Randol', + 'Jeffrie', + 'Kern', + 'Rohn', + 'Raef', + 'Arleigh', + 'Jef', + 'Reg', + 'Vinton', + 'Perrin', + 'Parry', + 'Sally', + 'Hoby', + 'Vint', + 'Dagmawi', + 'Mat', + 'Gregrey', + 'Darol', + 'Merik', + 'Rickard', + 'Clete', + 'Fredrik', + 'Darrol', + 'Lyall', + 'Jamare', + 'Duffy', + 'Barre', + 'Shawnee', + 'Tige', + 'Whittaker', + 'Tyrion', + 'Jamas', + 'Jud', + 'Spence', + 'Dione', + 'Erinn', + 'Bron', + 'Ackley', + 'Dal', + 'Monti', + 'Paco', + 'Kjell', + 'Gabor', + 'Davinder', + 'Shonte', + 'Maximiano', + 'Heshimu', + 'Jassen', + 'Jerami', + 'Jermon', + 'Keefe', + 'Keri', + 'Daric', + 'Christropher', + 'Johnney', + 'Dodd', + 'Wilferd', + 'Raymondo', + 'Keary', + 'Orlan', + 'Gerhart', + 'Clemence', + 'Pepe', + 'Whitaker', + 'Vaughan', + 'Wess', + 'Abenezer', + 'Miroslav', + 'Kurk', + 'Helmut', + 'Timothey', + 'Annette', + 'Cruise', + 'Jahel', + 'Itay', + 'Isaiahs', + 'Isack', + 'Eagan', + 'Finbar', + 'Famous', + 'Ethanjoseph', + 'Ethanjames', + 'Edi', + 'Isais', + 'Albeiro', + 'Abhijot', + 'Joshuajames', + 'Amine', + 'Edwardjames', + 'Donyae', + 'Danieljohn', + 'Avaneesh', + 'Aryav', + 'Andoni', + 'Yeison', + 'Lowen', + 'Obi', + 'Mycah', + 'Moksh', + 'Miliano', + 'Maxamillion', + 'Lazlo', + 'Jocsan', + 'Jibran', + 'Jerimyah', + 'Jefte', + 'Korde', + 'Kanav', + 'Tavita', + 'Taesean', + 'Yoltzin', + 'Xzavior', + 'Vibhav', + 'Romen', + 'Rocket', + 'Rai', + 'Orian', + 'Rumi', + 'Shota', + 'Shaheer', + 'Sadrac', + 'Semaje', + 'Sohrob', + 'Yuval', + 'Yuren', + 'Yannis', + 'Vineet', + 'Yarden', + 'Jesusjr', + 'Kartik', + 'Jairon', + 'Millen', + 'Nahun', + 'Krisna', + 'Kyrese', + 'Mher', + 'Mayan', + 'Kais', + 'Joshuan', + 'Jometh', + 'Keawe', + 'Siris', + 'Sinai', + 'Shuban', + 'Shian', + 'Sneijder', + 'Sota', + 'Uday', + 'Sevak', + 'Royale', + 'Yuuki', + 'Reyhan', + 'Seena', + 'Moisses', + 'Nayib', + 'Sumit', + 'Dayveon', + 'Christianpaul', + 'Garrin', + 'Edgerrin', + 'Edrees', + 'Estephan', + 'Assael', + 'Azad', + 'Tydus', + 'Yosuf', + 'Zekiel', + 'Strider', + 'Senai', + 'Edmar', + 'Dmorea', + 'Eman', + 'Darran', + 'Keston', + 'Keny', + 'Hardeep', + 'Heladio', + 'Hernesto', + 'Hovannes', + 'Sankalp', + 'Brenten', + 'Navraj', + 'Mavrik', + 'Nilmar', + 'Rishit', + 'Edwing', + 'Eswin', + 'Flabio', + 'Jasn', + 'Romar', + 'Sevan', + 'Shahab', + 'Justinmichael', + 'Joseandres', + 'Marcelus', + 'Mariana', + 'Andhy', + 'Angeles', + 'Tannor', + 'Tristain', + 'Joshuaray', + 'Luisdavid', + 'Damaris', + 'Daymond', + 'Anthonyjohn', + 'Dezhon', + 'Emelio', + 'Eulices', + 'Maclean', + 'Jaeson', + 'Ethanjohn', + 'Ethanjacob', + 'Jasiri', + 'Kaisei', + 'Khyle', + 'Jona', + 'Jeren', + 'Jeramyah', + 'Jesusantonio', + 'Jguadalupe', + 'Joseeduardo', + 'Elkin', + 'Prashant', + 'Anguel', + 'Anant', + 'Aisea', + 'Abhimanyu', + 'Daelen', + 'Dylin', + 'Dodge', + 'Nazaret', + 'Mikie', + 'Matthewjoseph', + 'Maximillan', + 'Savir', + 'Dhillon', + 'Donoven', + 'Ebin', + 'Edrei', + 'Elek', + 'Nykolas', + 'Nikash', + 'Nik', + 'Reyly', + 'Razi', + 'Presten', + 'Arul', + 'Avo', + 'Yandell', + 'Wynston', + 'Tallen', + 'Suhaib', + 'Joshuajohn', + 'Jesusmanuel', + 'Malacai', + 'Kethan', + 'Londen', + 'Larenzo', + 'Kriss', + 'Kohei', + 'Hamlet', + 'Martinjr', + 'Mansoor', + 'Archit', + 'Aniketh', + 'Kincaid', + 'Lunden', + 'Masaki', + 'Salam', + 'Sahith', + 'Nour', + 'Miqueas', + 'Estefano', + 'Hatim', + 'Gurvir', + 'Adeeb', + 'Tobiah', + 'Torrin', + 'Tushar', + 'Tyee', + 'Sulayman', + 'Takai', + 'Tayo', + 'Yoan', + 'Vegas', + 'Duilio', + 'Dyami', + 'Greko', + 'Harim', + 'Ioane', + 'Ashmit', + 'Bora', + 'Alekxander', + 'Alexanderjames', + 'Amanpreet', + 'Anthonny', + 'Brandom', + 'Daimon', + 'Sirus', + 'Seananthony', + 'Vignesh', + 'Vir', + 'Wisdom', + 'Rameen', + 'Kenzie', + 'Joshuamichael', + 'Josejr', + 'Joseenrique', + 'Jacksen', + 'Jeriko', + 'Jesua', + 'Myka', + 'Naithen', + 'Saurav', + 'Shalim', + 'Puneet', + 'Denali', + 'Daveyon', + 'Sohil', + 'Edilson', + 'Jafeth', + 'Nathin', + 'Maurion', + 'Mekai', + 'Nadim', + 'Jamani', + 'Jamisen', + 'Gared', + 'Gahel', + 'Emron', + 'Hanzel', + 'Xaviar', + 'Yohann', + 'Alam', + 'Brasen', + 'Ashlan', + 'Rury', + 'Ralphie', + 'Robertanthony', + 'Tomoki', + 'Zamuel', + 'Urian', + 'Vinayak', + 'Wilberth', + 'Jazziel', + 'Mizraim', + 'Mosiah', + 'Muneeb', + 'Lennin', + 'Chaitanya', + 'Cyrille', + 'Dilpreet', + 'Bhargav', + 'Captain', + 'Camil', + 'Jaion', + 'Eithen', + 'Dominyk', + 'Domenik', + 'Imad', + 'Dabin', + 'Ceejay', + 'Avishek', + 'Anoop', + 'Aaronjoshua', + 'Billal', + 'Euan', + 'Eion', + 'Beauregard', + 'Fouad', + 'Chriss', + 'Daimien', + 'Cyan', + 'Conall', + 'Inigo', + 'Jashan', + 'Jaicob', + 'Arek', + 'Benjaminjoseph', + 'Bodey', + 'Andrewjames', + 'Abdel', + 'Alian', + 'Artyom', + 'Anik', + 'Angeljesus', + 'Shriyan', + 'Sosaia', + 'Shabd', + 'Tayveon', + 'Samik', + 'Josephanthony', + 'Kaushal', + 'Gerardojr', + 'Haile', + 'Henok', + 'Imer', + 'Izaiha', + 'Vedanth', + 'Rishav', + 'Praveen', + 'Kenner', + 'Juanjr', + 'Kinan', + 'Maven', + 'Neven', + 'Niccolas', + 'Raynav', + 'Rani', + 'Noahjames', + 'Nirvan', + 'Nevaan', + 'Naythen', + 'Rhythm', + 'Samyak', + 'Sahas', + 'Roczen', + 'Kroy', + 'Johanna', + 'Miro', + 'Mayank', + 'Masson', + 'Yamato', + 'Xaden', + 'Vin', + 'Tyden', + 'Gaudencio', + 'Garreth', + 'Toryn', + 'Jaswinder', + 'Stiles', + 'Graciela', + 'Rutger', + 'Razmig', + 'Keo', + 'Kavir', + 'Kalev', + 'Kal', + 'Kabeer', + 'Jianni', + 'Terrace', + 'Vicken', + 'Westly', + 'Pardeep', + 'Lizeth', + 'Lucia', + 'Mandela', + 'Maricela', + 'Joshus', + 'Kayle', + 'Klyde', + 'Djavan', + 'Wang', + 'Aljandro', + 'Belisario', + 'Cristino', + 'Yihan', + 'Carina', + 'Chritian', + 'Juanramon', + 'Khan', + 'Jaiver', + 'Nefi', + 'Murtaza', + 'Raciel', + 'Marlene', + 'Maira', + 'Chima', + 'Cheenou', + 'Bijon', + 'Dorion', + 'Elber', + 'Emeka', + 'Ge', + 'Ratha', + 'Jaxxson', + 'Ryanjames', + 'Shannen', + 'Shue', + 'Sia', + 'Romaldo', + 'Zareh', + 'Tomy', + 'Vanna', + 'Xao', + 'Bertin', + 'Dhyan', + 'Dexton', + 'Esiah', + 'Ayce', + 'Avyukt', + 'Avner', + 'Caspar', + 'Cove', + 'Ciel', + 'Yen', + 'Yessenia', + 'Yony', + 'Fin', + 'Ezrael', + 'Ezel', + 'Ilay', + 'Harveer', + 'Hamad', + 'Asiah', + 'Ashwath', + 'Arcenio', + 'Aroldo', + 'Awet', + 'Alexx', + 'Arihant', + 'Arihaan', + 'Apolo', + 'Aero', + 'Advith', + 'Arren', + 'Beatriz', + 'Jony', + 'Joseramon', + 'Justinray', + 'Jamaul', + 'Tarren', + 'Cristal', + 'Dinh', + 'Chantra', + 'Dshawn', + 'Geraldine', + 'Fuad', + 'Edlin', + 'Jerren', + 'Jerrin', + 'Josje', + 'Chrystopher', + 'Darriel', + 'Takuya', + 'Vannak', + 'Zenas', + 'Miklos', + 'Marten', + 'Rondale', + 'Rothana', + 'Randeep', + 'Ryle', + 'Eduardoluis', + 'Christepher', + 'Davionne', + 'Eriverto', + 'Farbod', + 'Chauncy', + 'Charle', + 'Bayardo', + 'Ashneel', + 'Shoua', + 'Redmond', + 'Ustin', + 'Johnnathan', + 'Josephmichael', + 'Marisela', + 'Markandrew', + 'Michaeljoseph', + 'Marcua', + 'Nidal', + 'Phat', + 'Pritesh', + 'Seaver', + 'Ryananthony', + 'Tyan', + 'Vatche', + 'Thoren', + 'Othoniel', + 'Nicandro', + 'Rajdeep', + 'Tulio', + 'Soua', + 'Jovonte', + 'Kalyn', + 'Jamesryan', + 'Navdeep', + 'Maxmillian', + 'Kayon', + 'Koua', + 'Aaryn', + 'Wilver', + 'Zubair', + 'Ankush', + 'Andie', + 'Adonnis', + 'Jacobanthony', + 'Izekiel', + 'Izacc', + 'Escher', + 'Elijahjames', + 'Edrik', + 'Drayson', + 'Dj', + 'Giordan', + 'Dejaun', + 'Davidmichael', + 'Deshone', + 'Auron', + 'Auguste', + 'Athos', + 'Cutberto', + 'Hairo', + 'Anvay', + 'Adrick', + 'Aydeen', + 'Bassam', + 'Basem', + 'Kyrell', + 'Rjay', + 'Ozil', + 'Taisei', + 'Samanyu', + 'Marvion', + 'Mykael', + 'Mukund', + 'Namish', + 'Naoki', + 'Nishan', + 'Aideen', + 'Aalijah', + 'Hassani', + 'Harkirat', + 'Exzavier', + 'Hudsen', + 'Hrach', + 'Caelum', + 'Caeleb', + 'Destan', + 'Jaspal', + 'Huan', + 'Marcellous', + 'Mehran', + 'Luisfelipe', + 'Gelacio', + 'Eris', + 'Eneas', + 'Terin', + 'Sohrab', + 'Ravneet', + 'Uziah', + 'Vedansh', + 'Peni', + 'Nethaniel', + 'Niraj', + 'Odilon', + 'Kalden', + 'Mariela', + 'Levonte', + 'Elih', + 'Ej', + 'Eames', + 'Jarome', + 'Jishnu', + 'Gurtaaj', + 'Hamish', + 'Gryffin', + 'Jayin', + 'Trong', + 'Sebastain', + 'Sargon', + 'Wa', + 'Cheveyo', + 'Ariv', + 'Aum', + 'Caellum', + 'Bayan', + 'Balthazar', + 'Sagan', + 'Rowyn', + 'Sehaj', + 'Ivon', + 'Stavro', + 'Shrihan', + 'Noey', + 'Oswin', + 'Abrham', + 'Adalid', + 'Aldric', + 'Zayed', + 'Vonn', + 'Vaishnav', + 'Urias', + 'Yahshua', + 'Yago', + 'Darith', + 'Mantej', + 'Kyo', + 'Khyler', + 'Marcjacob', + 'Nayden', + 'Morrissey', + 'Benedicto', + 'Kendrix', + 'Xang', + 'Ranjit', + 'Raymar', + 'Milos', + 'Rayansh', + 'Rawley', + 'Paxon', + 'Krishang', + 'Leeam', + 'Yerick', + 'Yegor', + 'Viren', + 'Saathvik', + 'Shailen', + 'Sahaj', + 'Rydan', + 'Rollins', + 'Rivaan', + 'Soul', + 'Aerick', + 'Aladdin', + 'Catalino', + 'Berenice', + 'Branndon', + 'Kyleanthony', + 'Maclovio', + 'Kiven', + 'Johnchristopher', + 'Jonh', + 'Kassandra', + 'Jobanny', + 'Pastor', + 'Michaela', + 'Montre', + 'Morgen', + 'Gerber', + 'Danish', + 'Haroutun', + 'Duron', + 'Adrion', + 'Evrett', + 'Reegan', + 'Haskie', + 'Quamane', + 'Derrike', + 'Haydyn', + 'Glenville', + 'Dearl', + 'Deroe', + 'Dewell', + 'Lundy', + 'Cleaster', + 'Jeral', + 'Delontae', + 'Delford', + 'Argie', + 'Loise', + 'Elmar', + 'Donley', + 'Ferrel', + 'Carrel', + 'Athel', + 'Rector', + 'Cledith', + 'Dail', + 'Donzel', + 'Lenoard', + 'Winferd', + 'Birl', + 'Dorsie', + 'Olee', + 'Erman', + 'Dorsel', + 'Roma', + 'Othell', + 'Herold', + 'Chaffee', + 'Trygve', + 'Aubra', + 'Opha', + 'Dionne', + 'Colleen', + 'Ciara', + 'Cleotis', + 'Alissa', + 'Alesha', + 'Elise', + 'Emilie', + 'Tiera', + 'Tia', + 'Suzanne', + 'Jaleesa', + 'Jaclyn', + 'Ingrid', + 'India', + 'Georgia', + 'Francesca', + 'Female', + 'Fatima', + 'Rochelle', + 'Precious', + 'Nichelle', + 'Martina', + 'Lucy', + 'Latonya', + 'Cline', + 'Ott', + 'Ona', + 'Otmer', + 'Ersel', + 'Olufemi', + 'Gordy', + 'Marne', + 'Jahquez', + 'Daeshaun', + 'Nashaun', + 'Seiichi', + 'Shigeki', + 'Kazuto', + 'Shozo', + 'Alhaji', + 'Lonn', + 'Tevion', + 'Kaige', + 'Darlene', + 'Braydyn', + 'Masaaki', + 'Graeson', + 'Bernerd', + 'Lynne', + 'Dewaine', + 'Shig', + 'Junichi', + 'Toshiro', + 'Azavion', + 'Michio', + 'Yoshiro', + 'Heraldo', + 'Epitacio', + 'Mas', + 'Taequan', + 'Trindon', + 'Tirrell', + 'Dmonte', + 'Jaquante', + 'Yeeleng', + 'Maleik', + 'Airam', + 'Noname', + 'Shyhiem', + 'Tyquon', + 'Damonta', + 'Undray', + 'Shadrick', + 'Durwin', + 'Lataurus', + 'Corneall', + 'Dantonio', + 'Tilmon', + 'Mackie', + 'Ebbie', + 'Eligha', + 'Beth', + 'Barth', + 'Hezzie', + 'Artha', + 'Darrie', + 'Frederi', + 'Benford', + 'Elves', + 'Theodia', + 'Jaye', + 'Fran', + 'Khylan', + 'Berwyn', + 'Constance', + 'Markevion', + 'Martavion', + 'Jashun', + 'Jermarion', + 'Taylin', + 'Breland', + 'Franchot', + 'Chrishun', + 'Davarius', + 'Dearius', + 'Tredarius', + 'Jayland', + 'Cortavius', + 'Deyonta', + 'Tradarius', + 'Kemarrion', + 'Markavion', + 'Jmarion', + 'Jacarius', + 'Kairi', + 'Rasool', + 'Jarreau', + 'Khayree', + 'Brahin', + 'Hameed', + 'Rolen', + 'Cleason', + 'Cartez', + 'Nicholad', + 'Brahim', + 'Bryheem', + 'Khalief', + 'Anel', + 'Mcgwire', + 'Lula', + 'Gaddis', + 'Lowery', + 'Odies', + 'Rannie', + 'Artee', + 'Aurther', + 'Bookert', + 'Lenon', + 'Oree', + 'Gennie', + 'Emitt', + 'Sedgie', + 'Claudy', + 'Coyt', + 'Lieutenant', + 'Zannie', + 'Kenn', + 'Roosvelt', + 'Vertis', + 'Elex', + 'Eula', + 'Abron', + 'Perkins', + 'Emersyn', + 'Lakin', + 'Dravin', + 'Other', + 'President', + 'Carrie', + 'Cleother', + 'Estus', + 'Tee', + 'Raymont', + 'Woodard', + 'Ras', + 'Zennie', + 'Versie', + 'Mansfield', + 'Atha', + 'Bossie', + 'Smiley', + 'Kenard', + 'Jermie', + 'Vardell', + 'Kadan', + 'Roney', + 'Furney', + 'Caroll', + 'Benjy', + 'Shamond', + 'Tyrease', + 'Dontre', + 'Raekwan', + 'Raequon', + 'Chrishon', + 'Jahmez', + 'Jaques', + 'Zaveon', + 'Zaccheus', + 'Demaris', + 'Shaquile', + 'Shiheem', + 'Santario', + 'Monterio', + 'Jawaan', + 'Lavere', + 'Levere', + 'Guerino', + 'Lisle', + 'Fraser', + 'Grier', + 'Gurnie', + 'Lattie', + 'Wassil', + 'Domer', + 'Melio', + 'Zolton', + 'Haines', + 'Gervase', + 'Fermon', + 'Geneva', + 'Trask', + 'Linward', + 'Colen', + 'Dossie', + 'Zygmund', + 'Teofil', + 'Talbert', + 'Mosby', + 'Elworth', + 'Garvie', + 'Jiles', + 'Mallie', + 'Flay', + 'Stokes', + 'Bernis', + 'Gardiner', + 'Deno', + 'Algerd', + 'Handy', + 'Flake', + 'Hallet', + 'Coyte', + 'Wingate', + 'Burlie', + 'Sigmond', + 'Myrle', + 'Stiney', + 'Americus', + 'Claxton', + 'Acy', + 'Hill', + 'Fenner', + 'Festus', + 'Linnie', + 'Guilford', + 'Artice', + 'Constant', + 'Faber', + 'Jb', + 'Pleasant', + 'Dallis', + 'Vestal', + 'Terez', + 'English', + 'Allard', + 'Ingram', + 'Beaufort', + 'Chene', + 'Dequante', + 'Bubber', + 'Jamone', + 'Zebulun', + 'Daqwan', + 'Delshawn', + 'Jamond', + 'Dacota', + 'Wilmot', + 'Prue', + 'Wister', + 'Kenyata', + 'Darik', + 'Sumter', + 'Hovie', + 'Tallie', + 'Diontay', + 'Dontaye', + 'Brentt', + 'Felder', + 'Chappell', + 'Ralpheal', + 'Wofford', + 'Stclair', + 'Aiken', + 'Hashem', + 'Daire', + 'Grahm', + 'Jaivon', + 'Davarion', + 'Arnez', + 'Ryer', + 'Mousa', + 'Jahlon', + 'Leyland', + 'Maizen', + 'Zadyn', + 'Zein', + 'Amarri', + 'Hady', + 'Keegen', + 'Taeshawn', + 'Jontae', + 'Radwan', + 'Jsean', + 'Hartwell', + 'Roddey', + 'Arend', + 'Marjorie', + 'Clements', + 'Rae', + 'Pressley', + 'Saintclair', + 'Derrill', + 'Joann', + 'Cote', + 'Philo', + 'Urho', + 'Evart', + 'Vada', + 'Deo', + 'Tonie', + 'Irven', + 'Stjulian', + 'Durand', + 'Diarra', + 'Burnet', + 'Steed', + 'Demont', + 'Burris', + 'Donyell', + 'Gjon', + 'Demone', + 'Jodi', + 'Boban', + 'Brunson', + 'Mackey', + 'Delwyn', + 'Gordie', + 'Owens', + 'Efton', + 'Uel', + 'Ancel', + 'Zafir', + 'Kyeem', + 'Vencil', + 'Irl', + 'Tymeer', + 'Dymere', + 'Kier', + 'Murel', + 'Hale', + 'Lorn', + 'Tahjir', + 'Sufyaan', + 'Trig', + 'Yacqub', + 'Khadir', + 'Najib', + 'Ayuub', + 'Hamse', + 'Yassir', + 'Yussuf', + 'Abdihafid', + 'Abdinasir', + 'Abdiqani', + 'Tayon', + 'Abdirahim', + 'Abdishakur', + 'Mukhtar', + 'Bauer', + 'Damere', + 'Rashee', + 'Kalief', + 'Shyheed', + 'Dejour', + 'Kuran', + 'Qaadir', + 'Aldor', + 'Jasyah', + 'Hajj', + 'Ordell', + 'Gradyn', + 'Ayyub', + 'Atley', + 'Mahkai', + 'Lochlann', + 'Sakai', + 'Saamir', + 'Bernhardt', + 'Willmer', + 'Swen', + 'Hilding', + 'Knute', + 'Wael', + 'Thorvald', + 'Erle', + 'Melroy', + 'Valerian', + 'Jorgen', + 'Dacotah', + 'Shaydon', + 'Lamir', + 'Kahseem', + 'Jihaad', + 'Tylee', + 'Sakariye', + 'Qalid', + 'Syair', + 'Syire', + 'Safi', + 'Zaakir', + 'Sahmir', + 'Saahir', + 'Karlin', + 'Kowen', + 'Kahne', + 'Azir', + 'Tysir', + 'Maki', + 'Zekhi', + 'Pater', + 'Louden', + 'Jandiel', + 'Khaseem', + 'Livio', + 'Pellegrino', + 'Loretta', + 'Lothar', + 'Morty', + 'Harvard', + 'Jeris', + 'Arlene', + 'Salvotore', + 'Erasmus', + 'Canio', + 'Heywood', + 'Ivar', + 'Maitland', + 'Neale', + 'Gladys', + 'Ethelbert', + 'Fergus', + 'Arcangelo', + 'Sigismund', + 'Fremont', + 'Stillman', + 'Egidio', + 'Pincus', + 'Sabatino', + 'Solly', + 'Bela', + 'Stanly', + 'Faust', + 'Gesualdo', + 'Adolphe', + 'Ladislav', + 'Mandel', + 'Philander', + 'Catello', + 'Fordyce', + 'Brownie', + 'Darnley', + 'Alfio', + 'Emerito', + 'Darrly', + 'Delfin', + 'Chiam', + 'Beril', + 'Albie', + 'Roberts', + 'Ferdinando', + 'Maureen', + 'Herberto', + 'Lamark', + 'Philipp', + 'Uwe', + 'Dermott', + 'Amalio', + 'Sandford', + 'Shawnta', + 'Shannan', + 'Sheppard', + 'Jerauld', + 'Antoinne', + 'Oleh', + 'Tobie', + 'Thoms', + 'Valice', + 'Thurnell', + 'Deamonte', + 'Kendel', + 'Trevone', + 'Kaylob', + 'Carder', + 'Antrell', + 'Traven', + 'Jaymir', + 'Joni', + 'Keisean', + 'Krishawn', + 'Marquelle', + 'Dearis', + 'Delvonte', + 'Jamez', + 'Zebadiah', + 'Kreig', + 'Teran', + 'Resean', + 'Zackory', + 'Lamontae', + 'Albieri', + 'Albiery', + 'Chen', + 'Alexy', + 'Arslan', + 'Taliek', + 'Nakhi', + 'Naphtali', + 'Papa', + 'Pesach', + 'Michoel', + 'Salih', + 'Harshdeep', + 'Elhadj', + 'Izzy', + 'Jahkai', + 'Tyliek', + 'Vasilis', + 'Yaacov', + 'Sohaib', + 'Yissochor', + 'Mir', + 'Jasin', + 'Jensy', + 'Rehman', + 'Nazeer', + 'Jahmil', + 'Enson', + 'Nasif', + 'Rizwan', + 'Samiul', + 'Rahat', + 'Angelos', + 'Avroham', + 'Abdulai', + 'Adir', + 'Enes', + 'Yishay', + 'Doyt', + 'Gal', + 'Shoaib', + 'Quaron', + 'Ishraq', + 'Nazaire', + 'Nyzaiah', + 'Mattia', + 'Javone', + 'Mahesh', + 'Mamady', + 'Johnattan', + 'Jorman', + 'Kaliq', + 'Devendra', + 'Burhan', + 'Zishe', + 'Zeandre', + 'Arel', + 'Shalik', + 'Shameer', + 'Nisson', + 'Ralik', + 'Agim', + 'Amauris', + 'Atif', + 'Samory', + 'Shatiek', + 'Taner', + 'Rafat', + 'Zhen', + 'Radhames', + 'Raliek', + 'Ronel', + 'Sabbir', + 'Saqib', + 'Jeudy', + 'Hesham', + 'Hyun', + 'Lakeem', + 'Mishael', + 'Ivo', + 'Tajay', + 'Taleek', + 'Tishawn', + 'Tyreem', + 'Samori', + 'Nickholas', + 'Pearse', + 'Mamadi', + 'Elhadji', + 'Dawood', + 'Dilon', + 'Ishmel', + 'Yiannis', + 'Jahquel', + 'Jahquell', + 'El', + 'Equan', + 'Ho', + 'Delno', + 'Dinesh', + 'Damel', + 'Temitayo', + 'Tenzing', + 'Wahab', + 'Alisher', + 'Adonijah', + 'Bradan', + 'Efrayim', + 'Elnatan', + 'Elmin', + 'Hossain', + 'Eliav', + 'Azimjon', + 'Dovber', + 'Sheya', + 'Yahia', + 'Jasani', + 'Liav', + 'Kamare', + 'Kaysean', + 'Kinsley', + 'Nikoloz', + 'Nyrell', + 'Wyeth', + 'Jeremaih', + 'Mahin', + 'Matis', + 'Oriel', + 'Mourad', + 'Shmeil', + 'Messi', + 'Jonibek', + 'Jeyren', + 'Keyden', + 'Temur', + 'Tanveer', + 'Zyir', + 'Zidan', + 'Zayyan', + 'Varick', + 'Wesam', + 'Abdoulie', + 'Aqib', + 'Asani', + 'Bless', + 'Hasnain', + 'Hamdan', + 'Getzel', + 'Fatin', + 'Huzaifa', + 'Jarif', + 'Jahlani', + 'Davier', + 'Chuna', + 'Eashan', + 'Rafan', + 'Rakin', + 'Ngawang', + 'Mouhamad', + 'Rohaan', + 'Vanness', + 'Volvy', + 'Javel', + 'Jabir', + 'Jaevion', + 'Fahd', + 'Lean', + 'Machai', + 'Juniel', + 'Kaylin', + 'Jeremiyah', + 'Matisyahu', + 'Menasha', + 'Mikaeel', + 'Gaspard', + 'Lorik', + 'Shuaib', + 'Seif', + 'Shlomy', + 'Shneor', + 'Sonam', + 'Volf', + 'Yussef', + 'Ziv', + 'Krrish', + 'Machi', + 'Endi', + 'Frederik', + 'Abdo', + 'Alif', + 'Elchanan', + 'Yordy', + 'Shafin', + 'Siam', + 'Furkan', + 'Fallou', + 'Devyne', + 'Chaskel', + 'Arbi', + 'Younes', + 'Ziare', + 'Tanyon', + 'Terique', + 'Nicholaos', + 'Nickita', + 'Mordchai', + 'Saifullah', + 'Saliou', + 'Savier', + 'Jahmiere', + 'Jahson', + 'Javoni', + 'Jayel', + 'Jie', + 'Kwadwo', + 'Kahmani', + 'Johansel', + 'Murat', + 'Nasire', + 'Nezar', + 'Seydou', + 'Jamair', + 'Jahmeer', + 'Chanina', + 'Chezky', + 'Zyire', + 'Yoscar', + 'Alassane', + 'Aitan', + 'Dannon', + 'Donelle', + 'Harrington', + 'Sha', + 'Shamal', + 'Josph', + 'Torrell', + 'Ralphy', + 'Sharron', + 'Eleftherios', + 'Gedalia', + 'Kasheen', + 'Manoj', + 'Nuri', + 'Daran', + 'Devanand', + 'Evagelos', + 'Fatmir', + 'Haralambos', + 'Biju', + 'Nilson', + 'Wane', + 'Tarig', + 'Rober', + 'Sharone', + 'Lezer', + 'Odalis', + 'Glenston', + 'Josip', + 'Kostantinos', + 'Rahshawn', + 'Osei', + 'Shariyf', + 'Sotirios', + 'Aneudi', + 'Marios', + 'Biff', + 'Damiano', + 'Shean', + 'Rajendra', + 'Mare', + 'Richad', + 'Jaja', + 'Efstathios', + 'Nephtali', + 'Kowan', + 'Rhonda', + 'Pasqualino', + 'Confesor', + 'Linc', + 'Safet', + 'Sharrieff', + 'Kiron', + 'Damain', + 'Aurohom', + 'Kariem', + 'Tiheim', + 'Dushawn', + 'Kindu', + 'Aswad', + 'Kwane', + 'Oba', + 'Jermayne', + 'Dakeem', + 'Babatunde', + 'Ackeem', + 'Alvi', + 'Adetokunbo', + 'Akeel', + 'Kedwin', + 'Kayron', + 'Mergim', + 'Wilkins', + 'Wojciech', + 'Omair', + 'Kushtrim', + 'Kwamel', + 'Saiquan', + 'Naquon', + 'Quandell', + 'Veton', + 'Shaune', + 'Daguan', + 'Duquan', + 'Jency', + 'Ka', + 'Waqas', + 'Xiao', + 'Mahlik', + 'Kasiem', + 'Navindra', + 'Sayquan', + 'Shaquon', + 'Shiquan', + 'Rameek', + 'Jerelle', + 'Devaun', + 'Jakim', + 'Jaquell', + 'Eury', + 'Shaiquan', + 'Shakeal', + 'Shakiem', + 'Shaleek', + 'Ramesh', + 'Suhail', + 'Tylique', + 'Jawanza', + 'Jonell', + 'Hamdi', + 'Jaimeson', + 'Kerven', + 'Demetreus', + 'Giselle', + 'Aikeem', + 'Akiem', + 'Rondel', + 'Dow', + 'Gregroy', + 'Darnelle', + 'Naguan', + 'Tyronn', + 'Ricke', + 'Dishawn', + 'Rishawn', + 'Tarick', + 'Tynell', + 'Japhet', + 'Francesc', + 'Maximili', + 'Herby', + 'Jaqwan', + 'Kemal', + 'Akeen', + 'Azeez', + 'Devindra', + 'Deryck', + 'Deval', + 'Alessand', + 'Masood', + 'Uladimir', + 'Cadon', + 'Quanah', + 'Zimere', + 'Chatham', + 'Koi', + 'Zymire', + 'Jamaury', + 'Jahmire', + 'Ziyan', + 'Cowen', + 'Jamaurie', + 'Nyquan', + 'Jayleen', + 'Zymiere', + 'Zymarion', + 'Kahmari', + 'Langdon', + 'Zymari', + 'Jymir', + 'Kamaree', + 'Nycere', + 'Sayvion', + 'Jahmarion', + 'Justyce', + 'Tuck', + 'Thayer', + 'Mung', + 'Graison', + 'Delane', + 'Lemoyne', + 'Cinch', + 'Nevada', + 'Dhairya', + 'Jyaire', + 'Yazir', + 'Tahjmir', + 'Sequoyah', + 'Quention', + 'Tanmay', + 'Shreyansh', + 'Ahyan', + 'Aaryav', + 'Zaylin', + 'Laksh', + 'Basheer', + 'Bhavik', + 'Orley', + 'Vestel', + 'Altus', + 'Choice', + 'Bufford', + 'Quasir', + 'Emry', + 'Tressel', + 'Eppie', + 'Jayvier', + 'Prestin', + 'Haydin', + 'Caydan', + 'Corday', + 'Camdin', + 'Brodyn', + 'Liberato', + 'Trayon', + 'Telesfor', + 'Jayko', + 'Lavi', + 'Procopio', + 'Rubel', + 'Karder', + 'Jaymar', + 'Bryor', + 'Gottlob', + 'Saladin', + 'Tunis', + 'Saheed', + 'Alsexander', + 'Davonn', + 'Jaquill', + 'Shakeil', + 'Krunal', + 'Tashon', + 'Doyel', + 'Odes', + 'Thoams', + 'Rasul', + 'Wendyl', + 'Glendale', + 'Ahmid', + 'Altarik', + 'Amish', + 'Jaquis', + 'Dashan', + 'Salaam', + 'Bhavin', + 'Nashid', + 'Tauheed', + 'Jamill', + 'Cordney', + 'Derly', + 'Jamale', + 'Hristopher', + 'Camaron', + 'Domanique', + 'Desmund', + 'Keenon', + 'Paulanthony', + 'Demarques', + 'Meryl', + 'Medard', + 'Erbey', + 'Adrin', + 'Evo', + 'Pal', + 'Deke', + 'Glendal', + 'Tramayne', + 'Aloysuis', + 'Berthal', + 'Ashly', + 'Arien', + 'Teodulo', + 'Johsua', + 'Kelwin', + 'Quintan', + 'Mauel', + 'Quisto', + 'Gaylin', + 'Trayvion', + 'Tracer', + 'Ramsay', + 'Verlan', + 'Kyndal', + 'Donovon', + 'Samuell', + 'Treyveon', + 'Nereo', + 'Areli', + 'Dashun', + 'Devontre', + 'Stran', + 'Zarian', + 'Pacen', + 'Kamakani', + 'Alii', + 'Chidozie', + 'Cobie', + 'Acxel', + 'Jatavian', + 'Kelvon', + 'Keldon', + 'Giezi', + 'Gavon', + 'Virtus', + 'Burdell', + 'Dorrance', + 'Naail', + 'Lantz', + 'Travian', + 'Cleland', + 'Arish', + 'Elyan', + 'Chukwudi', + 'Shahrukh', + 'Coulter', + 'Karver', + 'Seeley', + 'Wynton', + 'Detric', + 'Quenten', + 'Joemichael', + 'Daruis', + 'Tyeler', + 'Montray', + 'Hermenegildo', + 'Donathan', + 'Mckenna', + 'Kijuan', + 'Braijon', + 'Vashawn', + 'Darvell', + 'Kennie', + 'Rejino', + 'Vickey', + 'Lyndall', + 'Reynoldo', + 'Malyk', + 'Armarion', + 'Brit', + 'Trayshawn', + 'Contrell', + 'Eutimio', + 'Dantrel', + 'Darrious', + 'Dawon', + 'Richey', + 'Arrion', + 'Zohair', + 'Randale', + 'Keyshone', + 'Kiwane', + 'Jibri', + 'Devell', + 'Beto', + 'Jaymz', + 'Ritchey', + 'Tremel', + 'Keante', + 'Vontrell', + 'Guadlupe', + 'Esiquiel', + 'Erasto', + 'Dub', + 'Augustas', + 'Panfilo', + 'Vuk', + 'Mickie', + 'Javonni', + 'Riddick', + 'Nikodem', + 'Marrion', + 'Kamareon', + 'Maks', + 'Eliverto', + 'Cresenciano', + 'Jerrol', + 'Joakim', + 'Maddax', + 'Kayvion', + 'Khizar', + 'Haze', + 'Aveon', + 'Amjad', + 'Audwin', + 'Almir', + 'Vicky', + 'Lonell', + 'Jabarie', + 'Jaylun', + 'Damarrion', + 'Mantas', + 'Dannye', + 'Aadarsh', + 'Caelen', + 'Tilton', + 'Kimmie', + 'Josgar', + 'Oleksandr', + 'Keyontae', + 'Fidelio', + 'Wiktor', + 'Maxymilian', + 'Cayce', + 'Rodric', + 'Manrique', + 'Kestutis', + 'Donnald', + 'Grayland', + 'Lavance', + 'Medgar', + 'Chaney', + 'Monta', + 'Lemond', + 'Medford', + 'Mareo', + 'Camerino', + 'Ronold', + 'Lancer', + 'Credell', + 'Elbridge', + 'Stony', + 'Dvid', + 'Hilberto', + 'Erineo', + 'Jerrald', + 'Antawan', + 'Cordario', + 'Levelle', + 'Ramsen', + 'Jigar', + 'Laroyce', + 'Lazerrick', + 'Artez', + 'Cordelro', + 'Creon', + 'Lonzell', + 'Shanton', + 'Orpheus', + 'Terris', + 'Renauld', + 'Deondra', + 'Fontaine', + 'Airrion', + 'Branko', + 'Enemencio', + 'Antiono', + 'Caprice', + 'Danyale', + 'Valdez', + 'Oswell', + 'Tahitoa', + 'Fannie', + 'Estes', + 'Herchel', + 'Seabron', + 'Bunyan', + 'Thelmon', + 'Agnew', + 'Broughton', + 'Harwell', + 'Mather', + 'Quillie', + 'Hardwick', + 'Phinizy', + 'Pope', + 'Addis', + 'Seals', + 'Thelman', + 'Summie', + 'Romano', + 'Zacari', + 'Kortney', + 'Makye', + 'Graycen', + 'Kavari', + 'Kamarri', + 'Ajahni', + 'Dayan', + 'Sharrod', + 'Pheonix', + 'Trentyn', + 'Jacai', + 'Jamesley', + 'Destyn', + 'Maddon', + 'Gianlucas', + 'Aydrian', + 'Bader', + 'Jaise', + 'Godson', + 'Gleb', + 'Jatniel', + 'Yaxiel', + 'Marvins', + 'Miron', + 'Yaroslav', + 'Legrande', + 'Lonzy', + 'Merrell', + 'Flemming', + 'Guerry', + 'Kimothy', + 'Remus', + 'Wyndell', + 'Barnard', + 'Denorris', + 'Edna', + 'Bevan', + 'Warnell', + 'Josie', + 'Arthor', + 'Theartis', + 'Kimsey', + 'Wymon', + 'Duglas', + 'Reshawn', + 'Natrone', + 'Treysen', + 'Davaris', + 'Jocqui', + 'Traivon', + 'Trevonne', + 'Tavarious', + 'Monson', + 'Kevis', + 'Ladonte', + 'Mackenson', + 'Bodee', + 'Chayden', + 'Dequon', + 'Keiondre', + 'Dewan', + 'Taige', + 'Renel', + 'Jasher', + 'Bayler', + 'Dodger', + 'Tyke', + 'Jarvin', + 'Edner', + 'Travonn', + 'Traxton', + 'Malosi', + 'Lavonta', + 'Janard', + 'Kyzer', + 'Packer', + 'Travoris', + 'Frantzy', + 'Makay', + 'Tamari', + 'Kanard', + 'Dairon', + 'Gabriell', + 'Kemaury', + 'Jshaun', + 'Karel', + 'Jakarri', + 'Rubens', + 'Zamauri', + 'Winsley', + 'Giulian', + 'Yosbel', + 'Kevaughn', + 'Jimson', + 'Kendly', + 'Dishon', + 'Dallyn', + 'Jephthe', + 'Luccas', + 'Kemuel', + 'Eddrick', + 'Ahmarion', + 'Amariyon', + 'Artavis', + 'Dewin', + 'Jacarie', + 'Jahn', + 'Janari', + 'Geordy', + 'Mardochee', + 'Jimari', + 'Yoshinobu', + 'Eiji', + 'Yasunobu', + 'Koon', + 'Hidemi', + 'Norio', + 'Kiyomi', + 'Shuichi', + 'Kazuyoshi', + 'Yoshitaka', + 'Kanji', + 'Tetsuro', + 'Asao', + 'Dominador', + 'Shogo', + 'Jakye', + 'Braelin', + 'Chrisangel', + 'Calab', + 'Morio', + 'Seiki', + 'Tsuyoshi', + 'Soichi', + 'Masakatsu', + 'Tadayoshi', + 'Tokuichi', + 'Yoshikatsu', + 'Matsuichi', + 'Lorrin', + 'Javeion', + 'Kail', + 'Jvon', + 'Joshwa', + 'Keylen', + 'Rylon', + 'Oved', + 'Kraven', + 'Koben', + 'Klever', + 'Nedved', + 'Dago', + 'Cortlen', + 'Reeves', + 'Yhair', + 'Xane', + 'Jamori', + 'Jayshon', + 'Jaiveon', + 'Joseth', + 'Drelynn', + 'Haldrin', + 'Keelyn', + 'Nathanuel', + 'Kvon', + 'Jayln', + 'Khyrie', + 'Zayveon', + 'Braxston', + 'Jaceion', + 'Jonavon', + 'Jesaiah', + 'Gaddiel', + 'Tobyn', + 'Becket', + 'Aydyn', + 'Arinze', + 'Dacian', + 'Aadin', + 'Fender', + 'Brysun', + 'Demarious', + 'Kaimi', + 'Ryson', + 'Jarrin', + 'Maleko', + 'Kamakana', + 'Kamalani', + 'Johnavon', + 'Kawena', + 'Aadil', + 'Blayde', + 'Garyn', + 'Izaih', + 'Bryndon', + 'Drelyn', + 'Demarian', + 'Kupaa', + 'Nalu', + 'Makena', + 'Lawaia', + 'Kaimalu', + 'Kanaloa', + 'Oshen', + 'Mj', + 'Kahekili', + 'Koalii', + 'Makua', + 'Promise', + 'Keylin', + 'Kevondrick', + 'Tobenna', + 'Infantboy', + 'Oluwatimilehin', + 'Nathanal', + 'Zakkery', + 'Shariq', + 'Sadler', + 'Rockne', + 'Drelon', + 'Ethon', + 'Catcher', + 'Clayten', + 'Kaniela', + 'Isaack', + 'Josten', + 'Zarius', + 'Tayte', + 'Ugochukwu', + 'Aiman', + 'Eduar', + 'Basel', + 'Canton', + 'Dyron', + 'Keaden', + 'Kayceon', + 'Kyrian', + 'Kree', + 'Jj', + 'Iaan', + 'Hudsyn', + 'Graceson', + 'Gatlyn', + 'Eydan', + 'Jak', + 'Townsend', + 'Owais', + 'Nandan', + 'Rayland', + 'Ridhaan', + 'Dantavious', + 'Lavoris', + 'Maricus', + 'Rodrigus', + 'Aayansh', + 'Chasten', + 'Durante', + 'Johnta', + 'Detavious', + 'Donterrius', + 'Rilyn', + 'Rilee', + 'Marquize', + 'Quinterius', + 'Jamarco', + 'Quinnton', + 'Deston', + 'Aceson', + 'Britten', + 'Adric', + 'Tabias', + 'Lajarvis', + 'Corderius', + 'Romon', + 'Que', + 'Nord', + 'Lerone', + 'Skylan', + 'Tobi', + 'Mccrae', + 'Mathayus', + 'Marcuz', + 'Levii', + 'Lander', + 'Oluwadarasimi', + 'Miklo', + 'Nijah', + 'Nero', + 'Quavis', + 'Zailyn', + 'Whitman', + 'Zavior', + 'Zlatan', + 'Crixus', + 'Cotton', + 'Chukwuebuka', + 'Draden', + 'Caston', + 'Aceyn', + 'Caeson', + 'Brax', + 'Azel', + 'Kaisyn', + 'Hunt', + 'Gaius', + 'Gabrian', + 'Falcon', + 'Iyan', + 'Jayjay', + 'Altonio', + 'Woodruff', + 'Tavare', + 'Kawaski', + 'Dontravious', + 'Gabreil', + 'Holten', + 'Dayvian', + 'Brennyn', + 'Chayson', + 'Dailon', + 'Keyshun', + 'Jaryn', + 'Jamyron', + 'Jakavion', + 'July', + 'Jonanthony', + 'Trenden', + 'Tobechukwu', + 'Yostin', + 'Casin', + 'Kaydyn', + 'Jshawn', + 'Keaghan', + 'Khalen', + 'Haylen', + 'Jamarques', + 'Alyjah', + 'Baylon', + 'Kemontae', + 'Taysean', + 'Slaton', + 'Saxton', + 'Yadir', + 'Tramon', + 'Traevion', + 'Raydon', + 'Raahim', + 'Olamide', + 'Oreoluwa', + 'Zyien', + 'Zayde', + 'Marqavious', + 'Marquavis', + 'Trevious', + 'Zyshonne', + 'Quindarrius', + 'Quintarious', + 'Quinterious', + 'Rodarius', + 'Deontavious', + 'Champion', + 'Decklan', + 'Daxx', + 'Pecos', + 'Jovonni', + 'Jaydrian', + 'Montravius', + 'Gunter', + 'Zerrick', + 'Quontavious', + 'Ayeden', + 'Audi', + 'Bentlie', + 'Brek', + 'Travonne', + 'Daquavious', + 'Jartavious', + 'Keldric', + 'Alezander', + 'Kamen', + 'Taytum', + 'Siler', + 'Yavuz', + 'Zaniel', + 'Yuriel', + 'Draiden', + 'Axzel', + 'Castin', + 'Keeland', + 'Jrake', + 'Jonhatan', + 'Jeziel', + 'Javery', + 'Severino', + 'Olavi', + 'Benoit', + 'Phillips', + 'Lothrop', + 'Konstanty', + 'Mato', + 'Carney', + 'Keithen', + 'Easley', + 'Chanler', + 'Erbie', + 'Ephriam', + 'Kentravion', + 'Kesan', + 'Ladamien', + 'Treshun', + 'Jakyron', + 'Burch', + 'Kaston', + 'Kyndall', + 'Jarden', + 'Shields', + 'Jontrell', + 'Thales', + 'Minnis', + 'Ida', + 'Hildred', + 'Helder', + 'Fernell', + 'Shone', + 'Laterrance', + 'Tuyen', + 'Roshun', + 'Vincient', + 'Ory', + 'Hilman', + 'Calton', + 'Clydell', + 'Vick', + 'Derrin', + 'Silton', + 'Tandy', + 'Emeal', + 'Rual', + 'Cardarius', + 'Jylan', + 'Hodge', + 'Charls', + 'Jacobey', + 'Jaqualon', + 'Jyrin', + 'Calib', + 'Fowler', + 'Kalep', + 'Osco', + 'Treylan', + 'Paschal', + 'Lowry', + 'Tydrick', + 'Ladavion', + 'Roe', + 'Jarmall', + 'Josuha', + 'Quindell', + 'Tra', + 'Jamaria', + 'Jermicheal', + 'Hobie', + 'Oluwaseun', + 'Trimayne', + 'Kaire', + 'Katrell', + 'Tradd', + 'Yohannes', + 'Oluwaseyi', + 'Tyski', + 'Lansana', + 'Tion', + 'Delontay', + 'Tavone', + 'Quante', + 'Taavon', + 'Daquane', + 'Burleigh', + 'Eyoel', + 'Cung', + 'Khodee', + 'Emilien', + 'Laurien', + 'Leonide', + 'Loomis', + 'Antrone', + 'Sewall', + 'Nicollas', + 'Vitor', + 'Jaythian', + 'Jasaun', + 'Tighe', + 'Colman', + 'Antionne', + 'Nygel', + 'Garnell', + 'Jamareon', + 'Alvey', + 'Carvel', + 'Carville', + 'Carlester', + 'Rutledge', + 'Mills', + 'Rayner', + 'Doil', + 'Gregario', + 'Aniseto', + 'Audon', + 'Brevyn', + 'Pio', + 'Tanis', + 'Jasinto', + 'Jaxtin', + 'Nugent', + 'Eldredge', + 'Egon', + 'Jong', + 'Pancho', + 'Lionardo', + 'Susano', + 'Trueman', + 'Braxtin', + 'Delphine', + 'Harroll', + 'Goree', + 'Manuela', + 'Epigmenio', + 'Laureano', + 'Josefina', + 'Tiodoro', + 'Silbestre', + 'Patrocinio', + 'Corando', + 'Maciah', + 'Quintyn', + 'Wrigley', + 'Onie', + 'Noal', + 'Duward', + 'Filomeno', + 'Cleburn', + 'Garvis', + 'Bisente', + 'Cedell', + 'Jap', + 'Rube', + 'Mavis', + 'Jarold', + 'Hijinio', + 'Dewie', + 'Trinida', + 'Jung', + 'Byrd', + 'Mcadoo', + 'Floy', + 'Eldie', + 'Volney', + 'Saragosa', + 'Derward', + 'Francico', + 'Genovevo', + 'Lindley', + 'Lasalle', + 'Borden', + 'Bonny', + 'Claudis', + 'Silberio', + 'Asuncion', + 'Rolly', + 'Doak', + 'Luvender', + 'Thurl', + 'Garl', + 'Arvine', + 'Johnnye', + 'Emiterio', + 'Crisoforo', + 'Eulojio', + 'Edell', + 'Infboy', + 'Ural', + 'Natalia', + 'Delia', + 'Acencion', + 'Joas', + 'Keagon', + 'Reice', + 'Esperanza', + 'Velton', + 'Eufemio', + 'Frumencio', + 'Dominga', + 'Eutiquio', + 'Dois', + 'Gean', + 'Odaniel', + 'Lyndel', + 'Kreigh', + 'Bobbye', + 'Rogue', + 'Deundra', + 'Cambron', + 'Kaitlynn', + 'Kayleigh', + 'Hailee', + 'Piper', + 'Sofia', + 'Carly', + 'Abigayle', + 'Angelina', + 'Tavish', + 'Christophere', + 'Anterrio', + 'Thimothy', + 'Montarius', + 'Marquarius', + 'Labradford', + 'Lawless', + 'Lenis', + 'Camile', + 'Tonya', + 'Hersey', + 'Abbie', + 'Loveless', + 'Aristide', + 'Ovey', + 'Ovide', + 'Robley', + 'Elward', + 'Leory', + 'Earlis', + 'Gaynell', + 'Printes', + 'Elzy', + 'Aswell', + 'Waver', + 'Wilma', + 'Minos', + 'Euclide', + 'Aster', + 'Demarrion', + 'Selbert', + 'Stoy', + 'Brack', + 'Strother', + 'Osa', + 'Ovel', + 'Custer', + 'Keveon', + 'Lenvil', + 'Hargus', + 'Kline', + 'Goldie', + 'Warfield', + 'Wavy', + 'Carless', + 'Proctor', + 'Holston', + 'Philopateer', + 'Loman', + 'Vernis', + 'Forster', + 'Jakie', + 'Martavis', + 'Louard', + 'Corbet', + 'Waldon', + 'Cluster', + 'Lafe', + 'Tayshun', + 'Browder', + 'Moss', + 'Rudell', + 'Loyde', + 'Glendel', + 'Elby', + 'Shafter', + 'Camila', + 'Elaine', + 'Scarlett', + 'Gertrude', + 'Bella', + 'Penelope', + 'Cathy', + 'Lizbeth', + 'Arianna', + 'Agnes', + 'Vicki', + 'Mila', + 'Ximena', + 'Delilah', + 'Stella', + 'Miranda', + 'Valentina', + 'Rosemary', + 'Khloe', + 'Heidi', + 'Desiree', + 'Violet', + 'Gianna', + 'Nayeli', + 'Luna', + 'Doreen', + 'Jennie', + 'Roberta', + 'Sheri', + 'Jeanne', + 'Alina', + 'Celeste', + 'Rosalie', + 'Naomi', + 'Teri', + 'Maryann', + 'Glenda', + 'Lynda', + 'Annabelle', + 'Antoinette', + 'Stephani', + 'Marcia', + 'Sherri', + 'Clara', + 'Julissa', + 'Becky', + 'Marianne', + 'Melody', + 'Sadie', + 'Sienna', + 'Marsha', + 'Belinda', + 'Jaylah', + 'Harriet', + 'Kristine', + 'Elizabet', + 'Paisley', + 'Genevieve', + 'Melinda', + 'Leilani', + 'Aubree', + 'Keira', + 'Kristy', + 'Sheryl', + 'Fernanda', + 'Tami', + 'Daleyza', + 'Rosemarie', + 'Francine', + 'Kristi', + 'Jaqueline', + 'Meagan', + 'Nichole', + 'Athena', + 'Anahi', + 'Marisa', + 'Yaretzi', + 'Lena', + 'Serena', + 'Miley', + 'Izabella', + 'Kate', + 'Joselyn', + 'Margie', + 'Krystle', + 'Dulce', + 'Pam', + 'Traci', + 'Mikayla', + 'Shari', + 'Delores', + 'Nellie', + 'Gisselle', + 'Blanche', + 'Clarissa', + 'Dianne', + 'Maxine', + 'Janis', + 'Carmela', + 'Mabel', + 'Estrella', + 'Emely', + 'Viola', + 'Penny', + 'Viviana', + 'Estelle', + 'Krista', + 'Adalynn', + 'Julianna', + 'Danna', + 'Marina', + 'Sheena', + 'Shawna', + 'Mya', + 'Leona', + 'Leila', + 'Isla', + 'Charlene', + 'Mindy', + 'Bernadette', + 'Audrina', + 'Tricia', + 'Adele', + 'Myrtle', + 'Nataly', + 'Kimberley', + 'Gwendolyn', + 'Emilia', + 'Janine', + 'Paulina', + 'Stefanie', + 'Marguerite', + 'Dayanara', + 'Katina', + 'Brielle', + 'Vera', + 'Jimena', + 'Aileen', + 'Bethany', + 'America', + 'Kellie', + 'Shanice', + 'Roxanne', + 'Darla', + 'Mamie', + 'Jocelyne', + 'Katherin', + 'Lyla', + 'Sonya', + 'Allyson', + 'Debora', + 'Chaya', + 'Jaslene', + 'Malia', + 'Daniella', + 'Alessandra', + 'Aimee', + 'Dina', + 'Arabella', + 'Juliet', + 'Laila', + 'Rhoda', + 'Angie', + 'Everly', + 'Adrianna', + 'Shelia', + 'Jana', + 'Analia', + 'Kamila', + 'Rebekah', + 'Myrna', + 'Concetta', + 'Amaya', + 'Juliette', + 'Litzy', + 'Marely', + 'Londyn', + 'Patti', + 'Adalyn', + 'Marla', + 'Tammie', + 'Cora', + 'Angelique', + 'Fiona', + 'Kari', + 'Jaylene', + 'Lucile', + 'Rubi', + 'Vivienne', + 'Hattie', + 'Noemi', + 'Celina', + 'Dena', + 'Sherlyn', + 'Selina', + 'Bonita', + 'Paulette', + 'Aisha', + 'Susie', + 'Adeline', + 'Elsa', + 'Shania', + 'Yasmin', + 'Dalia', + 'Jacquelyn', + 'Thalia', + 'Trina', + 'Allisson', + 'Chana', + 'Olive', + 'Helene', + 'Nelda', + 'Mireya', + 'Chelsey', + 'Cheri', + 'Kira', + 'Karissa', + 'Lynette', + 'Deneen', + 'Ivette', + 'Roslyn', + 'Kinley', + 'Rosalinda', + 'Lila', + 'Kaylie', + 'Dayana', + 'Melany', + 'Carissa', + 'Aniyah', + 'Kyla', + 'Yulissa', + 'Trisha', + 'Camilla', + 'Ansley', + 'Sarai', + 'Lola', + 'Arline', + 'Lara', + 'Stacie', + 'Annika', + 'Christi', + 'Brisa', + 'Gia', + 'Therese', + 'Abril', + 'Angeline', + 'Isabela', + 'Marcella', + 'Shanna', + 'Stephany', + 'Henrietta', + 'Tasha', + 'Brianne', + 'Rosanne', + 'Luann', + 'Frieda', + 'Renata', + 'Dianna', + 'Celia', + 'Sondra', + 'Aylin', + 'Melba', + 'Catina', + 'Alayna', + 'Mollie', + 'Nathalie', + 'Tabitha', + 'Tracie', + 'Scarlet', + 'Jayne', + 'Rachelle', + 'Jeannette', + 'Addyson', + 'Cecelia', + 'Annabella', + 'Dahlia', + 'Dorothea', + 'Annmarie', + 'Marlys', + 'Deirdre', + 'Evangeline', + 'Melina', + 'Erma', + 'Jeanine', + 'Roxana', + 'Yaritza', + 'Montserrat', + 'Lizzie', + 'Kerri', + 'Yoselin', + 'Migdalia', + 'Rivka', + 'Cathleen', + 'Lorene', + 'Yareli', + 'Bette', + 'Kyra', + 'Janette', + 'Beulah', + 'Danica', + 'Arely', + 'Lexi', + 'Shana', + 'Sherrie', + 'Alexus', + 'Mable', + 'Citlalli', + 'Nadine', + 'Shauna', + 'Ryleigh', + 'Jeri', + 'Phoebe', + 'Jazlyn', + 'Noreen', + 'Keisha', + 'Lora', + 'Brynlee', + 'Alivia', + 'Lottie', + 'Monserrat', + 'Giuliana', + 'Adelyn', + 'Deana', + 'Jacqueli', + 'Makenna', + 'Jeannie', + 'Noelle', + 'Imogene', + 'Daphne', + 'Reyna', + 'Katelynn', + 'Bettie', + 'Carmella', + 'Estefania', + 'Cassandr', + 'Betsy', + 'Brianda', + 'Iliana', + 'Bryanna', + 'Aranza', + 'Rihanna', + 'Anissa', + 'Alisa', + 'Azul', + 'Milagros', + 'Gemma', + 'Freda', + 'Ada', + 'Bettye', + 'Nia', + 'Oralia', + 'Alaina', + 'Anabelle', + 'Destinee', + 'Sallie', + 'Sonja', + 'Willow', + 'Staci', + 'Lia', + 'Breana', + 'Eliza', + 'Mikaela', + 'Mona', + 'Cataleya', + 'Jeannine', + 'Lilah', + 'Anabel', + 'Ashlynn', + 'Aleena', + 'Estella', + 'Ayla', + 'Adelaide', + 'Lilliana', + 'Kristie', + 'Nettie', + 'Cherie', + 'May', + 'Myra', + 'Nicolette', + 'Lissette', + 'Siena', + 'Ivanna', + 'Christa', + 'Caylee', + 'Roseann', + 'Anastasia', + 'Karin', + 'Corinne', + 'Ginger', + 'Flora', + 'Bria', + 'Gretchen', + 'Maryellen', + 'Lana', + 'Harmony', + 'Elvira', + 'Ilene', + 'Iesha', + 'Celine', + 'Faye', + 'Khadijah', + 'Elyse', + 'Joana', + 'Sharyn', + 'Leia', + 'Catherin', + 'Corina', + 'Sheree', + 'Salma', + 'Deja', + 'Liz', + 'Aracely', + 'Roselyn', + 'Samara', + 'Lorrie', + 'Frida', + 'Tessie', + 'Talia', + 'Rosalind', + 'Jailene', + 'Lisette', + 'Raelynn', + 'Yetta', + 'Catharine', + 'Adelynn', + 'Odalys', + 'Jolene', + 'Charity', + 'Aniya', + 'Sanjuanita', + 'Norah', + 'Terrie', + 'Yuliana', + 'Lorie', + 'Yazmin', + 'Eleanore', + 'Anika', + 'Elida', + 'Valery', + 'Matilda', + 'Nannie', + 'Eloise', + 'Gillian', + 'Tatyana', + 'Kimora', + 'Brynn', + 'Maliyah', + 'Madilyn', + 'Jenifer', + 'Maddison', + 'Colette', + 'Nanette', + 'Ayleen', + 'Winnie', + 'Jayda', + 'Deloris', + 'Tillie', + 'Kizzy', + 'Galilea', + 'Janessa', + 'Brenna', + 'Amelie', + 'Marybeth', + 'Lorna', + 'Kaia', + 'Sarahi', + 'Viridiana', + 'Rebeca', + 'Ericka', + 'Mareli', + 'Anaya', + 'Nathaly', + 'Candy', + 'Larissa', + 'Elle', + 'Yasmine', + 'Claudine', + 'Kyleigh', + 'Paloma', + 'Lenore', + 'Citlali', + 'Rosanna', + 'Misti', + 'Kasandra', + 'Zara', + 'Isis', + 'Alisson', + 'Cheyanne', + 'Reba', + 'Ariella', + 'Lavonne', + 'Miah', + 'Roxanna', + 'Anabella', + 'Suzette', + 'Kiera', + 'Gitty', + 'Farrah', + 'Helena', + 'Shaniqua', + 'Maryanne', + 'Liana', + 'Arleen', + 'Belle', + 'Katy', + 'Anya', + 'Selene', + 'Maura', + 'Chantel', + 'Keyla', + 'Maryjane', + 'Tisha', + 'Kisha', + 'Kaelyn', + 'Malka', + 'Maci', + 'Evelin', + 'Julianne', + 'Magdalena', + 'Kimberlee', + 'Ernestine', + 'Alyson', + 'Kaley', + 'Danika', + 'Kecia', + 'Leanne', + 'Tonia', + 'Nyla', + 'Ivonne', + 'Madelynn', + 'Ofelia', + 'Lakisha', + 'Adilene', + 'Wendi', + 'Susanne', + 'Katharine', + 'Faigy', + 'Raizy', + 'Tawny', + 'Jackeline', + 'Ariadne', + 'Giovanna', + 'Janiyah', + 'Alani', + 'Nayely', + 'Lilian', + 'Saundra', + 'Jazlynn', + 'Jaelynn', + 'Elliana', + 'Gayla', + 'Deena', + 'Earnestine', + 'Margo', + 'Herlinda', + 'Elinor', + 'Salina', + 'Casandra', + 'Nathalia', + 'Kaila', + 'Deanne', + 'Desirae', + 'Liza', + 'Bobbi', + 'Briella', + 'Gilda', + 'Averie', + 'Charlize', + 'Azalea', + 'Sanjuana', + 'Yajaira', + 'Brandie', + 'Aleah', + 'Della', + 'Elaina', + 'Yahaira', + 'Aja', + 'Bernadine', + 'Lela', + 'Annabel', + 'Xiomara', + 'Kassidy', + 'Nohely', + 'Aubrie', + 'Angelia', + 'Macie', + 'Shelbi', + 'Chelsie', + 'Lilyana', + 'Jazlene', + 'Amina', + 'Dorthy', + 'Noelia', + 'Addisyn', + 'Dalilah', + 'Clarisa', + 'Chrystal', + 'Oleta', + 'Georgina', + 'Adelina', + 'Edythe', + 'Lucinda', + 'Jannie', + 'Minerva', + 'Kelsie', + 'Madisyn', + 'Aida', + 'Katlyn', + 'Julieta', + 'Violeta', + 'Heidy', + 'Lea', + 'Leola', + 'Chasity', + 'Nell', + 'Felicity', + 'Kathi', + 'Karyn', + 'Hana', + 'Micaela', + 'Chandra', + 'Liberty', + 'Cielo', + 'Tameka', + 'Maude', + 'Malky', + 'Coraima', + 'Haylie', + 'Vanesa', + 'Sloane', + 'Karyme', + 'Evelynn', + 'Batsheva', + 'Nallely', + 'Tamra', + 'Maricruz', + 'Paislee', + 'Kynlee', + 'Marcela', + 'Marci', + 'Vonda', + 'Cinthia', + 'Amiyah', + 'Breanne', + 'Lisbeth', + 'Leanna', + 'Anais', + 'Flor', + 'Annemarie', + 'Amie', + 'Estela', + 'Tammi', + 'Rhiannon', + 'Denisse', + 'Leyla', + 'Iridian', + 'Dariana', + 'Romina', + 'Yamileth', + 'Lidia', + 'Sybil', + 'Elvia', + 'Debby', + 'Philomena', + 'Jacklyn', + 'Charlee', + 'Kathie', + 'Aryanna', + 'Katarina', + 'Elianna', + 'Zariah', + 'Andreina', + 'Filomena', + 'Xochitl', + 'Mariam', + 'Myla', + 'Janiya', + 'Kristal', + 'Estefany', + 'Debi', + 'Miracle', + 'Shaindy', + 'Evangelina', + 'Naya', + 'Maeve', + 'Judi', + 'Effie', + 'Lilia', + 'Dayami', + 'Kierra', + 'Vincenza', + 'Cari', + 'Lauri', + 'Bethzy', + 'Trudy', + 'Deidre', + 'Melisa', + 'Luciana', + 'Chantal', + 'Laisha', + 'Kennedi', + 'Ayanna', + 'Madalyn', + 'Dania', + 'Jaliyah', + 'Madilynn', + 'Citlaly', + 'Lolita', + 'Drema', + 'Iva', + 'Kailee', + 'Grecia', + 'Kailyn', + 'Ladonna', + 'Latanya', + 'Maia', + 'Jaquelin', + 'Alanna', + 'Etta', + 'Marlee', + 'Reina', + 'Aiyana', + 'Carolann', + 'Gizelle', + 'Greta', + 'Lynnette', + 'Cecile', + 'Shayna', + 'Savanah', + 'Annalise', + 'Nylah', + 'Lesa', + 'Jolie', + 'Arleth', + 'Laraine', + 'Selah', + 'Alysha', + 'Bridgette', + 'Madyson', + 'Marylou', + 'Adela', + 'Shaina', + 'Trista', + 'Katia', + 'Kayleen', + 'Lilianna', + 'Tamera', + 'Millicent', + 'Eugenia', + 'Myrtice', + 'Baila', + 'Charmaine', + 'Maegan', + 'Ruthie', + 'Jovanna', + 'Julisa', + 'Mayte', + 'Latrice', + 'Priscila', + 'Glenna', + 'Yitty', + 'Tawana', + 'Yessica', + 'Ina', + 'Brittni', + 'Johana', + 'Tess', + 'Caryn', + 'Natalee', + 'Barb', + 'Journee', + 'Malaysia', + 'Yulisa', + 'Alta', + 'Shaila', + 'Maurine', + 'Amira', + 'Tiffani', + 'Danette', + 'Fanny', + 'Justina', + 'Leann', + 'Dafne', + 'Ima', + 'Azucena', + 'Braylee', + 'Amaris', + 'Bailee', + 'Giana', + 'Josette', + 'Raegan', + 'Gena', + 'Luella', + 'Nita', + 'Laney', + 'Gisela', + 'Alexandrea', + 'Rosalia', + 'Odessa', + 'Laci', + 'Yamilex', + 'Tamia', + 'Astrid', + 'Luanne', + 'Gwen', + 'Tabatha', + 'Rivky', + 'Laureen', + 'Zina', + 'Amara', + 'Itzayana', + 'Adamaris', + 'Laylah', + 'Luisa', + 'Georgette', + 'Joselin', + 'Yamilet', + 'Nilda', + 'Luisana', + 'Coleen', + 'Cecily', + 'Jocelynn', + 'Mirella', + 'Jessika', + 'Moriah', + 'Halle', + 'Caren', + 'Earline', + 'Shantel', + 'Aliana', + 'Keila', + 'Maryam', + 'Marianna', + 'Magaly', + 'Sariah', + 'Marnie', + 'Kiersten', + 'Janeth', + 'Lyndsey', + 'Shelli', + 'Jaylee', + 'Ashlie', + 'Tianna', + 'Bree', + 'Isela', + 'Krystina', + 'Yaretzy', + 'Evelina', + 'Sarina', + 'Tyra', + 'Eloisa', + 'Maite', + 'Leilah', + 'Marcie', + 'Imelda', + 'Alena', + 'Juniper', + 'Shelbie', + 'Shakira', + 'Ember', + 'Emmalyn', + 'Elissa', + 'Skyla', + 'Lylah', + 'Xitlali', + 'Gisele', + 'Polly', + 'Ernestina', + 'Sandi', + 'Emmy', + 'Josefa', + 'Magali', + 'Ashely', + 'Eve', + 'Jayde', + 'Rosella', + 'Yuridia', + 'Sheyla', + 'Raelyn', + 'Domenica', + 'Valarie', + 'Herminia', + 'Katalina', + 'Shaquana', + 'Nelly', + 'Rosalyn', + 'Denice', + 'Saanvi', + 'Cambria', + 'Joseline', + 'Tomasa', + 'Milana', + 'Harriett', + 'Devorah', + 'Jackelyn', + 'Jacquelin', + 'Yadhira', + 'Antonella', + 'Shreya', + 'Janay', + 'Betzy', + 'Kaiya', + 'Terra', + 'Roseanne', + 'Karime', + 'Lina', + 'Macey', + 'Vilma', + 'Shaniya', + 'Deyanira', + 'Cindi', + 'Mandi', + 'Sanaa', + 'Lakesha', + 'Essence', + 'Faviola', + 'Brinley', + 'Kirstie', + 'Brissa', + 'Alia', + 'Janney', + 'Kaylynn', + 'Kamilah', + 'Kianna', + 'Adrianne', + 'Yasmeen', + 'Jerri', + 'Anayeli', + 'Ambar', + 'Lorri', + 'Hailie', + 'Demetria', + 'Awilda', + 'Isabell', + 'Leonor', + 'Florine', + 'Tennille', + 'Deann', + 'Nyah', + 'Jolette', + 'Xitlaly', + 'Vienna', + 'Lenora', + 'Keily', + 'Syble', + 'Ciera', + 'Milania', + 'Lainey', + 'Nyasia', + 'Carley', + 'Kelsi', + 'Blossom', + 'Maranda', + 'Ally', + 'Serina', + 'Charli', + 'Taraji', + 'Jena', + 'Natalya', + 'Hortencia', + 'Ila', + 'Kailani', + 'Mira', + 'Evie', + 'Ione', + 'Briseyda', + 'Aryana', + 'Yarely', + 'Susanna', + 'Amya', + 'Kaleigh', + 'Qiana', + 'Juli', + 'Mckayla', + 'Suzan', + 'Fallon', + 'Jacalyn', + 'Ileana', + 'Yesica', + 'Willa', + 'Fatoumata', + 'Arly', + 'Jakayla', + 'Chyna', + 'Jaida', + 'Sunshine', + 'Beyonce', + 'Lawanda', + 'Flossie', + 'Lupita', + 'Demi', + 'Keely', + 'Aliya', + 'Jeanie', + 'Tamiko', + 'Gigi', + 'Brissia', + 'Mariel', + 'Lluvia', + 'Jasleen', + 'Lizet', + 'Brittanie', + 'Kaci', + 'Alycia', + 'Madalynn', + 'Milena', + 'Coraline', + 'Kaela', + 'Soraya', + 'Mozelle', + 'Jessenia', + 'Wilhelmina', + 'Jazmyn', + 'Stefani', + 'Natali', + 'Christiana', + 'Ivana', + 'Eiza', + 'Zaria', + 'Zaira', + 'Lorelei', + 'Cherry', + 'Aline', + 'Briseida', + 'Siani', + 'Yara', + 'Rhianna', + 'Kalia', + 'Destiney', + 'Hindy', + 'Arlette', + 'Shyanne', + 'Joceline', + 'Janell', + 'Vianey', + 'Elnora', + 'Zoie', + 'Elba', + 'Jamila', + 'Rena', + 'Mari', + 'Chava', + 'Scarlette', + 'Shyla', + 'Corine', + 'Kaliyah', + 'Ailyn', + 'Liv', + 'Freya', + 'Diya', + 'Myrtis', + 'Aliah', + 'Margery', + 'Gracelyn', + 'Shira', + 'Riya', + 'Breann', + 'Siobhan', + 'Rochel', + 'Tiffanie', + 'Mirna', + 'Nilsa', + 'Tenley', + 'Aliza', + 'Celena', + 'Vianney', + 'Janel', + 'Toccara', + 'Dayna', + 'Rona', + 'Alba', + 'Althea', + 'Josselyn', + 'Karlie', + 'Alyce', + 'Erlinda', + 'Kadijah', + 'Rosalba', + 'Tangela', + 'Marlena', + 'Delois', + 'Chastity', + 'Coral', + 'Braelynn', + 'Dalila', + 'Rosetta', + 'Lu', + 'Venessa', + 'Kayley', + 'Barbra', + 'Jesica', + 'Dona', + 'Mitzi', + 'Catrina', + 'Gracelynn', + 'Ophelia', + 'Ayana', + 'Mara', + 'Calista', + 'Adyson', + 'Marilynn', + 'Tomeka', + 'Britni', + 'Whitley', + 'Karly', + 'Verenice', + 'Raylee', + 'Dayanna', + 'Shonda', + 'Felecia', + 'Betzaida', + 'Kaylani', + 'Shaylee', + 'Jazzlyn', + 'Giavanna', + 'Vivianna', + 'Jesusa', + 'Lashonda', + 'Maile', + 'Suzy', + 'Vania', + 'Giada', + 'Maisie', + 'Venus', + 'Emerald', + 'Wilda', + 'Saniya', + 'Naydelin', + 'Enid', + 'Leilany', + 'Jesenia', + 'Maliah', + 'Dortha', + 'Dalary', + 'Chany', + 'Amia', + 'Amalia', + 'Khaleesi', + 'Taina', + 'Abbey', + 'Dollie', + 'Joslyn', + 'Sommer', + 'Lilibeth', + 'Charleigh', + 'Sydell', + 'Shoshana', + 'Nechama', + 'Jamya', + 'Jeanmarie', + 'Albertha', + 'Akeelah', + 'Aanya', + 'Destini', + 'Kacie', + 'Maleah', + 'Cayla', + 'Bryana', + 'Zelma', + 'Anjanette', + 'Kaylah', + 'Tonja', + 'Amairani', + 'Karli', + 'Elina', + 'Aurelia', + 'Judie', + 'Letha', + 'Brittnee', + 'Yanira', + 'Ariza', + 'Kataleya', + 'Berta', + 'Soleil', + 'Marleen', + 'Desteny', + 'Gissel', + 'Suri', + 'Anjelica', + 'Lilith', + 'Breeanna', + 'Krysta', + 'Alysia', + 'Chrissy', + 'Lailah', + 'Cathryn', + 'Dawna', + 'Myah', + 'Lelia', + 'Aviana', + 'Xena', + 'Pansy', + 'Jazleen', + 'Kaylyn', + 'Mariann', + 'Celene', + 'Berniece', + 'Anjali', + 'Benita', + 'Reanna', + 'Sydnee', + 'Taliyah', + 'Raylene', + 'Kristyn', + 'Latonia', + 'Pa', + 'Nola', + 'Lyanne', + 'Danae', + 'Sharla', + 'Chanelle', + 'Aleyda', + 'Deb', + 'Sofie', + 'Shameka', + 'Emelia', + 'Miya', + 'Latricia', + 'Claribel', + 'Lacie', + 'Taisha', + 'Queen', + 'Breeana', + 'Ilana', + 'Erna', + 'Neha', + 'Melodie', + 'Ariah', + 'Ursula', + 'Janna', + 'Cienna', + 'Maryjo', + 'Vannessa', + 'Saniyah', + 'Mariajose', + 'Malaya', + 'Abbigail', + 'Elin', + 'Emi', + 'Shanaya', + 'Zahra', + 'Lorine', + 'Karrie', + 'Johnna', + 'Marni', + 'Karis', + 'Shelba', + 'Omayra', + 'Claudette', + 'Anitra', + 'Jenelle', + 'Zelda', + 'Alyse', + 'Alethea', + 'Jannet', + 'Myranda', + 'Corinna', + 'Pattie', + 'Jemma', + 'Avah', + 'Joycelyn', + 'Loriann', + 'Kirstin', + 'Davina', + 'Clementine', + 'Arantza', + 'Esme', + 'Vida', + 'Samira', + 'Alysa', + 'Ananya', + 'Cherish', + 'Jocelin', + 'Renae', + 'Jalisa', + 'Elease', + 'Salena', + 'Zhane', + 'Zulema', + 'Rubye', + 'Amerie', + 'Leatrice', + 'Geralyn', + 'Brigitte', + 'Sibyl', + 'Corrina', + 'Phylicia', + 'Karlee', + 'Kerrie', + 'Addilyn', + 'Alayah', + 'Jacquely', + 'Mirian', + 'Jovana', + 'Katelin', + 'Marielena', + 'Libby', + 'Aditi', + 'Nalani', + 'Lilyanna', + 'Mylee', + 'Goldy', + 'Melia', + 'Audriana', + 'Lillyana', + 'Enriqueta', + 'Tasia', + 'Debbi', + 'Ani', + 'Elyssa', + 'Yamile', + 'Bridgett', + 'Taniya', + 'Britany', + 'Latosha', + 'Shanda', + 'Estephanie', + 'Maudie', + 'Mariyah', + 'Tana', + 'Neva', + 'Kalea', + 'Oma', + 'Jazelle', + 'Neveah', + 'Leonora', + 'Miesha', + 'Corrine', + 'Jordynn', + 'Cornelia', + 'Ronni', + 'Malinda', + 'Janeen', + 'Neriah', + 'Brigette', + 'Windy', + 'Cassondra', + 'Klarissa', + 'Lizzette', + 'Tanika', + 'Izamar', + 'Tera', + 'Arianny', + 'Florene', + 'Evalyn', + 'Poppy', + 'Deisy', + 'Jannette', + 'Thania', + 'Kelsea', + 'Taniyah', + 'Geri', + 'Allyssa', + 'Zariyah', + 'Averi', + 'Leeann', + 'Kallie', + 'Loni', + 'Bryleigh', + 'Rosina', + 'Carlee', + 'Preslee', + 'Alexsandra', + 'Adamari', + 'Saray', + 'Yaneli', + 'Raina', + 'Lianna', + 'Keilani', + 'Tamela', + 'Ninfa', + 'Ireland', + 'Shante', + 'Racheal', + 'Zainab', + 'Blima', + 'Yocheved', + 'Gema', + 'Sayra', + 'Aretha', + 'Nya', + 'Criselda', + 'Anai', + 'Bracha', + 'Amirah', + 'Sury', + 'Twila', + 'Arissa', + 'Livia', + 'Jacquline', + 'Chiara', + 'Anneliese', + 'Quiana', + 'Monika', + 'Charisse', + 'Emerie', + 'Rosalva', + 'Halie', + 'Jenesis', + 'Zaylee', + 'Pricilla', + 'Ouida', + 'Felipa', + 'Latifah', + 'Kalley', + 'Clarice', + 'Nona', + 'Jaunita', + 'Hermelinda', + 'Analy', + 'Jizelle', + 'Theda', + 'Yoselyn', + 'Dottie', + 'Brittaney', + 'Meghann', + 'Azeneth', + 'Richelle', + 'Peggie', + 'Brittny', + 'Jaci', + 'Marietta', + 'Gissell', + 'Evolet', + 'Abbygail', + 'Naima', + 'Noelani', + 'Jaslyn', + 'Katheryn', + 'Ruthann', + 'Shelva', + 'Ashli', + 'Alianna', + 'Felicitas', + 'Delfina', + 'Rayna', + 'Christal', + 'Leta', + 'Tawnya', + 'Zaniyah', + 'Cathie', + 'Antonette', + 'Bethann', + 'Nannette', + 'Vita', + 'Santa', + 'Dejah', + 'Patience', + 'Alessia', + 'Ahuva', + 'Karely', + 'Anette', + 'Alfreda', + 'Cyndi', + 'Cami', + 'Shirlee', + 'Roxann', + 'Alvina', + 'Sima', + 'Star', + 'Tatianna', + 'Krissy', + 'Dreama', + 'Diann', + 'Birdie', + 'Yoshiko', + 'Violette', + 'Mylah', + 'Rosita', + 'Eartha', + 'Miabella', + 'Shanika', + 'Gricel', + 'Ariyah', + 'Emmalee', + 'Nidia', + 'Gladis', + 'Roxie', + 'Zoraida', + 'Kandace', + 'Annamarie', + 'Alannah', + 'Abrielle', + 'Mercy', + 'Lesli', + 'Sydni', + 'Kathrine', + 'Jiselle', + 'Anisa', + 'Felisha', + 'Kayli', + 'Nanci', + 'Ria', + 'Cailyn', + 'Melani', + 'Alyna', + 'Bambi', + 'Avril', + 'Amberly', + 'Towanda', + 'Malissa', + 'Kaleena', + 'Kinsey', + 'Andria', + 'Emogene', + 'Milani', + 'Milah', + 'Hadassah', + 'Avianna', + 'Aubri', + 'Pessy', + 'Dori', + 'Tea', + 'Keshia', + 'Adina', + 'Esha', + 'Magnolia', + 'Moesha', + 'Elana', + 'Vikki', + 'Lakendra', + 'Ilse', + 'Sydnie', + 'Laquita', + 'Hortense', + 'Elouise', + 'Tarah', + 'Shamika', + 'Genoveva', + 'Margot', + 'Aubrielle', + 'Aya', + 'Aleta', + 'Shantell', + 'Angelle', + 'Lakeshia', + 'Leota', + 'Stormie', + 'Caryl', + 'Cristy', + 'Sydelle', + 'Analisa', + 'Earlene', + 'Syreeta', + 'Paityn', + 'Citlally', + 'Nikole', + 'Leandra', + 'Elda', + 'Lizbet', + 'Blimy', + 'Lorelai', + 'Gittel', + 'Jasmyn', + 'Verania', + 'Zoya', + 'Anyssa', + 'Jeniffer', + 'Dorene', + 'Makaila', + 'Earlean', + 'Ysabella', + 'Brandee', + 'Nailea', + 'Stefany', + 'Amiya', + 'Carolee', + 'Kassie', + 'Theodora', + 'Merissa', + 'Skylah', + 'Alesia', + 'Leela', + 'Madge', + 'Shanta', + 'Soledad', + 'Sharonda', + 'Thea', + 'Capri', + 'Amparo', + 'Concha', + 'Karolina', + 'Keitha', + 'Harriette', + 'Evette', + 'Mylie', + 'Isha', + 'Suzie', + 'Carlene', + 'Brunilda', + 'Annamae', + 'Ariadna', + 'Sanai', + 'Gisell', + 'Danelle', + 'Dovie', + 'Lani', + 'Shavonne', + 'Janiah', + 'Kora', + 'Jessa', + 'Melva', + 'Yehudis', + 'Analee', + 'Enedina', + 'Oaklee', + 'Aubrianna', + 'Velia', + 'Zooey', + 'Dolly', + 'Shanae', + 'Lyndsay', + 'Allene', + 'Kamya', + 'Tedra', + 'Yecenia', + 'Nyree', + 'Shyann', + 'Kandice', + 'Edwina', + 'Aiyanna', + 'Carli', + 'Sariyah', + 'Gwyneth', + 'Roseanna', + 'Charla', + 'Nereyda', + 'Yides', + 'Helaine', + 'Evita', + 'Alanis', + 'Starr', + 'Rosalee', + 'Yaire', + 'Risa', + 'Kristel', + 'Greidys', + 'Lillianna', + 'Khushi', + 'Triniti', + 'Lilyan', + 'Myesha', + 'Kala', + 'Moira', + 'Neida', + 'Gisel', + 'Myriam', + 'Anali', + 'Izabel', + 'Savana', + 'Sanjana', + 'Willodean', + 'Briza', + 'Lyra', + 'Merry', + 'Cheryle', + 'Porsha', + 'Kaili', + 'Buffy', + 'Deidra', + 'Everleigh', + 'Gardenia', + 'Italia', + 'Novella', + 'Sahara', + 'Sirena', + 'Elide', + 'Madisen', + 'Katerina', + 'Ashlea', + 'Rianna', + 'Samatha', + 'Diandra', + 'Shanell', + 'Annalee', + 'Samiyah', + 'Joselyne', + 'Maylin', + 'Jazmyne', + 'Terese', + 'Nydia', + 'Stasia', + 'Saira', + 'Carlota', + 'Kathia', + 'Katya', + 'Elodie', + 'Priya', + 'Malena', + 'Aadhya', + 'Meera', + 'Tayla', + 'Jovita', + 'Rafaela', + 'Faiga', + 'Jaquelyn', + 'Elisheva', + 'Debbra', + 'Melyssa', + 'Chelsi', + 'Gricelda', + 'Tawanda', + 'Sharlene', + 'Mellissa', + 'Alene', + 'Amayah', + 'Nicolle', + 'Yanet', + 'Zissy', + 'Candi', + 'Hedwig', + 'Leyna', + 'Nichol', + 'Reva', + 'Fraidy', + 'Esty', + 'Kaily', + 'Mimi', + 'Shani', + 'Hadlee', + 'Naomy', + 'Kinslee', + 'Emmalynn', + 'Alverta', + 'Anushka', + 'Tinsley', + 'Armida', + 'Cleta', + 'Analise', + 'Ahtziri', + 'Anakaren', + 'Tracee', + 'Glynda', + 'Kaelynn', + 'Carie', + 'Avalon', + 'Eboni', + 'Shameeka', + 'Letitia', + 'Enola', + 'Rasheeda', + 'Taylee', + 'Jerrica', + 'Janely', + 'Taya', + 'Xochilt', + 'Rosana', + 'Doretha', + 'Henny', + 'Shaniece', + 'Charleen', + 'Abigale', + 'Marylyn', + 'Retha', + 'Keren', + 'Elly', + 'Ailani', + 'Aarna', + 'Starla', + 'Maren', + 'Nan', + 'Marivel', + 'Georgianna', + 'Era', + 'Kirra', + 'Maisha', + 'Caydence', + 'Dinah', + 'Noemy', + 'Tamatha', + 'Madonna', + 'Kristan', + 'Keana', + 'Kloe', + 'Maribeth', + 'Sana', + 'Korina', + 'Irania', + 'Izabelle', + 'Roxy', + 'Mariaguadalupe', + 'Sulema', + 'Vivien', + 'Tatia', + 'Holli', + 'Debrah', + 'Kattie', + 'Kaidence', + 'Cathey', + 'Anniston', + 'Refugia', + 'Renita', + 'Aubriella', + 'Kaleah', + 'Zuleyka', + 'Sherie', + 'Tomika', + 'Charisma', + 'Caridad', + 'Kailynn', + 'Gertie', + 'Jaslynn', + 'Agatha', + 'Avani', + 'Hennessy', + 'Pamala', + 'Malak', + 'Raizel', + 'Kami', + 'Rosalina', + 'Ferne', + 'Cloe', + 'Jeryl', + 'Louann', + 'Jacie', + 'Tais', + 'Johnsie', + 'Brittnie', + 'Collette', + 'Lettie', + 'Jeanna', + 'Kyara', + 'Renada', + 'Abrianna', + 'Nayelli', + 'Alda', + 'Yuna', + 'Cristi', + 'Yazmine', + 'Marlie', + 'Milly', + 'Anastacia', + 'Daria', + 'Caitlynn', + 'Shriya', + 'Vianca', + 'Sayuri', + 'Dennise', + 'Aleyna', + 'Jenni', + 'Tanesha', + 'Suzanna', + 'Zaniya', + 'Kesha', + 'Edie', + 'Ansleigh', + 'Emmie', + 'Marjory', + 'Lanette', + 'Babette', + 'Alaya', + 'Palma', + 'Tamie', + 'Nelle', + 'Haydee', + 'Zeinab', + 'Stephania', + 'Biridiana', + 'Yoshie', + 'Mayme', + 'Michaele', + 'Marimar', + 'Winona', + 'Christene', + 'Meadow', + 'Ariya', + 'Daleysa', + 'Thuy', + 'Nautica', + 'Hadleigh', + 'Aliyana', + 'Annabell', + 'Stacia', + 'Leonore', + 'Albina', + 'Daira', + 'Rhona', + 'Lisbet', + 'Alizae', + 'Aminata', + 'Samanta', + 'Jerilyn', + 'Darci', + 'Sudie', + 'Kynleigh', + 'Marva', + 'Karie', + 'Marbella', + 'Franchesca', + 'Kylah', + 'Lillyanna', + 'Melony', + 'Abygail', + 'Yulianna', + 'Sahana', + 'Velvet', + 'Michelina', + 'Treva', + 'Iona', + 'Adilynn', + 'Milla', + 'Teressa', + 'Coretta', + 'Venita', + 'Evalynn', + 'Chynna', + 'Janett', + 'Nohemi', + 'Symone', + 'Kaycee', + 'Racquel', + 'Jerica', + 'Chanda', + 'Vannesa', + 'Deasia', + 'Alanah', + 'Dasha', + 'Dian', + 'Iyana', + 'Katlin', + 'Shizue', + 'Mitsuko', + 'Shara', + 'Shanelle', + 'Sinead', + 'Jacinda', + 'Alecia', + 'Tanvi', + 'Genese', + 'Crissy', + 'Niki', + 'Shanequa', + 'Trish', + 'Shalonda', + 'Darleen', + 'Magda', + 'Annalisa', + 'Lashanda', + 'Carin', + 'Nahomi', + 'Londynn', + 'Alaysia', + 'Annaliese', + 'Valorie', + 'Naidelyn', + 'Abbe', + 'Karley', + 'Cinda', + 'Marilu', + 'Azaria', + 'Kitty', + 'Mechelle', + 'Jazzmin', + 'Malina', + 'Cianna', + 'Leesa', + 'Nahla', + 'Dorotha', + 'Jaeda', + 'Tinley', + 'Kelis', + 'Ayesha', + 'Cinthya', + 'Shawnte', + 'Fawn', + 'Calleigh', + 'Mittie', + 'Aide', + 'Lisset', + 'Tyesha', + 'Devora', + 'Analeigh', + 'Anahy', + 'Donnamarie', + 'Nala', + 'Haruko', + 'Lesia', + 'Aideliz', + 'Emme', + 'Mitsue', + 'Jamiya', + 'Joleen', + 'Missy', + 'Shawanda', + 'Chastelyn', + 'Jaleah', + 'Eulalia', + 'Elvera', + 'Kalina', + 'Adrina', + 'Nicolasa', + 'Belia', + 'Elodia', + 'Kazuko', + 'Ixchel', + 'Leena', + 'Yoseline', + 'Yocelin', + 'Jamiyah', + 'Mariama', + 'Audrianna', + 'Dasia', + 'Ieshia', + 'Malorie', + 'Toniann', + 'Genessis', + 'Makeda', + 'Cherise', + 'Tarsha', + 'Karri', + 'Romayne', + 'Beronica', + 'Nubia', + 'Shasta', + 'Cristin', + 'Cristine', + 'Eryn', + 'Jazzmine', + 'Alyssia', + 'Verona', + 'Divya', + 'Beatrix', + 'Chiyoko', + 'Destinie', + 'Hali', + 'Myisha', + 'Sabina', + 'Chante', + 'Brea', + 'Aundrea', + 'Harmoni', + 'Iyanna', + 'Rosaria', + 'Hettie', + 'Bronte', + 'Constanza', + 'Heavenly', + 'Georgiana', + 'Coco', + 'Eleni', + 'Brylie', + 'Ajee', + 'Jerrie', + 'Zella', + 'Xenia', + 'Djuana', + 'Bianka', + 'Lizett', + 'Destany', + 'Bettina', + 'Pennie', + 'Ciji', + 'Ciani', + 'Tosha', + 'Roxane', + 'Tenisha', + 'Pepper', + 'Ayva', + 'Dynasty', + 'Krysten', + 'Maud', + 'Janene', + 'Yomaira', + 'Kizzie', + 'Oriana', + 'Antionette', + 'Kamille', + 'Candis', + 'Kimberlie', + 'Britta', + 'Malika', + 'Khalilah', + 'Louisa', + 'Maiya', + 'Shanay', + 'Kellye', + 'Gaye', + 'Rosangelica', + 'Breonna', + 'Jenae', + 'Kaylene', + 'Rileigh', + 'Linnea', + 'Tawanna', + 'Harleen', + 'Tamya', + 'Makaylah', + 'Annabeth', + 'Alysson', + 'Adella', + 'Adalee', + 'Karisa', + 'Rosangela', + 'Ema', + 'Dayra', + 'Tena', + 'Mathilda', + 'Magan', + 'Dayanira', + 'Annelise', + 'Takisha', + 'Rosamaria', + 'Shifra', + 'Vianna', + 'Daysi', + 'Jalissa', + 'Samaya', + 'Aubriana', + 'Alora', + 'Emmeline', + 'Elora', + 'Laylani', + 'Willene', + 'Cathrine', + 'Ginny', + 'Lashunda', + 'Mikalah', + 'Kiyoko', + 'Wynter', + 'Zuleima', + 'Alease', + 'Louella', + 'Jubilee', + 'Allegra', + 'Karmen', + 'Emiliana', + 'Jianna', + 'Eisley', + 'Emmaline', + 'Teresita', + 'Mackenna', + 'Lauretta', + 'Krystin', + 'Kalene', + 'Aviva', + 'Zena', + 'Shanique', + 'Glynis', + 'Toya', + 'Linsey', + 'Denisha', + 'Marysol', + 'Marcelina', + 'Makiyah', + 'Masako', + 'Cintia', + 'Sharen', + 'Lahoma', + 'Magen', + 'Alyvia', + 'Shaniyah', + 'Anamaria', + 'Shivani', + 'Hannia', + 'Chavy', + 'Hayleigh', + 'Jaycie', + 'Mayah', + 'Delila', + 'Danita', + 'Modesta', + 'Arcelia', + 'Deedee', + 'Monserrath', + 'Angelie', + 'Mellisa', + 'Leisa', + 'Melannie', + 'Mafalda', + 'Kinlee', + 'Annetta', + 'Freida', + 'Anisha', + 'Mayrin', + 'Dajah', + 'Delylah', + 'Hortensia', + 'Joretta', + 'Lexy', + 'Laysha', + 'Anessa', + 'Jesusita', + 'Pearline', + 'Caleigh', + 'Liset', + 'Leilene', + 'Jaya', + 'Haily', + 'Tatyanna', + 'Desire', + 'Lisha', + 'Mindi', + 'Ivelisse', + 'Amariah', + 'Blythe', + 'Treasure', + 'Latarsha', + 'Emelda', + 'Latavia', + 'Debanhi', + 'Brynleigh', + 'Gala', + 'Jurnee', + 'Joslynn', + 'Harleigh', + 'Trang', + 'Audree', + 'Brande', + 'Genea', + 'Carri', + 'Kandy', + 'Kenisha', + 'Georgene', + 'Kamora', + 'Anabell', + 'Meranda', + 'Renesmee', + 'Rosaura', + 'Linette', + 'Rosamond', + 'Candida', + 'Crista', + 'Keeley', + 'Mykayla', + 'Rina', + 'Jonna', + 'Lorinda', + 'Wynona', + 'Kylene', + 'Kellee', + 'Elayne', + 'Chela', + 'Zykeria', + 'Shawnna', + 'Jaimee', + 'Zuleyma', + 'Britnee', + 'Mikala', + 'Coletta', + 'Morelia', + 'Isadora', + 'Anayah', + 'Amiah', + 'Ailin', + 'Jordana', + 'Casie', + 'Shakia', + 'Cordelia', + 'Analeah', + 'Janelly', + 'Adelita', + 'Yoana', + 'Lizabeth', + 'Latoria', + 'Pricila', + 'Margaretta', + 'Fumiko', + 'Lura', + 'Toshiko', + 'Marge', + 'Luana', + 'Marilee', + 'Jeana', + 'Tallulah', + 'Zia', + 'Betsabe', + 'Delanie', + 'Jenicka', + 'Kensington', + 'Navya', + 'Golda', + 'Kambree', + 'Orpha', + 'Rayleigh', + 'Kinleigh', + 'Karleigh', + 'Avalynn', + 'Addilynn', + 'Cambree', + 'Brinlee', + 'Liba', + 'Zendaya', + 'Farah', + 'Oumou', + 'Aislinn', + 'Karena', + 'Erendira', + 'Mariaelena', + 'Temperance', + 'Angelic', + 'Khadija', + 'Jonelle', + 'Aniah', + 'Aleigha', + 'Samaria', + 'Dedra', + 'Sammantha', + 'Bernardine', + 'Leilanie', + 'Makaela', + 'Samiya', + 'Porsche', + 'Krystel', + 'Simona', + 'Catarina', + 'Joi', + 'Etty', + 'Jannat', + 'Rubie', + 'Waneta', + 'Shaquita', + 'Shaindel', + 'Alida', + 'January', + 'Riana', + 'Jamilet', + 'Jala', + 'Gearldine', + 'Iola', + 'Tiesha', + 'Ariyana', + 'Josslyn', + 'Verla', + 'Gerri', + 'Emili', + 'Jennyfer', + 'Halo', + 'Raya', + 'Asusena', + 'Jessalyn', + 'Anaiah', + 'Sabine', + 'Dorinda', + 'Andriana', + 'Charissa', + 'Cambrie', + 'Daija', + 'Danyelle', + 'Maricarmen', + 'Melania', + 'Glinda', + 'Jaretzy', + 'Keesha', + 'Lucie', + 'Persephone', + 'Veda', + 'Avalyn', + 'Odilia', + 'Teena', + 'Daisha', + 'Shianne', + 'Nadya', + 'Peighton', + 'Shawana', + 'Lateefah', + 'Geena', + 'Aixa', + 'Magdalene', + 'Estefana', + 'China', + 'Tamekia', + 'Audrie', + 'Angely', + 'Charline', + 'Britny', + 'Quanisha', + 'Erykah', + 'Kenzi', + 'Carleigh', + 'Kamiyah', + 'Zayra', + 'Abagail', + 'Sulay', + 'Shelita', + 'Cattleya', + 'Ariela', + 'Yalitza', + 'Marleigh', + 'Colbie', + 'Lavergne', + 'Pyper', + 'Tawni', + 'Kasie', + 'Kati', + 'Cinnamon', + 'Trana', + 'Verda', + 'Romana', + 'Merrily', + 'Landri', + 'Bruchy', + 'Irlanda', + 'Lanie', + 'Kendyl', + 'Sanvi', + 'Akshara', + 'Aneesa', + 'Giulia', + 'Ruchy', + 'Giulianna', + 'Zahara', + 'Sumaya', + 'Guillermina', + 'Araseli', + 'Jackelin', + 'Norine', + 'Ariane', + 'Naidelin', + 'Gwenyth', + 'Kya', + 'Liyah', + 'Danya', + 'Sujey', + 'Grayce', + 'Honey', + 'Assunta', + 'Aleksandra', + 'Almeda', + 'Devany', + 'Spring', + 'Patrica', + 'Delisa', + 'Fantasia', + 'Cydney', + 'Laquisha', + 'Lynsey', + 'Stephenie', + 'Cassaundra', + 'Elisabet', + 'Echo', + 'Juliann', + 'Micayla', + 'Iridiana', + 'Antonietta', + 'Rosaisela', + 'Bayleigh', + 'Candelaria', + 'Zaida', + 'Mercedez', + 'Kindra', + 'Malayah', + 'Stephaine', + 'Nayla', + 'Tameeka', + 'Kiesha', + 'Pooja', + 'Sahar', + 'Paisleigh', + 'Kynslee', + 'Idella', + 'Arelis', + 'Shizuko', + 'Leslee', + 'Acacia', + 'Elexis', + 'Violetta', + 'Sailor', + 'Marceline', + 'Una', + 'Kamilla', + 'Aulani', + 'Aracelis', + 'Kikue', + 'Kasi', + 'Elwanda', + 'Brookelyn', + 'Kellyann', + 'Shaquanna', + 'Marielle', + 'Isel', + 'Agustina', + 'Vergie', + 'Arriana', + 'Perel', + 'Maylee', + 'Navy', + 'Lanell', + 'Rosann', + 'Carmelita', + 'Deisi', + 'Alyza', + 'Nailah', + 'Somaya', + 'Kiarra', + 'Tatiyana', + 'Nelida', + 'Demetra', + 'Thais', + 'Syriana', + 'Nicki', + 'Tyanna', + 'Idaly', + 'Ramonita', + 'Zuzanna', + 'Aiza', + 'Larae', + 'Alyanna', + 'Aleyah', + 'Elayna', + 'Blaire', + 'Laniyah', + 'Rilynn', + 'Kandi', + 'Sherryl', + 'Marti', + 'Cherri', + 'Kimberli', + 'Carma', + 'Trena', + 'Darcie', + 'Evelyne', + 'Allissa', + 'Meliza', + 'Regine', + 'Adalina', + 'Siya', + 'Seraphina', + 'Calliope', + 'Jiya', + 'Talisa', + 'Mistie', + 'Ignacia', + 'Crysta', + 'Lona', + 'Voncile', + 'Rutha', + 'Kamiya', + 'Anslee', + 'Janya', + 'Berenise', + 'Sonji', + 'Yaeko', + 'Nika', + 'Queena', + 'Yatziri', + 'Aiko', + 'Lisamarie', + 'Evalina', + 'Alline', + 'Alejandrina', + 'Trula', + 'Hinda', + 'Delinda', + 'Brisia', + 'Aminah', + 'Mariella', + 'Nayzeth', + 'Sherlin', + 'Idalia', + 'Madaline', + 'Shenika', + 'Janaya', + 'Fabiana', + 'Aleeah', + 'Lasonya', + 'Jania', + 'Breindy', + 'Mitzy', + 'Yaquelin', + 'Tzipora', + 'Serene', + 'Mikaila', + 'Aicha', + 'Brucha', + 'Myrka', + 'Kaaren', + 'Meg', + 'Lise', + 'Suhani', + 'Liane', + 'Celisse', + 'Jasmyne', + 'Sharde', + 'Dannielle', + 'Crystle', + 'Jenniffer', + 'Shaneka', + 'Leslye', + 'Hedy', + 'Tashina', + 'Letisia', + 'Carys', + 'Antonetta', + 'Tamisha', + 'Kaniya', + 'Darline', + 'Alizay', + 'Minna', + 'Raelene', + 'Rebecka', + 'Martika', + 'Makiya', + 'Idalis', + 'Keasia', + 'Breeann', + 'Vlasta', + 'Ellianna', + 'Caelyn', + 'Kaytlin', + 'Cathi', + 'Jamia', + 'Tahnee', + 'Zulma', + 'Mallorie', + 'Katlynn', + 'Mahi', + 'Carleen', + 'Honesty', + 'Rasheedah', + 'Ronna', + 'Lissa', + 'Sherika', + 'Carolynn', + 'Romona', + 'Jamesha', + 'Shakiyla', + 'Mccall', + 'Joanie', + 'Makala', + 'Brionna', + 'Shaunna', + 'Hawa', + 'Marylin', + 'Baylie', + 'Preslie', + 'Aaralyn', + 'Pia', + 'Beatris', + 'Adria', + 'Arianne', + 'Carmina', + 'Sebrina', + 'Malani', + 'Lovely', + 'Jahaira', + 'Miyah', + 'Sylvie', + 'Cassi', + 'Kaniyah', + 'Cailin', + 'Santina', + 'Nariah', + 'Calandra', + 'Georgine', + 'Serafina', + 'Keyana', + 'Amethyst', + 'Tehya', + 'Avni', + 'Alessa', + 'Novalee', + 'Mayleen', + 'Aadya', + 'Jacquelynn', + 'Izetta', + 'Sumiko', + 'Irasema', + 'Annamaria', + 'Niya', + 'Latrina', + 'Cicely', + 'Kristiana', + 'Kimiko', + 'Keara', + 'Mazie', + 'Najah', + 'Evelia', + 'Tiarra', + 'Jaela', + 'Montine', + 'Mandie', + 'Lavada', + 'Dimple', + 'Emiko', + 'Yocelyn', + 'Issabella', + 'Rowena', + 'Tanja', + 'Velda', + 'Chantell', + 'Gretel', + 'Jacelyn', + 'Kambri', + 'Zayla', + 'Anasofia', + 'Atiana', + 'Dulcemaria', + 'Zulay', + 'Tari', + 'Sahasra', + 'Rayleen', + 'Greydis', + 'Shiela', + 'Florinda', + 'Samya', + 'Shakima', + 'Shakeema', + 'Yanely', + 'Lavina', + 'Azalee', + 'Oneta', + 'Tammye', + 'Kelsy', + 'Kalie', + 'Keanna', + 'Daniya', + 'Antonina', + 'Katharin', + 'Tiare', + 'Yorley', + 'Jeslyn', + 'Emeli', + 'Zakia', + 'Massiel', + 'Latesha', + 'Jenessa', + 'Jayna', + 'Raylynn', + 'Ainslee', + 'Aralynn', + 'Khloee', + 'Ily', + 'Emeri', + 'Jeni', + 'Kassi', + 'Nakita', + 'Lakia', + 'Ariyanna', + 'Addalyn', + 'Keyanna', + 'Bibiana', + 'Genna', + 'Kathya', + 'Leana', + 'Trane', + 'Yomira', + 'Brigid', + 'Dionna', + 'Jerilynn', + 'Sarita', + 'Altha', + 'Laniya', + 'Zakiya', + 'Akilah', + 'Celestina', + 'Priyanka', + 'Taliah', + 'Donya', + 'Soila', + 'Quetzalli', + 'Cristel', + 'Naia', + 'Kailah', + 'Zitlaly', + 'Tonda', + 'Cate', + 'Lizzet', + 'Vesta', + 'Sherilyn', + 'Teruko', + 'Aldona', + 'Armandina', + 'Ciana', + 'Amairany', + 'Elysia', + 'Samarah', + 'Janyla', + 'Skylee', + 'Rolanda', + 'Sapphire', + 'Setsuko', + 'Miyoko', + 'Contina', + 'Imogen', + 'Jailine', + 'Vanellope', + 'Leora', + 'Jennah', + 'Perl', + 'Analiyah', + 'Hellen', + 'Tyasia', + 'Symphony', + 'Amada', + 'Otilia', + 'Avigail', + 'Tzivia', + 'Fradel', + 'Mariadelcarmen', + 'Ilona', + 'Dyan', + 'Zahraa', + 'Patrisia', + 'Jersey', + 'Lilla', + 'Lossie', + 'Somer', + 'Deserie', + 'Jaila', + 'Briseis', + 'Aniston', + 'Idell', + 'Raeleigh', + 'Gracyn', + 'Everlee', + 'Laurene', + 'Sherita', + 'Pinkie', + 'Nakisha', + 'Olevia', + 'Corene', + 'Loreen', + 'Sandie', + 'Keosha', + 'Kenleigh', + 'Alli', + 'Alyana', + 'Prisha', + 'Brookelynn', + 'Thaily', + 'Maddie', + 'Grettel', + 'Kinzley', + 'Jailynn', + 'Kalli', + 'Jazzlynn', + 'Klaudia', + 'Blanch', + 'Mariafernanda', + 'Makenzi', + 'Shonna', + 'Lita', + 'Karima', + 'Rebeccah', + 'Isaura', + 'Kalee', + 'Jori', + 'Allysa', + 'Tonisha', + 'Neda', + 'Jenine', + 'Chanell', + 'Jamaya', + 'Lorrayne', + 'Birtha', + 'Kanisha', + 'Nicollette', + 'Desiray', + 'Kaity', + 'Shamya', + 'Kathlene', + 'Jann', + 'Sari', + 'Lucila', + 'Tressie', + 'Charise', + 'Kalista', + 'Jamileth', + 'Kalena', + 'Sakura', + 'Blondell', + 'Thomasina', + 'Aila', + 'Mossie', + 'Tamala', + 'Siri', + 'Gertha', + 'Reta', + 'Easter', + 'Tala', + 'Vivianne', + 'Nila', + 'Merida', + 'Ahana', + 'Lanelle', + 'Hilaria', + 'Arlys', + 'Inell', + 'Rylynn', + 'Cosette', + 'Penne', + 'Jenevieve', + 'Jenilee', + 'Carlotta', + 'Ziva', + 'Hildegard', + 'Aleshia', + 'Nedra', + 'Madelaine', + 'Lisandra', + 'Pang', + 'Sindy', + 'Zenaida', + 'Lulu', + 'Shanya', + 'Shakema', + 'Katiria', + 'Raffaela', + 'Solange', + 'Illiana', + 'Chelsy', + 'Shanee', + 'Adriene', + 'Tyla', + 'Cailey', + 'Daijah', + 'Melonie', + 'Courteney', + 'Deysi', + 'Makinley', + 'Brynna', + 'Hildegarde', + 'Fiorella', + 'Kenadee', + 'Ellyn', + 'Ebonie', + 'Thu', + 'Charde', + 'Kaytlyn', + 'Kenadie', + 'Georgeann', + 'Analicia', + 'Emalee', + 'Shatara', + 'Lucerito', + 'Mckell', + 'Atiya', + 'Stormi', + 'Maleny', + 'Nariyah', + 'Steffanie', + 'Kirstyn', + 'Zayda', + 'Mariadejesus', + 'Deeann', + 'Abcde', + 'Eleanora', + 'Pearle', + 'Seana', + 'Denine', + 'Presleigh', + 'Keziah', + 'Queenie', + 'Henchy', + 'Merari', + 'Joscelyn', + 'Celest', + 'Mirel', + 'Sania', + 'Maryah', + 'Angelena', + 'Emelyn', + 'Gissele', + 'Fanta', + 'Gaylene', + 'Adelaida', + 'Madie', + 'Maja', + 'Nashaly', + 'Christel', + 'Rachele', + 'Raniyah', + 'Rashel', + 'Kavya', + 'Callista', + 'Elmira', + 'Rifky', + 'Syeda', + 'Tresa', + 'Detra', + 'Jarely', + 'Prisila', + 'Enedelia', + 'Trany', + 'Lainie', + 'Yisel', + 'Alynna', + 'Allysson', + 'Tamica', + 'Velva', + 'Nancee', + 'Breleigh', + 'Shanita', + 'Orelia', + 'Patrici', + 'Daja', + 'Shardae', + 'Abriana', + 'Halee', + 'Dorcas', + 'Kathey', + 'Rosia', + 'Princesa', + 'Lezly', + 'Dawnmarie', + 'Gaby', + 'Ania', + 'Denae', + 'Jahzara', + 'Jaymie', + 'Bari', + 'Suzann', + 'Alnisa', + 'Fatimah', + 'Zakiyyah', + 'Yana', + 'Naimah', + 'Tyisha', + 'Kathaleen', + 'Sameerah', + 'Chesney', + 'Shanteria', + 'Pamella', + 'Rayven', + 'Romelia', + 'Lucretia', + 'Tova', + 'Aura', + 'Chelsee', + 'Roizy', + 'Manha', + 'Nisha', + 'Tierney', + 'Girl', + 'Taelor', + 'Litzi', + 'Sneha', + 'Natisha', + 'Alliyah', + 'Sully', + 'Twyla', + 'Daisey', + 'Sarahy', + 'Shemeka', + 'Lexis', + 'Shalanda', + 'Kelcie', + 'Natacha', + 'Amyah', + 'Byanka', + 'Kymberly', + 'Navil', + 'Britani', + 'Karolyn', + 'Emelie', + 'Zana', + 'Vernita', + 'Leigha', + 'Romy', + 'Arlet', + 'Jazlin', + 'Laynie', + 'Jesslyn', + 'Adilyn', + 'Karoline', + 'Nyomi', + 'Maycee', + 'Nicol', + 'Daliah', + 'Lillyann', + 'Shawnda', + 'Dede', + 'Wiktoria', + 'Liah', + 'Liya', + 'Emmerson', + 'Aarohi', + 'Aribella', + 'Brayleigh', + 'Sumie', + 'Elke', + 'Taja', + 'Ahsley', + 'Tisa', + 'Dannette', + 'Gidget', + 'Misao', + 'Adelle', + 'Jamiah', + 'Joselynn', + 'Jalyssa', + 'Marnita', + 'Trinitee', + 'Bev', + 'Aleida', + 'Cloey', + 'Tahlia', + 'Melodee', + 'Anaiya', + 'Clover', + 'Prudence', + 'Kalynn', + 'Dezirae', + 'Solana', + 'Reena', + 'Mariko', + 'Tiffiny', + 'Elinore', + 'Madelyne', + 'Anela', + 'Bess', + 'Perri', + 'Loree', + 'Cyndy', + 'Yolonda', + 'Jolee', + 'Tequila', + 'Sumer', + 'Cherilyn', + 'Ela', + 'Kenlee', + 'Alexxis', + 'Larisa', + 'Nevaeha', + 'Nira', + 'Shaquasia', + 'Shanel', + 'Medina', + 'Rifka', + 'Sable', + 'Atara', + 'Aissatou', + 'Mecca', + 'Anastasi', + 'Falon', + 'Holley', + 'Yuliza', + 'Lili', + 'Siara', + 'Kiarah', + 'Tiffaney', + 'Alyah', + 'Annalia', + 'Naila', + 'Analiah', + 'Aymar', + 'Tambra', + 'Elna', + 'Eola', + 'Tkeyah', + 'Zola', + 'Francheska', + 'Aidee', + 'Alexzandra', + 'Cianni', + 'Myasia', + 'Carisa', + 'Ilah', + 'Yenifer', + 'Veronika', + 'Nahomy', + 'Madysen', + 'Elsy', + 'Lilli', + 'Belva', + 'Steffie', + 'Kaylea', + 'Ginamarie', + 'Sharman', + 'Latia', + 'Shakeria', + 'Audelia', + 'Odette', + 'Shaniah', + 'Diamantina', + 'Lorayne', + 'Ciarra', + 'Wilhelmena', + 'Zaina', + 'Niesha', + 'Kanesha', + 'Turquoise', + 'Tziporah', + 'Timi', + 'Fatou', + 'Karna', + 'Matsue', + 'Vina', + 'Ronisha', + 'Layan', + 'Viktoria', + 'Lilyann', + 'Maliya', + 'Jamilex', + 'Epifania', + 'Fidela', + 'Delphia', + 'Starasia', + 'Glennie', + 'Teodora', + 'Hatsue', + 'Margarett', + 'Margarette', + 'Laronda', + 'Vicenta', + 'Cotina', + 'Meilani', + 'Mannat', + 'Leylani', + 'Lailani', + 'Seerat', + 'Reya', + 'Amilia', + 'Avary', + 'Brocha', + 'Daneen', + 'Kimie', + 'Trudi', + 'Margret', + 'Djuna', + 'Charis', + 'Izzabella', + 'Brionne', + 'Elenora', + 'Lakeitha', + 'Jacki', + 'Beckie', + 'Guinevere', + 'Inara', + 'Landrie', + 'Nicoletta', + 'Ayari', + 'Zaniah', + 'Merlene', + 'Keli', + 'Maricella', + 'Leonela', + 'Donita', + 'Tehani', + 'Susannah', + 'Journi', + 'Machelle', + 'Tammara', + 'Cherrie', + 'Nelva', + 'Destanie', + 'Neyda', + 'Tabetha', + 'Wilhelmenia', + 'Brieanna', + 'Turkessa', + 'Ameera', + 'Avital', + 'Marycruz', + 'Zoila', + 'Tressa', + 'Joellen', + 'Raisa', + 'Bethanie', + 'Ermelinda', + 'Asiyah', + 'Monifa', + 'Samia', + 'Adamary', + 'Anahit', + 'Rania', + 'Miri', + 'Ether', + 'Desirea', + 'Chimere', + 'Erla', + 'Karisma', + 'Nalleli', + 'Larhonda', + 'Darlyn', + 'Anaisa', + 'Suellen', + 'Kamaria', + 'Nashla', + 'Yuriko', + 'Tzirel', + 'Tehila', + 'Myriah', + 'Frimet', + 'Cesilia', + 'Marika', + 'Frady', + 'Deloise', + 'Saleen', + 'Betsey', + 'Merri', + 'Laurette', + 'Sharita', + 'Shena', + 'Porscha', + 'Aerial', + 'Florrie', + 'Ayah', + 'Anusha', + 'Jeanelle', + 'Lessly', + 'Mahogany', + 'See', + 'Hang', + 'Karinna', + 'Leighann', + 'Elexus', + 'Markayla', + 'Kaneesha', + 'Barbie', + 'Aurea', + 'Kaeli', + 'Arwen', + 'Angelyn', + 'Jaclynn', + 'Tesla', + 'Maritsa', + 'Madelin', + 'Alisia', + 'Tyana', + 'Kimberlyn', + 'Dejanae', + 'Dalena', + 'Blessing', + 'Courtnie', + 'Amaria', + 'Micki', + 'Safa', + 'Jadah', + 'Mele', + 'Maryssa', + 'Channel', + 'Lianne', + 'Alea', + 'Chyanne', + 'Addelyn', + 'Aaleyah', + 'Michela', + 'Torri', + 'Indira', + 'Kanani', + 'Lashundra', + 'Mikaylah', + 'Zoee', + 'Taelyn', + 'Noheli', + 'Sarena', + 'Dariela', + 'Adalie', + 'Meggan', + 'Daniyah', + 'Sela', + 'Shaelyn', + 'Maylen', + 'Giovana', + 'Ayvah', + 'Arabelle', + 'Adaline', + 'Isyss', + 'Melanny', + 'Margaux', + 'Klara', + 'Janey', + 'Idolina', + 'Georgetta', + 'Amaiya', + 'Sianna', + 'Rebeka', + 'Meleny', + 'Kelle', + 'Angelika', + 'Malerie', + 'Latara', + 'Niamh', + 'Yevette', + 'Yomayra', + 'Karizma', + 'Nayelie', + 'Shantal', + 'Latoyia', + 'Jenee', + 'Shandra', + 'Magdalen', + 'Yatzari', + 'Jettie', + 'Charlsie', + 'Idy', + 'Inaya', + 'Yitta', + 'Reem', + 'Basya', + 'Skylynn', + 'Elyana', + 'Brynley', + 'Amor', + 'Amberlee', + 'Eternity', + 'Niyah', + 'Emiley', + 'Madeleyn', + 'Korie', + 'Sanaya', + 'Meira', + 'Chevonne', + 'Sabra', + 'Uma', + 'Kaira', + 'Isobel', + 'Elli', + 'Gurleen', + 'Berneice', + 'Alvera', + 'Ambrosia', + 'Roya', + 'Bettyann', + 'Alverda', + 'Tinamarie', + 'Tanasia', + 'Lavonda', + 'Jorja', + 'Heide', + 'Marwa', + 'Annaly', + 'Aaliah', + 'Ileen', + 'Lamonica', + 'Enjoli', + 'Ninel', + 'Milissa', + 'Dawne', + 'Joie', + 'Ashlei', + 'Elidia', + 'Maybelle', + 'Getsemani', + 'Gisella', + 'Mariya', + 'Adisyn', + 'Adia', + 'Caterina', + 'Bettyjane', + 'Kaydee', + 'Rasheda', + 'Camisha', + 'Chassidy', + 'Sadia', + 'Aislyn', + 'Ngoc', + 'Mirka', + 'Lanita', + 'Lashawnda', + 'Liridona', + 'Tynisa', + 'Arnelle', + 'Librada', + 'Marita', + 'Makyla', + 'Raniya', + 'Kandis', + 'Ethelyn', + 'Divina', + 'Genevie', + 'Jadelyn', + 'Ashleen', + 'Saya', + 'Marli', + 'Calli', + 'Anyla', + 'Sheng', + 'Vasiliki', + 'Yelena', + 'Darya', + 'Clarabelle', + 'Shirlene', + 'Tommye', + 'Julieann', + 'Jennefer', + 'Rana', + 'Raeann', + 'Suleima', + 'Lilyanne', + 'Jelisa', + 'Jaymee', + 'Rhylee', + 'Keyli', + 'Brooklin', + 'Meta', + 'Shakirah', + 'Loria', + 'Sharyl', + 'Sharday', + 'Manuelita', + 'Debera', + 'Lera', + 'Jacquie', + 'Ardella', + 'Jameria', + 'Winnifred', + 'Rossana', + 'Shemika', + 'Sedona', + 'Arvilla', + 'Samaira', + 'Aitana', + 'Daiana', + 'Cassia', + 'Lucianna', + 'Tama', + 'Shigeko', + 'Sueko', + 'Hatsuko', + 'Hazle', + 'Lida', + 'Wylene', + 'Sachiko', + 'Tahiry', + 'Renea', + 'Janina', + 'Becki', + 'Vy', + 'Cherryl', + 'Arionna', + 'Marrissa', + 'Randee', + 'Norita', + 'Sonali', + 'Susann', + 'Rachell', + 'Natashia', + 'Aspyn', + 'Malaika', + 'Nuvia', + 'Safiya', + 'Contessa', + 'Julyssa', + 'Analiese', + 'Jacee', + 'Kathlyn', + 'Gracey', + 'Chassity', + 'Kady', + 'Tytiana', + 'Katiana', + 'Eneida', + 'Teela', + 'Roiza', + 'Alaura', + 'Giuseppina', + 'Randa', + 'Danisha', + 'Mariza', + 'Marquisha', + 'Sharese', + 'Deseree', + 'Inaaya', + 'Rivkah', + 'Tawnie', + 'Miriah', + 'Shereen', + 'Candra', + 'Tomiko', + 'Whittney', + 'Aziza', + 'Ayala', + 'Hafsa', + 'Zaynab', + 'Kaileigh', + 'Yarima', + 'Danitza', + 'Maram', + 'Shakeya', + 'Emmer', + 'Kareema', + 'Maayan', + 'Rheanna', + 'Jaritza', + 'Marleny', + 'Zitlali', + 'Vanity', + 'Apryl', + 'Zully', + 'Tashia', + 'Courtnee', + 'Laporsha', + 'Luvenia', + 'Batya', + 'Ayelet', + 'Quetcy', + 'Tiny', + 'Felicita', + 'Omaira', + 'Nyssa', + 'Krystine', + 'Stevi', + 'Michiko', + 'Tennie', + 'Tomekia', + 'Billiejo', + 'Yohana', + 'Krystyna', + 'Kacee', + 'Naja', + 'Charmayne', + 'Twana', + 'Jeane', + 'Brittnay', + 'Cherelle', + 'Raechel', + 'Temeka', + 'Jasmeen', + 'Zuria', + 'Zailey', + 'Saydee', + 'Renatta', + 'Neta', + 'Bg', + 'Italy', + 'Terrica', + 'Goldia', + 'Monae', + 'Yelitza', + 'Ryanne', + 'Samirah', + 'Breckyn', + 'Nicolina', + 'Olympia', + 'Almeta', + 'Tamesha', + 'Zora', + 'Emmaleigh', + 'Loralei', + 'Kennadi', + 'Julieanna', + 'Jenavieve', + 'Shylah', + 'Akemi', + 'Fonda', + 'Nizhoni', + 'Iqra', + 'Gaetana', + 'Coreen', + 'Evonne', + 'Sadako', + 'Angele', + 'Macel', + 'Alinna', + 'Avneet', + 'Jannah', + 'Nickole', + 'Lotus', + 'Yukie', + 'Laiyah', + 'Kynzlee', + 'Mailen', + 'Nobuko', + 'Annaleigh', + 'Otila', + 'Shona', + 'Kimberely', + 'Delcie', + 'Zula', + 'Roselynn', + 'Aleeyah', + 'Bellarose', + 'Damya', + 'Cammie', + 'Treena', + 'Chanie', + 'Kaliah', + 'Abella', + 'Aahana', + 'Mileena', + 'Adaleigh', + 'Keiry', + 'Journie', + 'Myrtie', + 'Tsuruko', + 'Lyda', + 'Fernande', + 'Julee', + 'Estephany', + 'Louvenia', + 'Monserat', + 'Meena', + 'Jayline', + 'Brie', + 'Elicia', + 'Suzana', + 'Dusti', + 'Odile', + 'Hilma', + 'Katarzyna', + 'Jenise', + 'Hiromi', + 'Huong', + 'Deolinda', + 'Pamelia', + 'Awa', + 'Odelia', + 'Mattison', + 'Gwenda', + 'Sera', + 'Yuritzi', + 'Karishma', + 'Kaina', + 'Henna', + 'Norene', + 'Brina', + 'Chyenne', + 'Moncerrat', + 'Keilah', + 'Saphira', + 'Marylee', + 'Meri', + 'Lajuana', + 'Lindsy', + 'Shanise', + 'Sugey', + 'Jaimi', + 'Viki', + 'Ceola', + 'Naiya', + 'Adysen', + 'Shantavia', + 'Amberlyn', + 'Brihanna', + 'Laela', + 'Kenadi', + 'Hermine', + 'Bernita', + 'Deziree', + 'Anja', + 'Lawana', + 'Aletha', + 'Nella', + 'Irelyn', + 'Jakira', + 'Wynema', + 'Janai', + 'Keondra', + 'Venice', + 'Zenobia', + 'Jaelene', + 'Ammy', + 'Alizah', + 'Lakiesha', + 'Azure', + 'Aysha', + 'Saniah', + 'Mahnoor', + 'Ananda', + 'Asma', + 'Aissata', + 'Jaileen', + 'Yailin', + 'Xiana', + 'Laiza', + 'Liseth', + 'Marykate', + 'Daizy', + 'Neoma', + 'Tykeria', + 'Shamiya', + 'Nykeria', + 'Addalynn', + 'Kenzley', + 'Ardyce', + 'Anylah', + 'Vallie', + 'Darlyne', + 'Makiah', + 'Neela', + 'Naraly', + 'Danni', + 'Jolina', + 'Ailene', + 'Lyndia', + 'Erminia', + 'Asiya', + 'Alexius', + 'Mc', + 'Maylene', + 'Signe', + 'Adelfa', + 'Yusra', + 'Keyonna', + 'Yasuko', + 'Yukiko', + 'Augustina', + 'Leen', + 'Fumie', + 'Amora', + 'Annaleah', + 'Anvi', + 'Indie', + 'Haya', + 'Emmarie', + 'Enya', + 'Chieko', + 'Kinsleigh', + 'Shiann', + 'Eufemia', + 'Fusae', + 'Akiko', + 'Hosanna', + 'Alitzel', + 'Araya', + 'Anaiyah', + 'Rosy', + 'Nishka', + 'Gao', + 'Tamiya', + 'Lillyan', + 'Eudelia', + 'Kamea', + 'Berlyn', + 'Kahlan', + 'Alinah', + 'Mahealani', + 'Leeah', + 'Rosalynn', + 'Zadie', + 'Aolanis', + 'Esta', + 'Maisy', + 'Chevelle', + 'Jalayah', + 'Yatziry', + 'Alyne', + 'Jodell', + 'Sariya', + 'Yashica', + 'Jissel', + 'Letty', + 'Mariaisabel', + 'Lizzeth', + 'Yovana', + 'Dyanna', + 'Tamyra', + 'Monzerrat', + 'Seanna', + 'Eldora', + 'Mattea', + 'Zahira', + 'Jeanetta', + 'Aysia', + 'Ashlin', + 'Tenika', + 'Lezlie', + 'Kailie', + 'Jariyah', + 'Jovie', + 'Kiyah', + 'Lynlee', + 'Abriella', + 'Adleigh', + 'Ranada', + 'Vertie', + 'Flonnie', + 'Kynnedi', + 'Lya', + 'Acelynn', + 'Emalyn', + 'Emberly', + 'Yalexa', + 'Izabela', + 'Sadye', + 'Kamyla', + 'Jayleigh', + 'Cayleigh', + 'Ceil', + 'Inger', + 'Cindee', + 'Nena', + 'Loan', + 'Kiya', + 'Laure', + 'Cristen', + 'Celenia', + 'Fredda', + 'Ravyn', + 'Mee', + 'Graci', + 'Azalia', + 'Latina', + 'Hassie', + 'Dinorah', + 'Virna', + 'Autum', + 'Michala', + 'Macayla', + 'Franca', + 'Corissa', + 'Alysse', + 'Monisha', + 'Jessyca', + 'Nisa', + 'Jacqulyn', + 'Makaylee', + 'Ellin', + 'Jameelah', + 'Shalon', + 'Jlynn', + 'Kennady', + 'Brinkley', + 'Providence', + 'Phylis', + 'Eugenie', + 'Clementina', + 'Kadynce', + 'Yuvia', + 'Mailyn', + 'Taneisha', + 'Samone', + 'Aurore', + 'Brienne', + 'Tritia', + 'Fayth', + 'Jayci', + 'Jorie', + 'Loreal', + 'Taylar', + 'Maryn', + 'Melissia', + 'Midori', + 'Hisako', + 'Hulda', + 'Bobbijo', + 'Bulah', + 'Nancye', + 'Melvina', + 'Sherree', + 'Kierstin', + 'Merrilee', + 'Lonna', + 'Judyth', + 'Nancie', + 'Lark', + 'Candyce', + 'Kadejah', + 'Kenda', + 'Fara', + 'Estephania', + 'Cady', + 'Marilin', + 'Kadie', + 'Suleyma', + 'Jacquelyne', + 'Vonetta', + 'Tanairi', + 'Charlott', + 'Shannel', + 'Zenia', + 'Alise', + 'Takara', + 'Lyndsie', + 'Ivett', + 'Letisha', + 'Idania', + 'Lacee', + 'Candie', + 'Camelia', + 'Brittanee', + 'Mariaeduarda', + 'Geovanna', + 'Kirsti', + 'Michaella', + 'Kelsee', + 'Cheryll', + 'Cyrstal', + 'Keriann', + 'Latrisha', + 'Exie', + 'Deborha', + 'Verdie', + 'Zahava', + 'Zuleika', + 'Dorla', + 'Dominiqu', + 'Sharina', + 'Ardeth', + 'Alethia', + 'Starlene', + 'Shamira', + 'Shantelle', + 'Marilou', + 'Kyah', + 'Kyana', + 'Clemencia', + 'Cordie', + 'Meagen', + 'Xitlalic', + 'Gaia', + 'Ellia', + 'Elani', + 'Jaylani', + 'Krisha', + 'Khalia', + 'Aaradhya', + 'Aeris', + 'Avamarie', + 'Artemis', + 'Sheana', + 'Jolynn', + 'Sandee', + 'Wendie', + 'Willia', + 'Loriene', + 'Apolonia', + 'Eusebia', + 'Kazue', + 'Synthia', + 'Harue', + 'Siomara', + 'Nhi', + 'Maleni', + 'Toyoko', + 'Freeda', + 'Hideko', + 'Sherrye', + 'Bethanne', + 'Merrie', + 'Peri', + 'Ozella', + 'Venetia', + 'Revonda', + 'Breauna', + 'Arika', + 'Annissa', + 'Leeza', + 'Siera', + 'Jakiyah', + 'Kamaya', + 'Lashay', + 'Elvina', + 'Laquinta', + 'Faren', + 'Harmonie', + 'Brianny', + 'Jama', + 'Johna', + 'Sharalyn', + 'Aziyah', + 'Hadassa', + 'Shantinique', + 'Treasa', + 'Penni', + 'Shakera', + 'Carolyne', + 'Shaunda', + 'Talya', + 'Karyna', + 'Natosha', + 'Vivica', + 'Pauletta', + 'Laverna', + 'Danasia', + 'Shakita', + 'Sharee', + 'Yajayra', + 'Karlene', + 'Reatha', + 'Laiba', + 'Zamiyah', + 'Shirleen', + 'Bettylou', + 'Nakiya', + 'Eryka', + 'Bailie', + 'Janiece', + 'Keisa', + 'Kiah', + 'Jennica', + 'Niasia', + 'Hildy', + 'Jacquel', + 'Mahina', + 'Eshal', + 'Khloey', + 'Emelin', + 'Eesha', + 'Kaylei', + 'Aymee', + 'Alona', + 'Catelyn', + 'Arushi', + 'Ameerah', + 'Regenia', + 'Brailey', + 'Sparkle', + 'Giavonna', + 'Ashunti', + 'Naudia', + 'Kyrsten', + 'Emmalina', + 'Neve', + 'Aolani', + 'Gizella', + 'Tameika', + 'Leocadia', + 'Nidhi', + 'Wende', + 'Eshaal', + 'Cherice', + 'Lakeysha', + 'Menucha', + 'Ameena', + 'Kloey', + 'Nayelly', + 'Kathryne', + 'Lashawna', + 'Kristle', + 'Zaylie', + 'Keylee', + 'Landree', + 'Wynell', + 'Dezarae', + 'Angelli', + 'Haddie', + 'Ilyana', + 'Jaleigh', + 'Brilee', + 'Lakeya', + 'Kanika', + 'Lavinia', + 'Marykay', + 'Ruthanne', + 'Tenille', + 'Dorine', + 'Esabella', + 'Genavieve', + 'Zarah', + 'Mileidy', + 'Solara', + 'Yamila', + 'Amaia', + 'Season', + 'Cheree', + 'Luise', + 'Tracye', + 'Christeen', + 'Florida', + 'Breona', + 'Kathe', + 'Jakyra', + 'Zury', + 'Lakeesha', + 'Yaneth', + 'Keandra', + 'Agnieszka', + 'Markita', + 'Mariska', + 'Zada', + 'Breasia', + 'Aaniyah', + 'Kambria', + 'Lilit', + 'Sheilah', + 'Cherisse', + 'Hermione', + 'Angeli', + 'Britnie', + 'Lisett', + 'Joette', + 'Nga', + 'Ruthe', + 'Anamarie', + 'Mayeli', + 'Takia', + 'Lien', + 'Tenaya', + 'Kera', + 'Meah', + 'Mei', + 'Anoushka', + 'Annalyse', + 'Persia', + 'Sheccid', + 'Kyndra', + 'Susy', + 'Steffany', + 'Jennavecia', + 'Briannah', + 'Kynlie', + 'Alexxa', + 'Paizlee', + 'Jesika', + 'Kinzlee', + 'Ishani', + 'Freyja', + 'Julietta', + 'Raynette', + 'Nely', + 'Zayleigh', + 'Amberlynn', + 'Journei', + 'Eimy', + 'Delany', + 'Emarie', + 'Brilynn', + 'Audri', + 'Abilene', + 'Saoirse', + 'Naveah', + 'Ayelen', + 'Emeline', + 'Loryn', + 'Mykaela', + 'Skarlett', + 'Tionne', + 'Rashelle', + 'Jerline', + 'Ofilia', + 'Rufina', + 'Phillis', + 'Jenica', + 'Dineen', + 'Glory', + 'Camellia', + 'Alane', + 'Angelyna', + 'Amalie', + 'Kina', + 'Kateri', + 'Neyva', + 'Malisa', + 'Saida', + 'Jerika', + 'Bayli', + 'Crystale', + 'Silvana', + 'Inga', + 'Lyndi', + 'Leeanna', + 'Cheyanna', + 'Fayrene', + 'Torie', + 'Latashia', + 'Baleigh', + 'Fidencia', + 'Rori', + 'Lorianne', + 'Catrice', + 'Cherrelle', + 'Lashaunda', + 'Sammi', + 'Thomasena', + 'Roshanda', + 'Alazae', + 'Enza', + 'Mairead', + 'Pandora', + 'Kortni', + 'Timber', + 'Chasidy', + 'Danesha', + 'Camry', + 'Charlette', + 'Kaneshia', + 'Shadae', + 'Keturah', + 'Randye', + 'Kiyana', + 'Charlean', + 'Delana', + 'Tomasita', + 'Lilliam', + 'Bibi', + 'Marguita', + 'Maryalice', + 'Iraida', + 'Tyhessia', + 'Makeba', + 'Tanaya', + 'Keiara', + 'Madlyn', + 'Jelissa', + 'Shakayla', + 'Mickayla', + 'Aleisha', + 'Keyara', + 'Mekayla', + 'Mykala', + 'Riva', + 'Inaara', + 'Paulita', + 'Lashae', + 'Destynee', + 'Christianna', + 'Rise', + 'Larraine', + 'Luetta', + 'Merna', + 'Francena', + 'Diedre', + 'Georgiann', + 'Rubbie', + 'Denita', + 'Dyani', + 'Laticia', + 'Ludivina', + 'Suanne', + 'Hareem', + 'Nava', + 'Florie', + 'Sherly', + 'Vidhi', + 'Camie', + 'Sharell', + 'Chole', + 'Jolin', + 'Polina', + 'Brittiany', + 'Delicia', + 'Brystol', + 'Beaulah', + 'Leatha', + 'Jamilah', + 'Zona', + 'Elliette', + 'Joye', + 'Aashi', + 'Kerriann', + 'Xin', + 'Michaelene', + 'Senaida', + 'Emaan', + 'Nakayla', + 'Aine', + 'Jadalyn', + 'Maimouna', + 'Klaire', + 'Macee', + 'Shandi', + 'Heily', + 'Braylynn', + 'Aislynn', + 'Chevon', + 'Henretta', + 'Bellamy', + 'Icie', + 'Draya', + 'Darianna', + 'Zyana', + 'Zaelynn', + 'Story', + 'Kambrie', + 'Ranae', + 'Florencia', + 'Porfiria', + 'Elianny', + 'Karren', + 'Yachet', + 'Euna', + 'Naoma', + 'Stefania', + 'Liora', + 'Zlaty', + 'Margene', + 'Denna', + 'Isidra', + 'Faustina', + 'Bintou', + 'Arbutus', + 'Kelci', + 'Evelena', + 'Maudine', + 'Agapita', + 'Olyvia', + 'Loma', + 'Veola', + 'Mckinlee', + 'Lamya', + 'Nashali', + 'Baileigh', + 'Josselin', + 'Kaydance', + 'Paiton', + 'Myleigh', + 'Jazlyne', + 'Indya', + 'Siham', + 'Aryn', + 'Madalene', + 'Nefertiti', + 'Meridith', + 'Kamesha', + 'Peg', + 'Shelbey', + 'Pearlean', + 'Jamika', + 'Maryama', + 'Sabria', + 'Taniqua', + 'Maraya', + 'Joely', + 'Karys', + 'Charolette', + 'Orly', + 'Jennipher', + 'Kimbra', + 'Krislynn', + 'Kenlie', + 'Karrington', + 'Kambry', + 'Alasia', + 'Carletta', + 'Maija', + 'Nadirah', + 'Gladyce', + 'Shevy', + 'Jalessa', + 'Mushka', + 'Cyre', + 'Mabry', + 'Arihanna', + 'Brithany', + 'Ilianna', + 'Jozlynn', + 'Zandra', + 'Serinity', + 'Passion', + 'Lacresha', + 'Jeraldine', + 'Henriette', + 'Elenore', + 'Nastassia', + 'Ruchel', + 'Amal', + 'Madina', + 'Rosaline', + 'Liyana', + 'Pasqualina', + 'Keyra', + 'Kaycie', + 'Lyanna', + 'Naina', + 'Gennesis', + 'Aarushi', + 'Lariah', + 'Jakiya', + 'Kareena', + 'Rhiana', + 'Emilly', + 'Yeimi', + 'Matsuko', + 'Makhia', + 'Alin', + 'Addisen', + 'Lanae', + 'Oceana', + 'Laquanda', + 'Coralie', + 'Arina', + 'Harini', + 'Emy', + 'Emmarose', + 'Ellyana', + 'Eila', + 'Havana', + 'Dagny', + 'Leylah', + 'Shawanna', + 'Aleenah', + 'Adalia', + 'Aaliya', + 'Zyanya', + 'Felisa', + 'Tameca', + 'Sama', + 'Ripley', + 'Nayomi', + 'Quanesha', + 'Shequita', + 'Shanik', + 'Savina', + 'Nalah', + 'Magaby', + 'Cattaleya', + 'Calla', + 'Lillia', + 'Kaida', + 'Izabell', + 'Francia', + 'Caylin', + 'Bianey', + 'Hanah', + 'Julienne', + 'Viva', + 'Xochil', + 'Staphany', + 'Rayanne', + 'Marialuisa', + 'Devina', + 'Sabryna', + 'Estefanie', + 'Dinora', + 'Clarisse', + 'Josephina', + 'Milca', + 'Anjolie', + 'Akayla', + 'Malea', + 'Mea', + 'Meghana', + 'Briceida', + 'Betsaida', + 'Roselin', + 'Anuhea', + 'Megha', + 'Azusena', + 'Nandini', + 'Prisilla', + 'Shelsy', + 'Olene', + 'Kaneisha', + 'Onalee', + 'Jadore', + 'Monteen', + 'Trudie', + 'Leisha', + 'Drucilla', + 'Tamiyah', + 'Ashante', + 'Daysha', + 'Caliyah', + 'Sabella', + 'Emoni', + 'Jakyla', + 'Reginae', + 'Anyah', + 'Kierstyn', + 'Sharleen', + 'Doretta', + 'Harlene', + 'Gerrie', + 'Zofia', + 'Albertine', + 'Bronwyn', + 'Terresa', + 'Delta', + 'Anijah', + 'Mathilde', + 'Cindie', + 'Dalene', + 'Cyndee', + 'Eulah', + 'Ayako', + 'Beverlee', + 'Nicholle', + 'Kaitlan', + 'Yeraldin', + 'Tawney', + 'Tawnee', + 'Britteny', + 'Alishia', + 'Shireen', + 'Byanca', + 'Rebekka', + 'Annel', + 'Krizia', + 'Kerstin', + 'Shera', + 'Uyen', + 'Ligia', + 'Dallana', + 'Itsel', + 'Karine', + 'Sharmaine', + 'Noely', + 'Marrisa', + 'Karah', + 'Rayann', + 'Oksana', + 'Stephannie', + 'Brynne', + 'Alixandra', + 'Dyana', + 'Emilce', + 'Delmy', + 'Jamee', + 'Caitlan', + 'Marily', + 'Kiani', + 'Jennafer', + 'Nissa', + 'Jenell', + 'Jennette', + 'Marquitta', + 'Chimene', + 'Justyna', + 'Jenette', + 'Cassy', + 'Temika', + 'Katrice', + 'Brandis', + 'Consuela', + 'Chavon', + 'Angella', + 'Shantrell', + 'Christiane', + 'Shenna', + 'Donia', + 'Angelise', + 'Janyah', + 'Damiyah', + 'Luzmaria', + 'Meghna', + 'Natally', + 'Nerissa', + 'Kaori', + 'Laya', + 'Analyssa', + 'Teya', + 'Alizon', + 'Jasline', + 'Lavette', + 'Emmi', + 'Kamisha', + 'Taleah', + 'Shenita', + 'Kaytlynn', + 'Azariyah', + 'Dominica', + 'Malvina', + 'Skyy', + 'Shondra', + 'Lorina', + 'Donielle', + 'Kaisley', + 'Katalyna', + 'Jesslynn', + 'Yasmina', + 'Glada', + 'Maliha', + 'Irina', + 'Hiba', + 'Trinette', + 'Oona', + 'Aleeza', + 'Arisha', + 'Janean', + 'Cristie', + 'Syd', + 'Lavona', + 'Kennia', + 'Kyanna', + 'Lovenia', + 'Julieanne', + 'Launa', + 'Taunya', + 'Tytianna', + 'Becca', + 'Deonna', + 'Jihan', + 'Jomaira', + 'Shantay', + 'Talitha', + 'Shyra', + 'Alverna', + 'Chere', + 'Kamela', + 'Phaedra', + 'Stacee', + 'Gretta', + 'Kathyrn', + 'Shalee', + 'Beautiful', + 'Lissett', + 'Georgann', + 'Corrin', + 'Chelsa', + 'Cera', + 'Layna', + 'Lizanne', + 'Mariellen', + 'Lashandra', + 'Sophya', + 'Shruti', + 'Janea', + 'Rheta', + 'Jezebel', + 'Alizee', + 'Delaila', + 'Dayani', + 'Arieanna', + 'Amarah', + 'Janyia', + 'Makalah', + 'Dorie', + 'Tynisha', + 'Tran', + 'Prisma', + 'Shirin', + 'Tonette', + 'Suzi', + 'Alajah', + 'Lurline', + 'Adelia', + 'Tani', + 'Cassey', + 'Maha', + 'Cheyann', + 'Keyona', + 'Yezenia', + 'Vaness', + 'Stephine', + 'Cyndie', + 'Jaylanie', + 'Jeannemarie', + 'Mammie', + 'Sherice', + 'Delynn', + 'Aoife', + 'Kadiatou', + 'Sherese', + 'Trenyce', + 'Anaiz', + 'Anaiza', + 'Dajanae', + 'Lisaann', + 'Keiko', + 'Martiza', + 'Elysa', + 'Petrina', + 'Dierdre', + 'Davida', + 'Falyn', + 'Briona', + 'Maryjean', + 'Lanisha', + 'Marlenne', + 'Nawal', + 'Ethelene', + 'Alya', + 'Ariannah', + 'Jacinta', + 'Alaia', + 'Sindee', + 'Jalaya', + 'Mellanie', + 'Lasya', + 'Kyrah', + 'Mirabella', + 'Renay', + 'Seren', + 'Hiliana', + 'Kinzie', + 'Isra', + 'Hanan', + 'Kaleia', + 'Melynda', + 'Marine', + 'Twanna', + 'Lekisha', + 'Jamecia', + 'Penney', + 'Tiwanna', + 'Rylea', + 'Shekinah', + 'Mckenzi', + 'Abigael', + 'Patrizia', + 'Jamillah', + 'Caris', + 'Karmyn', + 'Kyli', + 'Princessa', + 'Sakinah', + 'Deserae', + 'Patrina', + 'Carmelina', + 'Mayela', + 'Sherise', + 'Ilda', + 'Florentina', + 'Nelwyn', + 'Jennine', + 'Aleeya', + 'Kynsley', + 'Arlett', + 'Tarra', + 'Lakyn', + 'Tyeisha', + 'Temima', + 'Mallori', + 'Ingeborg', + 'Elizaveta', + 'Jentry', + 'Kymber', + 'Maddisyn', + 'Allana', + 'Anistyn', + 'Emberlynn', + 'Faithlynn', + 'Arianah', + 'Tionna', + 'Lenda', + 'Laveta', + 'Alayla', + 'Aisling', + 'Miryam', + 'Marena', + 'Aneta', + 'Yzabella', + 'Mihika', + 'Raine', + 'Samiah', + 'Raveena', + 'Elfrieda', + 'Niccole', + 'Tatanisha', + 'Medha', + 'Katharina', + 'Jazmen', + 'Cally', + 'Louanne', + 'Caress', + 'Naylea', + 'Avarie', + 'Madelynne', + 'Dayla', + 'Shanterria', + 'Tesha', + 'Thanya', + 'Jalia', + 'Josalyn', + 'Ailey', + 'Brooklynne', + 'Dodie', + 'Champagne', + 'Taneka', + 'Tenesha', + 'Tinisha', + 'Deeanna', + 'Shelvia', + 'Chenoa', + 'Darcel', + 'Kailea', + 'Jatziry', + 'Merryl', + 'Sharlyn', + 'Harolyn', + 'Rilla', + 'Ayisha', + 'Jacklynn', + 'Chloee', + 'Makynzie', + 'Leyah', + 'Aalyiah', + 'Tynlee', + 'Statia', + 'Tyronda', + 'Tsuyako', + 'Casimira', + 'Kehaulani', + 'Ragan', + 'Lorissa', + 'Abelina', + 'Cuca', + 'Sachi', + 'Evany', + 'Elektra', + 'Sianni', + 'Raychel', + 'Natassia', + 'Vermell', + 'Sharifa', + 'Everley', + 'Ivanka', + 'Arisbeth', + 'Aleyza', + 'Bay', + 'Deedra', + 'Zarina', + 'Regena', + 'Kitana', + 'Latoshia', + 'Virgia', + 'Aili', + 'Breslyn', + 'Ishika', + 'Jhoana', + 'Dorrace', + 'Chanice', + 'Sheniqua', + 'Tashana', + 'Joetta', + 'Sanya', + 'Altamese', + 'Pari', + 'Niah', + 'Ysabelle', + 'Lisseth', + 'Parisa', + 'Aislin', + 'Leiah', + 'Atziri', + 'Anvita', + 'Jaydah', + 'Gabby', + 'Ashia', + 'Dymond', + 'Marah', + 'Uniqua', + 'Blimie', + 'Anny', + 'Dalinda', + 'Wauneta', + 'Gionna', + 'Rabia', + 'Jayanna', + 'Anica', + 'Maybell', + 'Kathern', + 'Amrita', + 'Mayerli', + 'Irais', + 'Kemberly', + 'Vena', + 'Kamri', + 'Destine', + 'Adreanna', + 'Seleste', + 'Claretha', + 'Brynnlee', + 'Anquette', + 'Komal', + 'Lysette', + 'Michayla', + 'Zamya', + 'Sierrah', + 'Felica', + 'Otelia', + 'Rihana', + 'Doloris', + 'Alanie', + 'Angelly', + 'Kassandr', + 'Rosemari', + 'Shaday', + 'Annemari', + 'Marlana', + 'Clorinda', + 'Oneida', + 'Shaunta', + 'Alexcia', + 'Takesha', + 'Amiracle', + 'Sharion', + 'Joline', + 'Jaziyah', + 'Teal', + 'Sueann', + 'Sora', + 'Kamiah', + 'Caressa', + 'Eleana', + 'Bernetha', + 'Alexyss', + 'Sharda', + 'Aishwarya', + 'Suhaill', + 'Radhika', + 'Wonda', + 'Renda', + 'Janny', + 'Ardelle', + 'Malory', + 'Jossie', + 'Anaid', + 'Mitsuye', + 'Shizuye', + 'Fariha', + 'Aiesha', + 'Nitya', + 'Nadiya', + 'Katerin', + 'Bruna', + 'Varsha', + 'Yaretsi', + 'Xitlalli', + 'Leshia', + 'Eda', + 'Sheronda', + 'Malikah', + 'Tayah', + 'Briann', + 'Tasnim', + 'Jayonna', + 'Kenedy', + 'Anarosa', + 'Zaya', + 'Kerline', + 'Brinda', + 'Amna', + 'Desarae', + 'Sarrah', + 'Silva', + 'Steffani', + 'Almarosa', + 'Alyshia', + 'Ariell', + 'Breeanne', + 'Alyxandra', + 'Juliane', + 'Jesseca', + 'Janisha', + 'Donisha', + 'Darnisha', + 'Jakeria', + 'Kirsty', + 'Markeisha', + 'Breena', + 'Selin', + 'Nikisha', + 'Adreana', + 'Elois', + 'Arrianna', + 'Melenie', + 'Rayanna', + 'Kaelee', + 'Shakyra', + 'Clotee', + 'Jakeline', + 'Kalysta', + 'Cesia', + 'Ankita', + 'Cristela', + 'Shunta', + 'Mozella', + 'Chrissie', + 'Adora', + 'Ashanty', + 'Ashna', + 'Lehua', + 'Nohealani', + 'Shruthi', + 'Metzli', + 'Jakelin', + 'Jisel', + 'Mikenna', + 'Miroslava', + 'Mansi', + 'Daphney', + 'Amisha', + 'Adara', + 'Alexzandria', + 'Alliah', + 'Yuriana', + 'Nanea', + 'Kahealani', + 'Ritika', + 'Arica', + 'Amayrani', + 'Kealani', + 'Dorina', + 'Lucienne', + 'Estrellita', + 'Kimberlin', + 'Lai', + 'Yovanna', + 'Rebekkah', + 'Azra', + 'Nada', + 'Gabryella', + 'Avigayil', + 'Binta', + 'Devoiry', + 'Raeanna', + 'Arlena', + 'Briauna', + 'Itati', + 'Grabiela', + 'Noella', + 'Teaghan', + 'Tzippy', + 'Faiza', + 'Zaara', + 'Tehilla', + 'Miki', + 'Sendy', + 'Kassondra', + 'Katherina', + 'Lissete', + 'Livier', + 'Lauran', + 'Dandrea', + 'Chelse', + 'Lizmarie', + 'Sunday', + 'Haidee', + 'Carrissa', + 'Nicholette', + 'Katey', + 'Katheryne', + 'Katty', + 'Kimia', + 'Leeanne', + 'Lizmary', + 'Jani', + 'Emmanuella', + 'Jahniya', + 'Talar', + 'Sintia', + 'Narda', + 'Chriselda', + 'Candance', + 'Delorise', + 'Daysy', + 'Lusine', + 'Raeanne', + 'Cherylann', + 'Ayat', + 'Halima', + 'Zissel', + 'Courtni', + 'Adahli', + 'Der', + 'Emree', + 'Brynlie', + 'Cherlyn', + 'Bostyn', + 'Francie', + 'Oaklie', + 'Shakeerah', + 'Hertha', + 'Haneefah', + 'Taheerah', + 'Nikkia', + 'Sheryll', + 'Donnabelle', + 'Teddi', + 'Jodee', + 'Tammera', + 'Janylah', + 'Laquesha', + 'Penina', + 'Gracee', + 'Thomasine', + 'Janyce', + 'Randie', + 'Mela', + 'Alka', + 'Cordia', + 'Shaquetta', + 'Mi', + 'Jaquetta', + 'Yoshiye', + 'Haruye', + 'Yoneko', + 'Fumi', + 'Wava', + 'Congetta', + 'Denee', + 'Kandyce', + 'Soraida', + 'Triana', + 'Kenedi', + 'Abena', + 'Talisha', + 'Rochell', + 'Sharisse', + 'Tijuana', + 'Amiee', + 'Nyesha', + 'Towana', + 'Lore', + 'Melodye', + 'Hayli', + 'Joyelle', + 'Shareen', + 'Amarilis', + 'Takiyah', + 'Takiya', + 'Keysha', + 'Feige', + 'Diahann', + 'Kloie', + 'Laynee', + 'Mariely', + 'Rainey', + 'Alizabeth', + 'Alyssandra', + 'Cambry', + 'Jadelynn', + 'Marylynn', + 'Keoka', + 'Jamaica', + 'Lus', + 'Shonta', + 'Kameelah', + 'Danell', + 'Evamarie', + 'Francoise', + 'Beata', + 'Caylie', + 'Elexa', + 'Joscelin', + 'Hessie', + 'Alazay', + 'Robena', + 'Texie', + 'Clarine', + 'Makennah', + 'Arletha', + 'Willette', + 'Amee', + 'Jetaun', + 'Anyia', + 'Aryssa', + 'Bonni', + 'Graciella', + 'Haileigh', + 'Sharae', + 'Shanea', + 'Ieisha', + 'Porche', + 'Teanna', + 'Ashanta', + 'Taiya', + 'Nicolett', + 'Naisha', + 'Sharice', + 'Madelein', + 'Kimberle', + 'Monifah', + 'Cameo', + 'Evelynne', + 'Edlyn', + 'Porcha', + 'Maricel', + 'Waleska', + 'Shakeena', + 'Shavone', + 'Ashlynne', + 'Yahira', + 'Shamecca', + 'Yashira', + 'Sherell', + 'Fiorela', + 'Nansi', + 'Shawntae', + 'Poonam', + 'Shala', + 'Kellyn', + 'Jazzmyn', + 'Asya', + 'Shatoya', + 'Yury', + 'Weronika', + 'Dawnette', + 'Lorita', + 'Michaelle', + 'Tomi', + 'Abbi', + 'Maudry', + 'Jaylinn', + 'Kynzie', + 'Lynnlee', + 'Madisson', + 'Denese', + 'Devona', + 'Sharika', + 'Sharilyn', + 'Zayna', + 'Janalee', + 'Sherril', + 'Timika', + 'Lynelle', + 'Rolayne', + 'Lubertha', + 'Jariah', + 'Kamala', + 'Taffy', + 'Marquetta', + 'Honora', + 'Frederica', + 'Monalisa', + 'Rashonda', + 'Francene', + 'Diedra', + 'Ceara', + 'Marylouise', + 'Kenesha', + 'Aisley', + 'Donnalee', + 'Genisis', + 'Debroah', + 'Helayne', + 'Raelee', + 'Maryrose', + 'Yalonda', + 'Chyla', + 'Edelmira', + 'Roselle', + 'Alyssah', + 'Brenley', + 'Gaynelle', + 'Shelvie', + 'Mackayla', + 'Linley', + 'Allizon', + 'Alonna', + 'Kendalyn', + 'Jozlyn', + 'Gwenn', + 'Jina', + 'Zariya', + 'Rosabella', + 'Emrie', + 'Tamu', + 'Senta', + 'Myia', + 'Emberlyn', + 'Emorie', + 'Arantxa', + 'Richele', + 'Christianne', + 'Lashan', + 'Koren', + 'Buffie', + 'Ronnette', + 'Marna', + 'Tuesday', + 'Helga', + 'Emilyn', + 'Cailee', + 'Shaquilla', + 'Dyamond', + 'Gerda', + 'Mckynzie', + 'Khloie', + 'Kendyll', + 'Maryfrances', + 'Khadejah', + 'Annalie', + 'Adaya', + 'Akia', + 'Markia', + 'Iyla', + 'Kaely', + 'Rafaella', + 'Tali', + 'Sukhmani', + 'Mili', + 'Kaylanie', + 'Maribelle', + 'Zharia', + 'Georgeanne', + 'Shamekia', + 'Siyona', + 'Layah', + 'Maylani', + 'Elianah', + 'Ellena', + 'Elyanna', + 'Yanilen', + 'Jashanti', + 'Lakita', + 'Juanell', + 'Caley', + 'Annella', + 'Vinita', + 'Zakiyah', + 'Sherian', + 'Palmira', + 'Delpha', + 'Creola', + 'Veta', + 'Sheneka', + 'Ameria', + 'Keonna', + 'Nathali', + 'Vaishnavi', + 'Zurisadai', + 'Mily', + 'Aalyah', + 'Hasini', + 'Irelynn', + 'Taneshia', + 'Lashanti', + 'Shatavia', + 'Shantoria', + 'Avelina', + 'Vanya', + 'Erline', + 'Surina', + 'Maribella', + 'Julieana', + 'Jazel', + 'Kalissa', + 'Marlis', + 'Hadasa', + 'Iveth', + 'Miliani', + 'Leiana', + 'Devynn', + 'Ahtziry', + 'Shilah', + 'Sicily', + 'Ashari', + 'Yarenis', + 'Tamiah', + 'Annis', + 'Azzie', + 'Sedalia', + 'Maebell', + 'Empress', + 'Fairy', + 'Najma', + 'Loreta', + 'Suhayla', + 'Sundus', + 'Vayda', + 'Doshia', + 'Ahlam', + 'Lashondra', + 'Ryanna', + 'Lala', + 'Merline', + 'Severa', + 'Kymora', + 'Fae', + 'Jameka', + 'Othella', + 'Wyoma', + 'Ailee', + 'Aishani', + 'Fransisca', + 'Noma', + 'Meztli', + 'Miliana', + 'Navaeh', + 'Swara', + 'Malillany', + 'Jaina', + 'Dia', + 'Ivyanna', + 'Jamira', + 'Jazaria', + 'Oletha', + 'Julieth', + 'Avia', + 'Elizebeth', + 'Yareni', + 'Korra', + 'Miraya', + 'Bernetta', + 'Helyn', + 'Suhaylah', + 'Laina', + 'Lassie', + 'Anyae', + 'Maleena', + 'Nirvana', + 'Danely', + 'Keilana', + 'Hildur', + 'Mariaclara', + 'Toshie', + 'Maniyah', + 'Hanako', + 'Asako', + 'Hiroko', + 'Hisae', + 'Suraya', + 'Kaileen', + 'Pearla', + 'Layal', + 'Batoul', + 'Johannah', + 'Gizel', + 'Venecia', + 'Yanelly', + 'Atianna', + 'Apple', + 'Arizbeth', + 'Sriya', + 'Natania', + 'Mayline', + 'Emmagrace', + 'Meriam', + 'Laree', + 'Tempie', + 'Sedonia', + 'Evalee', + 'Laquana', + 'Sheli', + 'Liesl', + 'Hazeline', + 'Blanchie', + 'Samyra', + 'Keelie', + 'Krislyn', + 'Yanelis', + 'Addysen', + 'Inis', + 'Tammra', + 'Johnette', + 'Amery', + 'Alayza', + 'Alaiyah', + 'Abree', + 'Amri', + 'Anapaula', + 'Jacelynn', + 'Kenzleigh', + 'Kenzlee', + 'Jaelah', + 'Brenlee', + 'Avalee', + 'Paizley', + 'Columbia', + 'Benedetta', + 'Daeja', + 'Myeshia', + 'Jeanene', + 'Terina', + 'Ethyl', + 'Oliwia', + 'Taniah', + 'Yaiza', + 'Eveline', + 'Monnie', + 'Margherita', + 'Jayana', + 'Macil', + 'Leontine', + 'Catera', + 'Wynelle', + 'Eldana', + 'Sallyann', + 'Yolande', + 'Marybelle', + 'Leanore', + 'Clothilde', + 'Tonita', + 'Kimaya', + 'Sumayah', + 'Latrenda', + 'Kelleen', + 'Deatrice', + 'Madelon', + 'Phyliss', + 'Argelia', + 'Mellie', + 'Emmah', + 'Jorley', + 'Muna', + 'Daphine', + 'Darina', + 'Bliss', + 'Karyl', + 'Taelynn', + 'Blenda', + 'Tonika', + 'Jerrilyn', + 'Sahra', + 'Keilyn', + 'Pearlene', + 'Arrie', + 'Ellene', + 'Fredericka', + 'Ladawn', + 'Maudell', + 'Rahma', + 'Jaylie', + 'Jaidah', + 'Vernetta', + 'Aleya', + 'Aubreigh', + 'Alaysha', + 'Adena', + 'Jacara', + 'Elfriede', + 'Maysel', + 'Munira', + 'Mumtaz', + 'Dorathy', + 'Chanin', + 'Ronette', + 'Maymie', + 'Providencia', + 'Mirta', + 'Loida', + 'Blakelyn', + 'Bentleigh', + 'Alliana', + 'Aleen', + 'Daliyah', + 'Jodene', + 'Johanne', + 'Timeka', + 'Ilhan', + 'Aloma', + 'Maris', + 'Arlyne', + 'Jene', + 'Hazelene', + 'Shakela', + 'Maida', + 'Maycie', + 'Makynlee', + 'Kawanda', + 'Consuella', + 'Sephora', + 'Andrianna', + 'Joshlyn', + 'Hollyn', + 'Kyliee', + 'Adaly', + 'Dailyn', + 'Averee', + 'Berklee', + 'Marly', + 'Gianella', + 'Ekaterina', + 'Colene', + 'Dayonna', + 'Shareka', + 'Roshni', + 'Latifa', + 'Merilyn', + 'Vernelle', + 'Marlyce', + 'Sabrena', + 'Jeneen', + 'Genie', + 'Lawanna', + 'Tashara', + 'Kayzlee', + 'Skylie', + 'Iyonna', + 'Honesti', + 'Cherylene', + 'Tahira', + 'Chizuko', + 'Aneesah', + 'Helmi', + 'Katrena', + 'Shyanna', + 'Zeola', + 'Lempi', + 'Arliss', + 'Madgie', + 'Verlie', + 'Ardys', + 'Twanda', + 'Kareemah', + 'Chardae', + 'Arlinda', + 'Darlena', + 'Karee', + 'Lorry', + 'Rolande', + 'Marlane', + 'Lelah', + 'Zahria', + 'Michalene', + 'Nayelis', + 'Abbigale', + 'Lorretta', + 'Sheril', + 'Priscille', + 'Cleda', + 'Kerrigan', + 'Wanita', + 'Ambria', + 'Wanetta', + 'Ebone', + 'Georgianne', + 'Karleen', + 'Laural', + 'Jonette', + 'Sharie', + 'Francina', + 'Yarelis', + 'Tempestt', + 'Kamie', + 'Julene', + 'Londa', + 'Haniya', + 'Kristeen', + 'Classie', + 'Nakiyah', + 'Valinda', + 'Kamree', + 'Micheline', + 'Mckaylee', + 'Prescilla', + 'Shaylynn', + 'Donelda', + 'Fayetta', + 'Terrye', + 'Dorthey', + 'Azilee', + 'Juanda', + 'Eustolia', + 'Nakeisha', + 'Hira', + 'Tarrah', + 'Jamyra', + 'Azaleah', + 'Aveline', + 'Chanae', + 'Andreana', + 'Banesa', + 'Berenis', + 'Brittini', + 'Orianna', + 'Reet', + 'Rayah', + 'Sofi', + 'Japji', + 'Kensie', + 'Roshonda', + 'Agripina', + 'Blasa', + 'Anevay', + 'Akari', + 'Krissi', + 'Maily', + 'Kitzia', + 'Keilly', + 'Raveen', + 'Kaiah', + 'Juliett', + 'Jocelynne', + 'Eowyn', + 'Calie', + 'Ebonee', + 'Chelcie', + 'Kayci', + 'Lauralee', + 'Trenity', + 'Deborrah', + 'Imagene', + 'Akasha', + 'Analaura', + 'Liani', + 'Lizania', + 'Lucina', + 'Melaine', + 'Sanah', + 'Stepanie', + 'Zabrina', + 'Janaye', + 'Jelena', + 'Kaylina', + 'Diavian', + 'Tasnia', + 'Nusrat', + 'Ashleymarie', + 'Maheen', + 'Ndeye', + 'Yumi', + 'Vittoria', + 'Amyra', + 'Yakelin', + 'Yudith', + 'Yumalay', + 'Juliza', + 'Daila', + 'Daenerys', + 'Calissa', + 'Tahirah', + 'Laquasia', + 'Jenay', + 'Crystina', + 'Eleonore', + 'Inessa', + 'Irine', + 'Vennie', + 'Oda', + 'Laurine', + 'Lavera', + 'Saraya', + 'Kerin', + 'Itzia', + 'Jennessa', + 'Katerine', + 'Rosselyn', + 'Leidy', + 'Adamariz', + 'Adylene', + 'Aylen', + 'Aniela', + 'Aleesha', + 'Alyssamarie', + 'Ainara', + 'Emalie', + 'Darlin', + 'Inna', + 'Emmely', + 'Eriana', + 'Esbeidy', + 'Chenelle', + 'Janise', + 'Sherrell', + 'Basilia', + 'Malayna', + 'Hilinai', + 'Mardell', + 'Romi', + 'Rosena', + 'Violett', + 'Zaylah', + 'Taia', + 'Anisah', + 'Esli', + 'Cleopatra', + 'Carisma', + 'Dezaray', + 'Swayze', + 'Raeven', + 'Neiva', + 'Myeisha', + 'Shelsea', + 'Yissel', + 'Velinda', + 'Josseline', + 'Denasia', + 'Digna', + 'Keiana', + 'Clytee', + 'Vernette', + 'Cheyene', + 'Roshunda', + 'Telisha', + 'Nilah', + 'Ayda', + 'Zykia', + 'Isabellamarie', + 'Melanee', + 'Laylanie', + 'Ajah', + 'Guiliana', + 'Oliva', + 'Mikela', + 'Mirabelle', + 'Nabiha', + 'Jasmina', + 'Hendy', + 'Ita', + 'Elif', + 'Reola', + 'Jamyah', + 'Tempest', + 'Arletta', + 'Keaira', + 'Ibeth', + 'Jerolyn', + 'Nelta', + 'Alishba', + 'Crisol', + 'Sabreena', + 'Silver', + 'Toba', + 'Yunuen', + 'Rishika', + 'Naomie', + 'Brittanya', + 'Annasophia', + 'Ayumi', + 'Jayleene', + 'Emmily', + 'Lyssa', + 'Natoya', + 'Vallerie', + 'Andee', + 'Annastasia', + 'Mazzy', + 'Zinnia', + 'Sheran', + 'Sumaiya', + 'Tasneem', + 'Aniylah', + 'Dua', + 'Tausha', + 'Jabria', + 'Lanora', + 'Janeli', + 'Mileydi', + 'Mikaella', + 'Ryah', + 'Rolonda', + 'Ajanae', + 'Ianna', + 'Xaria', + 'Winni', + 'Marializ', + 'Aidel', + 'Jonae', + 'Sanam', + 'Mao', + 'Tesia', + 'Yanina', + 'Brieana', + 'Genova', + 'Lashanae', + 'Anneke', + 'Siarra', + 'Sharhonda', + 'Zeldy', + 'Saron', + 'Johnisha', + 'Katelynne', + 'Janneth', + 'Corayma', + 'Helvi', + 'Asucena', + 'Lachelle', + 'Solmayra', + 'Tavia', + 'Marlina', + 'Rachal', + 'Sunni', + 'Nycole', + 'Aliannah', + 'Nafisa', + 'Simi', + 'Suki', + 'Jadalynn', + 'Kezia', + 'Athziri', + 'Huda', + 'Evy', + 'Jailah', + 'Jaselle', + 'Jaslyne', + 'Dalyla', + 'Emeraude', + 'Mahika', + 'Yoanna', + 'Fraida', + 'Tannia', + 'Selenne', + 'Analiz', + 'Angelene', + 'Anacristina', + 'Kylea', + 'Naydelyn', + 'Lecia', + 'Gitel', + 'Shareese', + 'Cassady', + 'Diem', + 'Perlita', + 'Monigue', + 'Marisha', + 'Emillee', + 'Kareli', + 'Shandreka', + 'Kerrin', + 'Tram', + 'Nohelani', + 'Monic', + 'Brandice', + 'Johnetta', + 'Evangelia', + 'Shakina', + 'Shunda', + 'Robbi', + 'Ariatna', + 'Shantae', + 'Sorangel', + 'Valene', + 'Aletta', + 'Libbie', + 'Marifer', + 'Deitra', + 'Despina', + 'Hayle', + 'Kassidi', + 'Dayrin', + 'Anjelina', + 'Gimena', + 'Llesenia', + 'Rainbow', + 'Muskaan', + 'Judit', + 'Kyley', + 'Tanna', + 'Luci', + 'Altagracia', + 'Kilee', + 'Kamry', + 'Kalyssa', + 'Jadeyn', + 'Virgen', + 'Damita', + 'Leinaala', + 'Illeana', + 'Nneka', + 'Onika', + 'Aralyn', + 'Mahalia', + 'Marelyn', + 'Jalene', + 'Bobbiejo', + 'Apollonia', + 'Anjuli', + 'Ricarda', + 'Fusako', + 'Michie', + 'Janira', + 'Citlalic', + 'Jannelle', + 'Tiffini', + 'Elisia', + 'Racine', + 'Marybel', + 'Xitlally', + 'Tynesha', + 'Sharay', + 'Shamara', + 'Aleene', + 'Rayssa', + 'Carlyn', + 'Falisha', + 'Lasandra', + 'Trinh', + 'Seema', + 'Tonianne', + 'Destani', + 'Nairobi', + 'Tomica', + 'Raena', + 'Ivania', + 'Odaliz', + 'Lilybeth', + 'Sheyenne', + 'Tereza', + 'Yuka', + 'Baleria', + 'Ayiana', + 'Floree', + 'Jhoanna', + 'Shakila', + 'Meleah', + 'Monserath', + 'Lelani', + 'Conception', + 'Zowie', + 'Teah', + 'Takayla', + 'Teaira', + 'Karyssa', + 'Delina', + 'Kamaile', + 'Rut', + 'Reanne', + 'Zamantha', + 'Ellyse', + 'Jisela', + 'Latonja', + 'Eiko', + 'Aylene', + 'Atziry', + 'Avila', + 'Andreya', + 'Delyla', + 'Aashna', + 'Dacia', + 'Shavonda', + 'Desirey', + 'Matea', + 'Makailah', + 'Henessy', + 'Naliyah', + 'Charlise', + 'Keirsten', + 'Ressie', + 'Halia', + 'Gweneth', + 'Manda', + 'Lilinoe', + 'Mariselda', + 'Tajuana', + 'Mahima', + 'Noeli', + 'Yanelli', + 'Sole', + 'Saloni', + 'Annistyn', + 'Marcille', + 'Thresa', + 'Cerenity', + 'Samnatha', + 'Alexah', + 'Analie', + 'Aryah', + 'Jazline', + 'Evony', + 'Erandy', + 'Jezelle', + 'Kamara', + 'Emelina', + 'Kadance', + 'Masae', + 'Davonna', + 'Shamaya', + 'Shalynn', + 'Rima', + 'Toria', + 'Zamira', + 'Cerina', + 'Fujiko', + 'Armine', + 'Morganne', + 'Gicela', + 'Desree', + 'Khaila', + 'Nikayla', + 'Kennedie', + 'Marylu', + 'Ilyssa', + 'Jatziri', + 'Shianna', + 'Dharma', + 'Resa', + 'Abra', + 'Neely', + 'Imo', + 'Betzabeth', + 'Briceyda', + 'Karenna', + 'Jakhia', + 'Ramiyah', + 'Khaliyah', + 'Tocarra', + 'Milee', + 'Athina', + 'Maleigha', + 'Shalyn', + 'Syliva', + 'Roseline', + 'Claira', + 'Jisselle', + 'Kiely', + 'Marisabel', + 'Maryanna', + 'Melena', + 'Mylene', + 'Mariangela', + 'Mailey', + 'Sonora', + 'Siana', + 'Shreeya', + 'Sevana', + 'Samhita', + 'Jackelyne', + 'Kyrstin', + 'Anslie', + 'Samella', + 'Jewelia', + 'Sammye', + 'Ayline', + 'Navneet', + 'Charlesetta', + 'Raye', + 'Yulonda', + 'Esmerelda', + 'Gianina', + 'Danessa', + 'Calia', + 'Everlena', + 'Sadaf', + 'Analucia', + 'Meriah', + 'Gwendalyn', + 'Disha', + 'Katana', + 'Kalaya', + 'Kaeley', + 'Tyonna', + 'Rozella', + 'Marjean', + 'Conchita', + 'Kylynn', + 'Aasiyah', + 'Maelynn', + 'Kahla', + 'Prachi', + 'Tajanae', + 'Megumi', + 'Micheala', + 'Yanitza', + 'Geselle', + 'Reather', + 'Annalicia', + 'Bonna', + 'Lilliann', + 'Callia', + 'Brigit', + 'Quintina', + 'Fujie', + 'Jolanda', + 'Nanami', + 'Yosselin', + 'Jakelyn', + 'Kadeja', + 'Eveny', + 'Emaly', + 'Ciena', + 'Julliana', + 'Jareli', + 'Jaretzi', + 'Kailin', + 'Kimiye', + 'Ammie', + 'Kiona', + 'Sumayyah', + 'Terre', + 'Laryssa', + 'Marleni', + 'Kamira', + 'Yulanda', + 'Jonda', + 'Lania', + 'Pippa', + 'Jazariah', + 'Takeya', + 'Shatima', + 'Ysenia', + 'Mikki', + 'Necole', + 'Etha', + 'Williemae', + 'Margurite', + 'Leonarda', + 'Inocencia', + 'Dominika', + 'Laisa', + 'Haylea', + 'Annamay', + 'Azia', + 'Mckynlee', + 'Maddilyn', + 'Scotlyn', + 'Lillith', + 'Mertie', + 'Kynzee', + 'Joshlynn', + 'Maelee', + 'Daleiza', + 'Xyla', + 'Royalty', + 'Railynn', + 'Patrycja', + 'Dotty', + 'Leda', + 'Toshiba', + 'Nelma', + 'Yeni', + 'Ottilie', + 'Lyna', + 'Leslieann', + 'Onita', + 'Darcey', + 'Marya', + 'Africa', + 'Seferina', + 'Theola', + 'Ysidra', + 'Zita', + 'Cing', + 'Zailynn', + 'Jennilee', + 'Sharmon', + 'Tyechia', + 'Irmgard', + 'Shameika', + 'Jemima', + 'Jazzelle', + 'Adlee', + 'Aliyanna', + 'Acelyn', + 'Catalaya', + 'Brileigh', + 'Braylie', + 'Angelin', + 'Arianni', + 'Ariani', + 'Kennya', + 'Maelyn', + 'Lillee', + 'Maripaz', + 'Laikyn', + 'Kenslee', + 'Ileane', + 'Puja', + 'Oanh', + 'Jakara', + 'Shawntay', + 'Cendy', + 'Erianna', + 'Chloie', + 'Birtie', + 'Korin', + 'Jannett', + 'Shawntel', + 'Markisha', + 'Nastassja', + 'Shalene', + 'Alexya', + 'Cloie', + 'Exa', + 'Jentri', + 'Modena', + 'Veronique', + 'Daina', + 'Mechele', + 'Lakesia', + 'Kawanna', + 'Clotilde', + 'Diamonique', + 'Teyana', + 'Rheagan', + 'Shanece', + 'Yanique', + 'Taysha', + 'Ulyssa', + 'Jadzia', + 'Kadija', + 'Towanna', + 'Lurlene', + 'Sharri', + 'Rosenda', + 'Daphna', + 'Hermina', + 'Shaquanda', + 'Saachi', + 'Sena', + 'Yazaira', + 'Yatzil', + 'Anam', + 'Sparrow', + 'Anetra', + 'Nalayah', + 'Jaylenne', + 'Joya', + 'Kensi', + 'Khylee', + 'Lilyrose', + 'Iasia', + 'Jaliah', + 'Melda', + 'Armella', + 'Zyasia', + 'Nazia', + 'Shanasia', + 'Krystie', + 'Dorothe', + 'Thora', + 'Adelene', + 'Avaya', + 'Aurielle', + 'Ailany', + 'Andromeda', + 'Loa', + 'Cleora', + 'Darling', + 'Caliana', + 'Keniyah', + 'Crystel', + 'Dimitra', + 'Renate', + 'Zyriah', + 'Taegan', + 'Marygrace', + 'Mckinzie', + 'Nivea', + 'Rhian', + 'Amarissa', + 'Kadee', + 'Devani', + 'Khara', + 'Aishia', + 'Annell', + 'Jaslin', + 'Jaide', + 'Briahna', + 'Merary', + 'Lauraine', + 'Tywana', + 'Athanasia', + 'Chantay', + 'Loretha', + 'Anyiah', + 'Marvine', + 'Jennelle', + 'Hiedi', + 'Sunnie', + 'Panagiota', + 'Lanesha', + 'Amity', + 'Denyse', + 'Nataleigh', + 'Amyia', + 'Avrie', + 'Analysa', + 'Ameris', + 'Ambrielle', + 'Kynnedy', + 'Gracy', + 'Kaelie', + 'Heydi', + 'Latrese', + 'Lavonia', + 'Latrelle', + 'Lynetta', + 'Graceann', + 'Susette', + 'Sarabeth', + 'Arnetta', + 'Shelonda', + 'Myiesha', + 'Shila', + 'Pascale', + 'Zenja', + 'Madelene', + 'Lalena', + 'Doria', + 'Dagmar', + 'Griselle', + 'Nitza', + 'Moraima', + 'Miguelina', + 'Brittania', + 'Emmalin', + 'Novie', + 'Chavonne', + 'Lashana', + 'Quyen', + 'Gennifer', + 'Zaryah', + 'Paytin', + 'Keeli', + 'Kolbi', + 'Maddyson', + 'Jackqueline', + 'Arnita', + 'Brynnley', + 'Edelyn', + 'Arial', + 'Yaneliz', + 'Ena', + 'Barbaraann', + 'Glendora', + 'Heavyn', + 'Neomi', + 'Rebbecca', + 'Laketa', + 'Renetta', + 'Carline', + 'Nezzie', + 'Shaneeka', + 'Desaray', + 'Hiromy', + 'Hallee', + 'Halli', + 'Sheba', + 'Tahisha', + 'Paetyn', + 'Katisha', + 'Joyell', + 'Joyel', + 'Zoei', + 'Zamiya', + 'Raygan', + 'Clydie', + 'Missouri', + 'Debany', + 'Kalisha', + 'Niurka', + 'Beverlyn', + 'Bell', + 'Zuly', + 'Lakayla', + 'Lainee', + 'Kynli', + 'Lundyn', + 'Erynn', + 'Braleigh', + 'Allena', + 'Lashanna', + 'Shaunya', + 'Tykia', + 'Leeba', + 'Bassheva', + 'Kandra', + 'Breyana', + 'Geovana', + 'Joandra', + 'Jessyka', + 'Analilia', + 'Charna', + 'Josefita', + 'Laurin', + 'Casi', + 'Jeniah', + 'Koraima', + 'Vivi', + 'Merlina', + 'Marinna', + 'Soriya', + 'Sarayu', + 'Ma', + 'Adali', + 'Abbygale', + 'Avonlea', + 'Bellah', + 'Makeyla', + 'Maanya', + 'Hania', + 'Ellah', + 'Esmee', + 'Jaylean', + 'Verlene', + 'Kendria', + 'Kasondra', + 'Kadesha', + 'Kadedra', + 'Reizel', + 'Reizy', + 'Sheryle', + 'Elka', + 'Caileigh', + 'Meya', + 'Rondi', + 'Janetta', + 'Dwana', + 'Yakira', + 'Donetta', + 'Laurissa', + 'Jordann', + 'Jenice', + 'Hasmik', + 'Mychelle', + 'Shabnam', + 'Sarahann', + 'Shaylene', + 'Zuleica', + 'Verenise', + 'Dejanee', + 'Alyx', + 'Breyanna', + 'Anum', + 'Jamesia', + 'Asheley', + 'Keya', + 'Lyzette', + 'Rossy', + 'Terilyn', + 'Rahaf', + 'Anabia', + 'Neala', + 'Payal', + 'Taheera', + 'Nakhia', + 'Shaela', + 'Krupa', + 'Suriya', + 'Victory', + 'Viviane', + 'Habiba', + 'Fortune', + 'Farida', + 'Erina', + 'Ranya', + 'Tifani', + 'Surie', + 'Aastha', + 'Joella', + 'Sherida', + 'Vonnie', + 'Bluma', + 'Gianny', + 'Naziyah', + 'Taylie', + 'Jakia', + 'Timia', + 'Farren', + 'Skylin', + 'Sabiha', + 'Nashley', + 'Blimi', + 'Annita', + 'Kristianna', + 'Delena', + 'Dalina', + 'Kyasia', + 'Cathlene', + 'Karalee', + 'Merilee', + 'Monette', + 'Asharia', + 'Jacquelina', + 'Nishat', + 'Charlcie', + 'Sukanya', + 'Celines', + 'Rashell', + 'Nadja', + 'Lamiyah', + 'Najae', + 'Zipporah', + 'Rawan', + 'Tailor', + 'Denesha', + 'Masiel', + 'Nida', + 'Assata', + 'Infiniti', + 'Cresencia', + 'Omega', + 'Meher', + 'Maneh', + 'Noura', + 'Yanine', + 'Maral', + 'Malori', + 'Safia', + 'Saori', + 'Vesper', + 'Audrinna', + 'Dea', + 'Kahlia', + 'Eliora', + 'Isley', + 'Laurinda', + 'Mignon', + 'Debie', + 'Denette', + 'Jolyn', + 'Casondra', + 'Donnisha', + 'Elysse', + 'Lazaria', + 'Aleia', + 'Shelbee', + 'Ivone', + 'Mazal', + 'Sherley', + 'Shantia', + 'Christelle', + 'Tatjana', + 'Roselia', + 'Pebbles', + 'Cleotilde', + 'Erendida', + 'Chardonnay', + 'Brittiny', + 'Brittanny', + 'Scarleth', + 'Mehar', + 'Neila', + 'Sofiya', + 'Lakshmi', + 'Lilianne', + 'Akeiba', + 'Shabreka', + 'Joannie', + 'Samiha', + 'Fatma', + 'Itzell', + 'Envy', + 'Maybelline', + 'Nashly', + 'Rya', + 'Kaelani', + 'Kailana', + 'Aylah', + 'Bellamarie', + 'Marizol', + 'Malyssa', + 'Madai', + 'Neelam', + 'Ysamar', + 'Sulma', + 'Sueling', + 'Song', + 'Sharayah', + 'Melisha', + 'Ashliegh', + 'Melodi', + 'Belem', + 'Chrystina', + 'Tonantzin', + 'Setareh', + 'Valeri', + 'Yaffa', + 'Niara', + 'Mame', + 'Janasia', + 'Flo', + 'Gustavia', + 'Lanya', + 'Nanie', + 'Velta', + 'Dot', + 'Luberta', + 'Ledora', + 'Olean', + 'Abbigayle', + 'Hadeel', + 'Rayma', + 'Mayola', + 'Nonnie', + 'Voncille', + 'Heloise', + 'Nolia', + 'Victorine', + 'Yola', + 'Vella', + 'Terrilyn', + 'Noelie', + 'Alean', + 'Allean', + 'Lorean', + 'Josiephine', + 'Heba', + 'Kerrianne', + 'Odeal', + 'Aigner', + 'Anaclara', + 'Gudrun', + 'Valborg', + 'Trenice', + 'Ardath', + 'Aune', + 'Teresia', + 'Lesha', + 'Dewanna', + 'Arlyce', + 'Jayliana', + 'Orene', + 'Paralee', + 'Jamyia', + 'Kemiyah', + 'Fredia', + 'Amyiah', + 'Doreatha', + 'Lashanta', + 'Cerissa', + 'Kawana', + 'Arizona', + 'Shanetta', + 'Jalesa', + 'Asmaa', + 'Garnette', + 'Clella', + 'Artemisa', + 'Liliya', + 'Oretha', + 'Adna', + 'Amyri', + 'Tyshae', + 'Maryan', + 'Santanna', + 'Bushra', + 'Jamyla', + 'Earma', + 'Delsie', + 'Verlean', + 'Sherena', + 'Carmelite', + 'Chari', + 'Darlean', + 'Shamia', + 'Audryna', + 'Genevia', + 'Avie', + 'Tamora', + 'Lavonna', + 'September', + 'Sharolyn', + 'Athziry', + 'Alyiah', + 'Aleina', + 'Alesandra', + 'Amoreena', + 'Nykia', + 'Drea', + 'Galilee', + 'Ainslie', + 'Ishita', + 'Jenavie', + 'Jezabel', + 'Erandi', + 'Evana', + 'Jiana', + 'Laniah', + 'Britanny', + 'Sanika', + 'Solash', + 'Laasya', + 'Nairi', + 'Leighla', + 'Kaiyah', + 'Suhana', + 'Taliya', + 'Maleia', + 'Candee', + 'Ninette', + 'Eugena', + 'Lateisha', + 'Salvatrice', + 'Quaneisha', + 'Mertis', + 'Bebe', + 'Rida', + 'Takyra', + 'Floye', + 'Christell', + 'Ozelle', + 'Juanice', + 'Genia', + 'Shaundra', + 'Shanin', + 'Wendee', + 'Cynde', + 'Adalynne', + 'Adelin', + 'Hayven', + 'Ayra', + 'Chimamanda', + 'Kenzlie', + 'Taylynn', + 'Zerenity', + 'Kynsleigh', + 'Dorthea', + 'Alley', + 'Melrose', + 'Keyondra', + 'Anglia', + 'Lynnea', + 'Tamira', + 'Terisa', + 'Tona', + 'Isaly', + 'Jeimy', + 'Giannah', + 'Leilanni', + 'Leya', + 'Quetzali', + 'Naylene', + 'Misaki', + 'Amely', + 'Donette', + 'Charlayne', + 'Selia', + 'Kittie', + 'Tamaya', + 'Lenna', + 'Zykerria', + 'Teisha', + 'Terrea', + 'Alita', + 'Bunny', + 'Deniece', + 'Inge', + 'Takira', + 'Monesha', + 'Mahala', + 'Donica', + 'Fortunata', + 'Valrie', + 'Zayah', + 'Ziyah', + 'Vela', + 'Vassie', + 'Omie', + 'Nadean', + 'Annalynn', + 'Adah', + 'Edmae', + 'Aalayah', + 'Yuritzy', + 'Ytzel', + 'Svetlana', + 'Soha', + 'Alfredia', + 'Kylei', + 'Landrey', + 'Lariyah', + 'Rozlyn', + 'Sakina', + 'Greer', + 'Bula', + 'Eura', + 'Harmonee', + 'Pecola', + 'Noreta', + 'Laveda', + 'Retta', + 'Rozlynn', + 'Skarlet', + 'Snow', + 'Zoha', + 'Sophiarose', + 'Anglea', + 'Itzabella', + 'Elanie', + 'Calirose', + 'Adhya', + 'Amaiyah', + 'Lavender', + 'Leylanie', + 'Kaliana', + 'Quetzaly', + 'Helon', + 'Nalia', + 'Cipriana', + 'Martyna', + 'Pola', + 'Dierra', + 'Maximina', + 'Sherica', + 'Murlene', + 'Berna', + 'Bernarda', + 'Ettie', + 'Laiken', + 'Hensley', + 'Fontella', + 'Modelle', + 'Timotea', + 'Venora', + 'Lakelyn', + 'Licia', + 'Laury', + 'Loralee', + 'Kamyah', + 'Verba', + 'Angelee', + 'Adalind', + 'Adaliz', + 'Ailynn', + 'Airi', + 'Alany', + 'Avika', + 'Avleen', + 'Leoni', + 'Saisha', + 'Savvy', + 'Philippa', + 'Jasneet', + 'Izabellah', + 'Elienai', + 'Kalayah', + 'Eureka', + 'Dionicia', + 'Zylah', + 'Zosia', + 'Yetzali', + 'Tigerlily', + 'Dorena', + 'Nakesha', + 'Lakenya', + 'Margarete', + 'Margarite', + 'Cloteal', + 'Adline', + 'Willadeen', + 'Anselma', + 'Marcheta', + 'Havyn', + 'Ilyanna', + 'Idalie', + 'Fallyn', + 'Emori', + 'Anzal', + 'Kalila', + 'Ellisyn', + 'Maddalyn', + 'Roslynn', + 'Hodan', + 'Emalynn', + 'Addy', + 'Adelyne', + 'Aizah', + 'Dalayza', + 'Cambri', + 'Annali', + 'Angelynn', + 'Caidence', + 'Auriana', + 'Azlynn', + 'Blakelee', + 'Brenleigh', + 'Tailynn', + 'Zyla', + 'Verline', + 'Pierina', + 'Panhia', + 'Valda', + 'Shela', + 'Uldine', + 'Vibha', + 'Wednesday', + 'Porshia', + 'Shabria', + 'Palmina', + 'Khristine', + 'Lannette', + 'Sandhya', + 'Janalyn', + 'Floreine', + 'Marchelle', + 'Minette', + 'Tawnia', + 'Wynne', + 'Sada', + 'Windi', + 'Clydene', + 'Shundra', + 'Joycie', + 'Delories', + 'Alvena', + 'Edmonia', + 'Denean', + 'Dhana', + 'Marjie', + 'Alicja', + 'Cammy', + 'Aryam', + 'Leonie', + 'Adrielle', + 'Felisita', + 'Tinnie', + 'Marinda', + 'Lamia', + 'Conchetta', + 'Naylah', + 'Sarayah', + 'Nataliya', + 'Delani', + 'Eknoor', + 'Ellee', + 'Maiah', + 'Mayumi', + 'Meara', + 'Kalliope', + 'Jewels', + 'Lanaya', + 'Yui', + 'Maxcine', + 'Yaqueline', + 'Yoceline', + 'Marilynne', + 'Maple', + 'Ronesha', + 'Marili', + 'Reema', + 'Rayana', + 'Aggie', + 'Talina', + 'Doristine', + 'Romelle', + 'Shaqueena', + 'Sharelle', + 'Caira', + 'Gelsey', + 'Tashawna', + 'Takeisha', + 'Jerlean', + 'Sunita', + 'Shalini', + 'Michaeline', + 'Audria', + 'Ronnisha', + 'Leonia', + 'Monna', + 'Ambra', + 'Corena', + 'Taren', + 'Alexiss', + 'Kajal', + 'Jordanne', + 'Kasia', + 'Brienna', + 'Gayane', + 'Deija', + 'Cidney', + 'Tabytha', + 'Raeleen', + 'Mkayla', + 'Harli', + 'Jassmin', + 'Ilo', + 'Lasheena', + 'Keianna', + 'Kally', + 'Makenzy', + 'Angelea', + 'Natasia', + 'Shaneequa', + 'Monay', + 'Moet', + 'Marcelline', + 'Shatia', + 'Sarafina', + 'Kaisha', + 'Tiffney', + 'Shenequa', + 'Sheretta', + 'Floria', + 'Alacia', + 'Kavita', + 'Kerianne', + 'Tameshia', + 'Jamye', + 'Shanese', + 'Latiqua', + 'Jesscia', + 'Johanny', + 'Daniqua', + 'Geneviev', + 'Bernadet', + 'Annice', + 'Megann', + 'Katee', + 'Nikeya', + 'Stavroula', + 'Tawna', + 'Sindia', + 'Marlaina', + 'Jury', + 'Tovah', + 'Shivonne', + 'Nekia', + 'Yvonnie', + 'Kyna', + 'Railey', + 'Xandria', + 'Genine', + 'Tashima', + 'Marycarmen', + 'Kiahna', + 'Jadynn', + 'Akua', + 'Eather', + 'Fatema', + 'Aiysha', + 'Allisa', + 'Ashleynicole', + 'Bobette', + 'Shandrika', + 'Hollace', + 'Chandni', + 'Cayley', + 'Brenae', + 'Areisy', + 'Annahi', + 'Anallely', + 'Klarisa', + 'Ayssa', + 'Jatavia', + 'Nohemy', + 'Mikyla', + 'Mariadelosang', + 'Shatina', + 'Kazandra', + 'Elsi', + 'Teryl', + 'Yennifer', + 'Destyni', + 'Damariz', + 'Areanna', + 'Everlean', + 'Lesslie', + 'Margrette', + 'Tuyet', + 'Jacquelene', + 'Grissel', + 'Walterine', + 'Shterna', + 'Gila', + 'Nabila', + 'Liel', + 'Sani', + 'Djeneba', + 'Angeliz', + 'Anari', + 'Amyrie', + 'Aissa', + 'Tichina', + 'Amariana', + 'Xiara', + 'Yamiles', + 'Isatou', + 'Airiana', + 'Carrigan', + 'Aldea', + 'Aarika', + 'Bryanne', + 'Alegandra', + 'Carrisa', + 'Andrina', + 'Casaundra', + 'Breanda', + 'Biviana', + 'Irena', + 'Denielle', + 'Lizzett', + 'Shaunice', + 'Sigourney', + 'Sona', + 'Paradise', + 'Lashanique', + 'Melaina', + 'Zoua', + 'Vaneza', + 'Tyresha', + 'Shyasia', + 'Tiyana', + 'Youa', + 'Zaneta', + 'Muskan', + 'Talissa', + 'Kennisha', + 'Lizandra', + 'Akosua', + 'Jaymi', + 'Chelby', + 'Chelci', + 'Aeriel', + 'Isamara', + 'Payge', + 'Hadja', + 'Fruma', + 'Fiza', + 'Fatumata', + 'Kabrina', + 'Feigy', + 'Zanaya', + 'Yanette', + 'Teairra', + 'Talor', + 'Kathrina', + 'Justeen', + 'Maryelizabeth', + 'Jannete', + 'Chantalle', + 'Haide', + 'Genelle', + 'Esthela', + 'Emilse', + 'Maegen', + 'Lyndsi', + 'Cristiana', + 'Clio', + 'Breindel', + 'Briyana', + 'Jamyria', + 'Jameshia', + 'Kadeshia', + 'Jamisha', + 'Faige', + 'Aishah', + 'Lorette', + 'Nandi', + 'Nastasia', + 'Shada', + 'Shakeia', + 'Shaneice', + 'Yanel', + 'Teryn', + 'Shaylyn', + 'Karimah', + 'Fabienne', + 'Shaianne', + 'Saleena', + 'Raychelle', + 'Pahoua', + 'Justyne', + 'Fransheska', + 'Katilyn', + 'Shadaya', + 'Quanasia', + 'Shantasia', + 'Nyasha', + 'Minahil', + 'Shahd', + 'Chani', + 'Bassy', + 'Zunairah', + 'Lynsie', + 'Charnelle', + 'Jaquana', + 'Taquana', + 'Shaasia', + 'Idelle', + 'Rogene', + 'Udy', + 'Devory', + 'Evanna', + 'Keisy', + 'Hadiya', + 'Brittainy', + 'Cortni', + 'Erikka', + 'Lindsie', + 'Mayraalejandra', + 'Topacio', + 'Elky', + 'Yita', + 'Sura', + 'Tiani', + 'Sadiya', + 'Kaitlen', + 'Jessicca', + 'Linna', + 'Stephy', + 'Hadia', + 'Jaiyana', + 'Aldina', + 'Frimy', + 'Tywanda', + 'Renarda', + 'Mardelle', + 'Alaijah', + 'Antoinetta', + 'Amyria', + 'Sheyanne', + 'Jackee', + 'Bina', + 'Khole', + 'Selenia', + 'Seidy', + 'Albertina', + 'Yoandra', + 'Yarelyn', + 'Kassaundra', + 'Lynzee', + 'Haneen', + 'Marshay', + 'Sharona', + 'Shanygne', + 'Nigeria', + 'Nechy', + 'Jhane', + 'Chrisette', + 'Gypsy', + 'Drusilla', + 'Milta', + 'Ranee', + 'Yvett', + 'Mykenzie', + 'Aracelia', + 'Vernessa', + 'Chekesha', + 'Cadance', + 'Moria', + 'Tsurue', + 'Yarisbel', + 'Verena', + 'Tomoe', + 'Breezy', + 'Swannie', + 'Tsuyuko', + 'Hisayo', + 'Gerianne', + 'Cailynn', + 'Adrionna', + 'Lillianne', + 'Eduarda', + 'Melinna', + 'Sanaiya', + 'Nohelia', + 'Zarela', + 'Yarethzy', + 'Sruthi', + 'Josefine', + 'Kiela', + 'Kersten', + 'Syriah', + 'Emaleigh', + 'Jazlynne', + 'Aeryn', + 'Danelly', + 'Dalylah', + 'Lexa', + 'Kherington', + 'Nivia', + 'Carolanne', + 'Sharlotte', + 'Vanda', + 'Deirdra', + 'Ilyse', + 'Judyann', + 'Venezia', + 'Mailee', + 'Latishia', + 'Ajla', + 'Lucine', + 'Shontell', + 'Rosiland', + 'Celinda', + 'Aanika', + 'Felicidad', + 'Denia', + 'Natsuko', + 'Analyse', + 'Angellina', + 'Brizeida', + 'Jazira', + 'Terah', + 'Reana', + 'Jennalyn', + 'Jenaya', + 'Kelani', + 'Miyuki', + 'Aracelie', + 'Dannika', + 'Danity', + 'Cadie', + 'Breelyn', + 'Kayra', + 'Mayli', + 'Malarie', + 'Tequilla', + 'Gerilyn', + 'Mieko', + 'Belynda', + 'Shamiyah', + 'Reaghan', + 'Ziya', + 'Rozanne', + 'Joyanne', + 'Zamaria', + 'Luiza', + 'Tamanika', + 'Kimya', + 'Patriciaann', + 'Eilene', + 'Bryna', + 'Yena', + 'Yarelly', + 'Maddyn', + 'Khylie', + 'Khyla', + 'Margueritte', + 'Ramya', + 'Jenea', + 'Jennavie', + 'Jazzlene', + 'Marelly', + 'Manya', + 'Lillyanne', + 'Gyselle', + 'Niyati', + 'Moana', + 'Kenosha', + 'Ezmeralda', + 'Anvitha', + 'Avelyn', + 'Dahlila', + 'Emmaly', + 'Dayamy', + 'Anajulia', + 'Mandee', + 'Valli', + 'Sharan', + 'Leasia', + 'Shiquita', + 'Malana', + 'Nadeen', + 'Parneet', + 'Lynna', + 'Saskia', + 'Samaiya', + 'Saffron', + 'Vianka', + 'Evey', + 'Ebelin', + 'Anishka', + 'Aneth', + 'Addelynn', + 'Kayly', + 'Alyzae', + 'Anniyah', + 'Ayme', + 'Alexsa', + 'Aidsa', + 'Elyn', + 'Illianna', + 'Greenlee', + 'Tinesha', + 'Sherline', + 'Yvanna', + 'Joslin', + 'Estee', + 'Lusia', + 'Nhung', + 'Janielle', + 'Smithie', + 'Yohanna', + 'Shanette', + 'Marilena', + 'Blannie', + 'Meleana', + 'Malie', + 'Jannine', + 'Kuulei', + 'Kawehi', + 'Velna', + 'Kuuipo', + 'Keani', + 'Tiffeny', + 'Billi', + 'Conni', + 'Elexia', + 'Sheily', + 'Mehak', + 'Ardelia', + 'Phung', + 'Aleasha', + 'Toyia', + 'Kalliopi', + 'Carrieann', + 'Shayal', + 'Brandye', + 'Shatisha', + 'Neola', + 'Pallavi', + 'Symantha', + 'Mackenzee', + 'Shalawn', + 'Krimson', + 'Jaquelinne', + 'Sonal', + 'Calysta', + 'Kaylamarie', + 'Kirah', + 'Belicia', + 'Anicia', + 'Aerin', + 'Marisel', + 'Priscella', + 'Lei', + 'Imaan', + 'Haruka', + 'Kila', + 'Jerusha', + 'Deva', + 'Charon', + 'Leida', + 'Deadra', + 'Areana', + 'Iriana', + 'Drenda', + 'Saadia', + 'Danne', + 'Jossalyn', + 'Kennadie', + 'Makaya', + 'Daelynn', + 'Daffne', + 'Galia', + 'Naida', + 'Yaira', + 'Latania', + 'Damarys', + 'Mireille', + 'Maribell', + 'Luzelena', + 'Anacani', + 'Sahira', + 'Shaylin', + 'Sejal', + 'Subrina', + 'Julaine', + 'Saby', + 'Zoraya', + 'Atalie', + 'Deseray', + 'Nacole', + 'Jennell', + 'Laneisha', + 'Ivie', + 'Darnella', + 'Lashone', + 'Lekeisha', + 'Puanani', + 'Uilani', + 'Donyale', + 'Terriann', + 'Marianela', + 'Josalynn', + 'Avari', + 'Blonnie', + 'Makya', + 'Seriah', + 'Nori', + 'Roselee', + 'Verbie', + 'Borghild', + 'Marcene', + 'Syretta', + 'Bama', + 'Eulene', + 'Chantale', + 'Shontae', + 'Mabell', + 'Hellon', + 'Shantanique', + 'Janki', + 'Dhara', + 'Buna', + 'Naeemah', + 'Tacara', + 'Shirleyann', + 'Tshwanda', + 'Nadege', + 'Georganne', + 'Leondra', + 'Fredricka', + 'Margaree', + 'Quincee', + 'Oaklynn', + 'Arlean', + 'Judee', + 'Nyoka', + 'Khia', + 'Kendia', + 'Mahek', + 'Anasia', + 'Jenin', + 'Gerline', + 'Elwillie', + 'Annsley', + 'Juhi', + 'Zettie', + 'Shacara', + 'Shantique', + 'Marijo', + 'Shakara', + 'Ersie', + 'Bionca', + 'Kolleen', + 'Ertha', + 'Chioma', + 'Roneisha', + 'Courtenay', + 'Altie', + 'Arla', + 'Delainey', + 'Rainelle', + 'Lockie', + 'Rayonna', + 'Nasiyah', + 'Zori', + 'Carollee', + 'Mima', + 'Irja', + 'Willadean', + 'Sigrid', + 'Myong', + 'Khaliah', + 'Sakeenah', + 'Saleemah', + 'Emmersyn', + 'Miyeko', + 'Brooksie', + 'Brailynn', + 'Raghad', + 'Nadira', + 'Hassana', + 'Toshiye', + 'Fumiye', + 'Kelise', + 'Angelis', + 'Earla', + 'Dilia', + 'Arwa', + 'Shaylie', + 'Synai', + 'Tanijah', + 'Jalaysia', + 'Charnita', + 'Marit', + 'Gaelle', + 'Shandiin', + 'Janelis', + 'Gatha', + 'Alahna', + 'Aniyla', + 'Mikelle', + 'Skai', + 'Merlinda', + 'Tariyah', + 'Arietta', + 'Terrika', + 'Elenor', + 'Ruthanna', + 'Evaline', + 'Abigaelle', + 'Alayjah', + 'Naysa', + 'Camya', + 'Pachia', + 'Kamia', + 'Sylvania', + 'Ambree', + 'Oakleigh', + 'Zania', + 'Murielle', + 'Charlyn', + 'Zykira', + 'Jestine', + 'Simonne', + 'Willodene', + 'Lyndee', + 'Sophonie', + 'Saddie', + 'Darlis', + 'Lynnda', + 'Marysa', + 'Seleena', + 'Raevyn', + 'Lilikoi', + 'Maiyer', + 'Kymberli', + 'Shayda', + 'Cassidee', + 'Jadira', + 'Delora', + 'Afsheen', + 'Adira', + 'Amena', + 'Canary', + 'Humaira', + 'Derricka', + 'Fatiha', + 'Xia', + 'Jaquelyne', + 'Aurianna', + 'Sarahjane', + 'Sanaz', + 'Taleen', + 'Teara', + 'Taiz', + 'Sharai', + 'Magally', + 'Manon', + 'Maizie', + 'Manisha', + 'Marisleysis', + 'Anjela', + 'Youlanda', + 'Jermani', + 'Elysha', + 'Claritza', + 'Gissela', + 'Icela', + 'Alixandria', + 'Asley', + 'Analuisa', + 'Maddalena', + 'Cortnee', + 'Coretha', + 'Audreanna', + 'Manal', + 'Kadijatou', + 'Pollie', + 'Mysti', + 'Tiffiany', + 'Corean', + 'Amiree', + 'Anner', + 'Cleone', + 'Lavone', + 'Fredna', + 'Konnie', + 'Robbyn', + 'Alica', + 'Bessy', + 'Aleesa', + 'Analleli', + 'Mischelle', + 'Bethani', + 'Baillie', + 'Odessie', + 'Erlene', + 'Marcile', + 'Edona', + 'Tylah', + 'Tyrah', + 'Rainell', + 'Precilla', + 'Genever', + 'Ajanee', + 'Chera', + 'Amye', + 'Monserratt', + 'Moorea', + 'Richa', + 'Willetta', + 'Shawne', + 'Trisa', + 'Lasonia', + 'Cleona', + 'Alizea', + 'Anayely', + 'Emelly', + 'Fionna', + 'Cerena', + 'Julyana', + 'Kaile', + 'Jacklin', + 'Brianca', + 'Ashleyann', + 'Richardine', + 'Kelcee', + 'Keyaira', + 'Mabelle', + 'Brecklyn', + 'Samyah', + 'Ayonna', + 'Mesha', + 'Tyeshia', + 'Tiffiney', + 'Tyara', + 'Azuri', + 'Merideth', + 'Hermie', + 'Leaner', + 'Mendi', + 'Kanoelani', + 'Kadeidra', + 'Akeela', + 'Lin', + 'Mindel', + 'Lashell', + 'Meegan', + 'Ia', + 'Ellamae', + 'Jasmen', + 'Nechuma', + 'Romilda', + 'Hiilei', + 'Osmara', + 'Keidy', + 'Rianne', + 'Afia', + 'Teylor', + 'Raquelle', + 'Grizelda', + 'Tasfia', + 'Laquasha', + 'Tandra', + 'Maeghan', + 'Kameshia', + 'Alara', + 'Emina', + 'Delaina', + 'Jacquetta', + 'Christena', + 'Topanga', + 'Viviann', + 'Eboney', + 'Kasha', + 'Sativa', + 'Secilia', + 'Niomi', + 'Neena', + 'Tanji', + 'Shandy', + 'Corryn', + 'Esly', + 'Silka', + 'Sanaii', + 'Annais', + 'Kaitlynne', + 'Epiphany', + 'Maniya', + 'Mali', + 'Madigan', + 'Sanii', + 'Jaeleen', + 'Faria', + 'Maralyn', + 'Johnae', + 'Lekesha', + 'Sharry', + 'Latecia', + 'Kimberl', + 'Charita', + 'Modean', + 'Marrie', + 'Lielle', + 'Zeina', + 'Pessel', + 'Sameera', + 'Eleonora', + 'Jannatul', + 'Coryn', + 'Dustie', + 'Demitria', + 'Jacqlyn', + 'Nekisha', + 'Latrecia', + 'Rabecca', + 'Malaysha', + 'Lugenia', + 'Elese', + 'Myrissa', + 'Lucrecia', + 'Lysandra', + 'Tarryn', + 'Tammey', + 'Bonnita', + 'Shiffy', + 'Shirel', + 'Clariza', + 'Analis', + 'Rechy', + 'Nusaiba', + 'Manahil', + 'Chamisa', + 'Almetta', + 'Moncia', + 'Leba', + 'Jeilyn', + 'Earnesteen', + 'Mennie', + 'Kieara', + 'Sheina', + 'Yo', + 'Sharnice', + 'Ravin', + 'Daisi', + 'Britini', + 'Carlina', + 'Arisa', + 'Margy', + 'Whitnee', + 'Krysti', + 'Odean', + 'Darlys', + 'Janita', + 'Donnetta', + 'Guynell', + 'Neomia', + 'Loyalty', + 'Serra', + 'Kaysie', + 'Preciosa', + 'Earleen', + 'Shatoria', + 'Kourtnie', + 'Kana', + 'Jahnavi', + 'Kyarra', + 'Licet', + 'Railyn', + 'Delisha', + 'Flordia', + 'Arsema', + 'Kena', + 'Kaelah', + 'Kashia', + 'Emonie', + 'Izola', + 'Linsay', + 'Naibe', + 'Natallie', + 'Rosi', + 'Taline', + 'Cortina', + 'Annett', + 'Kadi', + 'Lindsi', + 'Lasasha', + 'Tamre', + 'Yenny', + 'Yasaman', + 'Shawnice', + 'Thi', + 'Jannel', + 'Kaleen', + 'Demitra', + 'Meisha', + 'Mahira', + 'Emmanuela', + 'Janaiya', + 'Rechel', + 'Nazifa', + 'Zeynep', + 'Shalena', + 'Hila', + 'Ailish', + 'Altovise', + 'Anabeth', + 'Anavictoria', + 'Averey', + 'Berlynn', + 'Alitza', + 'Adelynne', + 'Aiva', + 'Alenna', + 'Harlowe', + 'Camrynn', + 'Daphnie', + 'Ezri', + 'Lanna', + 'Lua', + 'Maddilynn', + 'Maeva', + 'Maytte', + 'Jovi', + 'Karalyn', + 'Kataleah', + 'Kaylana', + 'Milliana', + 'Surveen', + 'Veera', + 'Nimrat', + 'Nimrit', + 'Radha', + 'Roisin', + 'Senna', + 'Ruhi', + 'Saja', + 'Glenice', + 'Damiana', + 'Mikeria', + 'Lakeria', + 'Yulia', + 'Zanna', + 'Lynnae', + 'Illa', + 'Buelah', + 'Novis', + 'Johnye', + 'Valree', + 'Santiaga', + 'Modell', + 'Maydell', + 'Elfida', + 'Charlyne', + 'Argentina', + 'Terica', + 'Kiandra', + 'Tangi', + 'Pascuala', + 'Narcisa', + 'Macaria', + 'Thomasa', + 'Verta', + 'Eulogia', + 'Trellis', + 'Tavaria', + 'Dakayla', + 'Oneita', + 'Kimberlynn', + 'Aslee', + 'Jenascia', + 'Shamaria', + 'Lakely', + 'Etna', + 'Gilberte', + 'Glena', + 'Delorse', + 'Margrett', + 'Endia', + 'Buena', + 'Alvilda', + 'Domitila', + 'Jasmaine', + 'Jaquita', + 'Shontavia', + 'Roneshia', + 'Leasa', + 'Feliciana', + 'Allyana', + 'Anaia', + 'Annalyn', + 'Ayane', + 'Belladonna', + 'Adanely', + 'Akshaya', + 'Aleiyah', + 'Tereasa', + 'Antonisha', + 'Darlah', + 'Dhalia', + 'Dianelly', + 'Elika', + 'Camillia', + 'Leonila', + 'Manreet', + 'Jazzlin', + 'Kaiulani', + 'Kashvi', + 'Talayah', + 'Viana', + 'Ximenna', + 'Shaylah', + 'Quorra', + 'Anagha', + 'Annalea', + 'Jaleyah', + 'Bethanny', + 'Zophia', + 'Alegria', + 'Advika', + 'Taneika', + 'Marye', + 'Latorya', + 'Sayler', + 'Nara', + 'Nithya', + 'Phoenyx', + 'Saiya', + 'Mellany', + 'Yazlin', + 'Adalena', + 'Adya', + 'Aliviah', + 'Aalia', + 'Rickia', + 'Eliyana', + 'Arella', + 'Audris', + 'Auria', + 'Avantika', + 'Aylani', + 'Beya', + 'Camilah', + 'Kaede', + 'Laylonie', + 'Jayani', + 'Katara', + 'Hera', + 'Audrea', + 'Nataley', + 'Nazli', + 'Neyla', + 'Noya', + 'Srinidhi', + 'Pranavi', + 'Sareen', + 'Satya', + 'Terika', + 'Zamora', + 'Jimmye', + 'Brigida', + 'Shereka', + 'Widline', + 'Natori', + 'Dorthie', + 'Berit', + 'Aretta', + 'Svea', + 'Wenona', + 'Amera', + 'Nayah', + 'Lollie', + 'Genice', + 'Fabianna', + 'Nazaria', + 'Edra', + 'Jamariah', + 'Willine', + 'Madolyn', + 'Wanell', + 'Lucetta', + 'Eudora', + 'Adda', + 'Shariah', + 'Jaelle', + 'Jalena', + 'Annelle', + 'Solveig', + 'Autherine', + 'Nobie', + 'Izora', + 'Eudell', + 'Wyolene', + 'Mariangel', + 'Mayar', + 'Luevenia', + 'Eniyah', + 'Lilie', + 'Eliany', + 'Ivyonna', + 'Beadie', + 'Zeta', + 'Merita', + 'Valjean', + 'Delbra', + 'Alanys', + 'Camiyah', + 'Edyth', + 'Kanya', + 'Perina', + 'Catelynn', + 'Angelisse', + 'Relda', + 'Eathel', + 'Kerrington', + 'Lyriq', + 'Brita', + 'Meda', + 'Zanya', + 'Emileigh', + 'Aracelys', + 'Lisania', + 'Evalena', + 'Traniya', + 'Janiyla', + 'Syesha', + 'Ahmya', + 'Camora', + 'Armonie', + 'Beula', + 'Veva', + 'Kateria', + 'Harumi', + 'Kimiyo', + 'Tangie', + 'Amayrany', + 'Alexiah', + 'Alyn', + 'Tokie', + 'Masayo', + 'Makenzee', + 'Arieana', + 'Asayo', + 'Seirra', + 'Elfrida', + 'Ariona', + 'Masue', + 'Mizuki', + 'Liliane', + 'Malanie', + 'Sabreen', + 'Yuritza', + 'Shanautica', + 'Kateleen', + 'Montanna', + 'Tiona', + 'Theresia', + 'Vernia', + 'Mahayla', + 'Glynna', + 'Shaelynn', + 'Isabelly', + 'Aileth', + 'Ailie', + 'Melvia', + 'Sherrel', + 'Ivah', + 'Himani', + 'Marayah', + 'Melane', + 'Evanie', + 'Atalia', + 'Athalia', + 'Bethsy', + 'Betzi', + 'California', + 'Bryonna', + 'Yaretsy', + 'Zamara', + 'Sanyah', + 'Gaylynn', + 'Vitoria', + 'Yoshino', + 'Hatsumi', + 'Tatsuko', + 'Samika', + 'Maili', + 'Charnae', + 'Jamilla', + 'Vieno', + 'Rylei', + 'Vanita', + 'Hydia', + 'Carmyn', + 'Kenslie', + 'Maryhelen', + 'Lamees', + 'Lilley', + 'Haunani', + 'Pualani', + 'Mikiyah', + 'Lovina', + 'Janith', + 'Kanoe', + 'Anouk', + 'Mayerly', + 'Kiele', + 'Lexia', + 'Janani', + 'Berlinda', + 'Belma', + 'Inayah', + 'Saloma', + 'Anely', + 'Anjolina', + 'Devonna', + 'Nikhita', + 'Nayana', + 'Naidely', + 'Hina', + 'Ismerai', + 'Daisie', + 'Sitlaly', + 'Yahayra', + 'Trinidy', + 'Vallery', + 'Ceaira', + 'Floretta', + 'Lavena', + 'Shawntavia', + 'Dessa', + 'Tareva', + 'Iyanla', + 'Kania', + 'Shakiya', + 'Latora', + 'Hermila', + 'Clora', + 'Tiyanna', + 'Saydie', + 'Sherlene', + 'Trixie', + 'Nadiyah', + 'Zarria', + 'Saidy', + 'Sabriya', + 'Keirra', + 'Leeana', + 'Leianna', + 'Jaia', + 'Ishanvi', + 'Ailed', + 'Fathima', + 'Hansika', + 'Delailah', + 'Caliah', + 'Dayleen', + 'Jolisa', + 'Sallye', + 'Levonia', + 'Tula', + 'Kristene', + 'Alanni', + 'Aleiah', + 'Aeva', + 'Ilean', + 'Annet', + 'Lateshia', + 'Markesha', + 'Nikol', + 'Nadolyn', + 'Kimyatta', + 'Ercilia', + 'Sheliah', + 'Heiley', + 'Metztli', + 'Teyla', + 'Saranya', + 'Tanishka', + 'Kayana', + 'Donnamae', + 'Lajoyce', + 'Kemya', + 'Kemora', + 'Jozelyn', + 'Keili', + 'Jaydy', + 'Linzy', + 'Marelin', + 'Melaney', + 'Aleksa', + 'Alynah', + 'Elyza', + 'Emmery', + 'Angeleen', + 'Annica', + 'Bindi', + 'Demya', + 'Nayleen', + 'Sadee', + 'Samah', + 'Shylee', + 'Talula', + 'Vannia', + 'Yarelli', + 'Zohar', + 'Miangel', + 'Orla', + 'Sundra', + 'Korinne', + 'Taniesha', + 'Zaliyah', + 'Zionna', + 'Amariyah', + 'Loris', + 'Cruzita', + 'Landa', + 'Eduvina', + 'Ileanna', + 'Ileene', + 'Jesselle', + 'Daviana', + 'Eleny', + 'Marijane', + 'Okla', + 'Violanda', + 'Dorma', + 'Leoma', + 'Esperansa', + 'Shanreka', + 'Baudelia', + 'Teasia', + 'Aubrei', + 'Jeree', + 'Ortencia', + 'Melida', + 'Pernie', + 'Sweetie', + 'Arelly', + 'Ariday', + 'Bhavya', + 'Aiyanah', + 'Akshita', + 'Ginette', + 'Docia', + 'Pegeen', + 'Alaynah', + 'Allanah', + 'Daniah', + 'Loriana', + 'Kenly', + 'Kenli', + 'Kendahl', + 'Kenady', + 'Senora', + 'Hetal', + 'Aloha', + 'Barri', + 'Shaniquah', + 'Feather', + 'Rica', + 'Adriann', + 'Fleta', + 'Shontel', + 'Kynisha', + 'Nahima', + 'Myracle', + 'Syniah', + 'Jomarie', + 'Leeandra', + 'Maylie', + 'Marijose', + 'Jaley', + 'Sydnei', + 'Amariya', + 'Alysandra', + 'Damia', + 'Laurieann', + 'Lucecita', + 'Miosotis', + 'Shelvy', + 'Bernina', + 'Darice', + 'Dorrie', + 'Myrta', + 'Yoko', + 'Vara', + 'Joanmarie', + 'Kerryann', + 'Carmesa', + 'Kenzington', + 'Oaklyn', + 'Shelbia', + 'Arhianna', + 'Ardyn', + 'Amarachi', + 'Cydnee', + 'Chloey', + 'Brailee', + 'Aily', + 'Rosette', + 'Geryl', + 'Luba', + 'Marguerita', + 'Ayannah', + 'Deziyah', + 'Lurdes', + 'Dawnelle', + 'Reiko', + 'Brynli', + 'Tenlee', + 'Kynadee', + 'Emersen', + 'Josilyn', + 'Jazalyn', + 'Maleyah', + 'Cozette', + 'Xoe', + 'Syria', + 'Charyl', + 'Gita', + 'Aniaya', + 'Yulemni', + 'Joleigh', + 'Kenzy', + 'Logann', + 'Genesys', + 'Cherita', + 'Trenise', + 'Stpehanie', + 'Riann', + 'Matilyn', + 'Akisha', + 'Coralee', + 'Presli', + 'Yariana', + 'Edda', + 'Lisabeth', + 'Farm', + 'Dennice', + 'Deepa', + 'Chiffon', + 'Alyzea', + 'Alexas', + 'Emylee', + 'Joellyn', + 'Zo', + 'Marybell', + 'Sapna', + 'Khristina', + 'Kellyanne', + 'Chrystie', + 'Damary', + 'Graziella', + 'Tene', + 'Shakisha', + 'Shirelle', + 'Gwynne', + 'Insha', + 'Lydiann', + 'Cuba', + 'Cortnie', + 'Denelle', + 'Huyen', + 'Brieann', + 'Cindia', + 'Shalina', + 'Linnette', + 'Kiamesha', + 'Anecia', + 'Brinna', + 'Kewanna', + 'Malke', + 'Yira', + 'Rashidah', + 'Karicia', + 'Chrislyn', + 'Idali', + 'Zandria', + 'Ruta', + 'Toshi', + 'Daena', + 'Aneliz', + 'Cherese', + 'Brandalyn', + 'Brieanne', + 'Chistina', + 'Denys', + 'Nyisha', + 'Lissie', + 'Sherine', + 'Marisal', + 'Tuwana', + 'Zyonna', + 'Shady', + 'Patrisha', + 'Laniece', + 'Jessamyn', + 'Letticia', + 'Shirlie', + 'Miyo', + 'Marilouise', + 'Yukiye', + 'Ltanya', + 'Geralynn', + 'Anastazia', + 'Mitzie', + 'Lluliana', + 'Rozanna', + 'Magalie', + 'Salima', + 'Bevin', + 'Gaudy', + 'Ieasha', + 'Makia', + 'Sacheen', + 'Sherene', + 'Mataya', + 'Hatsuye', + 'Chiyeko', + 'Devanny', + 'Nasya', + 'Odyssey', + 'Tunisia', + 'Caldonia', + 'Marsi', + 'Mindee', + 'Tamy', + 'Sherill', + 'Tsitsiki', + 'Arva', + 'Gayleen', + 'Kimmy', + 'Lenette', + 'Roxan', + 'Leanora', + 'Charlena', + 'Claudina', + 'Danise', + 'Denell', + 'Eydie', + 'Irish', + 'Hydeia', + 'Nichele', + 'Ronica', + 'Temre', + 'Cindra', + 'Vincenta', + 'Zyra', + 'Larita', + 'Jodine', + 'Ewelina', + 'Madylin', + 'Kinzleigh', + 'Malone', + 'Layken', + 'Verity', + 'Tinleigh', + 'Sophi', + 'Skyleigh', + 'Stanislawa', + 'Rylinn', + 'Natalynn', + 'Marlei', + 'Rhylie', + 'Payslee', + 'Paxtyn', + 'Brittyn', + 'Alaynna', + 'Avory', + 'Aubriee', + 'Jacqui', + 'Aseel', + 'Jannell', + 'Simra', + 'Raneem', + 'Kellene', + 'Shellee', + 'Tish', + 'Lashauna', + 'Ashira', + 'Sharrie', + 'Donnette', + 'Milarain', + 'Toshia', + 'Shariyah', + 'Dariah', + 'Gustava', + 'Leotha', + 'Sherelle', + 'Lindi', + 'Luanna', + 'Shanan', + 'Arelys', + 'Nyema', + 'Errin', + 'Fredrica', + 'Dhriti', + 'Yashvi', + 'Gaile', + 'Ermalinda', + 'Gregorita', + 'Klynn', + 'Kaedence', + 'Zaila', + 'Yaritzi', + 'Taylyn', + 'Tailyn', + 'Milka', + 'Maesyn', + 'Macyn', + 'Riyah', + 'Alleigh', + 'Aracelli', + 'Hadlie', + 'Iza', + 'Riddhi', + 'Kathleene', + 'Darely', + 'Eleyna', + 'Analiya', + 'Fanchon', + 'Allyce', + 'Jasma', + 'Porschia', + 'Deberah', + 'Zoi', + 'Sherlyne', + 'Favour', + 'Shakari', + 'Mckenzy', + 'Makinzie', + 'Maahi', + 'Jacqualine', + 'Nancyann', + 'Ronne', + 'Charmane', + 'Martie', + 'Leane', + 'Kama', + 'Corrinne', + 'Vangie', + 'Jonni', + 'Michon', + 'Sharise', + 'Shawnie', + 'Joane', + 'Rosary', + 'Noretta', + 'Zaylynn', + 'Paislie', + 'Infinity', + 'Amaryllis', + 'Altair', + 'Cookie', + 'Danyella', + 'Collyns', + 'Chrislynn', + 'Bryley', + 'Brelynn', + 'Finleigh', + 'Evianna', + 'Flavia', + 'Wilhemina', + 'Jaeliana', + 'Taija', + 'Naiomi', + 'Jennika', + 'Jenika', + 'Jaicee', + 'Laurice', + 'Ashaunti', + 'Alyxandria', + 'Delfinia', + 'Tyiesha', + 'Petrita', + 'Fedelina', + 'Eufelia', + 'Marshae', + 'Marquesha', + 'Feloniz', + 'Tyliyah', + 'Nadene', + 'Natascha', + 'Shawnette', + 'Jamese', + 'Tashay', + 'Mckenzee', + 'Mckinsey', + 'Langley', + 'Kensleigh', + 'Karolyna', + 'Coralyn', + 'Grethel', + 'Baylei', + 'Ariany', + 'Mekenzie', + 'Whitlee', + 'Sayde', + 'Willena', + 'Tzipporah', + 'Afsana', + 'Kearra', + 'Marialy', + 'Quiara', + 'Jing', + 'Dorathea', + 'Rachelann', + 'Melissaann', + 'Jeanett', + 'Jensine', + 'Jessicaann', + 'Ellesse', + 'Kaula', + 'Calley', + 'Malkie', + 'Shenelle', + 'Sheela', + 'Steffi', + 'Shadia', + 'Marielis', + 'Saima', + 'Tiarah', + 'Reginia', + 'Shaquala', + 'Shadiamond', + 'Kallista', + 'Allee', + 'Allexis', + 'Nakeya', + 'Reshma', + 'Sosha', + 'Kendrea', + 'Imalay', + 'Kyong', + 'Sharmin', + 'Sorah', + 'Alayshia', + 'Katja', + 'Chavie', + 'Farzana', + 'Lanasia', + 'Khayla', + 'Jamella', + 'Diva', + 'Ericca', + 'Brettany', + 'Imunique', + 'Tiasia', + 'Tajae', + 'Sidra', + 'Chelbi', + 'Kourtni', + 'Lamisha', + 'Krystyn', + 'Maly', + 'Mirtha', + 'Nary', + 'Nuria', + 'Falicia', + 'Zilpha', + 'Keyasia', + 'Ranisha', + 'Garnetta', + 'Alexxus', + 'Hae', + 'Herma', + 'Tasheena', + 'Philicia', + 'Fotini', + 'Avanell', + 'Czarina', + 'Kindle', + 'Antoinet', + 'Constanc', + 'Cassondr', + 'Destanee', + 'Christinia', + 'Shalisa', + 'Stepahnie', + 'Sopheap', + 'Somaly', + 'Shalane', + 'Saran', + 'Alaycia', + 'Carolynne', + 'Nikolette', + 'Saphire', + 'Dominigue', + 'Channa', + 'Leva', + 'Starquasia', + 'Shyan', + 'Sabah', + 'Shakiera', + 'Nagely', + 'Hajar', + 'Keniya', + 'Anhthu', + 'Ashle', + 'Taira', + 'Meline', + 'Rebeckah', + 'Daritza', + 'Kaysha', + 'Kathrin', + 'Edit', + 'Jennae', + 'Kaja', + 'Molli', + 'Hildreth', + 'Elyssia', + 'Keandrea', + 'Courtlyn', + 'Cova', + 'Kyndle', + 'Kadisha', + 'Mitchelle', + 'Chabeli', + 'Ashlen', + 'Feiga', + 'Shakena', + 'Lakeia', + 'Jehan', + 'Karianne', + 'Renisha', + 'Crystalyn', + 'Blia', + 'Amanada', + 'Neiba', + 'Oyuki', + 'Lianet', + 'Javaria', + 'Praise', + 'Sagal', + 'Avaleigh', + 'Amoni', + 'Fadumo', + 'Debhora', + 'Sharol', + 'Sahalie', + 'Aleana', + 'Dezire', + 'Catalia', + 'Barbarann', + 'Raelin', + 'Reniyah', + 'Jeniyah', + 'Jaziya', + 'Wilhemenia', + 'Wavie', + 'Modestine', + 'Tariah', + 'Cathern', + 'Asenath', + 'Nakya', + 'Reeva', + 'Tkai', + 'Orva', + 'Theora', + 'Brookie', + 'Breyonna', + 'Ellagrace', + 'Kaliya', + 'Jemimah', + 'Ahna', + 'Zetta', + 'Tanyia', + 'Dicie', + 'Malasia', + 'Janvi', + 'Talaysia', + 'Kaybree', + 'Teia', + 'Robertha', + 'Tilda', + 'Marykatherine', + 'Gusta', + 'Gola', + 'Malta', + 'Nija', + 'Kaija', + 'Tamaria', + 'Chyann', + 'Davianna', + 'Gae', + 'Ruther', + 'Kennadee', + 'Arvella', + 'Ashonti', + 'Euphemia', + 'Teyanna', + 'Jahnya', + 'Jamariya', + 'Ceanna', + 'Francenia', + 'Charletta', + 'Catheryn', + 'Theodosia', + 'Magdaline', + 'Samariah', + 'Jamara', + 'Nehemie', + 'Mikenzie', + 'Marielys', + 'Keilany', + 'Bernardita', + 'Marketa', + 'Takya', + 'Frona', + 'Draxie', + 'Genell', + 'Celesta', + 'Deloria', + 'Sister', + 'Icy', + 'Mardi', + 'Florance', + 'Azari', + 'Ahmiyah', + 'Chaniya', + 'Rheda', + 'Kateland', + 'Rielle', + 'Kjersten', + 'Olivette', + 'Tita', + 'Tharon', + 'Briasia', + 'Pakou', + 'Raniah', + 'Janaria', + 'Jaliya', + 'Alexiana', + 'Alayja', + 'Ailea', + 'Camiya', + 'Versa', + 'Vertell', + 'Loyola', + 'Mckelle', + 'Ebonique', + 'Jaynie', + 'Shamiah', + 'Keela', + 'Laterrica', + 'Fidelia', + 'Annia', + 'Rosslyn', + 'Robynn', + 'Darlynn', + 'Shakiara', + 'Shakeira', + 'Olinda', + 'Kionna', + 'Annslee', + 'Rudine', + 'Teonna', + 'Rudene', + 'Latrece', + 'Wynette', + 'Damiya', + 'Zonnie', + 'Jenne', + 'Deeanne', + 'Doree', + 'Jennilyn', + 'Lari', + 'Lourie', + 'Tedi', + 'Deaira', + 'Deairra', + 'Fatuma', + 'Gearldean', + 'Genise', + 'Karlyn', + 'Arleta', + 'Alla', + 'Donie', + 'Lady', + 'Rheba', + 'Nuha', + 'Olita', + 'Elzina', + 'Lutricia', + 'Tauna', + 'Teasha', + 'Elberta', + 'Jeralyn', + 'Shaketa', + 'Elonda', + 'Lafondra', + 'Shelle', + 'Lamiya', + 'Lejla', + 'Labria', + 'Wessie', + 'Cleola', + 'Suad', + 'Andretta', + 'Piccola', + 'Jadalee', + 'Louanna', + 'Donabelle', + 'Shauntel', + 'Vannie', + 'Naomia', + 'Ludell', + 'Ikram', + 'Ariyonna', + 'Anaelle', + 'Pamila', + 'Scheryl', + 'Kandee', + 'Donella', + 'Vicie', + 'Tajah', + 'Jodeen', + 'Debborah', + 'Varvara', + 'Jalisha', + 'Paw', + 'Tranette', + 'Ruwayda', + 'Jeanice', + 'Lowana', + 'Curlie', + 'Viveca', + 'Tommi', + 'Lynnel', + 'Shawneen', + 'Tora', + 'Ikhlas', + 'Delene', + 'Jillyn', + 'Abria', + 'Blondine', + 'Katharyn', + 'Gini', + 'Lynnell', + 'Laurey', + 'Ikran', + 'Madell', + 'Dura', + 'Trenia', + 'Arsie', + 'Runell', + 'Lawan', + 'Georgeanna', + 'Nashay', + 'Lasha', + 'Michi', + 'Arloa', + 'Kazuye', + 'Arnette', + 'Morghan', + 'Allure', + 'Kiyo', + 'Fusaye', + 'Sebrena', + 'Kikuye', + 'Mykia', + 'Soon', + 'Kyung', + 'Maysa', + 'Manessa', + 'Ople', + 'Amyre', + 'Katera', + 'Danaya', + 'Dorothey', + 'Shahidah', + 'Soliana', + 'Concettina', + 'Delphie', + 'Aqueelah', + 'Cassadee', + 'Larayne', + 'Burnette', + 'Diona', + 'Stasha', + 'Sheria', + 'Luciel', + 'Anise', + 'Cumi', + 'Marillyn', + 'Domenique', + 'Sumiye', + 'Masaye', + 'Imojean', + 'Louetta', + 'Taimi', + 'Berdie', + 'Jyl', + 'Cyrilla', + 'Kearstin', + 'Tosca', + 'Billee', + 'Milda', + 'Rema', + 'Tyne', + 'Altamease', + 'Aleaha', + 'Malaina', + 'Jersie', + 'Nadyne', + 'Suhailah', + 'Reghan', + 'Burma', + 'Kamyra', + 'Geraldean', + 'Ivalee', + 'Waunita', + 'Aritza', + 'Madalynne', + 'Talaya', + 'Azura', + 'Aldonia', + 'Robinette', + 'Ameenah', + 'Abeer', + 'Yamilette', + 'Tanae', + 'Mertha', + 'Jamirah', + 'Chun', + 'Avayah', + 'Janayah', + 'Bena', + 'Mahiyah', + 'Karn', + 'Kristien', + 'Mikesha', + 'Eriel', + 'Kemoni', + 'Aziya', + 'Raigan', + 'Rissie', + 'Tenna', + 'Tambria', + 'Birdell', + 'Almena', + 'Jonisha', + 'Marcey', + 'Rosebud', + 'Lakevia', + 'Shateria', + 'Nelia', + 'Rilda', + 'Doshie', + 'Onzell', + 'Safiyyah', + 'Lorilee', + 'Shiane', + 'Gauri', + 'Ashiya', + 'Yaileen', + 'Vendetta', + 'Margaretmary', + 'Telisa', + 'Imogean', + 'Sheryn', + 'Nance', + 'Mariette', + 'Keerthana', + 'Rosellen', + 'Michelene', + 'Kamrie', + 'Mayci', + 'Jerzi', + 'Vermelle', + 'Tondra', + 'Dorethea', + 'Wannetta', + 'Tilly', + 'Brightyn', + 'Patt', + 'Lynae', + 'Willo', + 'Cloma', + 'Yailyn', + 'Takeria', + 'Janyiah', + 'Rasheema', + 'Nafeesa', + 'Rosene', + 'Kellianne', + 'Taccara', + 'Quanda', + 'Patsie', + 'Chaquita', + 'Shakelia', + 'Guerline', + 'Tashika', + 'Taneesha', + 'Fatme', + 'Marliss', + 'Hye', + 'Marjo', + 'Meggie', + 'Maye', + 'Walline', + 'Dodi', + 'Kristyna', + 'Aliyyah', + 'Latravia', + 'Diania', + 'Elta', + 'Oralee', + 'Nikkita', + 'Rasha', + 'Sharena', + 'Tecora', + 'Pluma', + 'Ovell', + 'Keeya', + 'Dayja', + 'Sherrian', + 'Jinnie', + 'Ekta', + 'Javonda', + 'Shantrice', + 'Dava', + 'Kimbley', + 'Lafonda', + 'Lasonja', + 'Hiilani', + 'Danay', + 'Avree', + 'Kelliann', + 'Keasha', + 'Kimmarie', + 'Jannely', + 'Manasi', + 'Moncerat', + 'Miyu', + 'Jullianna', + 'Joelene', + 'Ynez', + 'Yazmeen', + 'Yasamin', + 'Syann', + 'Surena', + 'Tresia', + 'Trecia', + 'Sonjia', + 'Hokulani', + 'Amarilys', + 'Bethzaida', + 'Noraida', + 'Dietra', + 'Nealie', + 'Charice', + 'Alicea', + 'Jozie', + 'Delzora', + 'Jordis', + 'Jolett', + 'Kahlen', + 'Kallee', + 'Natilee', + 'Pecolia', + 'Iyari', + 'Shandrell', + 'Quintella', + 'Monchel', + 'Tysha', + 'Vanetta', + 'Shawneequa', + 'Odesser', + 'Lareina', + 'Jannifer', + 'Kinya', + 'Lateesha', + 'Dvora', + 'Katrin', + 'Denene', + 'Diondra', + 'Ciclali', + 'Sula', + 'Talena', + 'Afrika', + 'Cheron', + 'Emireth', + 'Cadee', + 'Jlyn', + 'Jermya', + 'Alyia', + 'Sitlali', + 'Sissy', + 'Felita', + 'Kerith', + 'Wendolyn', + 'Chaundra', + 'Angle', + 'Gladies', + 'Meygan', + 'Sereniti', + 'Saryn', + 'Vielka', + 'Tirzah', + 'Lynnmarie', + 'Lisanne', + 'Yliana', + 'Yamilett', + 'Keyoka', + 'Laquanta', + 'Teneshia', + 'Trenna', + 'Veronda', + 'Fronie', + 'Carlette', + 'Lanetta', + 'Raynelle', + 'Tianne', + 'Siria', + 'Mayda', + 'Lorien', + 'Celica', + 'Tabbitha', + 'Kayanna', + 'Julitza', + 'Kylia', + 'Heavenlee', + 'Nikka', + 'Rachana', + 'Mekenna', + 'Maritere', + 'Ai', + 'Angelisa', + 'Anysa', + 'Basia', + 'Ilka', + 'Geanine', + 'Kedra', + 'Caila', + 'Deysy', + 'Emilyann', + 'Samera', + 'Mackinzie', + 'Lynzie', + 'Akela', + 'Navpreet', + 'Reylene', + 'Reyanna', + 'Kathlynn', + 'Kiaira', + 'Guiselle', + 'Brinn', + 'Jerelyn', + 'Lorel', + 'Alandra', + 'Ardyth', + 'Kloee', + 'Mellody', + 'Carlisa', + 'Martinique', + 'Damali', + 'Cassandre', + 'Ivanelle', + 'Janaan', + 'Shontay', + 'Tamieka', + 'Tashema', + 'Irmalinda', + 'Tayna', + 'Berdena', + 'Janika', + 'Shauntay', + 'Nikea', + 'Ekaterini', + 'Glendaly', + 'Vernee', + 'Kang', + 'Candise', + 'Jamica', + 'Andera', + 'Katheleen', + 'Annagrace', + 'Bradleigh', + 'Kissy', + 'Lachandra', + 'Tamikia', + 'Shevon', + 'Wardean', + 'Betina', + 'Marcee', + 'Evia', + 'Carry', + 'Marica', + 'Tiwana', + 'Stacye', + 'Theressa', + 'Torsha', + 'Allayna', + 'Betania', + 'Berania', + 'Claryssa', + 'Clarise', + 'Cassidi', + 'Mehana', + 'Janella', + 'Mackenzy', + 'Kaeleigh', + 'Sanoe', + 'Neysa', + 'Shawntee', + 'Shannah', + 'Tihani', + 'Willye', + 'Zalma', + 'Serrina', + 'Shealyn', + 'Hiiaka', + 'Jeselle', + 'Mitsy', + 'Kela', + 'Aquila', + 'Marikay', + 'Christella', + 'Tameria', + 'Ebelina', + 'Maricar', + 'Shalimar', + 'Yanin', + 'Xuan', + 'Tifany', + 'Thy', + 'Quynh', + 'Shronda', + 'Kysha', + 'Lular', + 'Danee', + 'Christyna', + 'Antonieta', + 'Chara', + 'Bich', + 'Tishana', + 'Sophy', + 'Shoshanna', + 'Adrea', + 'Lavaun', + 'Keryn', + 'Okema', + 'Njeri', + 'Ashaki', + 'Alegra', + 'Anapatricia', + 'Terena', + 'Tuere', + 'Ensley', + 'Geraline', + 'Corrinna', + 'Carlye', + 'Dawnielle', + 'Fancy', + 'Akiba', + 'Korrie', + 'Lavita', + 'Chisa', + 'Lakishia', + 'Mandisa', + 'Lalita', + 'Sakeena', + 'Noami', + 'Olivea', + 'Lucilla', + 'Marialuiza', + 'Radonna', + 'Magaline', + 'Minda', + 'Annah', + 'Mitsuyo', + 'Kameko', + 'Miyako', + 'Satsuki', + 'Hatsuyo', + 'Aimie', + 'Jalexis', + 'Haruyo', + 'Tokiko', + 'Matsuyo', + 'Myiah', + 'Natalye', + 'Priseis', + 'Yeraldi', + 'Natsue', + 'Nobue', + 'Zyria', + 'Tierany', + 'Samyia', + 'Rhema', + 'Chiyo', + 'Lailoni', + 'Momoka', + 'Miku', + 'Havanna', + 'Izela', + 'Kendy', + 'Rashanda', + 'Aleysha', + 'Sherlita', + 'Tamana', + 'Kikuyo', + 'Tapanga', + 'Shauntell', + 'Adithi', + 'Chiamaka', + 'Devika', + 'Angy', + 'Arwyn', + 'Aparna', + 'Anneka', + 'Betzayra', + 'Analuiza', + 'Blondie', + 'October', + 'Yarexi', + 'Yarethzi', + 'Annaclaire', + 'Rosabel', + 'Jerlene', + 'Clelia', + 'Jatara', + 'Anzley', + 'Zamaya', + 'Venera', + 'Kalleigh', + 'Jaylynne', + 'Kaylor', + 'Milli', + 'Nelsy', + 'Laycee', + 'Arayah', + 'Betzabe', + 'Bethzi', + 'Haidy', + 'Chayla', + 'Elizah', + 'Evoleth', + 'Edyn', + 'Cyniah', + 'December', + 'Amerika', + 'Analea', + 'Ayshia', + 'Alauna', + 'Shamica', + 'Peaches', + 'Shenee', + 'Letecia', + 'Arminda', + 'Yolander', + 'Amariona', + 'Kaithlyn', + 'Jasiya', + 'Niharika', + 'Sareena', + 'Maryana', + 'Melanye', + 'Solei', + 'Suhey', + 'Soyla', + 'Koral', + 'Lilee', + 'Mercede', + 'Pennye', + 'Yumeka', + 'Mazel', + 'Vani', + 'Pattiann', + 'Shirell', + 'Carmencita', + 'Delayla', + 'Hailyn', + 'Brena', + 'Daana', + 'Lenise', + 'Ryhanna', + 'Lorely', + 'Tiannah', + 'Zabdi', + 'Kammy', + 'Josslynn', + 'Keilee', + 'Kamrynn', + 'Itza', + 'Jaidy', + 'Cherly', + 'Ladeana', + 'Memory', + 'Maresa', + 'Shauntae', + 'Risha', + 'Ilisa', + 'Debraann', + 'Gavriela', + 'Jenai', + 'Suzzette', + 'Mailani', + 'Leiloni', + 'Manasa', + 'Malin', + 'Faythe', + 'Haylei', + 'Haili', + 'Gwenivere', + 'Jamilette', + 'Naydeline', + 'Sakshi', + 'Nayda', + 'Nuala', + 'Chelsae', + 'Berenize', + 'Bahar', + 'Arpi', + 'Tearra', + 'Metta', + 'Lethia', + 'Akanksha', + 'Danine', + 'Alayne', + 'Jeanann', + 'Loyda', + 'Yamna', + 'Marsela', + 'Jolinda', + 'Leina', + 'Mariane', + 'Kaydince', + 'Etsuko', + 'Tinika', + 'Lashona', + 'Chidinma', + 'Jazell', + 'Derenda', + 'Cylinda', + 'Amaiah', + 'Alyzza', + 'Abbygayle', + 'Tashae', + 'Tesa', + 'Sarra', + 'Tanasha', + 'Latoy', + 'Dawnell', + 'Corinn', + 'Charmain', + 'Odetta', + 'Kimiya', + 'Kiaya', + 'Mairin', + 'Maelani', + 'Halena', + 'Dorianne', + 'Ilia', + 'Cheyenna', + 'Noora', + 'Nareh', + 'Namrata', + 'Sholanda', + 'Sita', + 'Dunia', + 'Betzayda', + 'Analissa', + 'Amulya', + 'Annaka', + 'Anneth', + 'Anaalicia', + 'Noemie', + 'Leni', + 'Robyne', + 'Skyleen', + 'Tiphanie', + 'Belmira', + 'Francelina', + 'Kreindy', + 'Kiri', + 'Kristena', + 'Lawren', + 'Christyn', + 'Deicy', + 'Hollyann', + 'Jamela', + 'Eriko', + 'Sotheary', + 'Lekeshia', + 'Onica', + 'Micole', + 'Marlisa', + 'Aqsa', + 'Bayla', + 'Abigal', + 'Charny', + 'Shaquira', + 'Rabab', + 'Yasemin', + 'Keishla', + 'Donasia', + 'Ellamarie', + 'Darianny', + 'Dahiana', + 'Areeba', + 'Shaquasha', + 'Oneisha', + 'Daicy', + 'Karem', + 'Kymberlee', + 'Kayleena', + 'Katryna', + 'Jessicamae', + 'Gessica', + 'Jameela', + 'Janele', + 'Naylani', + 'Anagabriela', + 'Andraya', + 'Andreanna', + 'Artavia', + 'Alexanderia', + 'Laporche', + 'Laporsche', + 'Folasade', + 'Kirandeep', + 'Davia', + 'Davona', + 'Darbi', + 'Baylea', + 'Sylwia', + 'Glendy', + 'Ivet', + 'Fritzi', + 'Lusero', + 'Marlayna', + 'Marlissa', + 'Leanny', + 'Duaa', + 'Ruchama', + 'Orli', + 'Nabeeha', + 'Maurissa', + 'Shevawn', + 'Shauni', + 'Shellby', + 'Sindi', + 'Taralyn', + 'Tanzania', + 'Sinthia', + 'Ondrea', + 'Nhu', + 'Narine', + 'Naly', + 'Yanett', + 'Temmy', + 'Manar', + 'Maimuna', + 'Arielys', + 'Dalya', + 'Allyse', + 'Mariateresa', + 'Mariade', + 'Lashea', + 'Kimberlyann', + 'Cyntia', + 'Cystal', + 'Elisse', + 'Tonimarie', + 'Nashalie', + 'Shatasia', + 'Teigan', + 'Muntaha', + 'Zlata', + 'Zehra', + 'Shaterra', + 'Leeya', + 'Keysi', + 'Christabel', + 'Alfrieda', + 'Mehgan', + 'Hyacinth', + 'Shley', + 'Caterin', + 'Darnesha', + 'Amaranta', + 'Jazzmen', + 'Kelia', + 'Kassy', + 'Grasiela', + 'Sheindy', + 'Yenty', + 'Tahani', + 'Umme', + 'Mayla', + 'Maryon', + 'Kiyanna', + 'Dezeray', + 'Macaela', + 'Nalley', + 'Mikeisha', + 'Sylvana', + 'Smantha', + 'Virdiana', + 'Afiya', + 'Chanise', + 'Glorimar', + 'Hui', + 'Hendel', + 'Junia', + 'Gioia', + 'Elene', + 'Dorothie', + 'Elynor', + 'Mercades', + 'Arfa', + 'Abiha', + 'Aayat', + 'Amarianna', + 'Raynisha', + 'Pahola', + 'Sarin', + 'Marixa', + 'Shavonna', + 'Tannya', + 'Tijera', + 'Girtha', + 'Tameko', + 'Caresse', + 'Bernyce', + 'Allisha', + 'Branda', + 'Jahmya', + 'Haleema', + 'Hodaya', + 'Samina', + 'Sheva', + 'Theadora', + 'Skylyn', + 'Razan', + 'Somalia', + 'Thalya', + 'Quadasia', + 'Yanil', + 'Arabia', + 'Edina', + 'Briyanna', + 'Verdia', + 'Sehar', + 'Naama', + 'Timberly', + 'Reann', + 'Narissa', + 'Maggy', + 'Marriah', + 'Joua', + 'Kellsie', + 'Kelcy', + 'Evonna', + 'Jacqueleen', + 'Xee', + 'Zaynah', + 'Janique', + 'Jailin', + 'Aniqa', + 'Melana', + 'Mariame', + 'Aundria', + 'Anacaren', + 'Anahid', + 'Jassmine', + 'Keoshia', + 'Keyera', + 'Delmi', + 'Briselda', + 'Carlisha', + 'Brittnei', + 'Clarrisa', + 'Dezerae', + 'Banessa', + 'Ariele', + 'Cherrell', + 'Daissy', + 'Cecila', + 'Jady', + 'Kristelle', + 'Kristinamarie', + 'Korinna', + 'Kortnee', + 'Jasimine', + 'Jahnay', + 'Farhana', + 'Shaliyah', + 'Nemesis', + 'Shakerria', + 'Phoua', + 'Carylon', + 'Ironesha', + 'Lariza', + 'Anesa', + 'Elantra', + 'Deandria', + 'Denecia', + 'Chelsia', + 'Teighlor', + 'Suzannah', + 'Zelene', + 'Zeena', + 'Catriona', + 'Tamarra', + 'Tannaz', + 'Titiana', + 'Briany', + 'Lyana', + 'Maytal', + 'Antanasia', + 'Kierston', + 'Dashia', + 'Ismenia', + 'Annessa', + 'Carolena', + 'Miasia', + 'Mikhaila', + 'Lamiracle', + 'Kassey', + 'Markeshia', + 'Hilarie', + 'Necha', + 'Ziara', + 'Jahniyah', + 'Safiyah', + 'Tanaisha', + 'Shamyra', + 'Laportia', + 'Shavy', + 'Viktoriya', + 'Khrystyne', + 'Kristyne', + 'Juanisha', + 'Jerrika', + 'Channelle', + 'Jacquiline', + 'Rakia', + 'Tamarah', + 'Sarha', + 'Mishelle', + 'Nastasha', + 'Acadia', + 'Brittiney', + 'Mickaela', + 'Natavia', + 'Seryna', + 'Ardene', + 'Special', + 'Simranjit', + 'Marivi', + 'Natassja', + 'Neira', + 'Nikkie', + 'Asiana', + 'Dazhane', + 'Channell', + 'Adryana', + 'Mariluz', + 'Dajia', + 'Breigh', + 'Zelpha', + 'Lataya', + 'Glenny', + 'Sharene', + 'Shaguana', + 'Henrine', + 'Camesha', + 'Birdia', + 'Dynisha', + 'Sherina', + 'Ayde', + 'Danille', + 'Charday', + 'Almadelia', + 'Larena', + 'Charlestine', + 'Suellyn', + 'Marry', + 'Constantina', + 'Tandi', + 'Lacretia', + 'Noralba', + 'Latresha', + 'Latacha', + 'Talynn', + 'Rox', + 'Chasey', + 'Nyia', + 'Alyissa', + 'Karilyn', + 'Shevonne', + 'Genny', + 'Tamicka', + 'Doneisha', + 'Cyrena', + 'Daisia', + 'Ravina', + 'Berdia', + 'Aneesha', + 'Vashti', + 'Latrica', + 'Kennetha', + 'Aarti', + 'Raiza', + 'Elspeth', + 'Kyleen', + 'Ronika', + 'Lyndsy', + 'Jone', + 'Chanta', + 'Serita', + 'Margree', + 'Ruthel', + 'Ruthella', + 'Breunna', + 'Cyann', + 'Atlanta', + 'Danniela', + 'Junita', + 'Floella', + 'Brittane', + 'Avanelle', + 'Priscill', + 'Luvina', + 'Jeneva', + 'Teretha', + 'Clarita', + 'Ilce', + 'Jacqualyn', + 'Justene', + 'Daysia', + 'Taylore', + 'Sadi', + 'Verenis', + 'Shyenne', + 'Toriana', + 'Alvira', + 'Kalah', + 'Rajanee', + 'Reonna', + 'Mariadelaluz', + 'Mychaela', + 'Charnele', + 'Aeisha', + 'Shaquaya', + 'Shaakira', + 'Tayana', + 'Cozetta', + 'Kensey', + 'Jazsmin', + 'Kaitlyne', + 'Hollye', + 'Lavren', + 'Sarit', + 'Shanieka', + 'Margorie', + 'Virgene', + 'Dannia', + 'Clorissa', + 'Breahna', + 'Rayla', + 'Dellanira', + 'Megen', + 'Matalie', + 'Taraneh', + 'Teila', + 'Etter', + 'Cheetara', + 'Shetara', + 'Jamielee', + 'Kariann', + 'Karess', + 'Bea', + 'Leyda', + 'Misa', + 'Mareena', + 'Maisee', + 'Yvonna', + 'Yocelyne', + 'Yilda', + 'Sabrinna', + 'Sirenia', + 'Tyriel', + 'Darrielle', + 'Siedah', + 'Yuko', + 'Stevee', + 'Chrystle', + 'Shaterrica', + 'Janyll', + 'Evelisse', + 'Belkis', + 'Renesmae', + 'Sahily', + 'Zurie', + 'Edelia', + 'Sequoya', + 'Waldine', + 'Marinell', + 'Moya', + 'Lavenia', + 'Liboria', + 'Meliah', + 'Meliyah', + 'Mio', + 'Xitllali', + 'Nare', + 'Oliviah', + 'Mayrani', + 'Sravya', + 'Valeska', + 'Riona', + 'Lashaundra', + 'Phebe', + 'Yeira', + 'Zarai', + 'Ayanah', + 'Kriti', + 'Kaileah', + 'Donata', + 'Jenavee', + 'Daphnee', + 'Gurneet', + 'Emmalie', + 'Rowrenia', + 'Haisley', + 'Harbor', + 'Arilyn', + 'Aubrii', + 'Avielle', + 'Avyn', + 'Bethenny', + 'Arienne', + 'Anyeli', + 'Brilyn', + 'Cataleyah', + 'Chisom', + 'Dalis', + 'Malaiya', + 'Meela', + 'Karsynn', + 'Kaselyn', + 'Kashlyn', + 'Amorette', + 'Lenita', + 'Adabelle', + 'Allisyn', + 'Alyzah', + 'Aaralynn', + 'Avyanna', + 'Aylinn', + 'Bexley', + 'Blakeleigh', + 'Caeli', + 'Chizaram', + 'Avriana', + 'Clarity', + 'Juanelle', + 'Jerelene', + 'Eluteria', + 'Lamerle', + 'Aletheia', + 'Abrie', + 'Adelie', + 'Elleigh', + 'Emmelyn', + 'Emsley', + 'Everlynn', + 'Galileah', + 'Derrica', + 'Keondria', + 'Keneshia', + 'Amberley', + 'Valkyrie', + 'Yazleemar', + 'Maybree', + 'Shloka', + 'Neah', + 'Oluwatomisin', + 'Saydi', + 'Jessalynn', + 'Katalaya', + 'Katniss', + 'Kendalynn', + 'Davionna', + 'Mercie', + 'Danett', + 'Deetya', + 'Dilynn', + 'Dunya', + 'Camyla', + 'Elliotte', + 'Ivee', + 'Jadie', + 'Kyleah', + 'Laelani', + 'Mileah', + 'Nalanie', + 'Nixie', + 'Oviya', + 'Lakecia', + 'Sharnae', + 'Abbagail', + 'Derica', + 'Truly', + 'Tvisha', + 'Vedika', + 'Xiclaly', + 'Syra', + 'Idamae', + 'Dashanti', + 'Neita', + 'Siona', + 'Jourdyn', + 'Analyn', + 'Shamiracle', + 'Daylene', + 'Kadeesha', + 'Malgorzata', + 'Dashay', + 'Else', + 'Pixie', + 'Myleah', + 'Myleen', + 'Nadiah', + 'Sadhana', + 'Samai', + 'Seraphine', + 'Sereen', + 'Sharanya', + 'Simar', + 'Mahlia', + 'Inika', + 'Jennavieve', + 'Genevy', + 'Harshita', + 'Hennessey', + 'Zari', + 'Jamiracle', + 'Loveta', + 'Coleta', + 'Adabella', + 'Alesana', + 'Brinleigh', + 'Azlyn', + 'Braelee', + 'Shaquila', + 'Shanyia', + 'Jamilia', + 'Corlis', + 'Dulcie', + 'Desha', + 'Timya', + 'Rakiya', + 'Tyliah', + 'Taura', + 'Terasha', + 'Gaynel', + 'Roylene', + 'Janecia', + 'Alonda', + 'Tyneisha', + 'Fleurette', + 'Mayleigh', + 'Meklit', + 'Sarenity', + 'Gulianna', + 'Itzayanna', + 'Ivyana', + 'Jazmynn', + 'Esmie', + 'Favor', + 'Kimbella', + 'Shanavia', + 'Yaritzel', + 'Daun', + 'Tykerria', + 'Antoria', + 'Shykemmia', + 'Remona', + 'Lucrezia', + 'Cicily', + 'Aradhya', + 'Esmae', + 'Evah', + 'Jhene', + 'Katalia', + 'Cyrine', + 'Delayza', + 'Eleonor', + 'Arohi', + 'Aseneth', + 'Avarose', + 'Caia', + 'Hulene', + 'Valera', + 'Nasaria', + 'Makesha', + 'Zera', + 'Aahna', + 'Aariyah', + 'Aashvi', + 'Adalene', + 'Annaliyah', + 'Aira', + 'Alaska', + 'Amila', + 'Amour', + 'Kaylinn', + 'Isidora', + 'Marija', + 'Suha', + 'Marigold', + 'Mayzie', + 'Liesel', + 'Darielle', + 'Sapphira', + 'Scotland', + 'Serah', + 'Srinika', + 'Novah', + 'Primrose', + 'Latresa', + 'Theia', + 'Alleen', + 'Agness', + 'Estanislada', + 'Ellouise', + 'Emilija', + 'Glynnis', + 'Paulene', + 'Wilna', + 'Maedell', + 'Lometa', + 'Cressie', + 'Allyne', + 'Calleen', + 'Joaquina', + 'Lashelle', + 'Modene', + 'Jonie', + 'Minta', + 'Milady', + 'Jearlene', + 'Rithika', + 'Simrat', + 'Vonzella', + 'Venna', + 'Pabla', + 'Benilde', + 'Eniya', + 'Shakendra', + 'Ailen', + 'Aina', + 'Marionna', + 'Millette', + 'Emiyah', + 'Kayloni', + 'Keerat', + 'Keeva', + 'Lailany', + 'Mishka', + 'Naevia', + 'Nathania', + 'Nyari', + 'Jayah', + 'Kaavya', + 'Frankee', + 'Anahita', + 'Anella', + 'Elizabella', + 'Damara', + 'Juaquina', + 'Gracia', + 'Rozalyn', + 'Ruhani', + 'Novalie', + 'Mialani', + 'Minka', + 'Nessa', + 'Sissi', + 'Sitara', + 'Jaynee', + 'Jeyla', + 'Gizzelle', + 'Maila', + 'Maizy', + 'Lamaya', + 'Katalea', + 'Khamila', + 'Shekita', + 'Chinita', + 'Anshika', + 'Aerabella', + 'Azelia', + 'Cici', + 'Daleyssa', + 'Divinity', + 'Fermina', + 'Murline', + 'Mattye', + 'Devra', + 'Jakya', + 'Santresa', + 'Larene', + 'Deola', + 'Liliann', + 'Lexxi', + 'Kamori', + 'Myonna', + 'Yitzel', + 'Lindalee', + 'Tira', + 'Mairyn', + 'Riyana', + 'Shaleen', + 'Rhyleigh', + 'Fleeta', + 'Gabrielly', + 'Deajah', + 'Yarielis', + 'Arelie', + 'Amore', + 'Sacoria', + 'Hedda', + 'Wanza', + 'Janyth', + 'Yaslin', + 'Brianah', + 'Anyelin', + 'Shayleigh', + 'Lace', + 'Kurstin', + 'Zakhia', + 'Charvi', + 'Raylie', + 'Nyellie', + 'Natalyn', + 'Libra', + 'Khianna', + 'Jolena', + 'Genevive', + 'Jadine', + 'Deniya', + 'Madysin', + 'Porchia', + 'Layleen', + 'Kemiya', + 'Donesha', + 'Jewelene', + 'Sakari', + 'Narely', + 'Maylyn', + 'Halina', + 'Nelli', + 'Myangel', + 'British', + 'Adore', + 'Alainah', + 'Shadonna', + 'Aminta', + 'Marolyn', + 'Jalea', + 'Breelynn', + 'Carah', + 'Sagrario', + 'Akyra', + 'Kailei', + 'Kenza', + 'Renette', + 'Joanann', + 'Solimar', + 'Semira', + 'Harneet', + 'Jahayra', + 'Evanny', + 'Gyzelle', + 'Nathalee', + 'Dalphine', + 'Mane', + 'Merelyn', + 'Kayliana', + 'Aubryn', + 'Brooklyne', + 'Kimari', + 'Dandra', + 'Cilia', + 'Laren', + 'Denetra', + 'Kandise', + 'Makynli', + 'Janan', + 'Rosalea', + 'Ludean', + 'Syndey', + 'Shaney', + 'Vannary', + 'Reynalda', + 'Rainee', + 'Trishia', + 'Kirbie', + 'Kristyl', + 'Lynzi', + 'Shardai', + 'Yaricza', + 'Tarina', + 'Lynley', + 'Maniah', + 'Arcilia', + 'Keaundra', + 'Karrigan', + 'Madeliene', + 'Lessley', + 'Laurynn', + 'Ragen', + 'Essance', + 'Celsey', + 'Caitlen', + 'Dulse', + 'Sulamita', + 'Evlyn', + 'Dorace', + 'Marciana', + 'Tenecia', + 'Natarsha', + 'Analiza', + 'Ladene', + 'Tatumn', + 'Maricsa', + 'Lysa', + 'Leydi', + 'Limayri', + 'Rebbeca', + 'Amreen', + 'Saina', + 'Remedy', + 'Rael', + 'Nami', + 'Nalini', + 'Naiyah', + 'Moxie', + 'Olina', + 'Whitni', + 'Dayannara', + 'Diara', + 'Arma', + 'Giorgia', + 'Evee', + 'Bricia', + 'Brizeyda', + 'Chihiro', + 'Ayram', + 'Ayushi', + 'Isolde', + 'Husna', + 'Khrystal', + 'Kriston', + 'Raylena', + 'Porschea', + 'Samanthia', + 'Mylinda', + 'Ginelle', + 'Coreena', + 'Aryel', + 'Mallary', + 'Maciel', + 'Kursten', + 'Leandrea', + 'Mackensie', + 'Camri', + 'Itzamara', + 'Aryiah', + 'Alayssa', + 'Andreah', + 'Anberlin', + 'Amrie', + 'Breah', + 'Ryane', + 'Tonna', + 'Valisa', + 'Adryanna', + 'Ajia', + 'Robynne', + 'Brystal', + 'Brylynn', + 'Kaleigha', + 'Danyka', + 'Dannica', + 'Caylen', + 'Jonier', + 'Ruthy', + 'Mada', + 'Vaida', + 'Yeila', + 'Zoelle', + 'Elzora', + 'Samreen', + 'Seylah', + 'Sayla', + 'Allina', + 'Stellarose', + 'Starlett', + 'Simrit', + 'Shina', + 'Bernestine', + 'Tranisha', + 'Tiffanyann', + 'Adamarys', + 'Tylyn', + 'Shahrzad', + 'Addisson', + 'Aeriana', + 'Alaiya', + 'Anni', + 'Ariely', + 'Anvika', + 'Aneya', + 'Bani', + 'Ayame', + 'Ayaka', + 'Aviella', + 'Alabama', + 'Adalyne', + 'Teresea', + 'Ishana', + 'Hargun', + 'Jasnoor', + 'Deby', + 'Dannelle', + 'Swetha', + 'Catherina', + 'Bridgitt', + 'Birgit', + 'Calisi', + 'Defne', + 'Delsa', + 'Demiyah', + 'Cataleah', + 'Icel', + 'Ixel', + 'Jazman', + 'Jessicamarie', + 'Desaree', + 'Chika', + 'Estephani', + 'Dilcia', + 'Dartha', + 'Lesieli', + 'Breyona', + 'Waynette', + 'Verma', + 'Calletana', + 'Cherisa', + 'Casara', + 'Jil', + 'Shella', + 'Renell', + 'Venise', + 'Loura', + 'Kaylia', + 'Leileen', + 'Jessel', + 'Janesa', + 'Kaelly', + 'Julina', + 'Joselinne', + 'Juna', + 'Hazelle', + 'Mauricia', + 'Octaviana', + 'Rumalda', + 'Kataleyah', + 'Kimela', + 'Mosella', + 'Delone', + 'Shemekia', + 'Balinda', + 'Hazell', + 'Deboraha', + 'Gizell', + 'Camilia', + 'Avalina', + 'Audreyana', + 'Baran', + 'Genesee', + 'Elyzabeth', + 'Eliya', + 'Kathyleen', + 'Deeksha', + 'Scherry', + 'Angelyne', + 'Amiliana', + 'Amaira', + 'Jeani', + 'Alysen', + 'Alania', + 'Adiana', + 'Chinyere', + 'Lamesha', + 'Keiley', + 'Lanea', + 'Rosely', + 'Surabhi', + 'Dyanne', + 'Mallika', + 'Tabbatha', + 'Shilpa', + 'Morgyn', + 'Narali', + 'Jenevie', + 'Lovette', + 'Nayleah', + 'Navi', + 'Meili', + 'Nazly', + 'Nethra', + 'Earlee', + 'Layloni', + 'Kiannah', + 'Lilyanah', + 'Liannah', + 'Jaylenn', + 'Jiayi', + 'Kattleya', + 'Kanna', + 'Jimin', + 'Kaleesi', + 'Kailia', + 'Itzy', + 'Itzela', + 'Jasminemarie', + 'Malynda', + 'Jeweline', + 'Eloiza', + 'Carolin', + 'Helma', + 'Arlyle', + 'Giannina', + 'Constancia', + 'Elyce', + 'Montoya', + 'Marline', + 'Krystale', + 'Maghan', + 'Laquitta', + 'Elishia', + 'Aliciana', + 'Maralee', + 'Brunetta', + 'Cybil', + 'Dannell', + 'Cherene', + 'Agueda', + 'Guillerma', + 'Haillie', + 'Bobbe', + 'Gesselle', + 'Esthefany', + 'Sian', + 'Ouita', + 'Sasheen', + 'Abigaile', + 'Demarie', + 'Edwena', + 'Aamiyah', + 'Breaunna', + 'Bryssa', + 'Catlyn', + 'Xaviera', + 'Sierria', + 'Skyelar', + 'Aujanae', + 'Rika', + 'Roshelle', + 'Roxsana', + 'Zonia', + 'Tifanie', + 'Thavy', + 'Teala', + 'Tanea', + 'Loukisha', + 'Melita', + 'Keiona', + 'Maryfer', + 'Delcenia', + 'Akila', + 'Gwenevere', + 'Obdulia', + 'Texana', + 'Licette', + 'Larina', + 'Lany', + 'Yailine', + 'Yomara', + 'Zavia', + 'Sydne', + 'Mariadelourdes', + 'Margeaux', + 'Daneille', + 'Doni', + 'Donalee', + 'Darilyn', + 'Jennfier', + 'Jeanny', + 'Haliegh', + 'Dymon', + 'Callee', + 'Cydni', + 'Daesha', + 'Tamila', + 'Tresha', + 'Mckennah', + 'Shouana', + 'Xcaret', + 'Yeneisy', + 'Yumalai', + 'Ziana', + 'Hanny', + 'Shanisha', + 'Nissi', + 'Mirabel', + 'Miarose', + 'Valerya', + 'Rosalin', + 'Saliha', + 'Samayah', + 'Smriti', + 'Jozette', + 'Gari', + 'Jeanell', + 'Dyann', + 'Vonna', + 'Velina', + 'Salli', + 'Nonie', + 'Olena', + 'Camela', + 'Eufracia', + 'Ethelyne', + 'Yuhan', + 'Silveria', + 'Silvestra', + 'Thressa', + 'Tiahna', + 'Vasti', + 'Calee', + 'Florentine', + 'Sherre', + 'Almira', + 'Zitlalli', + 'Vianne', + 'Yaribeth', + 'Yarelie', + 'Robbye', + 'Jasminne', + 'Sophiah', + 'Saryah', + 'Hermalinda', + 'Sinclaire', + 'Korissa', + 'Lanee', + 'Keeana', + 'Parlee', + 'Luceal', + 'Jetta', + 'Mairani', + 'Tameisha', + 'Haruna', + 'Chasiti', + 'Leighanne', + 'Anaisabel', + 'Aanchal', + 'Alesa', + 'Annisa', + 'Brigitta', + 'Elideth', + 'Chua', + 'Cherrish', + 'Aleece', + 'Maizee', + 'Navie', + 'Philomene', + 'Jilian', + 'Jesi', + 'Kortnie', + 'Beija', + 'Delissa', + 'Shiree', + 'Silbia', + 'Tamura', + 'Aerianna', + 'Abegail', + 'Braniya', + 'Calyn', + 'Carlynn', + 'Anjana', + 'Angelik', + 'Alyzabeth', + 'Amorie', + 'Joannamarie', + 'Kerissa', + 'Kennesha', + 'Laruen', + 'Korrina', + 'Felisitas', + 'Gilma', + 'Essica', + 'Gerarda', + 'Petronila', + 'Dorotea', + 'Maguadalupe', + 'Najla', + 'Loana', + 'Illyana', + 'Amunique', + 'Antwanette', + 'Krystan', + 'Shaniquia', + 'Shanequia', + 'Rainy', + 'Raynesha', + 'Shayleen', + 'Stephanee', + 'Sharaya', + 'Nikkole', + 'Cecille', + 'Christyne', + 'Auriel', + 'Franki', + 'Zelina', + 'Deshanae', + 'Deshawna', + 'Tyneshia', + 'Tyrisha', + 'Deangela', + 'Dynasia', + 'Maigan', + 'Jericka', + 'Jackalyn', + 'Kayln', + 'Ceslie', + 'Bethaney', + 'Samanvi', + 'Saidee', + 'Rosibel', + 'Spirit', + 'Srishti', + 'Varnika', + 'Vanshika', + 'Rosha', + 'Rheya', + 'Yoyo', + 'Veyda', + 'Weslyn', + 'Palak', + 'Sieanna', + 'Riannah', + 'Lovetta', + 'Lota', + 'Florice', + 'Hortence', + 'Zuley', + 'Zoejane', + 'Zemira', + 'Mineola', + 'Senona', + 'Concepsion', + 'Conrada', + 'Dardanella', + 'Rhina', + 'Rubicela', + 'Raissa', + 'Porchea', + 'Latiana', + 'Landy', + 'Monee', + 'Maritssa', + 'Marjani', + 'Meosha', + 'Cecilie', + 'Britanie', + 'Brandilyn', + 'Khrystina', + 'Atenas', + 'Kristeena', + 'Kristell', + 'Kristianne', + 'Angelicia', + 'Alexandera', + 'Jaimy', + 'Jeneffer', + 'Hayde', + 'Vickye', + 'Suzzanne', + 'Susi', + 'Sherrilyn', + 'Sanda', + 'Janeal', + 'Stephnie', + 'Luwana', + 'Shenae', + 'Yaris', + 'Marzell', + 'Lashane', + 'Liandra', + 'Keionna', + 'Korri', + 'Marlet', + 'Marytza', + 'Lorraina', + 'Deepika', + 'Devi', + 'Fion', + 'Darrah', + 'Dalisha', + 'Karessa', + 'Karrisa', + 'Kasara', + 'Ismar', + 'Jacquilyn', + 'Janica', + 'Jeannett', + 'Samanatha', + 'Samra', + 'Sayda', + 'Breklyn', + 'Ashika', + 'Bita', + 'Allysha', + 'Areil', + 'Arlenne', + 'Artelia', + 'Janicia', + 'Corinthia', + 'Angellica', + 'Maygen', + 'Maygan', + 'Odelle', + 'Wenonah', + 'Perfecta', + 'Anjelika', + 'Solmaira', + 'Fredonia', + 'Burgandy', + 'Chelcee', + 'Kellsey', + 'Lyann', + 'Jazmon', + 'Ardie', + 'Latunya', + 'Benetta', + 'Delphina', + 'Ortensia', + 'Obelia', + 'Lurene', + 'Refujia', + 'Noriko', + 'Ladelle', + 'Lella', + 'Shanie', + 'Shawndra', + 'Zell', + 'Zela', + 'Wenda', + 'Troylene', + 'Merrilyn', + 'Kapri', + 'Timesha', + 'Gwendlyn', + 'Jenean', + 'Lamona', + 'Ladana', + 'Cina', + 'Cybele', + 'Eugina', + 'Anjeanette', + 'Vana', + 'Jeneal', + 'Cherlene', + 'Railee', + 'Palin', + 'Yuliet', + 'Rechelle', + 'Sherisse', + 'Pollyanna', + 'Tiphani', + 'Tiffanee', + 'Vanisha', + 'Yurico', + 'Junko', + 'Shannell', + 'Shalise', + 'Kimberlina', + 'Kerra', + 'Shantee', + 'Emmelia', + 'Micala', + 'Lexxus', + 'Candiss', + 'Chauntel', + 'Alese', + 'Margit', + 'Any', + 'Ambur', + 'Chrysta', + 'Janese', + 'Jinny', + 'Zaydee', + 'Makisha', + 'Carola', + 'Marjan', + 'Samanth', + 'Shaquinta', + 'Polette', + 'Riane', + 'Nitasha', + 'Kasarah', + 'Jillianne', + 'Keidra', + 'Karrah', + 'Kaytie', + 'Sondi', + 'Swayzie', + 'Laporcha', + 'Bridgit', + 'Chanika', + 'Antoniette', + 'Jessicia', + 'Francies', + 'Kaizley', + 'Negin', + 'Mistica', + 'Lorenia', + 'Kalise', + 'Kynslie', + 'Dene', + 'Jizel', + 'Jinger', + 'Jayli', + 'Jariya', + 'Joelynn', + 'Haylin', + 'Isabellah', + 'Ciria', + 'Dealva', + 'Barbarita', + 'Prudencia', + 'Wanna', + 'Marieli', + 'Madisynn', + 'Madalyne', + 'Artisha', + 'Everlyn', + 'Cyerra', + 'Liezl', + 'Kabao', + 'Karmina', + 'Kashmir', + 'Nani', + 'Mithra', + 'Mishika', + 'Milynn', + 'Mehr', + 'Marybella', + 'Maisey', + 'Maddy', + 'Lyah', + 'Marnee', + 'Machele', + 'Ladona', + 'Lorilei', + 'Liara', + 'Alahni', + 'Analaya', + 'Amalya', + 'Alyannah', + 'Aayla', + 'Aarini', + 'Arliz', + 'Cyra', + 'Asenet', + 'Avy', + 'Avaree', + 'Ciela', + 'Evangelyn', + 'Kaidynce', + 'Isella', + 'Ilaria', + 'Kattaleya', + 'Laveah', + 'Lareen', + 'Lanah', + 'Deema', + 'Hannaley', + 'Fiora', + 'Eviana', + 'Ellieana', + 'Elisabetta', + 'Dejanira', + 'Manaia', + 'Malibu', + 'Charlsey', + 'Kaytee', + 'Kinberly', + 'Cinderella', + 'Miana', + 'Kimm', + 'Koni', + 'Eraina', + 'Dory', + 'Deette', + 'Nysa', + 'Nyima', + 'Nikitha', + 'Anasophia', + 'Alissandra', + 'Alisi', + 'Corynn', + 'Aubreyana', + 'Anjani', + 'Oliana', + 'Nura', + 'Nihira', + 'Loveda', + 'Gayathri', + 'Kleigh', + 'Ladaisha', + 'Ilette', + 'Jillene', + 'Jalina', + 'Izellah', + 'Tiaira', + 'Mickala', + 'Macarena', + 'Rubina', + 'Shadow', + 'Emillie', + 'Morine', + 'Novell', + 'Oletta', + 'Pura', + 'Winna', + 'Synia', + 'Shyloh', + 'Kaizlee', + 'Raley', + 'Merly', + 'Na', + 'Yenia', + 'Shayanne', + 'Raeana', + 'Tiauna', + 'Tanairy', + 'Georganna', + 'Mahsa', + 'Maiquel', + 'Korena', + 'Yamel', + 'Shamonica', + 'Romesha', + 'Terrisha', + 'Hannan', + 'Hillarie', + 'Feliza', + 'Courtny', + 'Lyndsee', + 'Katelan', + 'Lakedra', + 'Elisabel', + 'Cynthya', + 'Dannah', + 'Darienne', + 'Dejanique', + 'Madalin', + 'Makynzi', + 'Gwendolynn', + 'Alaine', + 'Bridney', + 'Kimorah', + 'Klee', + 'Kynedi', + 'Loreley', + 'Parthenia', + 'Aubryana', + 'Aryannah', + 'Edeline', + 'Elen', + 'Raguel', + 'Marizela', + 'Michella', + 'Haasini', + 'Tristine', + 'Elis', + 'Pattye', + 'Tanishia', + 'Jenel', + 'Jurea', + 'Laini', + 'Britania', + 'Christabelle', + 'Dafney', + 'Laterica', + 'Angelmarie', + 'Asuzena', + 'Aleea', + 'Teneka', + 'Yicel', + 'Malisha', + 'Prairie', + 'Makelle', + 'Shaelee', + 'Dafina', + 'Hisaye', + 'Adayah', + 'Alexsia', + 'Allysen', + 'Takako', + 'Thamara', + 'Trinitie', + 'Shaneen', + 'Sueellen', + 'Telma', + 'Meyah', + 'Rorie', + 'Preslea', + 'Elbia', + 'Ginna', + 'Marja', + 'Marites', + 'Neisha', + 'Shir', + 'Shastelyn', + 'Saraih', + 'Unity', + 'Makinna', + 'Franchelle', + 'Azadeh', + 'Charito', + 'Joli', + 'Amyrah', + 'Sharlee', + 'Jasey', + 'Kortlynn', + 'Kiari', + 'Kyria', + 'Eleina', + 'Elany', + 'Daleah', + 'Sumi', + 'Kileigh', + 'Lorianna', + 'Macady', + 'Naviah', + 'Mattilyn', + 'Raylyn', + 'Bridgitte', + 'Hasina', + 'Johnelle', + 'Gwendlyon', + 'Itxel', + 'Iyanah', + 'Jeidy', + 'Jaidynn', + 'Jaslynne', + 'Zoii', + 'Tensley', + 'Yolando', + 'Keyarah', + 'Keyri', + 'Katherinne', + 'Thersa', + 'Sinahi', + 'Secret', + 'Vivika', + 'Yobana', + 'Hailley', + 'Haliey', + 'Isys', + 'Deyla', + 'Kassidee', + 'Jalie', + 'Florestela', + 'Cyla', + 'Samyuktha', + 'Libni', + 'Laritza', + 'Breannah', + 'Breya', + 'Keelin', + 'Jarelly', + 'Jenyfer', + 'Julyanna', + 'Kaetlyn', + 'Mixtli', + 'Mykaila', + 'Nasia', + 'Judieth', + 'Misako', + 'Bre', + 'Shaley', + 'Gelila', + 'Aariana', + 'Laquetta', + 'Shizu', + 'Annay', + 'Annai', + 'Breeze', + 'Mahum', + 'Harsimran', + 'Helaina', + 'Alexza', + 'Tangelia', + 'Shellye', + 'Blondena', + 'Keva', + 'Suzzane', + 'Vallorie', + 'Absidy', + 'Alis', + 'Alexxia', + 'Allura', + 'Ariba', + 'Annete', + 'Anett', + 'Deyanara', + 'Ellise', + 'Majorie', + 'Hibah', + 'Chaselyn', + 'Hennesy', + 'Gayatri', + 'Kathelyn', + 'Caylah', + 'Athyna', + 'Arpita', + 'Ciclaly', + 'Emmamarie', + 'Virjinia', + 'Tyna', + 'Cyd', + 'Glennda', + 'Littie', + 'Orlean', + 'Derinda', + 'Hether', + 'Clata', + 'Pleshette', + 'Maricelda', + 'Charmin', + 'Matsuye', + 'Tamitha', + 'Armanda', + 'Sayaka', + 'Lacresia', + 'Demonica', + 'Skie', + 'Trynity', + 'Sereena', + 'Shefali', + 'Rewa', + 'Reshonda', + 'Yalanda', + 'Anissia', + 'Layni', + 'Paolina', + 'Manaal', + 'Mariali', + 'Merina', + 'Milenia', + 'Millenia', + 'Moncerrath', + 'Monzerrath', + 'Kaydie', + 'Adianna', + 'Toluwalase', + 'Trysta', + 'Ainsleigh', + 'Alianah', + 'Meuy', + 'Meloney', + 'Talea', + 'Sheetal', + 'Shalana', + 'Venesa', + 'Teana', + 'Kiki', + 'Imee', + 'Aubryanna', + 'Allyanna', + 'Ambrie', + 'Amory', + 'Aniyha', + 'Caelynn', + 'Reita', + 'Rylann', + 'Aijah', + 'Aaliyha', + 'Alezandra', + 'Yeraldine', + 'Forestine', + 'Sameeha', + 'Caeley', + 'Britzy', + 'Blessin', + 'Armilda', + 'Birda', + 'Lorrine', + 'Krisalyn', + 'Linell', + 'Maryl', + 'Karole', + 'Maryela', + 'Mckinzy', + 'Madailein', + 'Kendi', + 'Kayda', + 'Jenasis', + 'Madelis', + 'Jamyiah', + 'Gabryela', + 'Catie', + 'Genessa', + 'Jamelia', + 'Jenene', + 'Nicholl', + 'Saralyn', + 'Taylah', + 'Xandra', + 'Jezlyn', + 'Zakayla', + 'Jaira', + 'Veena', + 'Shaden', + 'Sahiti', + 'Sahian', + 'Shelsey', + 'Sreya', + 'Zianna', + 'Angeleah', + 'Camily', + 'Lesvia', + 'Sonda', + 'Franceska', + 'Cytlaly', + 'Ylonda', + 'Issis', + 'Moon', + 'Joei', + 'Mariposa', + 'Ramandeep', + 'Preeti', + 'Niobe', + 'Sherran', + 'Nichola', + 'Letrice', + 'Waneda', + 'Meka', + 'Takeshia', + 'Leaann', + 'Girlie', + 'Olar', + 'Pearlena', + 'Carlean', + 'Dhanya', + 'Chastelin', + 'Aryanah', + 'Brihana', + 'Bijou', + 'Haifa', + 'Genesiss', + 'Genavie', + 'Enna', + 'Jazzel', + 'Japleen', + 'Iana', + 'Rahel', + 'Rylyn', + 'Pragya', + 'Yosselyn', + 'Yarelin', + 'Ellasyn', + 'Charlaine', + 'Zayli', + 'Taide', + 'Jodean', + 'Emilynn', + 'Channon', + 'Carinne', + 'Anaira', + 'Amisadai', + 'Caraline', + 'Danella', + 'Debanhy', + 'Devanee', + 'Koneta', + 'Jenie', + 'Hollee', + 'Marelie', + 'Mahathi', + 'Madilynne', + 'Lylia', + 'Loreli', + 'Lolah', + 'Lexine', + 'Maylynn', + 'Clarinda', + 'Marlynn', + 'Netra', + 'Makaylin', + 'Naira', + 'Naleah', + 'Mishel', + 'Myli', + 'Charlotta', + 'Arlisa', + 'Kaylynne', + 'Kamillah', + 'Ksenia', + 'Briseidy', + 'Aysel', + 'Anaily', + 'Eulean', + 'Adilee', + 'Abri', + 'Aidynn', + 'Alisyn', + 'Alicen', + 'Marveline', + 'Lupie', + 'Mariabelen', + 'Makenah', + 'Kyliegh', + 'Foye', + 'Yajahira', + 'Trenda', + 'Tya', + 'Nattaly', + 'Netanya', + 'Supriya', + 'Teja', + 'Srija', + 'Sherra', + 'Janissa', + 'Mysha', + 'Essfa', + 'Alexandrya', + 'Abi', + 'Takhia', + 'Jaeli', + 'Jaelynne', + 'Dianey', + 'Denisa', + 'Aleli', + 'Akina', + 'Aayushi', + 'Adanna', + 'Aunika', + 'Ithzel', + 'Caricia', + 'Kallyn', + 'Karmin', + 'Kindall', + 'Gredmarie', + 'Peace', + 'Jennalee', + 'Yaindhi', + 'Arcola', + 'Trannie', + 'Lyza', + 'Mackynzie', + 'Peggye', + 'Zenab', + 'Megyn', + 'Navina', + 'Naileah', + 'Maddelyn', + 'Luxe', + 'Arkie', + 'Belvia', + 'Edilia', + 'Monda', + 'Ridhi', + 'Peyten', + 'Sorayah', + 'Syrena', + 'Amberle', + 'Johnita', + 'Jerrye', + 'Alfa', + 'Jonita', + 'Lakie', + 'Jenalee', + 'Minami', + 'Morena', + 'Elsbeth', + 'Sylia', + 'Eunique', + 'Ellisa', + 'Lanai', + 'Jesselyn', + 'Jolissa', + 'Julizza', + 'Laquitha', + 'Jobina', + 'Wyvonne', + 'Shalese', + 'Deshannon', + 'Almendra', + 'Alisandra', + 'Geraldene', + 'Abygale', + 'Katelyne', + 'Kennede', + 'Karisia', + 'Lindzy', + 'Keyhla', + 'Emilea', + 'Dacey', + 'Jalah', + 'Adrienna', + 'Aisa', + 'Alaisha', + 'Brithney', + 'Calynn', + 'Cassity', + 'Brendy', + 'Reagen', + 'Myrah', + 'Montserrath', + 'Pheobe', + 'Nyeli', + 'Jocell', + 'Serenidy', + 'Issabela', + 'Hanalei', + 'Laelah', + 'Emmylou', + 'Geraldy', + 'Ovetta', + 'Analena', + 'Allyna', + 'Aliyanah', + 'Magdalyn', + 'Suann', + 'Ronee', + 'Amey', + 'Chirstina', + 'Trude', + 'Jearldine', + 'Maeleigh', + 'Lizzy', + 'Liviana', + 'Eithel', + 'Meryem', + 'Yaneisy', + 'Shatika', + 'Zeniyah', + 'Xaylee', + 'Pennelope', + 'Xochilth', + 'Jullie', + 'Saki', + 'Shaiann', + 'Haille', + 'Dannya', + 'Kerie', + 'Chianti', + 'Leza', + 'Koreen', + 'Letricia', + 'Lamanda', + 'Kinza', + 'Marisella', + 'Joelyn', + 'Cinde', + 'Chyrl', + 'Cece', + 'Boni', + 'Felecity', + 'Faithe', + 'Delayna', + 'Diamon', + 'Daley', + 'Darah', + 'France', + 'Kolina', + 'Kieu', + 'Grizel', + 'Shaleigh', + 'Shaylea', + 'Anitza', + 'Carrolyn', + 'Olimpia', + 'Jeannene', + 'Victoriana', + 'Azara', + 'Avelynn', + 'Aveah', + 'Ariam', + 'Devanie', + 'Daleisa', + 'Karelly', + 'Karalynn', + 'Keyleen', + 'Kendallyn', + 'Graceyn', + 'Falynn', + 'Evoleht', + 'Everlie', + 'Emri', + 'Hartlee', + 'Eleena', + 'Jailee', + 'Insiya', + 'Analysia', + 'Chalee', + 'Amzie', + 'Amilya', + 'Celisa', + 'Airabella', + 'Laketha', + 'Kyoko', + 'Saria', + 'Neli', + 'Melonee', + 'Neidy', + 'Nyanza', + 'Aizlynn', + 'Arthurine', + 'Mikhaela', + 'Adalae', + 'Parveen', + 'Lotoya', + 'Evanjelina', + 'Deborra', + 'Lunna', + 'Makylah', + 'Mckinleigh', + 'Mayalen', + 'Ladasia', + 'Javia', + 'Evian', + 'Jaelee', + 'Oluwatamilore', + 'Payzlee', + 'Reiley', + 'Samarra', + 'Chyler', + 'Areona', + 'Vanesha', + 'Tomisha', + 'Betzaira', + 'Dalana', + 'Destenie', + 'Brennah', + 'Cassidie', + 'Deziray', + 'Dimond', + 'Braeleigh', + 'Aylee', + 'Anastyn', + 'Amillia', + 'Jailyne', + 'Jissell', + 'Jailenne', + 'Inioluwa', + 'Jensyn', + 'Allia', + 'Evolett', + 'Emmalynne', + 'Emberlee', + 'Emaline', + 'Ellayna', + 'Kollins', + 'Keyly', + 'Livi', + 'Judeen', + 'Eleah', + 'Vonceil', + 'Kaaliyah', + 'Girtie', + 'Gianelle', + 'Iniya', + 'Harlynn', + 'Greidy', + 'Shayli', + 'Belina', + 'Auri', + 'Avangeline', + 'Alizey', + 'Arlynn', + 'Anelise', + 'Aneli', + 'Delmira', + 'Vanassa', + 'Ceana', + 'Ambre', + 'Florita', + 'Balbina', + 'Clova', + 'Danice', + 'Aydee', + 'Carlena', + 'Benicia', + 'Soumya', + 'Lissandra', + 'Ling', + 'Liahna', + 'Leonna', + 'Leilana', + 'Reeya', + 'Krisinda', + 'Maleiah', + 'Maiyah', + 'Mailin', + 'Lucciana', + 'Naydeen', + 'Nailani', + 'Miette', + 'Yeva', + 'Suley', + 'Shravya', + 'Kyia', + 'Shree', + 'Cerise', + 'Katriana', + 'Jaskiran', + 'Mone', + 'Latijera', + 'Rosicela', + 'Sidnee', + 'Rosisela', + 'Troi', + 'Victorya', + 'Creasie', + 'Latorsha', + 'Erienne', + 'Jovonna', + 'Jessia', + 'Jeny', + 'Dejia', + 'Destynie', + 'Barbi', + 'Marlinda', + 'Shakeitha', + 'Mistelle', + 'Ziona', + 'Zarahi', + 'Xiadani', + 'Zyrah', + 'Zoriah', + 'Pamla', + 'Cinamon', + 'Bernardette', + 'Makensie', + 'Lexani', + 'Miyana', + 'Costella', + 'Cliffie', + 'Lashune', + 'Windie', + 'Rhondalyn', + 'Avonelle', + 'Marcine', + 'Berneda', + 'Rosabelle', + 'Huldah', + 'Emagene', + 'Clarabell', + 'Marceil', + 'Ula', + 'Renika', + 'Shaterica', + 'Labrittany', + 'Zelia', + 'Aidy', + 'Abeeha', + 'Maebelle', + 'Farzona', + 'Bryelle', + 'Aphrodite', + 'Diyora', + 'Zilphia', + 'Ercell', + 'Starlynn', + 'Renad', + 'Reham', + 'Marwah', + 'Raaina', + 'Mehreen', + 'Chermaine', + 'Ameliah', + 'Hajra', + 'Anamika', + 'Caoimhe', + 'Tasheka', + 'Cladie', + 'Claretta', + 'Ratzy', + 'Parizoda', + 'Tzurty', + 'Simrah', + 'Miamor', + 'Mala', + 'Yittel', + 'Ranata', + 'Clellie', + 'Dewana', + 'Kenyada', + 'Sennie', + 'Estie', + 'Oprah', + 'Chessie', + 'Rumaisa', + 'Rosmery', + 'Shenell', + 'Cosima', + 'Ellyanna', + 'Hebe', + 'Aamira', + 'Beily', + 'Areesha', + 'Amilah', + 'Mahdiya', + 'Ramata', + 'Naava', + 'Cannie', + 'Dorraine', + 'Verlee', + 'Anija', + 'Garnita', + 'Lorenda', + 'Mikia', + 'Marvella', + 'Sharma', + 'Pamula', + 'Anmarie', + 'Valicia', + 'Collene', + 'Ronetta', + 'Floris', + 'Andora', + 'Berdina', + 'Ivadell', + 'Lorain', + 'Kevinisha', + 'Corielle', + 'Rinda', + 'Jodelle', + 'Arta', + 'Kalima', + 'Kalifa', + 'Liat', + 'Dashawna', + 'Jahnae', + 'Eylin', + 'Tahmina', + 'Sherin', + 'Niambi', + 'Tonjua', + 'Hanifah', + 'Maham', + 'Sokhna', + 'Carliss', + 'Nimra', + 'Quianna', + 'Shadai', + 'Renella', + 'Eliska', + 'Alima', + 'Agata', + 'Adenike', + 'Charizma', + 'Shirlean', + 'Joycelin', + 'Cyanne', + 'Ambika', + 'Albana', + 'Noshin', + 'Merve', + 'Sanjida', + 'Khiabet', + 'Maudrey', + 'Manuella', + 'Linder', + 'Bisma', + 'Shataya', + 'Shandel', + 'Samanthamarie', + 'Liron', + 'Liann', + 'Merdis', + 'Daquana', + 'Chanee', + 'Ezora', + 'Janiqua', + 'Jamielyn', + 'Kyesha', + 'Eulalie', + 'Montressa', + 'Alzina', + 'Monez', + 'Casmira', + 'Eileene', + 'Ethelmae', + 'Veneta', + 'Madiha', + 'Akeema', + 'Daneisha', + 'Cecely', + 'Gwendola', + 'Javonna', + 'Teshia', + 'Yaniris', + 'Valbona', + 'Corita', + 'Deshanna', + 'Kameka', + 'Armina', + 'Georgian', + 'Shakeera', + 'Saudia', + 'Stacyann', + 'Shenique', + 'Ura', + 'Felicie', + 'Ezola', + 'Janeece', + 'Chavely', + 'Ashling', + 'Nakea', + 'Shiana', + 'Shadasia', + 'Petronella', + 'Virgin', + 'Gunhild', + 'Brianni', + 'Grainne', + 'Aneisha', + 'Chaniece', + 'Zalika', + 'Tynasia', + 'Tashauna', + 'Shazia', + 'Shatiqua', + 'Sharissa', + 'Shanyce', + 'Shandell', + 'Shakeyla', + 'Vergia', + 'Geraldyne', + 'Dorita', + 'Nathasha', + 'Samanthajo', + 'Amela', + 'Afnan', + 'Halimah', + 'Dayatra', + 'Shontrell', + 'Tziry', + 'Shanyah', + 'Shawntell', + 'Schwanda', + 'Magalene', + 'Si', + 'Ramisa', + 'Ioanna', + 'Imane', + 'Hadar', + 'Ettel', + 'Coumba', + 'Chumy', + 'Shiran', + 'Lianny', + 'Kimara', + 'Nicha', + 'Chestine', + 'Fatmata', + 'Chedva', + 'Shaima', + 'Shailyn', + 'Zarin', + 'Zahrah', + 'Wania', + 'Tsering', + 'Syrai', + 'Suriyah', + 'No', + 'Niylah', + 'Meerab', + 'Emanuela', + 'Draizy', + 'Giabella', + 'Jeily', + 'Sofya', + 'Shantrelle', + 'Analisse', + 'Ramatoulaye', + 'Raima', + 'Sumaiyah', + 'Stori', + 'Tremeka', + 'Beila', + 'Clodagh', + 'Lyniah', + 'Giavana', + 'Tikisha', + 'Kesia', + 'Shawan', + 'Mazelle', + 'Lear', + 'Rosilyn', + 'Jnaya', + 'Jahnia', + 'Shi', + 'Henya', + 'Jhoselyn', + 'Doha', + 'Dilara', + 'Adelisa', + 'Dedria', + 'Troylynn', + 'Basha', + 'Fatimata', + 'Ama', + 'Ashantee', + 'Chania', + 'Donzella', + 'Ya', + 'Fahmida', + 'Iysis', + 'Neviah', + 'Anastasiya', + 'Brandel', + 'Afra', + 'Lendora', + 'Zisel', + 'Dwanda', + 'Ciarah', + 'Brighid', + 'Rafia', + 'Keamber', + 'Virdie', + 'Girtrude', + 'Nakaya', + 'Donis', + 'Anslei', + 'Alyene', + 'Audell', + 'Nahriah', + 'Zakeria', + 'Zoria', + 'Nikeria', + 'Kynley', + 'Karaline', + 'Jacquita', + 'Shonteria', + 'Carlyon', + 'Tykira', + 'Nykerria', + 'Lema', + 'Destyne', + 'Kansas', + 'Aryonna', + 'Iyannah', + 'Jamayah', + 'Serenitee', + 'Jood', + 'Willean', + 'Makyah', + 'Kameria', + 'Shelagh', + 'Zarriah', + 'Avionna', + 'Arilynn', + 'Vira', + 'Lelar', + 'Miyonna', + 'Jaionna', + 'Nakiah', + 'Rubby', + 'Henrene', + 'Perlie', + 'Tanyah', + 'Luretha', + 'Fannye', + 'Arquilla', + 'Albirta', + 'Annakate', + 'Akeria', + 'Teola', + 'Darthy', + 'Amberleigh', + 'Floriene', + 'Alleyne', + 'Karra', + 'Shaneika', + 'Nekita', + 'Niketa', + 'Kiaraliz', + 'Anacarolina', + 'Sharonica', + 'Renota', + 'Shambrica', + 'Mylea', + 'Jalicia', + 'Shantavious', + 'Antania', + 'Derika', + 'Rashunda', + 'Shandrea', + 'Teneisha', + 'Wachovia', + 'Jalecia', + 'Leimomi', + 'Lasondra', + 'Tela', + 'Caleah', + 'Iwalani', + 'Jamyri', + 'Azyria', + 'Napua', + 'Lahela', + 'Lehuanani', + 'Lameka', + 'Davelyn', + 'Filippa', + 'Tywanna', + 'Toini', + 'Pota', + 'Berthe', + 'Aliesha', + 'Iolanda', + 'Seaira', + 'Kealohilani', + 'Leialoha', + 'Chastidy', + 'Taimane', + 'Taylorann', + 'Briunna', + 'Tyrielle', + 'Alohilani', + 'Jakala', + 'Lakendria', + 'Tiffinie', + 'Laprecious', + 'Kaylaann', + 'Marigny', + 'Roise', + 'Kaidance', + 'Niyla', + 'Mahari', + 'Zya', + 'Ruthia', + 'Timara', + 'Caniya', + 'Keirah', + 'Arieonna', + 'Alydia', + 'Alivea', + 'Ahmani', + 'Elynn', + 'Earnstine', + 'Ramiya', + 'Morrigan', + 'Masiyah', + 'Harmoney', + 'Pearley', + 'Jearlean', + 'Korrine', + 'Chyanna', + 'Catena', + 'Pacita', + 'Kalle', + 'Alzira', + 'Tashayla', + 'Tsugie', + 'Yachiyo', + 'Shellia', + 'Sueno', + 'Kazuyo', + 'Kikumi', + 'Shizuka', + 'Chiyono', + 'Shigeno', + 'Tatsue', + 'Fumiyo', + 'Misayo', + 'Momoyo', + 'Hanayo', + 'Misae', + 'Dalaney', + 'Dewanda', + 'Itsuko', + 'Nyamal', + 'Claris', + 'Virlee', + 'Lulabelle', + 'Valada', + 'Neleigh', + 'Rafelita', + 'Placida', + 'Dulcinea', + 'Pita', + 'Heer', + 'Beren', + 'Ramoncita', + 'Orlinda', + 'Florette', + 'Deluvina', + 'Lugarda', + 'Crucita', + 'Rafaelita', + 'Pablita', + 'Lamaria', + 'Terriana', + 'Terrianna', + 'Dariyah', + 'Carmie', + 'Clotine', + 'Antha', + 'Takyla', + 'Peachie', + 'Akirah', + 'Captola', + 'Sadeel', + 'Dosha', + 'Miquela', + 'Anilah', + 'Erielle', + 'Janiylah', + 'Aubriel', + 'Priti', + 'Purvi', + 'Shakeemah', + 'Anjail', + 'Shaheerah', + 'Amneris', + 'Melverine', + 'Twilla', + 'Kruti', + 'Jalee', + 'Shareefah', + 'Muslimah', + 'Tauheedah', + 'Anabela', + 'Yakima', + 'Lyllian', + 'Tanajah', + 'Sakiyah', + 'Eun', + 'Yashika', + 'Ji', + 'Demiana', + 'Mariaeduard', + 'Snigdha', + 'Dala', + 'Kum', + 'Myung', + 'Hadiyah', + 'Gopi', + 'Cresta', + 'In', + 'Davita', + 'Talayeh', + 'Tracyann', + 'Petula', + 'Nerida', + 'Jeaneen', + 'Ilissa', + 'Letta', + 'Kishia', + 'Gesenia', + 'Bethsaida', + 'Tanija', + 'Ivelise', + 'Marines', + 'Angenette', + 'Alanda', + 'Lauraann', + 'Darnetta', + 'Alisande', + 'Jeniya', + 'Patria', + 'Tieysha', + 'Tasheen', + 'Ife', + 'Loredana', + 'Amyjo', + 'Chane', + 'Nilka', + 'Sharema', + 'Grazia', + 'Renna', + 'Tahesha', + 'Tarita', + 'Jannis', + 'Geriann', + 'Areatha', + 'Rosangel', + 'Kemba', + 'Noni', + 'Margaretann', + 'Kimberleigh', + 'Latisa', + 'Kiriaki', + 'Bobbyjo', + 'Walida', + 'Lynanne', + 'Niyanna', + 'Daziah', + 'Kharma', + 'Pier', + 'Marymargaret', + 'Lorrain', + 'Ketty', + 'Helane', + 'Tarnisha', + 'Sherrice', + 'Swati', + 'Donnajean', + 'Tunya', + 'Annmargaret', + 'Raffaella', + 'Pina', + 'Deneene', + 'Lorriane', + 'Shenise', + 'Ziyonna', + 'Evagelia', + 'Chantae', + 'Tasheema', + 'Meaghen', + 'Shanikqua', + 'Lynnox', + 'Taiesha', + 'Sharima', + 'Shantai', + 'Shaena', + 'Jamine', + 'Rasheena', + 'Tashi', + 'Magdala', + 'Edia', + 'Lasheka', + 'Tiasha', + 'Quanita', + 'Jomayra', + 'Nairoby', + 'Danamarie', + 'Roena', + 'Zasha', + 'Shatema', + 'Orissa', + 'Elvire', + 'Louisiana', + 'Hoda', + 'Kashana', + 'Jaquanna', + 'Jacqulin', + 'Annamari', + 'Marquia', + 'Elmire', + 'Viney', + 'Sonna', + 'Yokasta', + 'Esma', + 'Rella', + 'Deloras', + 'Janill', + 'Samanthan', + 'Ketsia', + 'Chaunte', + 'Aderonke', + 'Sheindel', + 'Shameen', + 'Karema', + 'Amalin', + 'Glendaliz', + 'Finesse', + 'Talibah', + 'Lakima', + 'Geeta', + 'Suehay', + 'Dorice', + 'Aesha', + 'Lateasha', + 'Kimitra', + 'Omolola', + 'Bobbette', + 'Deliah', + 'Carianne', + 'Chanah', + 'Laquandra', + 'Laquanna', + 'Yanick', + 'Nathifa', + 'Nakima', + 'Gayl', + 'Shamaine', + 'Saquana', + 'Nixzaliz', + 'Chaye', + 'Maleka', + 'Latima', + 'Yamira', + 'Tashanna', + 'Kathiria', + 'Jameika', + 'Jamesetta', + 'Moniqua', + 'Yamaris', + 'Tasheba', + 'Virgina', + 'Aviance', + 'Calogera', + 'Candita', + 'Kinga', + 'Alissia', + 'Onnolee', + 'Johnda', + 'Sebastiana', + 'Michelena', + 'Tecla', + 'Mirriam', + 'Sydel', + 'Glema', + 'Tatiyanna', + 'Patrycia', + 'Fortuna', + 'Ebba', + 'Carmelia', + 'Liddie', + 'Genella', + 'Detta', + 'Malvery', + 'Evelene', + 'Loretto', + 'Nunziata', + 'Jenan', + 'Keshawna', + 'Kinisha', + 'Tikia', + 'Sueanne', + 'Cira', + 'Charda', + 'Midge', + 'Annina', + 'Delcina', + 'Barbette', + 'Danah', + 'Isolina', + 'Tanita', + 'Gracemarie', + 'Halleigh', + 'Julita', + 'Kaprice', + 'Dorothyann', + 'Binnie', + 'Bettyjean', + 'Frayda', + 'Tashiana', + 'Breshey', + 'Charnise', + 'Tashena', + 'Meribeth', + 'Sandralee', + 'Heena', + 'Walda', + 'Latika', + 'Rashaunda', + 'Linde', + 'Rosaleen', + 'Illona', + 'Clydette', + 'Benay', + 'Damonica', + 'Anajah', + 'Louelle', + 'Lunette', + 'Faduma', + 'Nadeige', + 'Meylin', + 'Elverna', + 'Etrulia', + 'Ellaree', + 'Rushie', + 'Jayona', + 'Mauri', + 'Radiah', + 'Runette', + 'Terrah', + 'Joia', + 'Ezma', + 'Glenys', + 'Ramla', + 'Shatasha', + 'Berma', + 'Chanteria', + 'Chantrell', + 'Elvi', + 'Sharnell', + 'Rether', + 'Keshana', + 'Ranesha', + 'Earther', + 'Zahirah', + 'Anye', + 'Khori', + 'Saniyyah', + 'Teniola', + 'Anniemae', + 'Oluwadamilola', + 'Aldene', + 'Amellia', + 'Junice', + 'Carolene', + 'Ireoluwa', + 'Nasra', + 'Vernease', + 'Delrose', + 'Marysue', + 'Mirlande', + 'Lashannon', + 'Taijah', + 'Markiesha', + 'Syanne', + 'Jahiya', + 'Vyonne', + 'Reniya', + 'Ryana', + 'Idonia', + 'Loette', + 'Etheleen', + 'Ariyon', + 'Jeneane', + 'Jamea', + 'Airyana', + 'Natesha', + 'Bonnell', + 'Savilla', + 'Daneshia', + 'Deneshia', + 'Alexzandrea', + 'Martharee', + 'Elfreda', + 'Danyla', + 'Retaj', + 'Childnotnamed', + 'Kariana', + 'Ladeja', + 'Johnesha', + 'Nariya', + 'Zamariah', + 'Shanyla', + 'Zykiria', + 'Micaella', + 'Angeliyah', + 'Camara', + 'Kenniyah', + 'Keyani', + 'Renie', + 'Aldena', + 'Paytyn', + 'Perma', + 'Annamary', + 'Roniyah', + 'Zeniya', + 'Capitola', + 'Jaiana', + 'Lakiya', + 'Reida', + 'Ahniya', + 'Elanor', + 'Dorothee', + 'Joud', + 'Ludmilla', + 'Traniyah', + 'Kjerstin', + 'Jeylin', + 'Teona', + 'Marypat', + 'Jacquelynne', + 'Harmonii', + 'Kenyah', + 'Anora', + 'Deniyah', + 'Tyleah', + 'Samora', + 'Almeter', + 'Floride', + 'Lether', + 'Aviah', + 'Livie', + 'Federica', + 'Khalani', + 'Dericka', + 'Ronisue', + 'Raziah', + 'Emaya', + 'Christyana', + 'Rasheka', + 'Jahira', + 'Jalana', + 'Lateria', + 'Baneen', + 'Davisha', + 'Joyanna', + 'Janelys', + 'Raneisha', + 'Israa', + 'Shauntavia', + 'Shericka', + 'Deloma', + 'Maryetta', + 'Jeannetta', + 'Tymber', + 'Charmon', + 'Lanise', + 'Charlisa', + 'Bloneva', + 'Andrena', + 'Katena', + 'Latorria', + 'Letoya', + 'Quovadis', + 'Lakeisa', + 'Sihaam', + 'Charo', + 'Annaclara', + 'Margretta', + 'Nataki', + 'Tyjae', + 'Bahja', + 'Shequila', + 'Quadira', + 'Toinette', + 'Sumeya', + 'Takita', + 'Sherlonda', + 'Daejah', + 'Zyanna', + 'Antonique', + 'Linnae', + 'Georgean', + 'Charlane', + 'Jakerria', + 'Nimo', + 'Saprina', + 'Detrice', + 'Nicolly', + 'Nayara', + 'Seandra', + 'Demetrica', + 'Kayton', + 'Jalayna', + 'Emanuelly', + 'Dondra', + 'Michaeleen', + 'Aquinnah', + 'Lakrisha', + 'Latoia', + 'Bernessia', + 'Jaydaliz', + 'Deona', + 'Donyelle', + 'Kearsten', + 'Tashira', + 'Kaisa', + 'Korrin', + 'Onelia', + 'Shawntia', + 'Faylene', + 'Nafeesah', + 'Synetta', + 'Robertine', + 'Krystn', + 'Nyjae', + 'Nijae', + 'Cieara', + 'Ellerie', + 'Thomasenia', + 'Tiki', + 'Lougenia', + 'Joeann', + 'Marlyss', + 'Saralee', + 'Dayona', + 'Alainna', + 'Gennell', + 'Berline', + 'Latoiya', + 'Eyvonne', + 'Cherline', + 'Tequesta', + 'Loann', + 'Kerstyn', + 'Najmo', + 'Shanitra', + 'Marnice', + 'Tamyah', + 'Ave', + 'Cierrah', + 'Deborahann', + 'Davette', + 'Kennidy', + 'Breelle', + 'Lundon', + 'Imoni', + 'Shamyah', + 'Lindia', + 'Caylyn', + 'Ghadeer', + 'Amirrah', + 'Arlayne', + 'Norrine', + 'Vondell', + 'Ruqaya', + 'Azariya', + 'Narice', + 'Glenadine', + 'Lallie', + 'Conola', + 'Airlie', + 'Lorelie', + 'Levis', + 'Sanyia', + 'Mckaela', + 'Arlina', + 'Dellar', + 'Zorianna', + 'Zanyiah', + 'Maleya', + 'Niyana', + 'Amonie', + 'Aryia', + 'Autie', + 'Keileigh', + 'Kyndel', + 'Saliyah', + 'Naziah', + 'Bernette', + 'Vona', + 'Venie', + 'Tyashia', + 'Khaliya', + 'Mckensie', + 'Kerigan', + 'Kaniah', + 'Eria', + 'Maziyah', + 'Kiasia', + 'Anice', + 'Dera', + 'Georgena', + 'Ezelle', + 'Eavan', + 'Marlyne', + 'Lovella', + 'Westonia', + 'Keniah', + 'Janiaya', + 'Mertice', + 'Marget', + 'Zyeria', + 'Marquerite', + 'Minha', + 'Redonna', + 'Deetta', + 'Aiyla', + 'Majel', + 'Elnor', + 'Deronda', + 'Viona', + 'Rosaleigh', + 'Virgiline', + 'Reeda', + 'Minnah', + 'Keerthi', + 'Kaleyah', + 'Myanna', + 'Remas', + 'Noralee', + 'Idabelle', + 'Albena', + 'Ellory', + 'Areej', + 'Zariel', + 'Laverle', + 'Hjordis', + 'Hilja', + 'Ragna', + 'Cordella', + 'Irean', + 'Ottilia', + 'Gerane', + 'Locklyn', + 'Equilla', + 'Dellie', + 'Aarvi', + 'Mardella', + 'Leighanna', + 'Theone', + 'Ordella', + 'Lidwina', + 'Alyda', + 'Arlyss', + 'Evangelita', + 'Hee', + 'Cherell', + 'Charelle', + 'Shealynn', + 'Anesha', + 'Jasman', + 'Stephie', + 'Ok', + 'Tacarra', + 'Sharnita', + 'Jessic', + 'Dulcey', + 'Natina', + 'Sharvae', + 'Nachelle', + 'Jillane', + 'Tarri', + 'Ajena', + 'Allexus', + 'Labrenda', + 'Pammy', + 'Shemeika', + 'Ysela', + 'Meghin', + 'Marketta', + 'Porshe', + 'Kayti', + 'Taylour', + 'Shavonte', + 'Aivah', + 'Khloi', + 'Jerzie', + 'Nikesha', + 'Cherron', + 'Coralynn', + 'Alvita', + 'Carlita', + 'Albany', + 'Deshawnda', + 'Lacole', + 'Lameeka', + 'Mashawn', + 'Kimyata', + 'Keenya', + 'Baya', + 'Kiva', + 'Samona', + 'Meggin', + 'Chanita', + 'Danissa', + 'Lileigh', + 'Addeline', + 'Shemeeka', + 'Aprille', + 'Donice', + 'Tannisha', + 'Angelette', + 'Lakeita', + 'Marcelyn', + 'Lesta', + 'Claudene', + 'Marney', + 'Tonyia', + 'Nellora', + 'Kimyetta', + 'Ameliana', + 'Electa', + 'Sherl', + 'Jeniece', + 'Jawana', + 'Errica', + 'Braya', + 'Titania', + 'Guydra', + 'Valeta', + 'Danetta', + 'Sharia', + 'Hawraa', + 'Danaja', + 'Makalynn', + 'Tayonna', + 'Kyrene', + 'Arieona', + 'Dallie', + 'Ruie', + 'Ophia', + 'Odella', + 'Vessie', + 'Offie', + 'Evadean', + 'Ample', + 'Aleecia', + 'Shakyla', + 'Makynna', + 'Lakyra', + 'Korryn', + 'Araina', + 'Semiyah', + 'Ndea', + 'Areonna', + 'Jasia', + 'Xavia', + 'Merikay', + 'Keshara', + 'Jennetta', + 'Vergene', + 'Wilodean', + 'Wyona', + 'Avonell', + 'Datha', + 'Ellar', + 'Morene', + 'Laverda', + 'Loetta', + 'Emmogene', + 'Arbadella', + 'Camaria', + 'Rochella', + 'Indiya', + 'Shayma', + 'Orneta', + 'Clotene', + 'Genoa', + 'Lanyah', + 'Oneda', + 'Glendola', + 'Rosala', + 'Zelphia', + 'Suda', + 'Jerrilynn', + 'Orlena', + 'Lorella', + 'Bernadean', + 'Novice', + 'Pheba', + 'Rukaya', + 'Gathel', + 'Meron', + 'Asianae', + 'Arriel', + 'Whisper', + 'Talesha', + 'Morgann', + 'Madissen', + 'Dajanay', + 'Karil', + 'Sherrita', + 'Chery', + 'Lezlee', + 'Daytona', + 'Raegen', + 'Dalal', + 'Majerle', + 'Lama', + 'Daijanae', + 'Celicia', + 'Cheril', + 'Cornesha', + 'Aniza', + 'Clytie', + 'Persis', + 'Aino', + 'Lawandra', + 'Deshonda', + 'Catrena', + 'Temekia', + 'Camella', + 'Arnetra', + 'Latoyna', + 'Tekisha', + 'Nalee', + 'Jennife', + 'Daphanie', + 'Shewanda', + 'Cheronda', + 'Latayna', + 'Almerinda', + 'Danene', + 'Jadwiga', + 'Ellora', + 'Tanga', + 'Tamekka', + 'Lashond', + 'Shinika', + 'Khyleigh', + 'Baelyn', + 'Clarene', + 'Monyette', + 'Lakisa', + 'Audreyanna', + 'Malayjah', + 'Keia', + 'Lajessica', + 'Marquite', + 'Odessia', + 'Marketia', + 'Malayshia', + 'Laconya', + 'Brayla', + 'Germani', + 'Luberdie', + 'Angla', + 'Cona', + 'Katrinia', + 'Shaletha', + 'Eutha', + 'Elmyra', + 'Cleva', + 'Elnore', + 'Vila', + 'Evone', + 'Margert', + 'Pairlee', + 'Bernelle', + 'Diannie', + 'Alinda', + 'Emerine', + 'Rogena', + 'Genette', + 'Jearline', + 'Estalee', + 'Bertina', + 'Cassand', + 'Kisa', + 'Veronic', + 'Idalina', + 'Walsie', + 'Gwendol', + 'Orvilla', + 'Latonga', + 'Elizabe', + 'Bernece', + 'Charlen', + 'Dola', + 'Alaija', + 'Martia', + 'Shanica', + 'Shariya', + 'Yuliya', + 'Atleigh', + 'Flannery', + 'Loeta', + 'Zakiah', + 'Alayia', + 'Glee', + 'Embree', + 'Kasidy', + 'Zacaria', + 'Derriona', + 'Jakyria', + 'Kiauna', + 'Garnelle', + 'Tyriana', + 'Juliya', + 'Maddisen', + 'Auna', + 'Jameisha', + 'Lurleen', + 'Kourtlyn', + 'Chelan', + 'Verlinda', + 'Sherria', + 'Alzada', + 'Ketara', + 'Anaka', + 'Breion', + 'Shadestiny', + 'Shanterica', + 'Tenia', + 'Keiosha', + 'Jamyriah', + 'Jamyrie', + 'Jalacia', + 'Ronita', + 'Maryln', + 'Earsie', + 'Kyri', + 'Markiyah', + 'Malajah', + 'Alandria', + 'Shaquitta', + 'Raymona', + 'Paeton', + 'Yaritzy', + 'Jonesha', + 'Anda', + 'Khadjah', + 'Amyree', + 'Vernestine', + 'Lavetta', + 'Jniya', + 'Shakiyah', + 'Aasia', + 'Roniya', + 'Keleigh', + 'Makalyn', + 'Kadasia', + 'Johneisha', + 'Jakaya', + 'Kinzey', + 'Wendelyn', + 'Darielys', + 'Wyteria', + 'Yarieliz', + 'Taysia', + 'Carmya', + 'Erionna', + 'Shameria', + 'Kearia', + 'Graycie', + 'Jurnie', + 'Calypso', + 'Finlee', + 'Fynlee', + 'Sophee', + 'Lorali', + 'Shacoria', + 'Kadeejah', + 'Lakira', + 'Kelsay', + 'Angelys', + 'Moeshia', + 'Keundra', + 'Mayara', + 'Josi', + 'Annaluiza', + 'Jacquese', + 'Jillaine', + 'Annajulia', + 'Nayeliz', + 'Maire', + 'Jamonica', + 'Jadalys', + 'Missie', + 'Machell', + 'Liisa', + 'Jalaine', + 'Odester', + 'Veria', + 'Virda', + 'Arleene', + 'Cigi', + 'Eloda', + 'Kacelyn', + 'Cidalia', + 'Vadie', + 'Wydell', + 'Donnita', + 'Lousie', + 'Oreatha', + 'Berdine', + 'Cielita', + 'Lilas', + 'Verneda', + 'Armelia', + 'Glender', + 'Elizbeth', + 'Vanella', + 'Florean', + 'Vyolet', + 'Albertia', + 'Albirda', + 'Sylva', + 'Lakresha', + 'Matha', + 'Nerine', + 'Dezzie', + 'Lodell', + 'Rosielee', + 'Julane', + 'Lodena', + 'Brookley', + 'Kynadi', + 'Krymson', + 'Etoile', + 'Meighan', + 'Izella', + 'Jakaria', + 'Jaleria', + 'Clister', + 'Alberdia', + 'Zykeriah', + 'Mileigh', + 'Isola', + 'Mamye', + 'Eller', + 'Kamoria', + 'Lakelynn', + 'Aslean', + 'Bular', + 'Emmaclaire', + 'Dasie', + 'Denotra', + 'Everlene', + 'Lynleigh', + 'Iantha', + 'Quinetta', + 'Lillion', + 'Sophronia', + 'Japonica', + 'Beauty', + 'Pearlina', + 'Evella', + 'Jatana', + 'Kechia', + 'Conswella', + 'Malissia', + 'Alexina', + 'Demeka', + 'Muguette', + 'Vaudine', + 'Aprill', + 'Villa', + 'Florece', + 'Tonjia', + 'Bethania', + 'Makinlee', + 'Latondra', + 'Audery', + 'Ericia', + 'Miyoshi', + 'Betti', + 'Harlym', + 'Novelle', + 'Liller', + 'Pinkey', + 'Narcille', + 'Lasheika', + 'Leonise', + 'Lydie', + 'Olla', + 'Rejeanne', + 'Athelene', + 'Eloyse', + 'Edolia', + 'Clotile', + 'Ethelrine', + 'Devonda', + 'Nakeshia', + 'Tomesha', + 'Orena', + 'Karlyne', + 'Enolia', + 'Faynell', + 'Margia', + 'Marvelene', + 'Justilia', + 'Iceola', + 'Shantina', + 'Shinita', + 'Loula', + 'Ireta', + 'Vanessia', + 'Ramonia', + 'Monita', + 'Shalva', + 'Ong', + 'Remonia', + 'Sheral', + 'Angelean', + 'Phyllistine', + 'Brenetta', + 'Madgeline', + 'Zyairah', + 'Anjolaoluwa', + 'Clotiel', + 'Eldine', + 'Tylia', + 'Ifeoluwa', + 'Florestine', + 'Althia', + 'Ravonda', + 'Tsion', + 'Zyaira', + 'Wylodene', + 'Janesha', + 'Vonciel', + 'Ruthey', + 'Khiana', + 'Kadesia', + 'Murdis', + 'Zhana', + 'Jillayne', + 'Quatisha', + 'Jaquasia', + 'Michaila', + 'Mashayla', + 'Travia', + 'Tyrika', + 'Aldah', + 'Makaiya', + 'Maridee', + 'Kyndell', + 'Nykira', + 'Mazell', + 'Luecile', + 'Quatasia', + 'Khala', + 'Sible', + 'Jakera', + 'Ovella', + 'Lealer', + 'Juleen', + 'Rinette', + 'Laykin', + 'Ozite', + 'Shaquanta', + 'Quanetta', + 'Shannyn', + 'Lacrystal', + 'Everline', + 'Editha', + 'Toneka', + 'Reinette', + 'Maclovia', + 'Ledia', + 'Shakeeka', + 'Shakeeta', + 'Taquanna', + 'Miyisha', + 'Patrecia', + 'Wylodean', + 'Solita', + 'Dalisa', + 'Jatoya', + 'Texanna', + 'Yvetta', + 'Lectoria', + 'Cyntrell', + 'Monyae', + 'Ibtisam', + 'Miski', + 'Renesha', + 'Maelle', + 'Azhar', + 'Zamzam', + 'Jamera', + 'Tyranika', + 'Ladan', + 'Ruweyda', + 'Jabrea', + 'Sherrica', + 'Clyda', + 'Treniece', + 'Fonnie', + 'Bedie', + 'Kewanda', + 'Mozel', + 'Tramika', + 'Quessie', + 'Tyshay', + 'Ladasha', + 'Kaarin', + 'Mazzie', + 'Genora', + 'Monie', + 'Muntas', + 'Hayat', + 'Jovanda', + 'Appolonia', + 'Cuma', + 'Briante', + 'Reneisha', + 'Zenovia', + 'Allysia', + 'Aliene', + 'Raini', + 'Tyja', + 'Iriel', + 'Deshante', + 'Shatira', + 'Demri', + 'Ajaysia', + 'Ireon', + 'Idil', + 'Nawaal', + 'Riham', + 'Nyeisha', + 'Jonique', + 'Keneisha', + 'Ravan', + 'Khadra', + 'Dawanna', + 'Gavriella', + 'Myrene', + 'Jasamine', + 'Brione', + 'Earlisha', + 'Dazia', + 'Jalesia', + 'Cabrina', + 'Marieme', + 'Gloristine', + 'Cattie', + 'Damilola', + 'Evora', + 'Almarie', + 'Vauda', + 'Tanzie', + 'Truby', + 'Tayona', + 'Francelia', + 'Brona', + 'Jannice', + 'Weltha', + 'Phylliss', + 'Vieva', + 'Danera', + 'Saratha', + 'Colinda', + 'Suzonne', + 'Shelene', + 'Shelda', + 'Annye', + 'Kaola', + 'Modine', + 'Velvie', + 'Vetra', + 'Tyrhonda', + 'Malissie', + 'Shemica', + 'Rockell', + 'Adgie', + 'Lachanda', + 'Kwanza', + 'Keyanta', + 'Hazeleen', + 'Yarnell', + 'Mettie', + 'Kissie', + 'Jawanna', + 'Ilham', + 'Enchantra', + 'Lucielle', + 'Salmo', + 'Sabrin', + 'Nicy', + 'Rubell', + 'Willet', + 'Ronata', + 'Semiko', + 'Idman', + 'Meoshia', + 'Maie', + 'Eulala', + 'Tiyonna', + 'Sabarin', + 'Merlie', + 'Oneka', + 'Khiya', + 'Geralene', + 'Hubbie', + 'Patches', + 'Robenia', + 'Carita', + 'Veleka', + 'Tamla', + 'Zondra', + 'Cheramie', + 'Nimco', + 'Chauntelle', + 'Calonia', + 'Mulki', + 'Clydia', + 'Glida', + 'Fartun', + 'Fardowsa', + 'Iyona', + 'Dwanna', + 'Angila', + 'Carletha', + 'Blakley', + 'Valecia', + 'Songa', + 'Shya', + 'Kamber', + 'Siah', + 'Sloka', + 'Sophiagrace', + 'Sophiamarie', + 'Setayesh', + 'Roselie', + 'Samhitha', + 'Savreen', + 'Zanayah', + 'Yilia', + 'Zareena', + 'Yeilin', + 'Ulyana', + 'Tylie', + 'Vaani', + 'Vasilisa', + 'Videl', + 'Xylia', + 'Rubylee', + 'Jessye', + 'Itasca', + 'Bonifacia', + 'Bennye', + 'Estellene', + 'Daycee', + 'Vung', + 'Babe', + 'Lucyle', + 'Laurencia', + 'Frankye', + 'Clariece', + 'Alsace', + 'Ernesteen', + 'Zuma', + 'Loleta', + 'Matiana', + 'Thyra', + 'Thekla', + 'Miladie', + 'Moselle', + 'Waldene', + 'Thula', + 'Ethelda', + 'Elbira', + 'Eddye', + 'Lafaye', + 'Beryle', + 'Beanna', + 'Basilisa', + 'Bernardina', + 'Vontressa', + 'Elner', + 'Gladine', + 'Saketha', + 'Nellene', + 'Margurette', + 'Levada', + 'Alcie', + 'Beuna', + 'Miaa', + 'Miia', + 'Miral', + 'Lunabella', + 'Manvi', + 'Nahlia', + 'Quetzal', + 'Preet', + 'Navreet', + 'Prajna', + 'Analayah', + 'Aalaya', + 'Aaleah', + 'Aaria', + 'Aby', + 'Adeena', + 'Adelaine', + 'Adhara', + 'Alekhya', + 'Avaline', + 'Avina', + 'Azaliah', + 'Azayla', + 'Anwita', + 'Arna', + 'Asmi', + 'Cutina', + 'Jaydalynn', + 'Jerusalem', + 'Hiyab', + 'Icey', + 'Jaanvi', + 'Khalessi', + 'Khiara', + 'Leelah', + 'Ketzaly', + 'Kaliyanei', + 'Karolynn', + 'Kaylonnie', + 'Harveen', + 'Danilynn', + 'Decklyn', + 'Deleyza', + 'Charm', + 'Calina', + 'Cathaleya', + 'Dailynn', + 'Corra', + 'Cyrene', + 'Eveleen', + 'Fia', + 'Galina', + 'Gohar', + 'Gursirat', + 'Harleyquinn', + 'Evalin', + 'Eevee', + 'Eira', + 'Elara', + 'Ellaina', + 'Ellarose', + 'Erabella', + 'Teofila', + 'Calamity', + 'Sherion', + 'Niang', + 'Oreta', + 'Leita', + 'Maedelle', + 'Othello', + 'Meshell', + 'Alfreida', + 'Detria', + 'Cloda', + 'Ermine', + 'Gertrudes', + 'Zudora', + 'Benigna', + 'Dolorez', + 'Narcissa', + 'Eduviges', + 'Dionisia', + 'Crisanta', + 'Adreena', + 'Aivy', + 'Sharanda', + 'Amma', + 'Danitra', + 'Lashuna', + 'Yasheka', + 'Sheronica', + 'Ameliya', + 'Cayetana', + 'Benancia', + 'Tiya', + 'Umaiza', + 'Vicktoria', + 'Vidushi', + 'Yaretzie', + 'Siennah', + 'Sofiah', + 'Stuti', + 'Taitum', + 'Yuli', + 'Zarya', + 'Zeriah', + 'Sadiee', + 'Rubee', + 'Ryenn', + 'Sayana', + 'Ezabella', + 'Galya', + 'Hayzel', + 'Evalette', + 'Eleanna', + 'Elize', + 'Elleana', + 'Hiya', + 'Jezabelle', + 'Jazzy', + 'Jeraldin', + 'Jocabed', + 'Kaloni', + 'Jazmeen', + 'Jasmarie', + 'Ilani', + 'Ilany', + 'Ariannie', + 'Angelinne', + 'Delaynie', + 'Calise', + 'Bethlehem', + 'Cateleya', + 'Paitynn', + 'Peytin', + 'Rainie', + 'Rhylin', + 'Rosaly', + 'Nomi', + 'Mirai', + 'Moksha', + 'Mylin', + 'Nazeli', + 'Nilani', + 'Marcelene', + 'Victorina', + 'Laiah', + 'Leeyah', + 'Miaisabella', + 'Ravleen', + 'Lazara', + 'Zuleidy', + 'Shraddha', + 'Simarpreet', + 'Rinoa', + 'Ridhima', + 'Ryla', + 'Ryleeann', + 'Ryli', + 'Sahori', + 'Smrithi', + 'Yeslin', + 'Yanessa', + 'Zeltzin', + 'Sonakshi', + 'Sophea', + 'Carlissa', + 'Bryttani', + 'Albesa', + 'Bonnye', + 'Daksha', + 'Terria', + 'Davinah', + 'Enalina', + 'Evolette', + 'Dhwani', + 'Eleora', + 'Leea', + 'Lexii', + 'Meilyn', + 'Nevah', + 'Noga', + 'Prabhleen', + 'Quinley', + 'Mursal', + 'Naiara', + 'Navah', + 'Izumi', + 'Janelli', + 'Jniyah', + 'Klaryssa', + 'Kritika', + 'Laksmi', + 'Lalani', + 'Joselle', + 'Kashish', + 'Kenyana', + 'Laquishia', + 'Deshonna', + 'Sentoria', + 'Ernestene', + 'Maxima', + 'Senovia', + 'Nestora', + 'Valta', + 'Casady', + 'Daphene', + 'Chonita', + 'Omelia', + 'Odena', + 'Melchora', + 'Quetzally', + 'Thera', + 'Gabina', + 'Donaciana', + 'Riddhima', + 'Lakessa', + 'Lakeeta', + 'Katasha', + 'Chaitra', + 'Chizara', + 'Aveyah', + 'Elah', + 'Eliannah', + 'Ellanore', + 'Emmalia', + 'Dalexa', + 'Delara', + 'Donatella', + 'Aubreanna', + 'Aberdeen', + 'Aerilyn', + 'Aleksia', + 'Annarose', + 'Anthea', + 'Aoi', + 'Amberrose', + 'Anaeli', + 'Lilou', + 'Lumen', + 'Manasvi', + 'Lillybeth', + 'Keylani', + 'Lenya', + 'Lidya', + 'Mulan', + 'Nirvi', + 'Ondine', + 'Meenakshi', + 'Mathea', + 'Melyna', + 'Io', + 'Izelle', + 'Jailia', + 'Eztli', + 'Gali', + 'Hade', + 'Hafsah', + 'Hannahgrace', + 'Kayleah', + 'Kayleeann', + 'Kemily', + 'Jeylah', + 'Jiaqi', + 'Sherrika', + 'Daffney', + 'Solstice', + 'Soriah', + 'Sumayya', + 'Saory', + 'Shaily', + 'Shanzay', + 'Sharvi', + 'Xylina', + 'Yeimy', + 'Yizel', + 'Zaidee', + 'Ziah', + 'Jesucita', + 'Madalena', + 'Vontresa', + 'Tangee', + 'Shekina', + 'Sista', + 'Norvis', + 'Winnell', + 'Yoshida', + 'Nikiya', + 'Vidala', + 'Shandria', + 'Rozelle', + 'Maragret', + 'Sixta', + 'Theta', + 'Wylma', + 'Jobita', + 'Gaudalupe', + 'Lurlean', + 'Oveta', + 'Heriberta', + 'Bacilia', + 'Senorina', + 'Denika', + 'Akeisha', + 'Tamecia', + 'Jera', + 'Crestina', + 'Shwanda', + 'Kelbie', + 'Sanayah', + 'Zaliah', + 'Nadezhda', + 'Maaliyah', + 'Mahaley', + 'Raziyah', + 'Saraiya', + 'Cyriah', + 'Chaniyah', + 'Emmarae', + 'Eleen', + 'Ashland', + 'Briniyah', + 'Ainhoa', + 'Aviyah', + 'Atarah', + 'Lutrelle', + 'Clevie', + 'Blossie', + 'Cola', + 'Para', + 'Verdelle', + 'Beddie', + 'Lilliemae', + 'Jurell', + 'Bertice', + 'Fozie', + 'Oppie', + 'Rozia', + 'Rozie', + 'Epsie', + 'Karman', + 'Estoria', + 'Dynesha', + 'Sarae', + 'Xolani', + 'Talyah', + 'Zanaria', + 'Zamiah', + 'Starkeisha', + 'Alys', + 'Izaria', + 'Cayenne', + 'Damiah', + 'Alwilda', + 'Leoda', + 'Yariah', + 'Tuleen', + 'Rhelda', + 'Carlesha', + 'Alfretta', + 'Orma', + 'Ornella', + 'Nazyia', + 'Samorah', + 'Keyonni', + 'Jeriyah', + 'Jazariyah', + 'Demaria', + 'Mikeyla', + 'Malania', + 'Miyanna', + 'Neriyah', + 'Naelle', + 'Lazariah', + 'Rea', + 'Annaya', + 'Aleanna', + 'Baylin', + 'Aela', + 'Emmilyn', + 'Anila', + 'Rodnesha', + 'Janeliz', + 'Kseniya', + 'Nyana', + 'Zemirah', + 'Somya', + 'Yanna', + 'Terryn', + 'Naika', + 'Laiyla', + 'Lyrica', + 'Loralie', + 'Lilya', + 'Wonnie', + 'Runelle', + 'Tynleigh', + 'Loralye', + 'Arynn', + 'Melvis', + 'Akiyah', + 'Matline', + 'Ellean', + 'Wylean', + 'Marfa', + 'Elliemae', + 'Nancey', + 'Waltina', + 'Ommie', + 'Lonia', + 'Reaver', + 'Virdell', + 'Rosabell', + 'Sarahgrace', + 'Faustine', + 'Euretha', + 'Sussie', + 'Rebie', + 'Oveline', + 'Reathel', + 'Algia', + 'Mylissa', + 'Rethel', + 'Nakyla', + 'Necia', + 'Deanie', + 'Beckey', + 'Yasmen', + 'Yukari', + 'Zamyra', + 'Roselinda', + 'Takeko', + 'Vicke', + 'Mckala', + 'Hanae', + 'Elley', + 'Ellyssa', + 'Geanna', + 'Geetika', + 'Elenoa', + 'Elane', + 'Deeya', + 'Deviny', + 'Genecis', + 'Jasminerose', + 'Ireri', + 'Hailei', + 'Hannya', + 'Harshini', + 'Holiday', + 'Arista', + 'Dannae', + 'Melayna', + 'Meleni', + 'Mystique', + 'Nathalya', + 'Natsumi', + 'Sharlize', + 'Shine', + 'Sindhu', + 'Starlyn', + 'Sarika', + 'Sarine', + 'Seleen', + 'Khalea', + 'Kirti', + 'Jocilyn', + 'Maille', + 'Mariaceleste', + 'Leelee', + 'Leidi', + 'Libertad', + 'Lizvet', + 'Kierstan', + 'Adja', + 'Debbye', + 'Dorenda', + 'Kiyono', + 'Katsuko', + 'Katsue', + 'Misue', + 'Umeno', + 'Rayvin', + 'Sachie', + 'Kinue', + 'Danajah', + 'Denay', + 'Tsuneko', + 'Tamae', + 'Saeko', + 'Tsutako', + 'Sumako', + 'Momoe', + 'Tomoko', + 'Asae', + 'Nautika', + 'Kourtnee', + 'Keauna', + 'Maydeen', + 'Chianne', + 'Macala', + 'Briaunna', + 'Ceirra', + 'Kimberlea', + 'Normalinda', + 'Milinda', + 'Jonetta', + 'Seleta', + 'Chryl', + 'Aaminah', + 'Mersades', + 'Mickenzie', + 'Tahlor', + 'Kimetha', + 'Hopie', + 'Guadulupe', + 'Blakelynn', + 'Orfelinda', + 'Aubre', + 'Ajayla', + 'Makenlee', + 'Journii', + 'Janayla', + 'Talulah', + 'Siddhi', + 'Shaira', + 'Yuridiana', + 'Yulitza', + 'Tulsi', + 'Yatana', + 'Jaleya', + 'Ayrianna', + 'Damaya', + 'Myana', + 'Lanyiah', + 'Kadince', + 'Aunna', + 'Avrielle', + 'Khyli', + 'Kariyah', + 'Bralynn', + 'Derrianna', + 'Maryella', + 'Charlynn', + 'Ilma', + 'Tresea', + 'Mersadies', + 'Macenzie', + 'Terriona', + 'Telia', + 'Tamryn', + 'Tahari', + 'Solyana', + 'Lyrical', + 'Akie', + 'Teruyo', + 'Shizuyo', + 'Tsuruyo', + 'Daviona', + 'Marshelia', + 'Connye', + 'Marka', + 'Adelmira', + 'Dorelia', + 'Nirel', + 'Oceanna', + 'Neeka', + 'Sherolyn', + 'Sheralyn', + 'Sharlet', + 'Milenka', + 'Astha', + 'Angeleena', + 'Anysia', + 'Apoorva', + 'Bryanah', + 'Carolyna', + 'Cecy', + 'Anadalay', + 'Akaylah', + 'Aika', + 'Aasha', + 'Ahniah', + 'Adelayda', + 'Kyaira', + 'Manmeet', + 'Linsy', + 'Malini', + 'Mairany', + 'Haeley', + 'Evelen', + 'Jezel', + 'Jinelle', + 'Joleena', + 'Hikari', + 'Inari', + 'Itcel', + 'Lokelani', + 'Keikilani', + 'Sherilynn', + 'Jamieann', + 'Lajuanna', + 'Roselind', + 'Rhetta', + 'Alysah', + 'Ameyalli', + 'Abigayl', + 'Aizza', + 'Alaiza', + 'Aslyn', + 'Anjalee', + 'Annaliza', + 'Antara', + 'Areen', + 'Carra', + 'Katieann', + 'Kimla', + 'Xan', + 'Mikiala', + 'Chrissa', + 'Belanna', + 'Ankitha', + 'Celestial', + 'Chiana', + 'Akhila', + 'Alique', + 'Alyssamae', + 'Betheny', + 'Stepheny', + 'Brittanyann', + 'Adonna', + 'Barbarella', + 'Shalamar', + 'Flecia', + 'Dlisa', + 'Anabelia', + 'Velen', + 'Xotchil', + 'Yairis', + 'Lytzy', + 'Faizah', + 'Eilleen', + 'Elona', + 'Esteffany', + 'Jesyka', + 'Jhovana', + 'Jisell', + 'Joclyn', + 'Teel', + 'Sundee', + 'Mechell', + 'Lisia', + 'Nandita', + 'Natalina', + 'Nattalie', + 'Rosaelena', + 'Siclali', + 'Skyllar', + 'Taeya', + 'Sadey', + 'Sadira', + 'Sanae', + 'Serenah', + 'Shamila', + 'Brizza', + 'Chalisa', + 'Shakeela', + 'Gordean', + 'Akane', + 'Akansha', + 'Angeni', + 'Annalina', + 'Anushree', + 'Allexa', + 'Katelynd', + 'Raenette', + 'Airiel', + 'Matina', + 'Teira', + 'Deatra', + 'Darolyn', + 'Hilliary', + 'Roanna', + 'Prissy', + 'Monya', + 'Armelinda', + 'Ginnie', + 'Darenda', + 'Leslea', + 'Marcedes', + 'Jeweliana', + 'Jewelissa', + 'Josselyne', + 'Lavanya', + 'Koryn', + 'Khushpreet', + 'Kierah', + 'Cyana', + 'Deeana', + 'Bibianna', + 'Bryannah', + 'Heidie', + 'Desteni', + 'Elleanna', + 'Sierah', + 'Sumedha', + 'Shantall', + 'Yarissa', + 'Yerania', + 'Tifanny', + 'Mehek', + 'Mirely', + 'Mitra', + 'Mar', + 'Rohini', + 'Prerana', + 'Naizeth', + 'Naydeli', + 'Melveen', + 'Moani', + 'Endora', + 'Jackquline', + 'Stefanny', + 'Tamanna', + 'Sofija', + 'Zitlalic', + 'Ymani', + 'Jumana', + 'Kailene', + 'Josephyne', + 'Leiya', + 'Letzy', + 'Litsy', + 'Lizbett', + 'Lizveth', + 'Jaiya', + 'Dreanna', + 'Celestia', + 'Electra', + 'Sevanna', + 'Sidnie', + 'Semone', + 'Sharra', + 'Sharlette', + 'Selinda', + 'Saumya', + 'Meilan', + 'Melea', + 'Maleeha', + 'Mitali', + 'Rheana', + 'Ruchi', + 'Oasis', + 'Preethi', + 'Aungelique', + 'Kristl', + 'Tashala', + 'Darcell', + 'Rolinda', + 'Toye', + 'Shirlyn', + 'Yvonda', + 'Tymia', + 'Oteka', + 'Ladora', + 'Deashia', + 'Janautica', + 'Sonnet', + 'Sucely', + 'Suriah', + 'Tallula', + 'Sanna', + 'Seniyah', + 'Seri', + 'Yexalen', + 'Yumiko', + 'Zayana', + 'Zohal', + 'Valerye', + 'Yarisbeth', + 'Vivyana', + 'Xela', + 'Brithanny', + 'Jasira', + 'Jenessy', + 'Jezebelle', + 'Leahna', + 'Leilanee', + 'Leily', + 'Kohana', + 'Dorsa', + 'Elanna', + 'Caralyn', + 'Erilyn', + 'Halyn', + 'Helayna', + 'Lionor', + 'Maela', + 'Masha', + 'Myley', + 'Malaak', + 'Malai', + 'Mariapaula', + 'Nathalye', + 'Remie', + 'Parnika', + 'Neveen', + 'Cherith', + 'Orvella', + 'Aurion', + 'Shonterria', + 'Natoria', + 'Shaterria', + 'Clo', + 'Donnia', + 'Cana', + 'Niaya', + 'Brelyn', + 'Aalliyah', + 'Shaaron', + 'Doylene', + 'Lowanda', + 'Henryetta', + 'Obera', + 'Marykathryn', + 'Dema', + 'Arcadia', + 'Lodema', + 'Aloni', + 'Analya', + 'Aashritha', + 'Ayani', + 'Audreena', + 'Audrena', + 'Ariahna', + 'Antonela', + 'Atzi', + 'Amunet', + 'Jaala', + 'Keambria', + 'Kanaya', + 'Emya', + 'Deijah', + 'Dayjah', + 'Tiye', + 'Nyja', + 'Markesia', + 'Valla', + 'Cesaria', + 'Eusevia', + 'Elpidia', + 'Jaquisha', + 'Romanita', + 'Shauntia', + 'Chasmine', + 'Deneisha', + 'Quatesha', + 'Nicosha', + 'Shandricka', + 'Shambria', + 'Shakerra', + 'Santrice', + 'Quinesha', + 'Shantika', + 'Roderica', + 'Whitnie', + 'Piedad', + 'Koleta', + 'Brazil', + 'Aamina', + 'Adaleen', + 'Adyline', + 'Bricola', + 'Analeigha', + 'Anara', + 'Ladawna', + 'Ruperta', + 'Deaundra', + 'Jaleisa', + 'Keria', + 'Sharaine', + 'Shanekqua', + 'Shanekia', + 'Kenyanna', + 'Jacoria', + 'Airianna', + 'Amana', + 'Amariz', + 'Ammi', + 'Miaya', + 'Aaniya', + 'Anaisha', + 'Bellina', + 'Annasofia', + 'Archita', + 'Arianie', + 'Shaquandra', + 'Shakeyra', + 'Tiandra', + 'Soveida', + 'Gonzala', + 'Gaylia', + 'Freddye', + 'Roxi', + 'Neya', + 'Nitika', + 'Noriah', + 'Raha', + 'Briah', + 'Syrah', + 'Talise', + 'Tarynn', + 'Tianah', + 'Solay', + 'Saraiah', + 'Sherlynn', + 'Leylany', + 'Lilu', + 'Maelie', + 'Lexxie', + 'Monzeratt', + 'Nari', + 'Naveyah', + 'Mianna', + 'Maylea', + 'Mery', + 'Marene', + 'Zeba', + 'Xymena', + 'Yaremi', + 'Yari', + 'Yulie', + 'Lile', + 'Dafnee', + 'Indra', + 'Itzelle', + 'Evangaline', + 'Evelett', + 'Evely', + 'Ghazal', + 'Arnisha', + 'Kassia', + 'Kayah', + 'Kalliyan', + 'Diannia', + 'Damyah', + 'Torianna', + 'Talasia', + 'Zakira', + 'Zyah', + 'Masiya', + 'Rhyanna', + 'Kemaya', + 'Jadasia', + 'Kanijah', + 'Henleigh', + 'Ciella', + 'Dayanne', + 'Ivannia', + 'Heydy', + 'Fergie', + 'Fianna', + 'Goretti', + 'Gwynneth', + 'Gyanna', + 'Haidi', + 'Christabella', + 'Angelinah', + 'Anina', + 'Annya', + 'Alejah', + 'Bradie', + 'Breanah', + 'Arihana', + 'Aryona', + 'Ashwika', + 'Aylet', + 'Ayleth', + 'Meleena', + 'Micel', + 'Misel', + 'Naiema', + 'Meiling', + 'Malaia', + 'Rehanna', + 'Raengel', + 'Padma', + 'Majestic', + 'Katelen', + 'Jenaveve', + 'Jennessy', + 'Jewelisa', + 'Joelie', + 'Lyliana', + 'Mahati', + 'Sherral', + 'Kamariah', + 'Larsen', + 'Khaniya', + 'Jakiah', + 'Darionna', + 'Bristal', + 'Ahlana', + 'Aireanna', + 'Alaila', + 'Jarethzy', + 'Orfalinda', + 'Nataliah', + 'Nayra', + 'Nishika', + 'Meeya', + 'Sanaia', + 'Sensi', + 'Percilla', + 'Pranathi', + 'Kathrynn', + 'Katriel', + 'Jordanna', + 'Jessilyn', + 'Jilliana', + 'Madeira', + 'Laia', + 'Leala', + 'Courtlynn', + 'Ahriana', + 'Aliena', + 'Adalay', + 'Nakyia', + 'Niema', + 'Leeasia', + 'Evenny', + 'Dorismar', + 'Dyanara', + 'Elonna', + 'Estreya', + 'Ashmita', + 'Anureet', + 'Angeliah', + 'Annaliz', + 'Dallanara', + 'Danaly', + 'Carely', + 'Sevilla', + 'Aleigh', + 'Allianna', + 'Alamar', + 'Jaiah', + 'Shellsea', + 'Sheylin', + 'Sonoma', + 'Hayla', + 'Yoali', + 'Yzabel', + 'Zeenat', + 'Zienna', + 'Shirlynn', + 'Shilynn', + 'Raphaella', + 'Makyia', + 'Inola', + 'Omaria', + 'Michiah', + 'Anareli', + 'Anacamila', + 'Anahis', + 'Anapaola', + 'Clowie', + 'Brizia', + 'Alexssa', + 'Ailanie', + 'Aileene', + 'Francille', + 'Jatoria', + 'Jaquitta', + 'Sybol', + 'Landra', + 'Danyela', + 'Cubia', + 'Arabela', + 'Adelfina', + 'Quaniya', + 'Paulyne', + 'Vanteen', + 'Treba', + 'Kaylena', + 'Kaelynne', + 'Kalanie', + 'Lezli', + 'Lithzy', + 'Lanessa', + 'Laylene', + 'Leilaney', + 'Emmajean', + 'Francella', + 'Eiliyah', + 'Jadey', + 'Jamilett', + 'Ingris', + 'Tayanna', + 'Skarlette', + 'Sady', + 'Senia', + 'Yakeline', + 'Yenna', + 'Yesmin', + 'Meily', + 'Mikeila', + 'Miu', + 'Rakel', + 'Niveah', + 'Nyemah', + 'Gorgeous', + 'Zaraya', + 'Lavaeh', + 'Meila', + 'Labella', + 'Lilyona', + 'Zykierra', + 'Orfa', + 'Seriyah', + 'Shivali', + 'Sibylla', + 'Sua', + 'Ulani', + 'Vianet', + 'Yanell', + 'Yolette', + 'Yudany', + 'Suheidy', + 'Sukhpreet', + 'Syanna', + 'Tatevik', + 'Tayde', + 'Sameria', + 'Mikiya', + 'Claramae', + 'Audine', + 'Francile', + 'Tynia', + 'Goddess', + 'Samoria', + 'Llana', + 'Oveda', + 'Amelya', + 'Auda', + 'Disaya', + 'Zanyah', + 'Samiyyah', + 'Jaianna', + 'Ruqayyah', + 'Nakira', + 'Shamirah', + 'Ta', + 'Giani', + 'Brya', + 'Cyani', + 'Ashiyah', + 'Kahli', + 'Beauton', + 'Kashay', + 'Sadiyah', + 'Mikaya', + 'Nasira', + 'Nasirah', + 'Ariauna', + 'Yasirah', + 'Skyelynn', + 'Naailah', + 'Nyelle', + 'Adessa', + 'Ayriana', + 'Mirielle', + 'Munirah', + 'Layani', + 'Haniyah', + 'Ovida', + 'Haniyyah', + 'Layonna', + 'Jazmarie', + 'Wicahpi', + 'Cante', + 'Zamyah', + 'Tanyiah', + 'Shalita', + 'Salley', + 'Jnya', + 'Santasia', + 'Shaneque', + 'Quantina', + 'Temeika', + 'Narvis', + 'Pearlee', + 'Nykesha', + 'Orrie', + 'Mozter', + 'Earthalee', + 'Rozena', + 'Anniebell', + 'Hannie', + 'Pretto', + 'Caro', + 'Everlina', + 'Arnetha', + 'Glenora', + 'Asalee', + 'Parniece', + 'Rubena', + 'Wilhemena', + 'Perline', + 'Elloree', + 'Clorine', + 'Richardean', + 'Rovena', + 'Arthuree', + 'Mikea', + 'Charnice', + 'Tylashia', + 'Rebacca', + 'Caretha', + 'Dynasti', + 'Marvie', + 'Hermenia', + 'Tekela', + 'Trenace', + 'Valetta', + 'Topaz', + 'Debara', + 'Jaquasha', + 'Markeria', + 'Alkeria', + 'Salwa', + 'Tatayana', + 'Dianelys', + 'Beyounce', + 'Drena', + 'Julysa', + 'Shuntel', + 'Antasia', + 'Alyze', + 'Marytheresa', + 'Raechelle', + 'Trevia', + 'Tomara', + 'Jermeka', + 'Curtisha', + 'Kebrina', + 'Kayte', + 'Shakeila', + 'Ronnesha', + 'Shavontae', + 'Taquila', + 'Shaquia', + 'Lynnann', + 'Markevia', + 'Terrilynn', + 'Carime', + 'Quaneshia', + 'Shaylen', + 'Corneisha', + 'Rodneshia', + 'Nateria', + 'Marycatherine', + 'Ashlyne', + 'Reyne', + 'Natia', + 'Taquisha', + 'Mikeshia', + 'Khadeja', + 'Lismary', + 'Prisca', + 'Antwonette', + 'Anesia', + 'Clotilda', + 'Willavene', + 'Lovey', + 'Aleda', + 'Karita', + 'Rakiyah', + 'Nyasiah', + 'Timaya', + 'Gabryelle', + 'Caniyah', + 'Ethelreda', + 'Aryelle', + 'Trianna', + 'Yesli', + 'Yareliz', + 'Tanyla', + 'Keyshia', + 'Makinsey', + 'Daily', + 'Caylynn', + 'Kalyse', + 'Sarabelle', + 'Araminta', + 'Magdelene', + 'Kristalyn', + 'Lianni', + 'Layana', + 'Haedyn', + 'Teyona', + 'Taziyah', + 'Ranijah', + 'Darneisha', + 'Jahzaria', + 'Palmyra', + 'Altheda', + 'Armanii', + 'Blodwyn', + 'Colletta', + 'Yelenis', + 'Yazlyn', + 'Leira', + 'Anaysia', + 'Anayiah', + 'Valia', + 'Bambina', + 'Burnetta', + 'Clarabel', + 'Philomenia', + 'Lorma', + 'Janeka', + 'Danaisha', + 'Cayci', + 'Jermia', + 'Idalys', + 'Sarajane', + 'Shakenya', + 'Kashanti', + 'Lanika', + 'Ceira', + 'Deshanti', + 'Adianez', + 'Alannis', + 'Lubov', + 'Aylana', + 'Nephtalie', + 'Harlean', + 'Shelvey', + 'Yalissa', + 'Asianna', + 'Jahnyah', + 'Jahliyah', + 'Ellissa', + 'Gabrianna', + 'Katonya', + 'Elsia', + 'Ketina', + 'Kateena', + 'Claudean', + 'Chenita', + 'Belkys', + 'Kerryn', + 'Teria', + 'Charron', + 'Charnissa', + 'Alura', + 'Bashirah', + 'Gerldine', + 'Katilynn', + 'Trellany', + 'Lacheryl', + 'Twalla', + 'Sharnise', + 'Yoland', + 'Shanai', + 'Ikia', + 'Aquilla', + 'Shalandra', + 'Nekesha', + 'Sonni', + 'Kutana', + 'Sharnay', + 'Timitra', + 'Shareena', + 'Tyeesha', + 'Natara', + 'Amatullah', + 'Nydirah', + 'Shahadah', + 'Inetha', + 'Clatie', + 'Ladye', + 'Makalia', + 'Sabriyah', + 'Graple', + 'Lorell', + 'Vercie', + 'Rayona', + 'Dayshia', + 'Nakirah', + 'Mcneva', + 'Bunia', + 'Brooxie', + 'Delcia', + 'Naje', + 'Eilish', + 'Lashara', + 'Crystall', + 'Shearon', + 'Kafi', + 'Kea', + 'Shantrel', + 'Jeanni', + 'Andreia', + 'Myrlande', + 'Jennifier', + 'Damika', + 'Carloyn', + 'Lashera', + 'Kamika', + 'Chrisann', + 'Lashavia', + 'Ivis', + 'Quinisha', + 'Yanelys', + 'Taralee', + 'Ibis', + 'Jazma', + 'Shakevia', + 'Deneane', + 'Kimala', + 'Casee', + 'Audreana', + 'Shahida', + 'Latangela', + 'Lashira', + 'Lashawndra', + 'Sherrina', + 'Shawntrell', + 'Latronda', + 'Meghaan', + 'Ayasha', + 'Raushanah', + 'Serrita', + 'Tennile', + 'Keyonda', + 'Idalmis', + 'Telicia', + 'Takeia', + 'Aristea', + 'Letesha', + 'Badia', + 'Nykea', + 'Bilan', + 'Ieva', + 'Kimmi', + 'Geniel', + 'Tamberly', + 'Tammee', + 'Sherma', + 'Emira', + 'Agena', + 'Carrin', + 'Ladean', + 'Caera', + 'Shatha', + 'Utahna', + 'Lujean', + 'Joylyn', + 'Kathren', + 'Kristiane', + 'Lenee', + 'Angi', + 'Vichelle', + 'Rochele', + 'Shonnie', + 'Anastasija', + 'Clea', + 'Myrlene', + 'Dniyah', + 'Tashanti', + 'Sireen', + 'Vincie', + 'Wreatha', + 'Josphine', + 'Casimera', + 'Hildagarde', + 'Margeret', + 'Grettell', + 'Greenley', + 'Gloriana', + 'Eyleen', + 'Evaleigh', + 'Davanee', + 'Corley', + 'Liliah', + 'Leanah', + 'Kynzlie', + 'Kynzleigh', + 'Kolette', + 'Lively', + 'Makenlie', + 'Lochlyn', + 'Kinslie', + 'Jleigh', + 'Jeslynn', + 'Jenisis', + 'Jenisha', + 'Kensli', + 'Addalie', + 'Demia', + 'Cele', + 'Aderinsola', + 'Auriella', + 'Blyss', + 'Cashlynn', + 'Callyn', + 'Allyzon', + 'Aleiya', + 'Alazne', + 'Alayzia', + 'Ailah', + 'Annora', + 'Analynn', + 'Leonilda', + 'Minnette', + 'Onolee', + 'Michaelina', + 'Rosemond', + 'Milica', + 'Ednamae', + 'Floribel', + 'Nur', + 'Ndia', + 'Thecla', + 'Immaculate', + 'Mayfred', + 'Selda', + 'Vincenzia', + 'Vitina', + 'Tammatha', + 'Joley', + 'Kelene', + 'Kriste', + 'Liese', + 'Mariaemilia', + 'Lasaundra', + 'Letica', + 'Karene', + 'Devera', + 'Denyce', + 'Dawnn', + 'Maryum', + 'Giovannina', + 'Roze', + 'Reygan', + 'Quinlyn', + 'Stassi', + 'Meelah', + 'Novaleigh', + 'Navey', + 'Mirakle', + 'Naiovy', + 'Munachiso', + 'Montzerrat', + 'Misk', + 'Mireyah', + 'Temiloluwa', + 'Zaiya', + 'Varshini', + 'Tiwatope', + 'Tinlee', + 'Geneve', + 'Kotryna', + 'Janila', + 'Janeah', + 'Mollye', + 'Dody', + 'Doreena', + 'Chelle', + 'Javaeh', + 'Dim', + 'Jamylah', + 'Kamyia', + 'Ramie', + 'Kandie', + 'Kitt', + 'Gaylyn', + 'Marji', + 'Laurena', + 'Lorre', + 'Ronelle', + 'Kresta', + 'Jonylah', + 'Kornelia', + 'Mindie', + 'Kendis', + 'Dorri', + 'Seaneen', + 'Lorilyn', + 'Lolly', + 'Pati', + 'Shalayne', + 'Dorise', + 'Joani', + 'Yailene', + 'Batool', + 'Cyntha', + 'Coni', + 'Kae', + 'Cynia', + 'Rhonna', + 'Lynnetta', + 'Terrisa', + 'Nishi', + 'Delise', + 'Ladena', + 'Bronwen', + 'Tere', + 'Tippi', + 'Peggi', + 'Portland', + 'Sherrin', + 'Tacy', + 'Terie', + 'Dore', + 'Daphane', + 'Juliene', + 'Kamile', + 'Janeil', + 'Megin', + 'Shenandoah', + 'Rashada', + 'Disa', + 'Elita', + 'Kelee', + 'Genee', + 'Taneya', + 'Storie', + 'Sheza', + 'Rielyn', + 'Venicia', + 'Zamyria', + 'Yisell', + 'Appollonia', + 'Meryle', + 'Frann', + 'Lucyann', + 'Clarivel', + 'Marguarite', + 'Nelsa', + 'Reanetta', + 'Roshaunda', + 'Channie', + 'Bathsheba', + 'Jannessa', + 'Jakaylah', + 'Jesalyn', + 'Ellyson', + 'Hally', + 'Haelyn', + 'Gabbie', + 'Emmerie', + 'Makailyn', + 'Maddi', + 'Lirio', + 'Lexee', + 'Matalyn', + 'Kenzee', + 'Kenlei', + 'Kaydi', + 'Kynlei', + 'Krissa', + 'Adalin', + 'Alayiah', + 'Ellice', + 'Caydee', + 'Annalysa', + 'Anisty', + 'Abeni', + 'Aliha', + 'Aerith', + 'Adrie', + 'Peggyann', + 'Pietrina', + 'Amberlie', + 'Dabria', + 'Cylee', + 'Amyriah', + 'Ambry', + 'Berkleigh', + 'Azula', + 'Zaryiah', + 'Zanyia', + 'Gerardine', + 'Joycelynn', + 'Jeslin', + 'Kenzli', + 'Keisi', + 'Kayelynn', + 'Jaselyn', + 'Mckinnley', + 'Maryse', + 'Peightyn', + 'Latausha', + 'Lety', + 'Tekia', + 'Arasely', + 'Arlynne', + 'Noell', + 'Patrcia', + 'Morning', + 'Meika', + 'Tanda', + 'Terasa', + 'Tika', + 'Roshon', + 'Marlaine', + 'Stephaie', + 'Franne', + 'Ewa', + 'Tomeca', + 'Chequita', + 'Dierdra', + 'Doriann', + 'Tammika', + 'Jeananne', + 'Cythia', + 'Laconda', + 'Catiria', + 'Migna', + 'Latiesha', + 'Sharin', + 'Tekesha', + 'Elga', + 'Barbarajean', + 'Ilena', + 'Evett', + 'Timiko', + 'Kachina', + 'Desere', + 'Galadriel', + 'Lynea', + 'Laurajean', + 'Rukiya', + 'Sakara', + 'Snezana', + 'Tashonda', + 'Orquidea', + 'Myshia', + 'Latrease', + 'Monquie', + 'Robina', + 'Vesna', + 'Faline', + 'Glori', + 'Jennel', + 'Keyatta', + 'Dimitria', + 'Uzma', + 'Lalia', + 'Krystiana', + 'Kaedynce', + 'Juany', + 'Kesley', + 'Kennedee', + 'Keeleigh', + 'Paiten', + 'Neelah', + 'Naylee', + 'Sairy', + 'Rocsi', + 'Mckenzey', + 'Modesty', + 'Abbiegail', + 'Jasalyn', + 'Genises', + 'Emmory', + 'Elisea', + 'Dlaney', + 'Haelee', + 'Jadence', + 'Audryana', + 'Carizma', + 'Josanne', + 'Nashira', + 'Meesha', + 'Taneil', + 'Sobeida', + 'Zakyra', + 'Syndee', + 'Zipora', + 'Amita', + 'Bridie', + 'Hilde', + 'Aspasia', + 'Yalexi', + 'Tenleigh', + 'Anjannette', + 'Zniyah', + 'Zayley', + 'Kyerra', + 'Lynnsey', + 'Dashae', + 'Jasha', + 'Anjenette', + 'Lelania', + 'Mija', + 'Lorrene', + 'Shanyn', + 'Shindana', + 'Shamra', + 'Dove', + 'Drina', + 'Caralee', + 'Charmian', + 'Katrine', + 'Lagina', + 'Jahna', + 'Nesita', + 'Teriana', + 'Dajae', + 'Kyiah', + 'Keslyn', + 'Kayelee', + 'Kamberlyn', + 'Raygen', + 'Orchid', + 'Maleigh', + 'Mairim', + 'Amily', + 'Ameli', + 'Alie', + 'Adelai', + 'Eniola', + 'Enaya', + 'Brealynn', + 'Blakleigh', + 'Ayelene', + 'Camrie', + 'Dianely', + 'Delayne', + 'Cortlyn', + 'Jaylei', + 'Jaycelynn', + 'Jaleigha', + 'Iviana', + 'Kaedance', + 'Jewelz', + 'Jillianna', + 'Faithlyn', + 'Isabeau', + 'Irany', + 'Galiana', + 'Makynzee', + 'Maebry', + 'Merit', + 'Mckinzee', + 'Kinzee', + 'Kendrah', + 'Laityn', + 'Amberlin', + 'Ahliyah', + 'Raphaela', + 'Ameri', + 'Brecklynn', + 'Cristabel', + 'Annalucia', + 'Avri', + 'Averly', + 'Shalia', + 'Sheilla', + 'Dejana', + 'Tonnette', + 'Tracia', + 'Trese', + 'Lalanya', + 'Kristiann', + 'Zunaira', + 'Zinachidi', + 'Xayla', + 'Zaybree', + 'Zanae', + 'Xoey', + 'Sirenity', + 'Renesme', + 'Raeley', + 'Preslyn', + 'Nyx', + 'Nyelli', + 'Rozalynn', + 'Safaa', + 'Abaigeal', + 'Perle', + 'Ersilia', + 'Ethlyn', + 'Dashanae', + 'Dajana', + 'Tahja', + 'Shavona', + 'Vernisha', + 'Sunya', + 'Zenorah', + 'Dorota', + 'Ramsha', + 'Nirali', + 'Najia', + 'Maryclaire', + 'Ismay', + 'Alfonsina', + 'Letizia', + 'Lotta', + 'Honore', + 'Jamille', + 'Kashe', + 'Bonnielee', + 'Lorelle', + 'Gloriajean', + 'Trenae', + 'Tonesha', + 'Maxene', + 'Aliz', + 'Annelyse', + 'Avagrace', + 'Adanelly', + 'Dariella', + 'Colbi', + 'Tema', + 'Marlea', + 'Elleen', + 'Veroncia', + 'Shelina', + 'Sundae', + 'Jericca', + 'Liduvina', + 'Jenney', + 'Pascha', + 'Roshell', + 'Marlies', + 'Marny', + 'Judithann', + 'Nancylee', + 'Freyda', + 'Joyceann', + 'Caroleann', + 'Desirie', + 'Christol', + 'Shulamith', + 'Marlise', + 'Rocquel', + 'Tamsen', + 'Sukari', + 'Tinna', + 'Magdelena', + 'Ruba', + 'Patra', + 'Erryn', + 'Buffi', + 'Chantil', + 'Kerensa', + 'Annastacia', + 'Zailee', + 'Lamika', + 'Kashlynn', + 'Jaedynn', + 'Kaly', + 'Paisyn', + 'Seraiah', + 'Mckenzye', + 'Nhyla', + 'Chandrika', + 'Dawana', + 'Elesha', + 'Caryle', + 'Karrin', + 'Valency', + 'Kianga', + 'Shawndee', + 'Tamasha', + 'Rhodora', + 'Shivangi', + 'Vermont', + 'Diasia', + 'Aniyyah', + 'Azhane', + 'Katleyn', + 'Tynetta', + 'Negan', + 'Marilyne', + 'Leronia', + 'Charmie', + 'Lateefa', + 'Hassanah', + 'Louvinia', + 'Shirly', + 'Sanjna', + 'Andelyn', + 'Jaima', + 'Aftyn', + 'Atira', + 'Weslie', + 'Tayzlee', + 'Rossi', + 'Nayvie', + 'Livvy', + 'Brinklee', + 'Drinda', + 'Nazirah', + 'Krithika', + 'Taisley', + 'Starlee', + 'Bijal', + 'Hiral', + 'Gwynn', + 'Orlene', + 'Maurene', + 'Sweta', + 'Naasia', + 'Luvinia', + 'Sayoko', + 'Geannie', + 'Rupal', + 'Zerlina', + 'Nobu', + 'Taeko', + 'Miye', + 'Carnation', + 'Joplin', + 'Yayeko', + 'Sakaye', + 'Ernell', + 'Tazuko', + 'Bayyinah', + 'Konstantina', + 'Danuta', + 'Cariann', + 'Charnette', + 'Michiye', + 'Tejal', + 'Shaheedah', + 'Zakkiyya', + 'Latoyah', + 'Audre', + 'Tayeko', + 'Qadriyyah', + 'Nikema', + 'Wadeeah', + 'Quanika', + 'Fareeda', + 'Ivelis', + 'Karigan', + 'Yayoi', + 'Tauni', + 'Shailee', + 'Ronnah', + 'Roseana', + 'Rosalita', + 'Orlidia', + 'Mckall', + 'Seattle', + 'Lauree', + 'Georgi', + 'Jacolyn', + 'Meichele', + 'Starlet', + 'Shandee', + 'Miquelle', + 'Cathe', + 'Nondas', + 'Roben', + 'Manette', + 'Monzelle', + 'Genieve', + 'Rumaysa', + 'Dariya', + 'Brynnleigh', + 'Vicci', + 'Sharli', + 'Chandi', + 'Guadelupe', + 'Jamilyn', + 'Willadene', + 'Centhia', + 'Cheryal', + 'Normalee', + 'Wilmajean', + 'Roanne', + 'Dyane', + 'Jinx', + 'Jorene', + 'Ceceilia', + 'Arikka', + 'Latanza', + 'Lacinda', + 'Rus', + 'Sangeeta', + 'Demita', + 'Jerene', + 'Marcellina', + 'Zani', + 'Izzabelle', + 'Graycee', + 'Sajada', + 'Quinlee', + 'Brooklee', + 'Shulamis', + 'Bunnie', + 'Michaelyn', + 'Dhruvi', + 'Sreeja', + 'Tzipa', + 'Doreene', + 'Bedelia', + 'Eutimia', + 'Tomacita', + 'Jerra', + 'Rosela', + 'Ignacita', + 'Conferina', + 'Andreita', + 'Lugardita', + 'Estefanita', + 'Suetta', + 'Debbe', + 'Amadita', + 'Mardel', + 'Mliss', + 'Korla', + 'Felipita', + 'Erminda', + 'Chrys', + 'Karthika', + 'Guilianna', + 'Chasya', + 'Bryndee', + 'Taeler', + 'Sinforosa', + 'Brinnley', + 'Aviya', + 'Jayma', + 'Zimal', + 'Vivia', + 'Arielis', + 'Arshiya', + 'Adiba', + 'Afreen', + 'Ajooni', + 'Alianny', + 'Fariza', + 'Breina', + 'Sila', + 'Aaima', + 'Amesha', + 'Antigone', + 'Kayse', + 'Aurelie', + 'Marianny', + 'Naba', + 'Salimata', + 'Retal', + 'Pema', + 'Pesha', + 'Reemas', + 'Emunah', + 'Farzeen', + 'Safina', + 'Sema', + 'Seynabou', + 'Roza', + 'Romaisa', + 'Yehudit', + 'Tzivi', + 'Tzivy', + 'Zahro', + 'Jeylen', + 'Klea', + 'Namirah', + 'Lamiah', + 'Mahjabeen', + 'Daielle', + 'Ogechi', + 'Laresha', + 'Laqueta', + 'Anayla', + 'Bashy', + 'Naeema', + 'Sarrinah', + 'Sevinch', + 'Frimmy', + 'Hibba', + 'Fajr', + 'Rayhona', + 'Rokia', + 'Wafa', + 'Britne', + 'Crystalann', + 'Reah', + 'Maggi', + 'Lenae', + 'Kambra', + 'Tabita', + 'Tamlyn', + 'Thuytien', + 'Titianna', + 'Trenisha', + 'Yuan', + 'Yarithza', + 'Yarixa', + 'Satin', + 'Elizeth', + 'Gabiela', + 'Jackline', + 'Janisa', + 'Graviela', + 'Gudalupe', + 'Hena', + 'Bryanda', + 'Avilene', + 'Ayerim', + 'Breiana', + 'Nicoleanne', + 'Merisa', + 'Relina', + 'Rebecah', + 'Rachyl', + 'Kasaundra', + 'Katryn', + 'Jeaneth', + 'Jenah', + 'Jocely', + 'Jorgina', + 'Lindsee', + 'Lizvette', + 'Oleen', + 'Waveline', + 'Laurabelle', + 'Charma', + 'Gleneva', + 'Yesika', + 'Felina', + 'Nguyet', + 'Krissie', + 'Silvina', + 'Stephanny', + 'Teera', + 'Kristol', + 'Karisha', + 'Lorisa', + 'Iracema', + 'Temesha', + 'Tamber', + 'Shelisa', + 'Roshana', + 'Rheannon', + 'Amala', + 'Anabelen', + 'Daizhane', + 'Darbie', + 'Dezaree', + 'Dezhane', + 'Carrina', + 'Chessa', + 'Christinejoy', + 'Aliea', + 'Adalhi', + 'Alexandrina', + 'Abrina', + 'Madaleine', + 'Maressa', + 'Marki', + 'Koryna', + 'Lilibet', + 'Mystic', + 'Neyra', + 'Ivonna', + 'Jenalyn', + 'Truc', + 'Berneta', + 'Quinci', + 'Rachelanne', + 'Raylina', + 'Nykole', + 'Stephaney', + 'Seleni', + 'Marvene', + 'Melizza', + 'Aimme', + 'Anaissa', + 'Anhelica', + 'Celyna', + 'Azalie', + 'Bereniz', + 'Meliss', + 'Leanza', + 'Lenina', + 'Karrina', + 'Kalynne', + 'Kanwal', + 'Kazzandra', + 'Mandalyn', + 'Limairy', + 'Lizzete', + 'Lyly', + 'Coua', + 'Icsel', + 'Izamary', + 'Lakindra', + 'Rosezella', + 'Wilhelmine', + 'Clela', + 'Marvelle', + 'Jenafer', + 'Katye', + 'Eliabeth', + 'Angelicamaria', + 'Adrieanna', + 'Caludia', + 'Caycee', + 'Chenay', + 'Cherika', + 'Arpine', + 'Kimberlyanne', + 'Jully', + 'Jyoti', + 'Mariha', + 'Meganelizabeth', + 'Melysa', + 'Lashanay', + 'Jericha', + 'Eliset', + 'Esmirna', + 'Clarie', + 'Conny', + 'Derrisha', + 'Frania', + 'Jeena', + 'Gresia', + 'Hlee', + 'Emanie', + 'Liany', + 'Aisatou', + 'Ashya', + 'Nefertari', + 'Nyanna', + 'Mariem', + 'Michellee', + 'Amenda', + 'Markella', + 'Kiyara', + 'Issamar', + 'Cecilee', + 'Rehana', + 'Nube', + 'Simy', + 'Laneshia', + 'Vasthi', + 'Treanna', + 'Tria', + 'Tuongvi', + 'Brany', + 'Niza', + 'Shandale', + 'Shanley', + 'Shastina', + 'Sheyna', + 'Ronniesha', + 'Rubit', + 'Ruvi', + 'Siobhain', + 'Shauntal', + 'Linzie', + 'Linzi', + 'Fatimatou', + 'Efrat', + 'Jasmely', + 'Kadidia', + 'Kamily', + 'Meirav', + 'Areebah', + 'Fatim', + 'Nuzhat', + 'Saribel', + 'Zorah', + 'Ting', + 'Laporscha', + 'Mieshia', + 'Vanecia', + 'Brittne', + 'Denetria', + 'Deamber', + 'Cymone', + 'Arieal', + 'Araly', + 'Shamieka', + 'Deshay', + 'Britainy', + 'Matraca', + 'Krystyne', + 'Kristela', + 'Kindell', + 'Ceyda', + 'Jahnasia', + 'Halimatou', + 'Graciana', + 'Haja', + 'Safiatou', + 'Su', + 'Zaineb', + 'Yianna', + 'Shilat', + 'Zanai', + 'Zeinabou', + 'Jalysa', + 'Garcia', + 'Jinna', + 'Brytni', + 'Crystalmarie', + 'Kyrstie', + 'Labrea', + 'Laurita', + 'Kathleena', + 'Salimatou', + 'Martisha', + 'Damisha', + 'Londin', + 'Toree', + 'Yadria', + 'Yaminah', + 'Nili', + 'Pella', + 'Menna', + 'Minah', + 'Porshay', + 'Rahwa', + 'Parissa', + 'Nury', + 'Sheeva', + 'Sendi', + 'Aroush', + 'Jerlyn', + 'Momina', + 'Nylia', + 'Mahreen', + 'Mattingly', + 'Emanuella', + 'Ceylin', + 'Biana', + 'Ishrat', + 'Genendy', + 'Hindel', + 'Chavi', + 'Freidy', + 'Rouguiatou', + 'Osnas', + 'Yagmur', + 'Yitel', + 'Hudy', + 'Jamielynn', + 'Valyncia', + 'Cheyla', + 'Assa', + 'Tasmia', + 'Yaslene', + 'Zaima', + 'Jenisse', + 'Juliannah', + 'Reveca', + 'Amra', + 'Anaria', + 'Arlenis', + 'Anastassia', + 'Anique', + 'Arilene', + 'Adileni', + 'Chelcy', + 'Chelesa', + 'Columba', + 'Corri', + 'Briane', + 'Carine', + 'Deziah', + 'Jojo', + 'Jaidalyn', + 'Cecelie', + 'Meagon', + 'Raysha', + 'Mylinh', + 'Madelena', + 'Saniyya', + 'Shama', + 'Shifa', + 'Nyala', + 'Lafaun', + 'Ronnetta', + 'Rondia', + 'Christe', + 'Tynnetta', + 'Ethyle', + 'Bobi', + 'Rayetta', + 'Wilmina', + 'Tangala', + 'Chloris', + 'Marvyl', + 'Larinda', + 'Narcedalia', + 'Tiaa', + 'Terressa', + 'Missi', + 'Ardythe', + 'Briget', + 'Julya', + 'Emilyanne', + 'Ayano', + 'Eliane', + 'Tatem', + 'Roselani', + 'Zareen', + 'Yaxeni', + 'Marleena', + 'Nicolemarie', + 'Patzy', + 'Morgana', + 'Mirca', + 'Mystica', + 'Rosaicela', + 'Rosaysela', + 'Serrena', + 'Shiori', + 'Yannely', + 'Threasa', + 'Zohra', + 'Lanitra', + 'Laquinthia', + 'Deshundra', + 'Mirasol', + 'Lladira', + 'Tejuana', + 'Michaelann', + 'Normajean', + 'Leasha', + 'Kajuana', + 'Xianna', + 'Yaquelyn', + 'Marcea', + 'Mohini', + 'Jaysha', + 'Saysha', + 'Makamae', + 'Lynnett', + 'Mistee', + 'Kaysee', + 'Lizel', + 'Kiora', + 'Kla', + 'Lanay', + 'Kainani', + 'Pomaikai', + 'Piilani', + 'Aulii', + 'Khristi', + 'Delfa', + 'Toka', + 'Satonya', + 'Jammi', + 'Iolani', + 'Hinaea', + 'Ilihia', + 'Kulia', + 'Darcus', + 'Raejean', + 'Brisamar', + 'Francessca', + 'Dhamar', + 'Lehiwa', + 'Ajane', + 'Alexsys', + 'Jema', + 'Imara', + 'Itzanami', + 'Ivori', + 'Tabby', + 'Charnell', + 'Vanessamarie', + 'Vibiana', + 'Kameisha', + 'Edica', + 'Shanetra', + 'Shametria', + 'Quinette', + 'Abreanna', + 'Corazon', + 'Correna', + 'Lilac', + 'Najwa', + 'Moranda', + 'Monik', + 'Deise', + 'Edid', + 'Karinne', + 'Ilsa', + 'Irazema', + 'Pegge', + 'Chenique', + 'Temisha', + 'Cristella', + 'Christle', + 'Falan', + 'Mekesha', + 'Jonquil', + 'Latarya', + 'Maretta', + 'Sonceria', + 'Latamara', + 'Ladina', + 'Rozann', + 'Suz', + 'Aleja', + 'Wray', + 'Indica', + 'Harkiran', + 'Gemini', + 'Erikah', + 'Fey', + 'Gudelia', + 'Komalpreet', + 'Anah', + 'Angelicamarie', + 'Cammi', + 'Dejane', + 'Dejanay', + 'Cilicia', + 'Merla', + 'Janann', + 'Maurita', + 'Aireana', + 'Shuronda', + 'Shunte', + 'Lacrisha', + 'Kwana', + 'Krisi', + 'Kaysi', + 'Latressa', + 'Tyronza', + 'Debralee', + 'Crissie', + 'Crissa', + 'Jameca', + 'Alicha', + 'Ketra', + 'Chrisie', + 'Delecia', + 'Rokisha', + 'Natoshia', + 'Shajuana', + 'Jenipher', + 'Jenefer', + 'Anjanae', + 'Azita', + 'Clairissa', + 'Brezhane', + 'Keera', + 'Siarah', + 'Smita', + 'Savonna', + 'Raquelin', + 'Lorren', + 'Omunique', + 'Molina', + 'Nixaliz', + 'Melitza', + 'Shylo', + 'Teniqua', + 'Charmine', + 'Deonne', + 'Kima', + 'Galit', + 'Ikesha', + 'Jamala', + 'Cherl', + 'Ageliki', + 'Ydania', + 'Kortlyn', + 'Lisvet', + 'Khya', + 'Kearstyn', + 'Seline', + 'Stormey', + 'Rehma', + 'Mckynna', + 'Brynnan', + 'Abiola', + 'Ambriel', + 'Akaysha', + 'Hailea', + 'Fryda', + 'Fedra', + 'Dacie', + 'Deissy', + 'Deyna', + 'Mayling', + 'Tessy', + 'Yaa', + 'Shameca', + 'Shivon', + 'Taesha', + 'Dinamarie', + 'Ifeoma', + 'Ashlye', + 'Patriciajo', + 'Danute', + 'Amalyn', + 'Nakeia', + 'Takima', + 'Shavonn', + 'Katira', + 'Lakema', + 'Jahaida', + 'Marshelle', + 'Angeliki', + 'Carrianne', + 'Carrieanne', + 'Tarika', + 'Sherece', + 'Kalimah', + 'Kinda', + 'Sadiga', + 'Paraskevi', + 'Ayianna', + 'Alezay', + 'Cadynce', + 'Haely', + 'Heavenleigh', + 'Dajanique', + 'Lasharn', + 'Drita', + 'Genene', + 'Gittle', + 'Carriann', + 'Emerita', + 'Jenniferann', + 'Kammie', + 'Bryony', + 'Rupinder', + 'Tenise', + 'Yazmyn', + 'Maricris', + 'Rhianon', + 'Nicolet', + 'Mui', + 'Nacy', + 'Naoko', + 'Gaila', + 'Charene', + 'Bas', + 'Geni', + 'Lorez', + 'Taneeka', + 'Tanikqua', + 'Tulani', + 'Sotiria', + 'Sheeba', + 'Katiuscia', + 'Eleftheria', + 'Ghislaine', + 'Jamiylah', + 'Omotayo', + 'Yuleidy', + 'Tylene', + 'Zanetta', + 'Yizza', + 'Ngan', + 'Natassha', + 'Sophear', + 'Starkisha', + 'Stehanie', + 'Jasie', + 'Aprile', + 'Billiejean', + 'Wilnelia', + 'Yaasmiyn', + 'Ednita', + 'Engracia', + 'Grisell', + 'Christinamarie', + 'Eftihia', + 'Jenniefer', + 'Chantee', + 'Afua', + 'Shamea', + 'Shamina', + 'Vickiana', + 'Sharoya', + 'Shateema', + 'Aubrea', + 'Alexcis', + 'Wallis', + 'Jalyne', + 'Harlea', + 'Carisia', + 'Cheynne', + 'Daylee', + 'Kyera', + 'Latayvia', + 'Raashida', + 'Saajida', + 'Nakema', + 'Annalyssa', + 'Chivonne', + 'Lyndie', + 'Sabrian', + 'Rahcel', + 'Hoai', + 'Krisann', + 'Jilliane', + 'Saide', + 'Matti', + 'Raigen', + 'Tenea', + 'Staphanie', + 'Zitlally', + 'Yudelca', + 'Raysa', + 'Monea', + 'Shanigua', + 'Shirah', + 'Chemise', + 'Jajaira', + 'Tunisha', + 'Yelissa', + 'Yudelka', + 'Taria', + 'Taralynn', + 'Condol', + 'Nikima', + 'Syrianna', + 'Anndrea', + 'Charae', + 'Ebelia', + 'Comfort', + 'Denishia', + 'Lanyia', + 'Lahna', + 'Iraima', + 'Josaline', + 'Onyinyechi', + 'Mykalah', + 'Shamyia', + 'Sarely', + 'Makaylie', + 'Madasyn', + 'Carron', + 'Shawnetta', + 'Dorca', + 'Subrena', + 'Romanda', + 'Sallyanne', + 'Ahniyah', + 'Annalissa', + 'Anikah', + 'Anet', + 'Emelee', + 'Branae', + 'Rosemaria', + 'Kimerly', + 'Lorra', + 'Breda', + 'Graceanne', + 'Kathyann', + 'Letetia', + 'Allaina', + 'Anaceli', + 'Brendalee', + 'Aidaly', + 'Arlana', + 'Trinetta', + 'Tennesha', + 'Talonda', + 'Sherrilynn', + 'Maloree', + 'Laiya', + 'Kynlea', + 'Ludwika', + 'Raeli', + 'Yadirah', + 'Yveth', + 'Sabrie', + 'Dannielynn', + 'Breely', + 'Jozlin', + 'Jewelyssa', + 'Keylie', + 'Jazzalyn', + 'Ijeoma', + 'Jaydie', + 'Irianna', + 'Ronya', + 'Lynee', + 'Myrian', + 'Cristalle', + 'Delinah', + 'Arnetia', + 'Guisela', + 'Orna', + 'Samehesha', + 'Scherrie', + 'Marylynne', + 'Judianne', + 'Tomasina', + 'Sanora', + 'Cheray', + 'Gordana', + 'Torina', + 'Yolandra', + 'Tyese', + 'Sharine', + 'Marea', + 'Areti', + 'Sharmila', + 'Charrise', + 'Cyndia', + 'Cinzia', + 'Gecenia', + 'Tarshia', + 'Luwanda', + 'Negar', + 'Sharah', + 'Sherah', + 'Sokha', + 'Marium', + 'Taslin', + 'Taleyah', + 'Parys', + 'Odeth', + 'Mirabai', + 'Myree', + 'Tyhesha', + 'Soyini', + 'Liria', + 'Jenille', + 'Marivic', + 'Mey', + 'Adrena', + 'Cristyn', + 'Jodette', + 'Ilea', + 'Jennett', + 'Latoi', + 'Charrisse', + 'Correne', + 'Reannon', + 'Shanah', + 'Shavaun', + 'Shelena', + 'Macrina', + 'Lashonna', + 'Tecia', + 'Zobeida', + 'Casilda', + 'Ketsy', + 'Lizza', + 'Lucesita', + 'Anelis', + 'Amori', + 'Atlantis', + 'Aslynn', + 'Kimbery', + 'Yolunda', + 'Pasqua', + 'Magalis', + 'Yanellie', + 'Tryniti', + 'Tniya', + 'Ziza', + 'Nadina', + 'Lloana', + 'Shoshannah', + 'Tamarie', + 'Ronique', + 'Keatyn', + 'Matison', + 'Micalah', + 'Nataya', + 'Mama', + 'Bailea', + 'Sidrah', + 'Jazzman', + 'Deanndra', + 'Shawniece', + 'Polett', + 'Rathana', + 'Timisha', + 'Tristina', + 'Vanezza', + 'Shiri', + 'Stephanieann', + 'Genessy', + 'Hema', + 'Huma', + 'Alessandria', + 'Yarisa', + 'Oyindamola', + 'Tianni', + 'Monasia', + 'Kely', + 'Khady', + 'Pegah', + 'Casarah', + 'Cassara', + 'Chalise', + 'Arti', + 'Natanya', + 'Masuma', + 'Shellyann', + 'Taje', + 'Saher', + 'Kelsye', + 'Odaly', + 'Talicia', + 'Mollee', + 'Tashea', + 'Shima', + 'Janaia', + 'Jenia', + 'Jharline', + 'Chabely', + 'Chalon', + 'Charnesha', + 'Christna', + 'Melika', + 'Melis', + 'Lesleyann', + 'Maleeka', + 'Krystalyn', + 'Krystalynn', + 'Marnisha', + 'Mariele', + 'Michelleann', + 'Melessa', + 'Diasy', + 'Dioselina', + 'Jenita', + 'Jaynae', + 'Jeanae', + 'Hripsime', + 'Janete', + 'Lanique', + 'Ashlon', + 'Aroosa', + 'Enisa', + 'Danaysha', + 'Briani', + 'Arjeta', + 'Sapir', + 'Naysha', + 'Kharisma', + 'Laterra', + 'Yannet', + 'Aruna', + 'Anaja', + 'Fahima', + 'Dasmine', + 'Amberlea', + 'Latiera', + 'Kimanh', + 'Mayuri', + 'Meshelle', + 'Morgane', + 'Nahal', + 'Mariacristina', + 'Marlisha', + 'Elaura', + 'Kacia', + 'Neesha', + 'Tila', + 'Waynisha', + 'Witney', + 'Niloofar', + 'Solina', + 'Soo', + 'Stphanie', + 'Shanesha', + 'Sharrell', + 'Nene', + 'Bleona', + 'Hudes', + 'Isatu', + 'Aylssa', + 'Camerina', + 'Arrielle', + 'Allycia', + 'Anacecilia', + 'Anairis', + 'Courney', + 'Dashanique', + 'Cedrina', + 'Celida', + 'Taaliyah', + 'Clarrissa', + 'Egla', + 'Duyen', + 'Kendle', + 'Janil', + 'Adeola', + 'Jazmene', + 'Leesha', + 'Lyzeth', + 'Madeley', + 'Khrystyna', + 'Charisa', + 'Crystelle', + 'Carinna', + 'Channy', + 'Flory', + 'Glenisha', + 'Sheida', + 'Naara', + 'Nassim', + 'Ngozi', + 'Nidya', + 'Marche', + 'Mariaesther', + 'Shaleena', + 'Kioni', + 'Nayab', + 'Nzinga', + 'Fizza', + 'Diavion', + 'Zanib', + 'Tionni', + 'Temitope', + 'Nasreen', + 'Melaysia', + 'Maame', + 'Sameen', + 'Azka', + 'Basma', + 'Virjean', + 'Jarmila', + 'Louren', + 'Mckenize', + 'Malyn', + 'Mercadies', + 'Vika', + 'Suong', + 'Mariadel', + 'Mariatheresa', + 'Marison', + 'Meleane', + 'Shabana', + 'Salote', + 'Raquell', + 'Rekha', + 'Sibel', + 'Shavaughn', + 'Shaquoia', + 'Shatera', + 'Fatina', + 'Jestina', + 'Latasia', + 'Geraldin', + 'Shirleymae', + 'Lubna', + 'Maxiel', + 'Naquasha', + 'Dalissa', + 'Chaniqua', + 'Chanele', + 'Jahlisa', + 'Faatimah', + 'Abagayle', + 'Adwoa', + 'Angeliqu', + 'Gelisa', + 'Bradi', + 'Shantice', + 'Sharece', + 'Nyiesha', + 'Yanill', + 'Yocasta', + 'Stepheni', + 'Suleika', + 'Takeema', + 'Kerrilyn', + 'Jamiyla', + 'Josephin', + 'Margarit', + 'Ilaisaane', + 'Jamilee', + 'Corvette', + 'Janitza', + 'Lexey', + 'Jazzmyne', + 'Kirstan', + 'Kattia', + 'Yatzary', + 'Pricsilla', + 'Gisette', + 'Panayiota', + 'Pinar', + 'Rasheida', + 'Tiffay', + 'Venisha', + 'Jennier', + 'Margulia', + 'Katima', + 'Anjoli', + 'Evelise', + 'Chetara', + 'Jaquelynn', + 'Pessie', + 'Quintessa', + 'Orit', + 'Nelissa', + 'Shekia', + 'Sherrise', + 'Abbye', + 'Imagine', + 'Britlyn', + 'Baley', + 'Tanequa', + 'Tanique', + 'Nocole', + 'Sokhom', + 'Krystelle', + 'Marqui', + 'Mariaangelica', + 'Raiven', + 'Nini', + 'Lesliee', + 'Crystalee', + 'Amadi', + 'Suzett', + 'Thelda', + 'Wladyslawa', + 'Shaqueen', + 'Shayra', + 'Domingue', + 'Garine', + 'Johnanna', + 'Karia', + 'Jany', + 'Ardele', + 'Bilma', + 'Lindita', + 'Lisbel', + 'Lyasia', + 'Kianie', + 'Saidah', + 'Niasha', + 'Chantele', + 'Brette', + 'Cydnie', + 'Chealsea', + 'Jaritsa', + 'Hanaa', + 'Jordain', + 'Kerria', + 'Shannara', + 'Shaquna', + 'Sultana', + 'Tajana', + 'Taquasha', + 'Queenasia', + 'Wandalee', + 'Mikalyn', + 'Jossette', + 'Jazsmine', + 'Keairra', + 'Arleny', + 'Selest', + 'Sabryn', + 'Jilliann', + 'Janin', + 'Kayliegh', + 'Alyss', + 'Asuka', + 'Chenin', + 'Eiliana', + 'Fahm', + 'Cyndle', + 'Daniesha', + 'Saranda', + 'Shany', + 'Veridiana', + 'Yanai', + 'Melanieann', + 'Mishell', + 'Mariadelosangel', + 'Rupa', + 'Orabelle', + 'Taquasia', + 'Tyquasia', + 'Cecillia', + 'Jeanet', + 'Lucely', + 'Kar', + 'Niaja', + 'Naquana', + 'Joanny', + 'Anjelique', + 'Aquasia', + 'Ardita', + 'Jatasia', + 'Donika', + 'Fantasha', + 'Dominiqua', + 'Elecia', + 'Deyra', + 'Erial', + 'Bayle', + 'Ninoska', + 'Jonee', + 'Jullisa', + 'Lavasia', + 'Laniqua', +]; + +export const maxStringLength = 15; diff --git a/drizzle-seed/src/datasets/jobsTitles.ts b/drizzle-seed/src/datasets/jobsTitles.ts new file mode 100644 index 000000000..e7993da2a --- /dev/null +++ b/drizzle-seed/src/datasets/jobsTitles.ts @@ -0,0 +1,154 @@ +/** + * The original source for the job titles data was taken from https://www.kaggle.com/datasets/ravindrasinghrana/job-description-dataset + */ +export default [ + 'Digital marketing specialist', + 'Web developer', + 'Operations manager', + 'Network engineer', + 'Event manager', + 'Software tester', + 'Teacher', + 'Ux/ui designer', + 'Wedding planner', + 'Qa analyst', + 'Litigation attorney', + 'Mechanical engineer', + 'Network administrator', + 'Account manager', + 'Brand manager', + 'Social worker', + 'Social media coordinator', + 'Email marketing specialist', + 'Hr generalist', + 'Legal assistant', + 'Nurse practitioner', + 'Account director', + 'Software engineer', + 'Purchasing agent', + 'Sales consultant', + 'Civil engineer', + 'Network security specialist', + 'Ui developer', + 'Financial planner', + 'Event planner', + 'Psychologist', + 'Electrical designer', + 'Data analyst', + 'Technical writer', + 'Tax consultant', + 'Account executive', + 'Systems administrator', + 'Database administrator', + 'Research analyst', + 'Data entry clerk', + 'Registered nurse', + 'Investment analyst', + 'Speech therapist', + 'Sales manager', + 'Landscape architect', + 'Key account manager', + 'Ux researcher', + 'Investment banker', + 'It support specialist', + 'Art director', + 'Software developer', + 'Project manager', + 'Customer service manager', + 'Procurement manager', + 'Substance abuse counselor', + 'Supply chain analyst', + 'Data engineer', + 'Accountant', + 'Sales representative', + 'Environmental consultant', + 'Electrical engineer', + 'Systems engineer', + 'Art teacher', + 'Human resources manager', + 'Inventory analyst', + 'Legal counsel', + 'Database developer', + 'Procurement specialist', + 'Systems analyst', + 'Copywriter', + 'Content writer', + 'Hr coordinator', + 'Business development manager', + 'Java developer', + 'Supply chain manager', + 'Event coordinator', + 'Family nurse practitioner', + 'Front-end engineer', + 'Customer success manager', + 'Procurement coordinator', + 'Urban planner', + 'Architectural designer', + 'Financial analyst', + 'Environmental engineer', + 'Back-end developer', + 'Structural engineer', + 'Market research analyst', + 'Customer service representative', + 'Customer support specialist', + 'Business analyst', + 'Social media manager', + 'Family lawyer', + 'Chemical analyst', + 'Network technician', + 'Interior designer', + 'Software architect', + 'Nurse manager', + 'Veterinarian', + 'Process engineer', + 'It manager', + 'Quality assurance analyst', + 'Pharmaceutical sales representative', + 'Office manager', + 'Architect', + 'Physician assistant', + 'Marketing director', + 'Front-end developer', + 'Research scientist', + 'Executive assistant', + 'Hr manager', + 'Marketing manager', + 'Public relations specialist', + 'Financial controller', + 'Investment advisor', + 'Aerospace engineer', + 'Marketing analyst', + 'Paralegal', + 'Landscape designer', + 'Web designer', + 'Occupational therapist', + 'Legal advisor', + 'Marketing coordinator', + 'Dental hygienist', + 'Sem specialist', + 'Seo specialist', + 'Pediatrician', + 'Qa engineer', + 'Data scientist', + 'Financial advisor', + 'Personal assistant', + 'Seo analyst', + 'Network analyst', + 'Mechanical designer', + 'Marketing specialist', + 'Graphic designer', + 'Finance manager', + 'Physical therapist', + 'Product designer', + 'Administrative assistant', + 'Brand ambassador', + 'Project coordinator', + 'Product manager', + 'It administrator', + 'Sales associate', + 'Chemical engineer', + 'Legal secretary', + 'Market analyst', +]; + +export const maxStringLength = 35; diff --git a/drizzle-seed/src/datasets/lastNames.ts b/drizzle-seed/src/datasets/lastNames.ts new file mode 100644 index 000000000..9d35f7cf7 --- /dev/null +++ b/drizzle-seed/src/datasets/lastNames.ts @@ -0,0 +1,50005 @@ +/** + * The original source for last names data was taken from https://www.kaggle.com/datasets/fivethirtyeight/fivethirtyeight-most-common-name-dataset?resource=download&select=surnames.csv + */ +export default [ + 'Smith', + 'Johnson', + 'Williams', + 'Brown', + 'Jones', + 'Miller', + 'Davis', + 'Garcia', + 'Rodriguez', + 'Wilson', + 'Martinez', + 'Anderson', + 'Taylor', + 'Thomas', + 'Hernandez', + 'Moore', + 'Martin', + 'Jackson', + 'Thompson', + 'White', + 'Lopez', + 'Lee', + 'Gonzalez', + 'Harris', + 'Clark', + 'Lewis', + 'Robinson', + 'Walker', + 'Perez', + 'Hall', + 'Young', + 'Allen', + 'Sanchez', + 'Wright', + 'King', + 'Scott', + 'Green', + 'Baker', + 'Adams', + 'Nelson', + 'Hill', + 'Ramirez', + 'Campbell', + 'Mitchell', + 'Roberts', + 'Carter', + 'Phillips', + 'Evans', + 'Turner', + 'Torres', + 'Parker', + 'Collins', + 'Edwards', + 'Stewart', + 'Flores', + 'Morris', + 'Nguyen', + 'Murphy', + 'Rivera', + 'Cook', + 'Rogers', + 'Morgan', + 'Peterson', + 'Cooper', + 'Reed', + 'Bailey', + 'Bell', + 'Gomez', + 'Kelly', + 'Howard', + 'Ward', + 'Cox', + 'Diaz', + 'Richardson', + 'Wood', + 'Watson', + 'Brooks', + 'Bennett', + 'Gray', + 'James', + 'Reyes', + 'Cruz', + 'Hughes', + 'Price', + 'Myers', + 'Long', + 'Foster', + 'Sanders', + 'Ross', + 'Morales', + 'Powell', + 'Sullivan', + 'Russell', + 'Ortiz', + 'Jenkins', + 'Gutierrez', + 'Perry', + 'Butler', + 'Barnes', + 'Fisher', + 'Henderson', + 'Coleman', + 'Simmons', + 'Patterson', + 'Jordan', + 'Reynolds', + 'Hamilton', + 'Graham', + 'Kim', + 'Gonzales', + 'Alexander', + 'Ramos', + 'Wallace', + 'Griffin', + 'West', + 'Cole', + 'Hayes', + 'Chavez', + 'Gibson', + 'Bryant', + 'Ellis', + 'Stevens', + 'Murray', + 'Ford', + 'Marshall', + 'Owens', + 'Mcdonald', + 'Harrison', + 'Ruiz', + 'Kennedy', + 'Wells', + 'Alvarez', + 'Woods', + 'Mendoza', + 'Castillo', + 'Olson', + 'Webb', + 'Washington', + 'Tucker', + 'Freeman', + 'Burns', + 'Henry', + 'Vasquez', + 'Snyder', + 'Simpson', + 'Crawford', + 'Jimenez', + 'Porter', + 'Mason', + 'Shaw', + 'Gordon', + 'Wagner', + 'Hunter', + 'Romero', + 'Hicks', + 'Dixon', + 'Hunt', + 'Palmer', + 'Robertson', + 'Black', + 'Holmes', + 'Stone', + 'Meyer', + 'Boyd', + 'Mills', + 'Warren', + 'Fox', + 'Rose', + 'Rice', + 'Moreno', + 'Schmidt', + 'Patel', + 'Ferguson', + 'Nichols', + 'Herrera', + 'Medina', + 'Ryan', + 'Fernandez', + 'Weaver', + 'Daniels', + 'Stephens', + 'Gardner', + 'Payne', + 'Kelley', + 'Dunn', + 'Pierce', + 'Arnold', + 'Tran', + 'Spencer', + 'Peters', + 'Hawkins', + 'Grant', + 'Hansen', + 'Castro', + 'Hoffman', + 'Hart', + 'Elliott', + 'Cunningham', + 'Knight', + 'Bradley', + 'Carroll', + 'Hudson', + 'Duncan', + 'Armstrong', + 'Berry', + 'Andrews', + 'Johnston', + 'Ray', + 'Lane', + 'Riley', + 'Carpenter', + 'Perkins', + 'Aguilar', + 'Silva', + 'Richards', + 'Willis', + 'Matthews', + 'Chapman', + 'Lawrence', + 'Garza', + 'Vargas', + 'Watkins', + 'Wheeler', + 'Larson', + 'Carlson', + 'Harper', + 'George', + 'Greene', + 'Burke', + 'Guzman', + 'Morrison', + 'Munoz', + 'Jacobs', + 'Obrien', + 'Lawson', + 'Franklin', + 'Lynch', + 'Bishop', + 'Carr', + 'Salazar', + 'Austin', + 'Mendez', + 'Gilbert', + 'Jensen', + 'Williamson', + 'Montgomery', + 'Harvey', + 'Oliver', + 'Howell', + 'Dean', + 'Hanson', + 'Weber', + 'Garrett', + 'Sims', + 'Burton', + 'Fuller', + 'Soto', + 'Mccoy', + 'Welch', + 'Chen', + 'Schultz', + 'Walters', + 'Reid', + 'Fields', + 'Walsh', + 'Little', + 'Fowler', + 'Bowman', + 'Davidson', + 'May', + 'Day', + 'Schneider', + 'Newman', + 'Brewer', + 'Lucas', + 'Holland', + 'Wong', + 'Banks', + 'Santos', + 'Curtis', + 'Pearson', + 'Delgado', + 'Valdez', + 'Pena', + 'Rios', + 'Douglas', + 'Sandoval', + 'Barrett', + 'Hopkins', + 'Keller', + 'Guerrero', + 'Stanley', + 'Bates', + 'Alvarado', + 'Beck', + 'Ortega', + 'Wade', + 'Estrada', + 'Contreras', + 'Barnett', + 'Caldwell', + 'Santiago', + 'Lambert', + 'Powers', + 'Chambers', + 'Nunez', + 'Craig', + 'Leonard', + 'Lowe', + 'Rhodes', + 'Byrd', + 'Gregory', + 'Shelton', + 'Frazier', + 'Becker', + 'Maldonado', + 'Fleming', + 'Vega', + 'Sutton', + 'Cohen', + 'Jennings', + 'Parks', + 'Mcdaniel', + 'Watts', + 'Barker', + 'Norris', + 'Vaughn', + 'Vazquez', + 'Holt', + 'Schwartz', + 'Steele', + 'Benson', + 'Neal', + 'Dominguez', + 'Horton', + 'Terry', + 'Wolfe', + 'Hale', + 'Lyons', + 'Graves', + 'Haynes', + 'Miles', + 'Park', + 'Warner', + 'Padilla', + 'Bush', + 'Thornton', + 'Mccarthy', + 'Mann', + 'Zimmerman', + 'Erickson', + 'Fletcher', + 'Mckinney', + 'Page', + 'Dawson', + 'Joseph', + 'Marquez', + 'Reeves', + 'Klein', + 'Espinoza', + 'Baldwin', + 'Moran', + 'Love', + 'Robbins', + 'Higgins', + 'Ball', + 'Cortez', + 'Le', + 'Griffith', + 'Bowen', + 'Sharp', + 'Cummings', + 'Ramsey', + 'Hardy', + 'Swanson', + 'Barber', + 'Acosta', + 'Luna', + 'Chandler', + 'Blair', + 'Daniel', + 'Cross', + 'Simon', + 'Dennis', + 'Oconnor', + 'Quinn', + 'Gross', + 'Navarro', + 'Moss', + 'Fitzgerald', + 'Doyle', + 'Mclaughlin', + 'Rojas', + 'Rodgers', + 'Stevenson', + 'Singh', + 'Yang', + 'Figueroa', + 'Harmon', + 'Newton', + 'Paul', + 'Manning', + 'Garner', + 'Mcgee', + 'Reese', + 'Francis', + 'Burgess', + 'Adkins', + 'Goodman', + 'Curry', + 'Brady', + 'Christensen', + 'Potter', + 'Walton', + 'Goodwin', + 'Mullins', + 'Molina', + 'Webster', + 'Fischer', + 'Campos', + 'Avila', + 'Sherman', + 'Todd', + 'Chang', + 'Blake', + 'Malone', + 'Wolf', + 'Hodges', + 'Juarez', + 'Gill', + 'Farmer', + 'Hines', + 'Gallagher', + 'Duran', + 'Hubbard', + 'Cannon', + 'Miranda', + 'Wang', + 'Saunders', + 'Tate', + 'Mack', + 'Hammond', + 'Carrillo', + 'Townsend', + 'Wise', + 'Ingram', + 'Barton', + 'Mejia', + 'Ayala', + 'Schroeder', + 'Hampton', + 'Rowe', + 'Parsons', + 'Frank', + 'Waters', + 'Strickland', + 'Osborne', + 'Maxwell', + 'Chan', + 'Deleon', + 'Norman', + 'Harrington', + 'Casey', + 'Patton', + 'Logan', + 'Bowers', + 'Mueller', + 'Glover', + 'Floyd', + 'Hartman', + 'Buchanan', + 'Cobb', + 'French', + 'Kramer', + 'Mccormick', + 'Clarke', + 'Tyler', + 'Gibbs', + 'Moody', + 'Conner', + 'Sparks', + 'Mcguire', + 'Leon', + 'Bauer', + 'Norton', + 'Pope', + 'Flynn', + 'Hogan', + 'Robles', + 'Salinas', + 'Yates', + 'Lindsey', + 'Lloyd', + 'Marsh', + 'Mcbride', + 'Owen', + 'Solis', + 'Pham', + 'Lang', + 'Pratt', + 'Lara', + 'Brock', + 'Ballard', + 'Trujillo', + 'Shaffer', + 'Drake', + 'Roman', + 'Aguirre', + 'Morton', + 'Stokes', + 'Lamb', + 'Pacheco', + 'Patrick', + 'Cochran', + 'Shepherd', + 'Cain', + 'Burnett', + 'Hess', + 'Li', + 'Cervantes', + 'Olsen', + 'Briggs', + 'Ochoa', + 'Cabrera', + 'Velasquez', + 'Montoya', + 'Roth', + 'Meyers', + 'Cardenas', + 'Fuentes', + 'Weiss', + 'Hoover', + 'Wilkins', + 'Nicholson', + 'Underwood', + 'Short', + 'Carson', + 'Morrow', + 'Colon', + 'Holloway', + 'Summers', + 'Bryan', + 'Petersen', + 'Mckenzie', + 'Serrano', + 'Wilcox', + 'Carey', + 'Clayton', + 'Poole', + 'Calderon', + 'Gallegos', + 'Greer', + 'Rivas', + 'Guerra', + 'Decker', + 'Collier', + 'Wall', + 'Whitaker', + 'Bass', + 'Flowers', + 'Davenport', + 'Conley', + 'Houston', + 'Huff', + 'Copeland', + 'Hood', + 'Monroe', + 'Massey', + 'Roberson', + 'Combs', + 'Franco', + 'Larsen', + 'Pittman', + 'Randall', + 'Skinner', + 'Wilkinson', + 'Kirby', + 'Cameron', + 'Bridges', + 'Anthony', + 'Richard', + 'Kirk', + 'Bruce', + 'Singleton', + 'Mathis', + 'Bradford', + 'Boone', + 'Abbott', + 'Charles', + 'Allison', + 'Sweeney', + 'Atkinson', + 'Horn', + 'Jefferson', + 'Rosales', + 'York', + 'Christian', + 'Phelps', + 'Farrell', + 'Castaneda', + 'Nash', + 'Dickerson', + 'Bond', + 'Wyatt', + 'Foley', + 'Chase', + 'Gates', + 'Vincent', + 'Mathews', + 'Hodge', + 'Garrison', + 'Trevino', + 'Villarreal', + 'Heath', + 'Dalton', + 'Valencia', + 'Callahan', + 'Hensley', + 'Atkins', + 'Huffman', + 'Roy', + 'Boyer', + 'Shields', + 'Lin', + 'Hancock', + 'Grimes', + 'Glenn', + 'Cline', + 'Delacruz', + 'Camacho', + 'Dillon', + 'Parrish', + 'Oneill', + 'Melton', + 'Booth', + 'Kane', + 'Berg', + 'Harrell', + 'Pitts', + 'Savage', + 'Wiggins', + 'Brennan', + 'Salas', + 'Marks', + 'Russo', + 'Sawyer', + 'Baxter', + 'Golden', + 'Hutchinson', + 'Liu', + 'Walter', + 'Mcdowell', + 'Wiley', + 'Rich', + 'Humphrey', + 'Johns', + 'Koch', + 'Suarez', + 'Hobbs', + 'Beard', + 'Gilmore', + 'Ibarra', + 'Keith', + 'Macias', + 'Khan', + 'Andrade', + 'Ware', + 'Stephenson', + 'Henson', + 'Wilkerson', + 'Dyer', + 'Mcclure', + 'Blackwell', + 'Mercado', + 'Tanner', + 'Eaton', + 'Clay', + 'Barron', + 'Beasley', + 'Oneal', + 'Preston', + 'Small', + 'Wu', + 'Zamora', + 'Macdonald', + 'Vance', + 'Snow', + 'Mcclain', + 'Stafford', + 'Orozco', + 'Barry', + 'English', + 'Shannon', + 'Kline', + 'Jacobson', + 'Woodard', + 'Huang', + 'Kemp', + 'Mosley', + 'Prince', + 'Merritt', + 'Hurst', + 'Villanueva', + 'Roach', + 'Nolan', + 'Lam', + 'Yoder', + 'Mccullough', + 'Lester', + 'Santana', + 'Valenzuela', + 'Winters', + 'Barrera', + 'Leach', + 'Orr', + 'Berger', + 'Mckee', + 'Strong', + 'Conway', + 'Stein', + 'Whitehead', + 'Bullock', + 'Escobar', + 'Knox', + 'Meadows', + 'Solomon', + 'Velez', + 'Odonnell', + 'Kerr', + 'Stout', + 'Blankenship', + 'Browning', + 'Kent', + 'Lozano', + 'Bartlett', + 'Pruitt', + 'Buck', + 'Barr', + 'Gaines', + 'Durham', + 'Gentry', + 'Mcintyre', + 'Sloan', + 'Rocha', + 'Melendez', + 'Herman', + 'Sexton', + 'Moon', + 'Hendricks', + 'Rangel', + 'Stark', + 'Lowery', + 'Hardin', + 'Hull', + 'Sellers', + 'Ellison', + 'Calhoun', + 'Gillespie', + 'Mora', + 'Knapp', + 'Mccall', + 'Morse', + 'Dorsey', + 'Weeks', + 'Nielsen', + 'Livingston', + 'Leblanc', + 'Mclean', + 'Bradshaw', + 'Glass', + 'Middleton', + 'Buckley', + 'Schaefer', + 'Frost', + 'Howe', + 'House', + 'Mcintosh', + 'Ho', + 'Pennington', + 'Reilly', + 'Hebert', + 'Mcfarland', + 'Hickman', + 'Noble', + 'Spears', + 'Conrad', + 'Arias', + 'Galvan', + 'Velazquez', + 'Huynh', + 'Frederick', + 'Randolph', + 'Cantu', + 'Fitzpatrick', + 'Mahoney', + 'Peck', + 'Villa', + 'Michael', + 'Donovan', + 'Mcconnell', + 'Walls', + 'Boyle', + 'Mayer', + 'Zuniga', + 'Giles', + 'Pineda', + 'Pace', + 'Hurley', + 'Mays', + 'Mcmillan', + 'Crosby', + 'Ayers', + 'Case', + 'Bentley', + 'Shepard', + 'Everett', + 'Pugh', + 'David', + 'Mcmahon', + 'Dunlap', + 'Bender', + 'Hahn', + 'Harding', + 'Acevedo', + 'Raymond', + 'Blackburn', + 'Duffy', + 'Landry', + 'Dougherty', + 'Bautista', + 'Shah', + 'Potts', + 'Arroyo', + 'Valentine', + 'Meza', + 'Gould', + 'Vaughan', + 'Fry', + 'Rush', + 'Avery', + 'Herring', + 'Dodson', + 'Clements', + 'Sampson', + 'Tapia', + 'Bean', + 'Lynn', + 'Crane', + 'Farley', + 'Cisneros', + 'Benton', + 'Ashley', + 'Mckay', + 'Finley', + 'Best', + 'Blevins', + 'Friedman', + 'Moses', + 'Sosa', + 'Blanchard', + 'Huber', + 'Frye', + 'Krueger', + 'Bernard', + 'Rosario', + 'Rubio', + 'Mullen', + 'Benjamin', + 'Haley', + 'Chung', + 'Moyer', + 'Choi', + 'Horne', + 'Yu', + 'Woodward', + 'Ali', + 'Nixon', + 'Hayden', + 'Rivers', + 'Estes', + 'Mccarty', + 'Richmond', + 'Stuart', + 'Maynard', + 'Brandt', + 'Oconnell', + 'Hanna', + 'Sanford', + 'Sheppard', + 'Church', + 'Burch', + 'Levy', + 'Rasmussen', + 'Coffey', + 'Ponce', + 'Faulkner', + 'Donaldson', + 'Schmitt', + 'Novak', + 'Costa', + 'Montes', + 'Booker', + 'Cordova', + 'Waller', + 'Arellano', + 'Maddox', + 'Mata', + 'Bonilla', + 'Stanton', + 'Compton', + 'Kaufman', + 'Dudley', + 'Mcpherson', + 'Beltran', + 'Dickson', + 'Mccann', + 'Villegas', + 'Proctor', + 'Hester', + 'Cantrell', + 'Daugherty', + 'Cherry', + 'Bray', + 'Davila', + 'Rowland', + 'Levine', + 'Madden', + 'Spence', + 'Good', + 'Irwin', + 'Werner', + 'Krause', + 'Petty', + 'Whitney', + 'Baird', + 'Hooper', + 'Pollard', + 'Zavala', + 'Jarvis', + 'Holden', + 'Haas', + 'Hendrix', + 'Mcgrath', + 'Bird', + 'Lucero', + 'Terrell', + 'Riggs', + 'Joyce', + 'Mercer', + 'Rollins', + 'Galloway', + 'Duke', + 'Odom', + 'Andersen', + 'Downs', + 'Hatfield', + 'Benitez', + 'Archer', + 'Huerta', + 'Travis', + 'Mcneil', + 'Hinton', + 'Zhang', + 'Hays', + 'Mayo', + 'Fritz', + 'Branch', + 'Mooney', + 'Ewing', + 'Ritter', + 'Esparza', + 'Frey', + 'Braun', + 'Gay', + 'Riddle', + 'Haney', + 'Kaiser', + 'Holder', + 'Chaney', + 'Mcknight', + 'Gamble', + 'Vang', + 'Cooley', + 'Carney', + 'Cowan', + 'Forbes', + 'Ferrell', + 'Davies', + 'Barajas', + 'Shea', + 'Osborn', + 'Bright', + 'Cuevas', + 'Bolton', + 'Murillo', + 'Lutz', + 'Duarte', + 'Kidd', + 'Key', + 'Cooke', + 'Goff', + 'Dejesus', + 'Marin', + 'Dotson', + 'Bonner', + 'Cotton', + 'Merrill', + 'Lindsay', + 'Lancaster', + 'Mcgowan', + 'Felix', + 'Salgado', + 'Slater', + 'Carver', + 'Guthrie', + 'Holman', + 'Fulton', + 'Snider', + 'Sears', + 'Witt', + 'Newell', + 'Byers', + 'Lehman', + 'Gorman', + 'Costello', + 'Donahue', + 'Delaney', + 'Albert', + 'Workman', + 'Rosas', + 'Springer', + 'Kinney', + 'Justice', + 'Odell', + 'Lake', + 'Donnelly', + 'Law', + 'Dailey', + 'Guevara', + 'Shoemaker', + 'Barlow', + 'Marino', + 'Winter', + 'Craft', + 'Katz', + 'Pickett', + 'Espinosa', + 'Maloney', + 'Daly', + 'Goldstein', + 'Crowley', + 'Vogel', + 'Kuhn', + 'Pearce', + 'Hartley', + 'Cleveland', + 'Palacios', + 'Mcfadden', + 'Britt', + 'Wooten', + 'Cortes', + 'Dillard', + 'Childers', + 'Alford', + 'Dodd', + 'Emerson', + 'Wilder', + 'Lange', + 'Goldberg', + 'Quintero', + 'Beach', + 'Enriquez', + 'Quintana', + 'Helms', + 'Mackey', + 'Finch', + 'Cramer', + 'Minor', + 'Flanagan', + 'Franks', + 'Corona', + 'Kendall', + 'Mccabe', + 'Hendrickson', + 'Moser', + 'Mcdermott', + 'Camp', + 'Mcleod', + 'Bernal', + 'Kaplan', + 'Medrano', + 'Lugo', + 'Tracy', + 'Bacon', + 'Crowe', + 'Richter', + 'Welsh', + 'Holley', + 'Ratliff', + 'Mayfield', + 'Talley', + 'Haines', + 'Dale', + 'Gibbons', + 'Hickey', + 'Byrne', + 'Kirkland', + 'Farris', + 'Correa', + 'Tillman', + 'Sweet', + 'Kessler', + 'England', + 'Hewitt', + 'Blanco', + 'Connolly', + 'Pate', + 'Elder', + 'Bruno', + 'Holcomb', + 'Hyde', + 'Mcallister', + 'Cash', + 'Christopher', + 'Whitfield', + 'Meeks', + 'Hatcher', + 'Fink', + 'Sutherland', + 'Noel', + 'Ritchie', + 'Rosa', + 'Leal', + 'Joyner', + 'Starr', + 'Morin', + 'Delarosa', + 'Connor', + 'Hilton', + 'Alston', + 'Gilliam', + 'Wynn', + 'Wills', + 'Jaramillo', + 'Oneil', + 'Nieves', + 'Britton', + 'Rankin', + 'Belcher', + 'Guy', + 'Chamberlain', + 'Tyson', + 'Puckett', + 'Downing', + 'Sharpe', + 'Boggs', + 'Truong', + 'Pierson', + 'Godfrey', + 'Mobley', + 'John', + 'Kern', + 'Dye', + 'Hollis', + 'Bravo', + 'Magana', + 'Rutherford', + 'Ng', + 'Tuttle', + 'Lim', + 'Romano', + 'Trejo', + 'Arthur', + 'Knowles', + 'Lyon', + 'Shirley', + 'Quinones', + 'Childs', + 'Dolan', + 'Head', + 'Reyna', + 'Saenz', + 'Hastings', + 'Kenney', + 'Cano', + 'Foreman', + 'Denton', + 'Villalobos', + 'Pryor', + 'Sargent', + 'Doherty', + 'Hopper', + 'Phan', + 'Womack', + 'Lockhart', + 'Ventura', + 'Dwyer', + 'Muller', + 'Galindo', + 'Grace', + 'Sorensen', + 'Courtney', + 'Parra', + 'Rodrigues', + 'Nicholas', + 'Ahmed', + 'Mcginnis', + 'Langley', + 'Madison', + 'Locke', + 'Jamison', + 'Nava', + 'Gustafson', + 'Sykes', + 'Dempsey', + 'Hamm', + 'Rodriquez', + 'Mcgill', + 'Xiong', + 'Esquivel', + 'Simms', + 'Kendrick', + 'Boyce', + 'Vigil', + 'Downey', + 'Mckenna', + 'Sierra', + 'Webber', + 'Kirkpatrick', + 'Dickinson', + 'Couch', + 'Burks', + 'Sheehan', + 'Slaughter', + 'Pike', + 'Whitley', + 'Magee', + 'Cheng', + 'Sinclair', + 'Cassidy', + 'Rutledge', + 'Burris', + 'Bowling', + 'Crabtree', + 'Mcnamara', + 'Avalos', + 'Vu', + 'Herron', + 'Broussard', + 'Abraham', + 'Garland', + 'Corbett', + 'Corbin', + 'Stinson', + 'Chin', + 'Burt', + 'Hutchins', + 'Woodruff', + 'Lau', + 'Brandon', + 'Singer', + 'Hatch', + 'Rossi', + 'Shafer', + 'Ott', + 'Goss', + 'Gregg', + 'Dewitt', + 'Tang', + 'Polk', + 'Worley', + 'Covington', + 'Saldana', + 'Heller', + 'Emery', + 'Swartz', + 'Cho', + 'Mccray', + 'Elmore', + 'Rosenberg', + 'Simons', + 'Clemons', + 'Beatty', + 'Harden', + 'Herbert', + 'Bland', + 'Rucker', + 'Manley', + 'Ziegler', + 'Grady', + 'Lott', + 'Rouse', + 'Gleason', + 'Mcclellan', + 'Abrams', + 'Vo', + 'Albright', + 'Meier', + 'Dunbar', + 'Ackerman', + 'Padgett', + 'Mayes', + 'Tipton', + 'Coffman', + 'Peralta', + 'Shapiro', + 'Roe', + 'Weston', + 'Plummer', + 'Helton', + 'Stern', + 'Fraser', + 'Stover', + 'Fish', + 'Schumacher', + 'Baca', + 'Curran', + 'Vinson', + 'Vera', + 'Clifton', + 'Ervin', + 'Eldridge', + 'Lowry', + 'Childress', + 'Becerra', + 'Gore', + 'Seymour', + 'Chu', + 'Field', + 'Akers', + 'Carrasco', + 'Bingham', + 'Sterling', + 'Greenwood', + 'Leslie', + 'Groves', + 'Manuel', + 'Swain', + 'Edmonds', + 'Muniz', + 'Thomson', + 'Crouch', + 'Walden', + 'Smart', + 'Tomlinson', + 'Alfaro', + 'Quick', + 'Goldman', + 'Mcelroy', + 'Yarbrough', + 'Funk', + 'Hong', + 'Portillo', + 'Lund', + 'Ngo', + 'Elkins', + 'Stroud', + 'Meredith', + 'Battle', + 'Mccauley', + 'Zapata', + 'Bloom', + 'Gee', + 'Givens', + 'Cardona', + 'Schafer', + 'Robison', + 'Gunter', + 'Griggs', + 'Tovar', + 'Teague', + 'Swift', + 'Bowden', + 'Schulz', + 'Blanton', + 'Buckner', + 'Whalen', + 'Pritchard', + 'Pierre', + 'Kang', + 'Metcalf', + 'Butts', + 'Kurtz', + 'Sanderson', + 'Tompkins', + 'Inman', + 'Crowder', + 'Dickey', + 'Hutchison', + 'Conklin', + 'Hoskins', + 'Holbrook', + 'Horner', + 'Neely', + 'Tatum', + 'Hollingsworth', + 'Draper', + 'Clement', + 'Lord', + 'Reece', + 'Feldman', + 'Kay', + 'Hagen', + 'Crews', + 'Bowles', + 'Post', + 'Jewell', + 'Daley', + 'Cordero', + 'Mckinley', + 'Velasco', + 'Masters', + 'Driscoll', + 'Burrell', + 'Valle', + 'Crow', + 'Devine', + 'Larkin', + 'Chappell', + 'Pollock', + 'Ly', + 'Kimball', + 'Schmitz', + 'Lu', + 'Rubin', + 'Self', + 'Barrios', + 'Pereira', + 'Phipps', + 'Mcmanus', + 'Nance', + 'Steiner', + 'Poe', + 'Crockett', + 'Jeffries', + 'Amos', + 'Nix', + 'Newsome', + 'Dooley', + 'Payton', + 'Rosen', + 'Swenson', + 'Connelly', + 'Tolbert', + 'Segura', + 'Esposito', + 'Coker', + 'Biggs', + 'Hinkle', + 'Thurman', + 'Drew', + 'Ivey', + 'Bullard', + 'Baez', + 'Neff', + 'Maher', + 'Stratton', + 'Egan', + 'Dubois', + 'Gallardo', + 'Blue', + 'Rainey', + 'Yeager', + 'Saucedo', + 'Ferreira', + 'Sprague', + 'Lacy', + 'Hurtado', + 'Heard', + 'Connell', + 'Stahl', + 'Aldridge', + 'Amaya', + 'Forrest', + 'Erwin', + 'Gunn', + 'Swan', + 'Butcher', + 'Rosado', + 'Godwin', + 'Hand', + 'Gabriel', + 'Otto', + 'Whaley', + 'Ludwig', + 'Clifford', + 'Grove', + 'Beaver', + 'Silver', + 'Dang', + 'Hammer', + 'Dick', + 'Boswell', + 'Mead', + 'Colvin', + 'Oleary', + 'Milligan', + 'Goins', + 'Ames', + 'Dodge', + 'Kaur', + 'Escobedo', + 'Arredondo', + 'Geiger', + 'Winkler', + 'Dunham', + 'Temple', + 'Babcock', + 'Billings', + 'Grimm', + 'Lilly', + 'Wesley', + 'Mcghee', + 'Siegel', + 'Painter', + 'Bower', + 'Purcell', + 'Block', + 'Aguilera', + 'Norwood', + 'Sheridan', + 'Cartwright', + 'Coates', + 'Davison', + 'Regan', + 'Ramey', + 'Koenig', + 'Kraft', + 'Bunch', + 'Engel', + 'Tan', + 'Winn', + 'Steward', + 'Link', + 'Vickers', + 'Bragg', + 'Piper', + 'Huggins', + 'Michel', + 'Healy', + 'Jacob', + 'Mcdonough', + 'Wolff', + 'Colbert', + 'Zepeda', + 'Hoang', + 'Dugan', + 'Meade', + 'Kilgore', + 'Guillen', + 'Do', + 'Hinojosa', + 'Goode', + 'Arrington', + 'Gary', + 'Snell', + 'Willard', + 'Renteria', + 'Chacon', + 'Gallo', + 'Hankins', + 'Montano', + 'Browne', + 'Peacock', + 'Ohara', + 'Cornell', + 'Sherwood', + 'Castellanos', + 'Thorpe', + 'Stiles', + 'Sadler', + 'Latham', + 'Redmond', + 'Greenberg', + 'Cote', + 'Waddell', + 'Dukes', + 'Diamond', + 'Bui', + 'Madrid', + 'Alonso', + 'Sheets', + 'Irvin', + 'Hurt', + 'Ferris', + 'Sewell', + 'Carlton', + 'Aragon', + 'Blackmon', + 'Hadley', + 'Hoyt', + 'Mcgraw', + 'Pagan', + 'Land', + 'Tidwell', + 'Lovell', + 'Miner', + 'Doss', + 'Dahl', + 'Delatorre', + 'Stanford', + 'Kauffman', + 'Vela', + 'Gagnon', + 'Winston', + 'Gomes', + 'Thacker', + 'Coronado', + 'Ash', + 'Jarrett', + 'Hager', + 'Samuels', + 'Metzger', + 'Raines', + 'Spivey', + 'Maurer', + 'Han', + 'Voss', + 'Henley', + 'Caballero', + 'Caruso', + 'Coulter', + 'North', + 'Finn', + 'Cahill', + 'Lanier', + 'Souza', + 'Mcwilliams', + 'Deal', + 'Urban', + 'Schaffer', + 'Houser', + 'Cummins', + 'Romo', + 'Crocker', + 'Bassett', + 'Kruse', + 'Bolden', + 'Ybarra', + 'Metz', + 'Root', + 'Mcmullen', + 'Hagan', + 'Crump', + 'Guidry', + 'Brantley', + 'Kearney', + 'Beal', + 'Toth', + 'Jorgensen', + 'Timmons', + 'Milton', + 'Tripp', + 'Hurd', + 'Sapp', + 'Whitman', + 'Messer', + 'Burgos', + 'Major', + 'Westbrook', + 'Castle', + 'Serna', + 'Carlisle', + 'Varela', + 'Cullen', + 'Wilhelm', + 'Bergeron', + 'Burger', + 'Posey', + 'Barnhart', + 'Hackett', + 'Madrigal', + 'Eubanks', + 'Sizemore', + 'Hilliard', + 'Hargrove', + 'Boucher', + 'Thomason', + 'Melvin', + 'Roper', + 'Barnard', + 'Fonseca', + 'Pedersen', + 'Quiroz', + 'Washburn', + 'Holliday', + 'Yee', + 'Rudolph', + 'Bermudez', + 'Coyle', + 'Gil', + 'Pina', + 'Goodrich', + 'Elias', + 'Lockwood', + 'Cabral', + 'Carranza', + 'Duvall', + 'Cornelius', + 'Mccollum', + 'Street', + 'Mcneal', + 'Connors', + 'Angel', + 'Paulson', + 'Hinson', + 'Keenan', + 'Sheldon', + 'Farr', + 'Eddy', + 'Samuel', + 'Ring', + 'Ledbetter', + 'Betts', + 'Fontenot', + 'Gifford', + 'Hannah', + 'Hanley', + 'Person', + 'Fountain', + 'Levin', + 'Stubbs', + 'Hightower', + 'Murdock', + 'Koehler', + 'Ma', + 'Engle', + 'Smiley', + 'Carmichael', + 'Sheffield', + 'Langston', + 'Mccracken', + 'Yost', + 'Trotter', + 'Story', + 'Starks', + 'Lujan', + 'Blount', + 'Cody', + 'Rushing', + 'Benoit', + 'Herndon', + 'Jacobsen', + 'Nieto', + 'Wiseman', + 'Layton', + 'Epps', + 'Shipley', + 'Leyva', + 'Reeder', + 'Brand', + 'Roland', + 'Fitch', + 'Rico', + 'Napier', + 'Cronin', + 'Mcqueen', + 'Paredes', + 'Trent', + 'Christiansen', + 'Spangler', + 'Pettit', + 'Langford', + 'Benavides', + 'Penn', + 'Paige', + 'Weir', + 'Dietz', + 'Prater', + 'Brewster', + 'Louis', + 'Diehl', + 'Pack', + 'Spaulding', + 'Ernst', + 'Aviles', + 'Nowak', + 'Olvera', + 'Rock', + 'Mansfield', + 'Aquino', + 'Ogden', + 'Stacy', + 'Rizzo', + 'Sylvester', + 'Gillis', + 'Sands', + 'Machado', + 'Lovett', + 'Duong', + 'Hyatt', + 'Landis', + 'Platt', + 'Bustamante', + 'Hedrick', + 'Pritchett', + 'Gaston', + 'Dobson', + 'Caudill', + 'Tackett', + 'Bateman', + 'Landers', + 'Carmona', + 'Gipson', + 'Uribe', + 'Mcneill', + 'Ledford', + 'Mims', + 'Abel', + 'Gold', + 'Smallwood', + 'Thorne', + 'Mchugh', + 'Dickens', + 'Leung', + 'Tobin', + 'Kowalski', + 'Medeiros', + 'Cope', + 'Quezada', + 'Kraus', + 'Overton', + 'Montalvo', + 'Staley', + 'Woody', + 'Hathaway', + 'Osorio', + 'Laird', + 'Dobbs', + 'Capps', + 'Putnam', + 'Lay', + 'Francisco', + 'Bernstein', + 'Adair', + 'Hutton', + 'Burkett', + 'Rhoades', + 'Yanez', + 'Richey', + 'Bledsoe', + 'Mccain', + 'Beyer', + 'Cates', + 'Roche', + 'Spicer', + 'Queen', + 'Doty', + 'Darling', + 'Darby', + 'Sumner', + 'Kincaid', + 'Hay', + 'Grossman', + 'Lacey', + 'Wilkes', + 'Humphries', + 'Paz', + 'Darnell', + 'Keys', + 'Kyle', + 'Lackey', + 'Vogt', + 'Locklear', + 'Kiser', + 'Presley', + 'Bryson', + 'Bergman', + 'Peoples', + 'Fair', + 'Mcclendon', + 'Corley', + 'Prado', + 'Christie', + 'Delong', + 'Skaggs', + 'Dill', + 'Shearer', + 'Judd', + 'Stapleton', + 'Flaherty', + 'Casillas', + 'Pinto', + 'Youngblood', + 'Haywood', + 'Toney', + 'Ricks', + 'Granados', + 'Crum', + 'Triplett', + 'Soriano', + 'Waite', + 'Hoff', + 'Anaya', + 'Crenshaw', + 'Jung', + 'Canales', + 'Cagle', + 'Denny', + 'Marcus', + 'Berman', + 'Munson', + 'Ocampo', + 'Bauman', + 'Corcoran', + 'Keen', + 'Zimmer', + 'Friend', + 'Ornelas', + 'Varner', + 'Pelletier', + 'Vernon', + 'Blum', + 'Albrecht', + 'Culver', + 'Schuster', + 'Cuellar', + 'Mccord', + 'Shultz', + 'Mcrae', + 'Moreland', + 'Calvert', + 'William', + 'Whittington', + 'Eckert', + 'Keene', + 'Mohr', + 'Hanks', + 'Kimble', + 'Cavanaugh', + 'Crowell', + 'Russ', + 'Feliciano', + 'Crain', + 'Busch', + 'Mccormack', + 'Drummond', + 'Omalley', + 'Aldrich', + 'Luke', + 'Greco', + 'Mott', + 'Oakes', + 'Mallory', + 'Mclain', + 'Burrows', + 'Otero', + 'Allred', + 'Eason', + 'Finney', + 'Weller', + 'Waldron', + 'Champion', + 'Jeffers', + 'Coon', + 'Rosenthal', + 'Huddleston', + 'Solano', + 'Hirsch', + 'Akins', + 'Olivares', + 'Song', + 'Sneed', + 'Benedict', + 'Bain', + 'Okeefe', + 'Hidalgo', + 'Matos', + 'Stallings', + 'Paris', + 'Gamez', + 'Kenny', + 'Quigley', + 'Marrero', + 'Fagan', + 'Dutton', + 'Pappas', + 'Atwood', + 'Mcgovern', + 'Bagley', + 'Read', + 'Lunsford', + 'Moseley', + 'Oakley', + 'Ashby', + 'Granger', + 'Shaver', + 'Hope', + 'Coe', + 'Burroughs', + 'Helm', + 'Neumann', + 'Ambrose', + 'Michaels', + 'Prescott', + 'Light', + 'Dumas', + 'Flood', + 'Stringer', + 'Currie', + 'Comer', + 'Fong', + 'Whitlock', + 'Lemus', + 'Hawley', + 'Ulrich', + 'Staples', + 'Boykin', + 'Knutson', + 'Grover', + 'Hobson', + 'Cormier', + 'Doran', + 'Thayer', + 'Woodson', + 'Whitt', + 'Hooker', + 'Kohler', + 'Vandyke', + 'Addison', + 'Schrader', + 'Haskins', + 'Whittaker', + 'Madsen', + 'Gauthier', + 'Burnette', + 'Keating', + 'Purvis', + 'Aleman', + 'Huston', + 'Pimentel', + 'Hamlin', + 'Gerber', + 'Hooks', + 'Schwab', + 'Honeycutt', + 'Schulte', + 'Alonzo', + 'Isaac', + 'Conroy', + 'Adler', + 'Eastman', + 'Cottrell', + 'Orourke', + 'Hawk', + 'Goldsmith', + 'Rader', + 'Crandall', + 'Reynoso', + 'Shook', + 'Abernathy', + 'Baer', + 'Olivas', + 'Grayson', + 'Bartley', + 'Henning', + 'Parr', + 'Duff', + 'Brunson', + 'Baum', + 'Ennis', + 'Laughlin', + 'Foote', + 'Valadez', + 'Adamson', + 'Begay', + 'Stovall', + 'Lincoln', + 'Cheung', + 'Malloy', + 'Rider', + 'Giordano', + 'Jansen', + 'Lopes', + 'Arnett', + 'Pendleton', + 'Gage', + 'Barragan', + 'Keyes', + 'Navarrete', + 'Amador', + 'Hoffmann', + 'Schilling', + 'Hawthorne', + 'Perdue', + 'Schreiber', + 'Arevalo', + 'Naylor', + 'Deluca', + 'Marcum', + 'Altman', + 'Mark', + 'Chadwick', + 'Doan', + 'Easley', + 'Ladd', + 'Woodall', + 'Betancourt', + 'Shin', + 'Maguire', + 'Bellamy', + 'Quintanilla', + 'Ham', + 'Sorenson', + 'Mattson', + 'Brenner', + 'Means', + 'Faust', + 'Calloway', + 'Ojeda', + 'Mcnally', + 'Dietrich', + 'Ransom', + 'Hare', + 'Felton', + 'Whiting', + 'Burkhart', + 'Clinton', + 'Schwarz', + 'Cleary', + 'Wetzel', + 'Reagan', + 'Stjohn', + 'Chow', + 'Hauser', + 'Dupree', + 'Brannon', + 'Lyles', + 'Prather', + 'Willoughby', + 'Sepulveda', + 'Nugent', + 'Pickens', + 'Mosher', + 'Joiner', + 'Stoner', + 'Dowling', + 'Trimble', + 'Valdes', + 'Cheek', + 'Scruggs', + 'Coy', + 'Tilley', + 'Barney', + 'Saylor', + 'Nagy', + 'Horvath', + 'Lai', + 'Corey', + 'Ruth', + 'Sauer', + 'Baron', + 'Thao', + 'Rowell', + 'Grubbs', + 'Schaeffer', + 'Hillman', + 'Sams', + 'Hogue', + 'Hutson', + 'Busby', + 'Nickerson', + 'Bruner', + 'Parham', + 'Rendon', + 'Anders', + 'Lombardo', + 'Iverson', + 'Kinsey', + 'Earl', + 'Borden', + 'Titus', + 'Jean', + 'Tellez', + 'Beavers', + 'Cornett', + 'Sotelo', + 'Kellogg', + 'Silverman', + 'Burnham', + 'Mcnair', + 'Jernigan', + 'Escamilla', + 'Barrow', + 'Coats', + 'London', + 'Redding', + 'Ruffin', + 'Yi', + 'Boudreaux', + 'Goodson', + 'Dowell', + 'Fenton', + 'Mock', + 'Dozier', + 'Bynum', + 'Gale', + 'Jolly', + 'Beckman', + 'Goddard', + 'Craven', + 'Whitmore', + 'Leary', + 'Mccloud', + 'Gamboa', + 'Kerns', + 'Brunner', + 'Negron', + 'Hough', + 'Cutler', + 'Ledesma', + 'Pyle', + 'Monahan', + 'Tabor', + 'Burk', + 'Leone', + 'Stauffer', + 'Hayward', + 'Driver', + 'Ruff', + 'Talbot', + 'Seals', + 'Boston', + 'Carbajal', + 'Fay', + 'Purdy', + 'Mcgregor', + 'Sun', + 'Orellana', + 'Gentile', + 'Mahan', + 'Brower', + 'Patino', + 'Thurston', + 'Shipman', + 'Torrez', + 'Aaron', + 'Weiner', + 'Call', + 'Wilburn', + 'Oliva', + 'Hairston', + 'Coley', + 'Hummel', + 'Arreola', + 'Watt', + 'Sharma', + 'Lentz', + 'Arce', + 'Power', + 'Longoria', + 'Wagoner', + 'Burr', + 'Hsu', + 'Tinsley', + 'Beebe', + 'Wray', + 'Nunn', + 'Prieto', + 'German', + 'Rowley', + 'Grubb', + 'Brito', + 'Royal', + 'Valentin', + 'Bartholomew', + 'Schuler', + 'Aranda', + 'Flint', + 'Hearn', + 'Venegas', + 'Unger', + 'Mattingly', + 'Boles', + 'Casas', + 'Barger', + 'Julian', + 'Dow', + 'Dobbins', + 'Vann', + 'Chester', + 'Strange', + 'Lemon', + 'Kahn', + 'Mckinnon', + 'Gannon', + 'Waggoner', + 'Conn', + 'Meek', + 'Cavazos', + 'Skelton', + 'Lo', + 'Kumar', + 'Toledo', + 'Lorenz', + 'Vallejo', + 'Starkey', + 'Kitchen', + 'Reaves', + 'Demarco', + 'Farrar', + 'Stearns', + 'Michaud', + 'Higginbotham', + 'Fernandes', + 'Isaacs', + 'Marion', + 'Guillory', + 'Priest', + 'Meehan', + 'Oliveira', + 'Palma', + 'Oswald', + 'Loomis', + 'Galvez', + 'Lind', + 'Mena', + 'Stclair', + 'Hinds', + 'Reardon', + 'Alley', + 'Barth', + 'Crook', + 'Bliss', + 'Nagel', + 'Banuelos', + 'Parish', + 'Harman', + 'Douglass', + 'Kearns', + 'Newcomb', + 'Mulligan', + 'Coughlin', + 'Way', + 'Fournier', + 'Lawler', + 'Kaminski', + 'Barbour', + 'Sousa', + 'Stump', + 'Alaniz', + 'Ireland', + 'Rudd', + 'Carnes', + 'Lundy', + 'Godinez', + 'Pulido', + 'Dennison', + 'Burdick', + 'Baumann', + 'Dove', + 'Stoddard', + 'Liang', + 'Dent', + 'Roark', + 'Mcmahan', + 'Bowser', + 'Parnell', + 'Mayberry', + 'Wakefield', + 'Arndt', + 'Ogle', + 'Worthington', + 'Durbin', + 'Escalante', + 'Pederson', + 'Weldon', + 'Vick', + 'Knott', + 'Ryder', + 'Zarate', + 'Irving', + 'Clemens', + 'Shelley', + 'Salter', + 'Jack', + 'Cloud', + 'Dasilva', + 'Muhammad', + 'Squires', + 'Rapp', + 'Dawkins', + 'Polanco', + 'Chatman', + 'Maier', + 'Yazzie', + 'Gruber', + 'Staton', + 'Blackman', + 'Mcdonnell', + 'Dykes', + 'Laws', + 'Whitten', + 'Pfeiffer', + 'Vidal', + 'Early', + 'Kelsey', + 'Baughman', + 'Dias', + 'Starnes', + 'Crespo', + 'Lombardi', + 'Kilpatrick', + 'Deaton', + 'Satterfield', + 'Wiles', + 'Weinstein', + 'Rowan', + 'Delossantos', + 'Hamby', + 'Estep', + 'Daigle', + 'Elam', + 'Creech', + 'Heck', + 'Chavis', + 'Echols', + 'Foss', + 'Trahan', + 'Strauss', + 'Vanhorn', + 'Winslow', + 'Rea', + 'Heaton', + 'Fairchild', + 'Minton', + 'Hitchcock', + 'Linton', + 'Handy', + 'Crouse', + 'Coles', + 'Upton', + 'Foy', + 'Herrington', + 'Mcclelland', + 'Hwang', + 'Rector', + 'Luther', + 'Kruger', + 'Salcedo', + 'Chance', + 'Gunderson', + 'Tharp', + 'Griffiths', + 'Graf', + 'Branham', + 'Humphreys', + 'Renner', + 'Lima', + 'Rooney', + 'Moya', + 'Almeida', + 'Gavin', + 'Coburn', + 'Ouellette', + 'Goetz', + 'Seay', + 'Parrott', + 'Harms', + 'Robb', + 'Storey', + 'Barbosa', + 'Barraza', + 'Loyd', + 'Merchant', + 'Donohue', + 'Carrier', + 'Diggs', + 'Chastain', + 'Sherrill', + 'Whipple', + 'Braswell', + 'Weathers', + 'Linder', + 'Chapa', + 'Bock', + 'Oh', + 'Lovelace', + 'Saavedra', + 'Ferrara', + 'Callaway', + 'Salmon', + 'Templeton', + 'Christy', + 'Harp', + 'Dowd', + 'Forrester', + 'Lawton', + 'Epstein', + 'Gant', + 'Tierney', + 'Seaman', + 'Corral', + 'Dowdy', + 'Zaragoza', + 'Morrissey', + 'Eller', + 'Chau', + 'Breen', + 'High', + 'Newberry', + 'Beam', + 'Yancey', + 'Jarrell', + 'Cerda', + 'Ellsworth', + 'Lofton', + 'Thibodeaux', + 'Pool', + 'Rinehart', + 'Arteaga', + 'Marlow', + 'Hacker', + 'Will', + 'Mackenzie', + 'Hook', + 'Gilliland', + 'Emmons', + 'Pickering', + 'Medley', + 'Willey', + 'Andrew', + 'Shell', + 'Randle', + 'Brinkley', + 'Pruett', + 'Tobias', + 'Edmondson', + 'Grier', + 'Saldivar', + 'Batista', + 'Askew', + 'Moeller', + 'Chavarria', + 'Augustine', + 'Troyer', + 'Layne', + 'Mcnulty', + 'Shank', + 'Desai', + 'Herrmann', + 'Hemphill', + 'Bearden', + 'Spear', + 'Keener', + 'Holguin', + 'Culp', + 'Braden', + 'Briscoe', + 'Bales', + 'Garvin', + 'Stockton', + 'Abreu', + 'Suggs', + 'Mccartney', + 'Ferrer', + 'Rhoads', + 'Ha', + 'Nevarez', + 'Singletary', + 'Chong', + 'Alcala', + 'Cheney', + 'Westfall', + 'Damico', + 'Snodgrass', + 'Devries', + 'Looney', + 'Hein', + 'Lyle', + 'Lockett', + 'Jacques', + 'Barkley', + 'Wahl', + 'Aponte', + 'Myrick', + 'Bolin', + 'Holm', + 'Slack', + 'Scherer', + 'Martino', + 'Bachman', + 'Ely', + 'Nesbitt', + 'Marroquin', + 'Bouchard', + 'Mast', + 'Jameson', + 'Hills', + 'Mireles', + 'Bueno', + 'Pease', + 'Vitale', + 'Alarcon', + 'Linares', + 'Schell', + 'Lipscomb', + 'Arriaga', + 'Bourgeois', + 'Markham', + 'Bonds', + 'Wisniewski', + 'Ivy', + 'Oldham', + 'Wendt', + 'Fallon', + 'Joy', + 'Stamper', + 'Babb', + 'Steinberg', + 'Asher', + 'Fuchs', + 'Blank', + 'Willett', + 'Heredia', + 'Croft', + 'Lytle', + 'Lance', + 'Lassiter', + 'Barrientos', + 'Condon', + 'Barfield', + 'Darden', + 'Araujo', + 'Noonan', + 'Guinn', + 'Burleson', + 'Belanger', + 'Main', + 'Traylor', + 'Messina', + 'Zeigler', + 'Danielson', + 'Millard', + 'Kenyon', + 'Radford', + 'Graff', + 'Beaty', + 'Baggett', + 'Salisbury', + 'Crisp', + 'Trout', + 'Lorenzo', + 'Parson', + 'Gann', + 'Garber', + 'Adcock', + 'Covarrubias', + 'Scales', + 'Acuna', + 'Thrasher', + 'Card', + 'Van', + 'Mabry', + 'Mohamed', + 'Montanez', + 'Stock', + 'Redd', + 'Willingham', + 'Redman', + 'Zambrano', + 'Gaffney', + 'Herr', + 'Schubert', + 'Devlin', + 'Pringle', + 'Houck', + 'Casper', + 'Rees', + 'Wing', + 'Ebert', + 'Jeter', + 'Cornejo', + 'Gillette', + 'Shockley', + 'Amato', + 'Girard', + 'Leggett', + 'Cheatham', + 'Bustos', + 'Epperson', + 'Dubose', + 'Seitz', + 'Frias', + 'East', + 'Schofield', + 'Steen', + 'Orlando', + 'Myles', + 'Caron', + 'Grey', + 'Denney', + 'Ontiveros', + 'Burden', + 'Jaeger', + 'Reich', + 'Witherspoon', + 'Najera', + 'Frantz', + 'Hammonds', + 'Xu', + 'Leavitt', + 'Gilchrist', + 'Adam', + 'Barone', + 'Forman', + 'Ceja', + 'Ragsdale', + 'Sisk', + 'Tubbs', + 'Elizondo', + 'Pressley', + 'Bollinger', + 'Linn', + 'Huntley', + 'Dewey', + 'Geary', + 'Carlos', + 'Ragland', + 'Mixon', + 'Mcarthur', + 'Baugh', + 'Tam', + 'Nobles', + 'Clevenger', + 'Lusk', + 'Foust', + 'Cooney', + 'Tamayo', + 'Robert', + 'Longo', + 'Overstreet', + 'Oglesby', + 'Mace', + 'Churchill', + 'Matson', + 'Hamrick', + 'Rockwell', + 'Trammell', + 'Wheatley', + 'Carrington', + 'Ferraro', + 'Ralston', + 'Clancy', + 'Mondragon', + 'Carl', + 'Hu', + 'Hopson', + 'Breaux', + 'Mccurdy', + 'Mares', + 'Mai', + 'Chisholm', + 'Matlock', + 'Aiken', + 'Cary', + 'Lemons', + 'Anguiano', + 'Herrick', + 'Crawley', + 'Montero', + 'Hassan', + 'Archuleta', + 'Farias', + 'Cotter', + 'Parris', + 'Felder', + 'Luu', + 'Pence', + 'Gilman', + 'Killian', + 'Naranjo', + 'Duggan', + 'Scarborough', + 'Swann', + 'Easter', + 'Ricketts', + 'France', + 'Bello', + 'Nadeau', + 'Still', + 'Rincon', + 'Cornwell', + 'Slade', + 'Fierro', + 'Mize', + 'Christianson', + 'Greenfield', + 'Mcafee', + 'Landrum', + 'Adame', + 'Dinh', + 'Lankford', + 'Lewandowski', + 'Rust', + 'Bundy', + 'Waterman', + 'Milner', + 'Mccrary', + 'Hite', + 'Curley', + 'Donald', + 'Duckworth', + 'Cecil', + 'Carrera', + 'Speer', + 'Birch', + 'Denson', + 'Beckwith', + 'Stack', + 'Durant', + 'Lantz', + 'Dorman', + 'Christman', + 'Spann', + 'Masterson', + 'Hostetler', + 'Kolb', + 'Brink', + 'Scanlon', + 'Nye', + 'Wylie', + 'Beverly', + 'Woo', + 'Spurlock', + 'Sommer', + 'Shelby', + 'Reinhardt', + 'Robledo', + 'Bertrand', + 'Ashton', + 'Cyr', + 'Edgar', + 'Doe', + 'Harkins', + 'Brubaker', + 'Stoll', + 'Dangelo', + 'Zhou', + 'Moulton', + 'Hannon', + 'Falk', + 'Rains', + 'Broughton', + 'Applegate', + 'Hudgins', + 'Slone', + 'Yoon', + 'Farnsworth', + 'Perales', + 'Reedy', + 'Milam', + 'Franz', + 'Ponder', + 'Ricci', + 'Fontaine', + 'Irizarry', + 'Puente', + 'New', + 'Selby', + 'Cazares', + 'Doughty', + 'Moffett', + 'Balderas', + 'Fine', + 'Smalley', + 'Carlin', + 'Trinh', + 'Dyson', + 'Galvin', + 'Valdivia', + 'Benner', + 'Low', + 'Turpin', + 'Lyman', + 'Billingsley', + 'Mcadams', + 'Cardwell', + 'Fraley', + 'Patten', + 'Holton', + 'Shanks', + 'Mcalister', + 'Canfield', + 'Sample', + 'Harley', + 'Cason', + 'Tomlin', + 'Ahmad', + 'Coyne', + 'Forte', + 'Riggins', + 'Littlejohn', + 'Forsythe', + 'Brinson', + 'Halverson', + 'Bach', + 'Stuckey', + 'Falcon', + 'Wenzel', + 'Talbert', + 'Champagne', + 'Mchenry', + 'Vest', + 'Shackelford', + 'Ordonez', + 'Collazo', + 'Boland', + 'Sisson', + 'Bigelow', + 'Wharton', + 'Hyman', + 'Brumfield', + 'Oates', + 'Mesa', + 'Morrell', + 'Beckett', + 'Reis', + 'Alves', + 'Chiu', + 'Larue', + 'Streeter', + 'Grogan', + 'Blakely', + 'Brothers', + 'Hatton', + 'Kimbrough', + 'Lauer', + 'Wallis', + 'Jett', + 'Pepper', + 'Hildebrand', + 'Rawls', + 'Mello', + 'Neville', + 'Bull', + 'Steffen', + 'Braxton', + 'Cowart', + 'Simpkins', + 'Mcneely', + 'Blalock', + 'Spain', + 'Shipp', + 'Lindquist', + 'Oreilly', + 'Butterfield', + 'Perrin', + 'Qualls', + 'Edge', + 'Havens', + 'Luong', + 'Switzer', + 'Troutman', + 'Fortner', + 'Tolliver', + 'Monk', + 'Poindexter', + 'Rupp', + 'Ferry', + 'Negrete', + 'Muse', + 'Gresham', + 'Beauchamp', + 'Schmid', + 'Barclay', + 'Chun', + 'Brice', + 'Faulk', + 'Watters', + 'Briones', + 'Guajardo', + 'Harwood', + 'Grissom', + 'Harlow', + 'Whelan', + 'Burdette', + 'Palumbo', + 'Paulsen', + 'Corrigan', + 'Garvey', + 'Levesque', + 'Dockery', + 'Delgadillo', + 'Gooch', + 'Cao', + 'Mullin', + 'Ridley', + 'Stanfield', + 'Noriega', + 'Dial', + 'Ceballos', + 'Nunes', + 'Newby', + 'Baumgartner', + 'Hussain', + 'Wyman', + 'Causey', + 'Gossett', + 'Ness', + 'Waugh', + 'Choate', + 'Carman', + 'Daily', + 'Kong', + 'Devore', + 'Irby', + 'Breeden', + 'Whatley', + 'Ellington', + 'Lamar', + 'Fultz', + 'Bair', + 'Zielinski', + 'Colby', + 'Houghton', + 'Grigsby', + 'Fortune', + 'Paxton', + 'Mcmillian', + 'Hammons', + 'Bronson', + 'Keck', + 'Wellman', + 'Ayres', + 'Whiteside', + 'Menard', + 'Roush', + 'Warden', + 'Espino', + 'Strand', + 'Haggerty', + 'Banda', + 'Krebs', + 'Fabian', + 'Bowie', + 'Branson', + 'Lenz', + 'Benavidez', + 'Keeler', + 'Newsom', + 'Ezell', + 'Jeffrey', + 'Pulliam', + 'Clary', + 'Byrnes', + 'Kopp', + 'Beers', + 'Smalls', + 'Sommers', + 'Gardiner', + 'Fennell', + 'Mancini', + 'Osullivan', + 'Sebastian', + 'Bruns', + 'Giron', + 'Parent', + 'Boyles', + 'Keefe', + 'Muir', + 'Wheat', + 'Vergara', + 'Shuler', + 'Pemberton', + 'South', + 'Brownlee', + 'Brockman', + 'Royer', + 'Fanning', + 'Herzog', + 'Morley', + 'Bethea', + 'Tong', + 'Needham', + 'Roque', + 'Mojica', + 'Bunn', + 'Francois', + 'Noe', + 'Kuntz', + 'Snowden', + 'Withers', + 'Harlan', + 'Seibert', + 'Limon', + 'Kiefer', + 'Bone', + 'Sell', + 'Allan', + 'Skidmore', + 'Wren', + 'Dunaway', + 'Finnegan', + 'Moe', + 'Wolford', + 'Seeley', + 'Kroll', + 'Lively', + 'Janssen', + 'Montague', + 'Rahman', + 'Boehm', + 'Nettles', + 'Dees', + 'Krieger', + 'Peek', + 'Hershberger', + 'Sage', + 'Custer', + 'Zheng', + 'Otoole', + 'Jaimes', + 'Elrod', + 'Somers', + 'Lira', + 'Nagle', + 'Grooms', + 'Soria', + 'Drury', + 'Keane', + 'Bostic', + 'Hartmann', + 'Pauley', + 'Murrell', + 'Manzo', + 'Morey', + 'Agee', + 'Hamel', + 'Tavares', + 'Dunning', + 'Mccloskey', + 'Plunkett', + 'Maples', + 'March', + 'Armenta', + 'Waldrop', + 'Espinal', + 'Fajardo', + 'Christenson', + 'Robins', + 'Bagwell', + 'Massie', + 'Leahy', + 'Urbina', + 'Medlin', + 'Zhu', + 'Pantoja', + 'Barbee', + 'Clawson', + 'Reiter', + 'Ko', + 'Crider', + 'Maxey', + 'Worrell', + 'Brackett', + 'Mclemore', + 'Younger', + 'Her', + 'Hardesty', + 'Danner', + 'Ragan', + 'Almanza', + 'Nielson', + 'Graber', + 'Mcintire', + 'Tirado', + 'Griswold', + 'Seifert', + 'Valles', + 'Laney', + 'Gupta', + 'Malik', + 'Libby', + 'Marvin', + 'Koontz', + 'Marr', + 'Kozlowski', + 'Lemke', + 'Brant', + 'Phelan', + 'Kemper', + 'Gooden', + 'Beaulieu', + 'Cardoza', + 'Healey', + 'Zhao', + 'Hardwick', + 'Kitchens', + 'Box', + 'Stepp', + 'Comstock', + 'Poston', + 'Sager', + 'Conti', + 'Borges', + 'Farrow', + 'Acker', + 'Glaser', + 'Antonio', + 'Lennon', + 'Gaither', + 'Freitas', + 'Alicea', + 'Mcmillen', + 'Chapin', + 'Ratcliff', + 'Lerma', + 'Severson', + 'Wilde', + 'Mortensen', + 'Winchester', + 'Flannery', + 'Villasenor', + 'Centeno', + 'Burkholder', + 'Horan', + 'Meador', + 'Ingle', + 'Roldan', + 'Estrella', + 'Pullen', + 'Newkirk', + 'Gaytan', + 'Lindberg', + 'Windham', + 'Gatlin', + 'Stoltzfus', + 'Behrens', + 'Cintron', + 'Broderick', + 'Solorzano', + 'Jaime', + 'Venable', + 'Culbertson', + 'Garay', + 'Caputo', + 'Grantham', + 'Hanlon', + 'Parry', + 'Crist', + 'Cosby', + 'Shore', + 'Everhart', + 'Dorn', + 'Turley', + 'Eng', + 'Valerio', + 'Rand', + 'Hiatt', + 'Mota', + 'Judge', + 'Kinder', + 'Colwell', + 'Ashworth', + 'Tejeda', + 'Sikes', + 'Oshea', + 'Westmoreland', + 'Faber', + 'Culpepper', + 'Logsdon', + 'Fugate', + 'Apodaca', + 'Lindley', + 'Samson', + 'Liles', + 'Mcclanahan', + 'Burge', + 'Vail', + 'Etheridge', + 'Boudreau', + 'Andres', + 'Noll', + 'Higgs', + 'Snead', + 'Layman', + 'Turk', + 'Nolen', + 'Wayne', + 'Betz', + 'Victor', + 'Lafferty', + 'Carbone', + 'Skipper', + 'Zeller', + 'Kasper', + 'Desantis', + 'Fogle', + 'Gandy', + 'Mendenhall', + 'Seward', + 'Schweitzer', + 'Gulley', + 'Stine', + 'Sowers', + 'Duenas', + 'Monson', + 'Brinkman', + 'Hubert', + 'Motley', + 'Pfeifer', + 'Weinberg', + 'Eggleston', + 'Isom', + 'Quinlan', + 'Gilley', + 'Jasso', + 'Loya', + 'Mull', + 'Reichert', + 'Wirth', + 'Reddy', + 'Hodgson', + 'Stowe', + 'Mccallum', + 'Ahrens', + 'Huey', + 'Mattox', + 'Dupont', + 'Aguayo', + 'Pak', + 'Tice', + 'Alba', + 'Colburn', + 'Currier', + 'Gaskins', + 'Harder', + 'Cohn', + 'Yoo', + 'Garnett', + 'Harter', + 'Wenger', + 'Charlton', + 'Littleton', + 'Minter', + 'Henriquez', + 'Cone', + 'Vines', + 'Kimmel', + 'Crooks', + 'Caraballo', + 'Searcy', + 'Peyton', + 'Renfro', + 'Groff', + 'Thorn', + 'Moua', + 'Jay', + 'Leigh', + 'Sanborn', + 'Wicker', + 'Martens', + 'Broome', + 'Abney', + 'Fisk', + 'Argueta', + 'Upchurch', + 'Alderman', + 'Tisdale', + 'Castellano', + 'Legg', + 'Wilbur', + 'Bills', + 'Dix', + 'Mauldin', + 'Isbell', + 'Mears', + 'Latimer', + 'Ashcraft', + 'Earley', + 'Tejada', + 'Partridge', + 'Anglin', + 'Caswell', + 'Easton', + 'Kirchner', + 'Mehta', + 'Lanham', + 'Blaylock', + 'Binder', + 'Catalano', + 'Handley', + 'Storm', + 'Albertson', + 'Free', + 'Tuck', + 'Keegan', + 'Moriarty', + 'Dexter', + 'Mancuso', + 'Allard', + 'Pino', + 'Chamberlin', + 'Moffitt', + 'Haag', + 'Schott', + 'Agnew', + 'Malcolm', + 'Hallman', + 'Heckman', + 'Karr', + 'Soares', + 'Alfonso', + 'Tom', + 'Wadsworth', + 'Schindler', + 'Garibay', + 'Kuykendall', + 'Penny', + 'Littlefield', + 'Mcnabb', + 'Sam', + 'Lea', + 'Berrios', + 'Murry', + 'Regalado', + 'Dehart', + 'Mohammed', + 'Counts', + 'Solorio', + 'Preciado', + 'Armendariz', + 'Martell', + 'Barksdale', + 'Frick', + 'Haller', + 'Broyles', + 'Doll', + 'Cable', + 'Delvalle', + 'Weems', + 'Kelleher', + 'Gagne', + 'Albers', + 'Kunz', + 'Hoy', + 'Hawes', + 'Guenther', + 'Johansen', + 'Chaffin', + 'Whitworth', + 'Wynne', + 'Mcmurray', + 'Luce', + 'Fiore', + 'Straub', + 'Majors', + 'Mcduffie', + 'Bohannon', + 'Rawlings', + 'Freed', + 'Sutter', + 'Lindstrom', + 'Buss', + 'Loera', + 'Hoyle', + 'Witte', + 'Tyree', + 'Luttrell', + 'Andrus', + 'Steed', + 'Thiel', + 'Cranford', + 'Fulmer', + 'Gable', + 'Porras', + 'Weis', + 'Maas', + 'Packard', + 'Noyes', + 'Kwon', + 'Knoll', + 'Marx', + 'Feeney', + 'Israel', + 'Bohn', + 'Cockrell', + 'Glick', + 'Cosgrove', + 'Keefer', + 'Mundy', + 'Batchelor', + 'Loveless', + 'Horowitz', + 'Haskell', + 'Kunkel', + 'Colson', + 'Hedges', + 'Staggs', + 'Swisher', + 'Lomeli', + 'Padron', + 'Cota', + 'Homan', + 'Musser', + 'Curtin', + 'Salerno', + 'Segovia', + 'Keeton', + 'Brandenburg', + 'Starling', + 'Tsai', + 'Mahon', + 'Klinger', + 'Paquette', + 'Haddad', + 'Mccune', + 'Mathew', + 'Shull', + 'Higdon', + 'Guest', + 'Shay', + 'Swafford', + 'Angulo', + 'Hackney', + 'Evers', + 'Sibley', + 'Woodworth', + 'Ostrander', + 'Mangum', + 'Smyth', + 'Quarles', + 'Mccarter', + 'Close', + 'Truitt', + 'Stpierre', + 'Mackay', + 'Bayer', + 'Timm', + 'Thatcher', + 'Bess', + 'Trinidad', + 'Jacoby', + 'Proffitt', + 'Concepcion', + 'Parkinson', + 'Carreon', + 'Ramon', + 'Monroy', + 'Leger', + 'Jauregui', + 'Glynn', + 'Taggart', + 'Neil', + 'Reddick', + 'Wiese', + 'Dover', + 'Wicks', + 'Hennessy', + 'Bittner', + 'Mcclung', + 'Mcwhorter', + 'Derrick', + 'Strom', + 'Beckham', + 'Kee', + 'Coombs', + 'Schrock', + 'Holtz', + 'Maki', + 'Willson', + 'Hulsey', + 'Whitson', + 'Haugen', + 'Lumpkin', + 'Scholl', + 'Gall', + 'Carvalho', + 'Kovach', + 'Vieira', + 'Millan', + 'Irvine', + 'Held', + 'Jolley', + 'Jasper', + 'Cadena', + 'Runyon', + 'Lomax', + 'Fahey', + 'Hoppe', + 'Bivens', + 'Ruggiero', + 'Hussey', + 'Ainsworth', + 'Hardman', + 'Ulloa', + 'Dugger', + 'Fitzsimmons', + 'Scroggins', + 'Sowell', + 'Toler', + 'Barba', + 'Biddle', + 'Rafferty', + 'Trapp', + 'Byler', + 'Brill', + 'Delagarza', + 'Thigpen', + 'Hiller', + 'Martins', + 'Jankowski', + 'Findley', + 'Hollins', + 'Stull', + 'Pollack', + 'Poirier', + 'Reno', + 'Bratton', + 'Jeffery', + 'Menendez', + 'Mcnutt', + 'Kohl', + 'Forster', + 'Clough', + 'Deloach', + 'Bader', + 'Hanes', + 'Sturm', + 'Tafoya', + 'Beall', + 'Coble', + 'Demers', + 'Kohn', + 'Santamaria', + 'Vaught', + 'Correia', + 'Mcgrew', + 'Sarmiento', + 'Roby', + 'Reinhart', + 'Rosenbaum', + 'Bernier', + 'Schiller', + 'Furman', + 'Grabowski', + 'Perryman', + 'Kidwell', + 'Sabo', + 'Saxton', + 'Noland', + 'Seaton', + 'Packer', + 'Seal', + 'Ruby', + 'Smoot', + 'Lavoie', + 'Putman', + 'Fairbanks', + 'Neill', + 'Florence', + 'Beattie', + 'Tarver', + 'Stephen', + 'Bolen', + 'Mccombs', + 'Freedman', + 'Barnhill', + 'Gaddis', + 'Goad', + 'Worden', + 'Canada', + 'Vickery', + 'Calvin', + 'Mcclintock', + 'Slocum', + 'Clausen', + 'Mccutcheon', + 'Ripley', + 'Razo', + 'Southard', + 'Bourne', + 'Aiello', + 'Knudsen', + 'Angeles', + 'Keeney', + 'Stacey', + 'Neeley', + 'Holly', + 'Gallant', + 'Eads', + 'Lafleur', + 'Fredrickson', + 'Popp', + 'Bobo', + 'Pardo', + 'Artis', + 'Lawless', + 'Shen', + 'Headley', + 'Pedraza', + 'Pickard', + 'Salvador', + 'Hofmann', + 'Davey', + 'Szymanski', + 'Dallas', + 'Erb', + 'Perea', + 'Alcantar', + 'Ashford', + 'Harry', + 'Crutchfield', + 'Goebel', + 'Ridgeway', + 'Mcvey', + 'Cordell', + 'Kovacs', + 'Florez', + 'Calkins', + 'Redden', + 'Ricker', + 'Salcido', + 'Farrington', + 'Reimer', + 'Mullis', + 'Mayhew', + 'Register', + 'Kaye', + 'Blocker', + 'Buford', + 'Munguia', + 'Cady', + 'Burley', + 'Sander', + 'Robinette', + 'Stubblefield', + 'Shuman', + 'Santillan', + 'Loy', + 'Deutsch', + 'Sales', + 'Langdon', + 'Mazur', + 'Clapp', + 'Teal', + 'Buffington', + 'Elliot', + 'Halstead', + 'Sturgeon', + 'Colley', + 'Koehn', + 'Bergstrom', + 'Dunne', + 'Pond', + 'Gantt', + 'Cousins', + 'Viera', + 'Wilks', + 'Haase', + 'Sweat', + 'Simonson', + 'Breedlove', + 'Munn', + 'Pitt', + 'Faircloth', + 'Peter', + 'Wheaton', + 'Howland', + 'Merriman', + 'Fusco', + 'Burney', + 'Bedford', + 'Baltazar', + 'Persaud', + 'Gerard', + 'Bourque', + 'Chao', + 'Slagle', + 'Kirsch', + 'Volk', + 'Heim', + 'Glasgow', + 'Borders', + 'Rauch', + 'Goforth', + 'Batson', + 'Basham', + 'Mount', + 'Peace', + 'Lazo', + 'Samples', + 'Amaro', + 'Slattery', + 'Ibrahim', + 'Weatherford', + 'Taft', + 'Santoro', + 'Aparicio', + 'Jiang', + 'Ritchey', + 'Goble', + 'Spring', + 'Strain', + 'Scully', + 'Villareal', + 'Toro', + 'Duval', + 'Jonas', + 'Neuman', + 'Wozniak', + 'Varney', + 'Dell', + 'Conover', + 'Landon', + 'Sigler', + 'Galbraith', + 'Boss', + 'Cepeda', + 'Back', + 'Mateo', + 'Peebles', + 'Arsenault', + 'Cathey', + 'Calabrese', + 'Dodds', + 'Gilbertson', + 'Hoke', + 'Greenlee', + 'Sauceda', + 'Vue', + 'Lehmann', + 'Zink', + 'Lapointe', + 'Laster', + 'Moy', + 'Ammons', + 'Llamas', + 'Foltz', + 'Fleck', + 'Chew', + 'Amaral', + 'Geer', + 'Su', + 'Carden', + 'Nunley', + 'Creel', + 'Clarkson', + 'Provost', + 'Covey', + 'Paine', + 'Wofford', + 'Frame', + 'Dube', + 'Grice', + 'Tully', + 'Molnar', + 'Luciano', + 'Bartels', + 'Winstead', + 'Canady', + 'Moreau', + 'Burnside', + 'Bratcher', + 'Infante', + 'Peterman', + 'Swope', + 'Freeland', + 'Vetter', + 'Lanning', + 'Marquis', + 'Schulze', + 'Thai', + 'Coppola', + 'Rayburn', + 'Conte', + 'Martz', + 'Showalter', + 'Quinonez', + 'Bandy', + 'Rao', + 'Bunting', + 'Belt', + 'Cruse', + 'Hamblin', + 'Himes', + 'Raney', + 'Merrell', + 'See', + 'Gough', + 'Maciel', + 'Wimberly', + 'Craddock', + 'Marquardt', + 'Wentz', + 'Meeker', + 'Sandberg', + 'Mosier', + 'Wasson', + 'Hundley', + 'Joe', + 'Shumaker', + 'Fortin', + 'Embry', + 'Olivarez', + 'Akin', + 'Seidel', + 'Coons', + 'Corrales', + 'Earle', + 'Matheny', + 'Kish', + 'Outlaw', + 'Lieberman', + 'Spalding', + 'Barnette', + 'Martel', + 'Hargis', + 'Kelso', + 'Merrick', + 'Fullerton', + 'Fries', + 'Doucette', + 'Clouse', + 'Prewitt', + 'Hawks', + 'Keaton', + 'Worthy', + 'Zook', + 'Montez', + 'Poore', + 'Autry', + 'Lemay', + 'Shifflett', + 'Forsyth', + 'Briseno', + 'Piazza', + 'Welker', + 'Tennant', + 'Heinz', + 'Haggard', + 'Leighton', + 'Brittain', + 'Begley', + 'Flanders', + 'Hermann', + 'Botello', + 'Mathias', + 'Hofer', + 'Hutto', + 'Godoy', + 'Cave', + 'Pagano', + 'Asbury', + 'Bowens', + 'Withrow', + 'Olivo', + 'Harbin', + 'Andre', + 'Sandlin', + 'Wertz', + 'Desimone', + 'Greiner', + 'Heinrich', + 'Whitcomb', + 'Dayton', + 'Petrie', + 'Hair', + 'Ketchum', + 'Shanahan', + 'Bianco', + 'Heil', + 'Cochrane', + 'Wegner', + 'Dagostino', + 'Couture', + 'Ling', + 'Wingate', + 'Arenas', + 'Keel', + 'Casteel', + 'Boothe', + 'Derosa', + 'Horst', + 'Rau', + 'Palermo', + 'Mccorkle', + 'Altamirano', + 'Nall', + 'Shumate', + 'Lightfoot', + 'Creamer', + 'Romeo', + 'Coffin', + 'Hutchings', + 'Jerome', + 'Hutcheson', + 'Damron', + 'Sorrell', + 'Nickel', + 'Sells', + 'Pinkerton', + 'Dao', + 'Dion', + 'Mcfarlane', + 'Ridenour', + 'Atwell', + 'Sturgill', + 'Schoen', + 'Partin', + 'Nemeth', + 'Almonte', + 'Pan', + 'Rickard', + 'Wentworth', + 'Sammons', + 'Sayre', + 'Southerland', + 'Parisi', + 'Ahn', + 'Carrion', + 'Testa', + 'Shorter', + 'Covert', + 'Gorham', + 'Alcantara', + 'Belton', + 'Bannister', + 'Sharkey', + 'Mccreary', + 'Pannell', + 'Scarbrough', + 'Keeling', + 'Gainey', + 'Mill', + 'Camarena', + 'Herbst', + 'Roller', + 'Wild', + 'Dellinger', + 'Lovejoy', + 'Manson', + 'Dupuis', + 'Clem', + 'Resendez', + 'Burkhardt', + 'Williford', + 'Mclendon', + 'Mazza', + 'Mccaffrey', + 'Lum', + 'Settle', + 'Hefner', + 'Dupre', + 'Louie', + 'Gunther', + 'Weimer', + 'Turnbull', + 'Bradbury', + 'Maness', + 'Urena', + 'Lor', + 'Sides', + 'Wick', + 'Monaco', + 'Gillen', + 'Ives', + 'Battaglia', + 'Ulmer', + 'Schreiner', + 'Caceres', + 'Sprouse', + 'Scoggins', + 'Ahern', + 'Tracey', + 'Terrazas', + 'Bracken', + 'Gurley', + 'Soliz', + 'Alcaraz', + 'Martines', + 'Weidner', + 'Criswell', + 'Wilbanks', + 'Hennessey', + 'Mendes', + 'Peak', + 'Ruelas', + 'Caudle', + 'Fuqua', + 'Jewett', + 'Chism', + 'Volpe', + 'Nino', + 'Logue', + 'Mcculloch', + 'Furr', + 'Kersey', + 'Shinn', + 'Yan', + 'Rausch', + 'Stinnett', + 'Mowery', + 'Rivero', + 'Weed', + 'Bertram', + 'Durand', + 'Gatewood', + 'Tilton', + 'Mahaffey', + 'Niles', + 'Mccue', + 'Vargo', + 'Holcombe', + 'Ralph', + 'Castleberry', + 'Snipes', + 'Wilt', + 'Vanmeter', + 'Nutter', + 'Mendiola', + 'Burchett', + 'Enos', + 'Jobe', + 'Kirkwood', + 'Pedroza', + 'Iglesias', + 'Leong', + 'Cromer', + 'Trice', + 'Magnuson', + 'Eagle', + 'Montenegro', + 'Troy', + 'Cato', + 'Edmond', + 'Hendrick', + 'Lebron', + 'Lathrop', + 'Budd', + 'Appel', + 'Knowlton', + 'Bianchi', + 'Camarillo', + 'Ginn', + 'Pulley', + 'True', + 'Gaddy', + 'Domingo', + 'Kingsley', + 'Loftus', + 'Denham', + 'Sifuentes', + 'Siler', + 'Hardison', + 'Kwan', + 'Pendergrass', + 'Frasier', + 'Hutchens', + 'Fort', + 'Montiel', + 'Fincher', + 'Eggers', + 'Moen', + 'Griffis', + 'Hauck', + 'Lister', + 'Lundberg', + 'Tanaka', + 'Cornish', + 'Whitlow', + 'Chou', + 'Griego', + 'Robson', + 'Prosser', + 'Ballinger', + 'Fogarty', + 'Allman', + 'Atchison', + 'Conaway', + 'Riddick', + 'Rupert', + 'Krug', + 'Pinkston', + 'Coggins', + 'Narvaez', + 'Earnest', + 'Fain', + 'Rash', + 'Olmstead', + 'Sherrod', + 'Beeler', + 'Spearman', + 'Poland', + 'Rousseau', + 'Hyland', + 'Rhea', + 'Son', + 'Redmon', + 'Wilke', + 'Valenti', + 'Paulino', + 'Geyer', + 'Blackwood', + 'Leclair', + 'Olguin', + 'Maestas', + 'Buckingham', + 'Blythe', + 'Samuelson', + 'Bounds', + 'Nakamura', + 'Batts', + 'Galarza', + 'Sisco', + 'Mcvay', + 'Hynes', + 'Mertz', + 'Tremblay', + 'Orosco', + 'Prentice', + 'Wilhite', + 'Seiler', + 'Archibald', + 'Wooldridge', + 'Winfield', + 'Oden', + 'Zelaya', + 'Chestnut', + 'Guardado', + 'Mccallister', + 'Canty', + 'Grasso', + 'Collett', + 'Hylton', + 'Easterling', + 'Deangelis', + 'Treadway', + 'Ferrari', + 'Ethridge', + 'Milburn', + 'Mercier', + 'Bickford', + 'Thibodeau', + 'Bolanos', + 'Fellows', + 'Hales', + 'Greathouse', + 'Buchholz', + 'Strunk', + 'Faison', + 'Purnell', + 'Clegg', + 'Steinmetz', + 'Wojcik', + 'Alcorn', + 'Ballesteros', + 'Basile', + 'Paez', + 'Armour', + 'Devito', + 'Tello', + 'Flick', + 'Yount', + 'Estevez', + 'Hitt', + 'Houle', + 'Cha', + 'Travers', + 'Cass', + 'Loper', + 'Getz', + 'Cade', + 'Gonsalves', + 'Lear', + 'Cromwell', + 'Stephan', + 'Ocasio', + 'Deluna', + 'Tolentino', + 'Picard', + 'Eaves', + 'Toscano', + 'Ault', + 'Osburn', + 'Ruvalcaba', + 'Szabo', + 'Kozak', + 'Bear', + 'Eck', + 'Deyoung', + 'Morehead', + 'Herrin', + 'Tillery', + 'Royster', + 'Kehoe', + 'Swank', + 'Yamamoto', + 'Schoonover', + 'Clanton', + 'Stutzman', + 'Swearingen', + 'Martinson', + 'Harrelson', + 'Leo', + 'Keyser', + 'Guyton', + 'Lucio', + 'Veal', + 'Vanwinkle', + 'Angelo', + 'Zamudio', + 'Haddock', + 'Quach', + 'Thomsen', + 'Curiel', + 'Badger', + 'Teel', + 'Hibbard', + 'Dvorak', + 'Ballew', + 'Falls', + 'Bostick', + 'Monaghan', + 'Segal', + 'Denning', + 'Bahr', + 'Serrato', + 'Toomey', + 'Lacroix', + 'Antoine', + 'Resendiz', + 'Sperry', + 'Rosser', + 'Bogan', + 'Gaspar', + 'Amin', + 'Schramm', + 'Lemaster', + 'Echevarria', + 'Lilley', + 'Poling', + 'Villagomez', + 'Conde', + 'Delrio', + 'Lerner', + 'Leroy', + 'Otis', + 'Durkin', + 'Lavender', + 'Schenk', + 'Ong', + 'Guess', + 'Alanis', + 'Jacobo', + 'Ramsay', + 'Henke', + 'Sledge', + 'Whited', + 'Frazer', + 'Fortier', + 'Macleod', + 'Pascual', + 'Casanova', + 'Olds', + 'Jenson', + 'Tijerina', + 'Flora', + 'Casto', + 'Rinaldi', + 'Blunt', + 'Fontana', + 'Minnick', + 'Larios', + 'Raynor', + 'Fung', + 'Marek', + 'Valladares', + 'Clemmons', + 'Gracia', + 'Rohrer', + 'Fryer', + 'Folsom', + 'Gearhart', + 'Sumpter', + 'Kraemer', + 'Aceves', + 'Pettigrew', + 'Mclaurin', + 'Southern', + 'Barrows', + 'Landeros', + 'Janes', + 'Deguzman', + 'Mcfall', + 'Fredericks', + 'Ashe', + 'Mauro', + 'Merino', + 'Windsor', + 'Taber', + 'Armijo', + 'Bricker', + 'Pitman', + 'Morrill', + 'Sanches', + 'Deboer', + 'Conlon', + 'Reuter', + 'Stegall', + 'Clemente', + 'Romine', + 'Dykstra', + 'Ehlers', + 'Tallman', + 'Lovato', + 'Brent', + 'Pearl', + 'Pyles', + 'Cloutier', + 'Mccurry', + 'Mckeever', + 'Graziano', + 'Heflin', + 'Garman', + 'Isaacson', + 'Mcreynolds', + 'Meister', + 'Stroup', + 'Everson', + 'Halsey', + 'Mcewen', + 'Sparkman', + 'Yager', + 'Bucher', + 'Berryman', + 'Derr', + 'Jester', + 'Mickelson', + 'Sayers', + 'Whiteman', + 'Riordan', + 'Mcinnis', + 'Jose', + 'Goolsby', + 'Stidham', + 'Donley', + 'Johnsen', + 'Stallworth', + 'Franke', + 'Silvers', + 'Reitz', + 'Nathan', + 'Brogan', + 'Cardoso', + 'Linville', + 'Baptiste', + 'Gorski', + 'Rey', + 'Hazen', + 'Damon', + 'Shores', + 'Boling', + 'Jablonski', + 'Lemieux', + 'Hecht', + 'Dong', + 'Langlois', + 'Burrow', + 'Hernandes', + 'Mcdevitt', + 'Pichardo', + 'Lew', + 'Stillwell', + 'Savoy', + 'Teixeira', + 'Matheson', + 'Hildreth', + 'Warfield', + 'Hogg', + 'Tiller', + 'Unruh', + 'Rudy', + 'Bristol', + 'Matias', + 'Buxton', + 'Ambriz', + 'Chiang', + 'Pomeroy', + 'Pogue', + 'Hammock', + 'Bethel', + 'Miguel', + 'Cassell', + 'Towns', + 'Bunker', + 'Mcmichael', + 'Kress', + 'Newland', + 'Whitehurst', + 'Fazio', + 'Batten', + 'Calvillo', + 'Wallen', + 'Lung', + 'Turney', + 'Sparrow', + 'Steadman', + 'Battles', + 'Berlin', + 'Lindgren', + 'Mckeon', + 'Luckett', + 'Spradlin', + 'Sherry', + 'Timmerman', + 'Utley', + 'Beale', + 'Driggers', + 'Hintz', + 'Pellegrino', + 'Hazel', + 'Grim', + 'Desmond', + 'Spellman', + 'Boren', + 'Staten', + 'Schlegel', + 'Maya', + 'Johnstone', + 'Harwell', + 'Pinson', + 'Barreto', + 'Spooner', + 'Candelaria', + 'Hammett', + 'Sessions', + 'Mckeown', + 'Mccool', + 'Gilson', + 'Knudson', + 'Irish', + 'Spruill', + 'Kling', + 'Gerlach', + 'Carnahan', + 'Markley', + 'Laporte', + 'Flanigan', + 'Spires', + 'Cushman', + 'Plante', + 'Schlosser', + 'Sachs', + 'Jamieson', + 'Hornsby', + 'Armstead', + 'Kremer', + 'Madera', + 'Thornburg', + 'Briley', + 'Garris', + 'Jorgenson', + 'Moorman', + 'Vuong', + 'Ard', + 'Irons', + 'Fiedler', + 'Jackman', + 'Kuehn', + 'Jenks', + 'Bristow', + 'Mosby', + 'Aldana', + 'Maclean', + 'Freund', + 'Creighton', + 'Smothers', + 'Melson', + 'Lundgren', + 'Donato', + 'Usher', + 'Thornhill', + 'Lowman', + 'Mariano', + 'Button', + 'Mcbee', + 'Cupp', + 'Wickham', + 'Destefano', + 'Nutt', + 'Rambo', + 'Voigt', + 'Talbott', + 'Saxon', + 'Cedillo', + 'Mattison', + 'Speed', + 'Reiss', + 'Nan', + 'Westphal', + 'Whittle', + 'Bernhardt', + 'Boatwright', + 'Bussey', + 'Rojo', + 'Eden', + 'Crites', + 'Place', + 'He', + 'Chaves', + 'Larose', + 'Thames', + 'Hoch', + 'Knotts', + 'Simone', + 'Binkley', + 'Koester', + 'Pettis', + 'Moye', + 'Napolitano', + 'Heffner', + 'Sasser', + 'Jessup', + 'Aguiar', + 'Ogrady', + 'Pippin', + 'Worth', + 'Shively', + 'Whitmire', + 'Rutter', + 'Cedeno', + 'Welborn', + 'Mcdougal', + 'Angell', + 'Sacco', + 'Hailey', + 'Neel', + 'Paniagua', + 'Pointer', + 'Rohde', + 'Holloman', + 'Strother', + 'Guffey', + 'Fenner', + 'Huntington', + 'Shane', + 'Yuen', + 'Gosnell', + 'Martini', + 'Loving', + 'Molloy', + 'Olmos', + 'Christ', + 'Oaks', + 'Ostrowski', + 'Badillo', + 'To', + 'Laplante', + 'Martindale', + 'Richie', + 'Pleasant', + 'Palomino', + 'Rodarte', + 'Stamps', + 'Peeples', + 'Ries', + 'Brownell', + 'Walz', + 'Arana', + 'Tenney', + 'Roddy', + 'Lindner', + 'Bolt', + 'Rigsby', + 'Matteson', + 'Fielder', + 'Randazzo', + 'Deanda', + 'Drayton', + 'Ridge', + 'Tarr', + 'Shade', + 'Upshaw', + 'Woodcock', + 'Miley', + 'Hargrave', + 'Langer', + 'Yun', + 'Wilkie', + 'Choe', + 'Ching', + 'Dugas', + 'Saul', + 'Corder', + 'Bobbitt', + 'Spurgeon', + 'Gladden', + 'Woodbury', + 'Tibbs', + 'Mcgarry', + 'Mcdaniels', + 'Weigel', + 'Bickel', + 'Michels', + 'Hughey', + 'Apple', + 'Bosley', + 'Nesmith', + 'Farber', + 'Ackley', + 'Goodin', + 'Almond', + 'Garrity', + 'Bettencourt', + 'Koss', + 'Falcone', + 'Lavigne', + 'Rainwater', + 'Nation', + 'Blodgett', + 'Dabney', + 'Mabe', + 'Trowbridge', + 'Lundquist', + 'Rosenberger', + 'Dombrowski', + 'Ferro', + 'Evangelista', + 'Bowlin', + 'Mckelvey', + 'Roderick', + 'Michalski', + 'Berkowitz', + 'Sato', + 'Mayorga', + 'Corwin', + 'Mckenney', + 'Salyer', + 'Walling', + 'Abell', + 'Palacio', + 'Lash', + 'Collado', + 'Gass', + 'Luis', + 'Cooksey', + 'Moll', + 'Miramontes', + 'Luster', + 'Shrader', + 'Toliver', + 'Hard', + 'Tu', + 'Sena', + 'Mckoy', + 'Wainwright', + 'Barela', + 'Keiser', + 'Hoag', + 'Backus', + 'Huskey', + 'Brannan', + 'Brumley', + 'Palm', + 'Boynton', + 'Krauss', + 'Steel', + 'Jurado', + 'Mulder', + 'Paterson', + 'Woolsey', + 'Smithson', + 'Joslin', + 'Richman', + 'Partida', + 'Grisham', + 'Wooden', + 'Gooding', + 'Fang', + 'Mcdade', + 'Spriggs', + 'Fishman', + 'Gabel', + 'Rutkowski', + 'Pride', + 'Beals', + 'Gaskin', + 'Friday', + 'Underhill', + 'Rodas', + 'Melo', + 'Sipes', + 'Zimmermann', + 'Mosqueda', + 'Haight', + 'Beeson', + 'Judy', + 'Bankston', + 'Pieper', + 'Siebert', + 'Horning', + 'Butt', + 'Bice', + 'Sills', + 'Philips', + 'Eisenberg', + 'Schumann', + 'Conger', + 'Bare', + 'Hume', + 'Nolasco', + 'Trainor', + 'Weatherly', + 'Huebner', + 'Bosch', + 'Gayle', + 'Kuhns', + 'Byron', + 'Glaze', + 'Poulin', + 'Enright', + 'Large', + 'Comeaux', + 'Rountree', + 'Tavarez', + 'Beardsley', + 'Rubino', + 'Fee', + 'Grider', + 'Bechtel', + 'Gaona', + 'Wallin', + 'Mashburn', + 'Dalrymple', + 'Gingerich', + 'Vaccaro', + 'Hass', + 'Manzano', + 'Tyner', + 'Loza', + 'Lowell', + 'Kaufmann', + 'Bischoff', + 'Doolittle', + 'Shivers', + 'Valente', + 'Bozeman', + 'Howes', + 'Felts', + 'Feller', + 'Justus', + 'Schnell', + 'Boettcher', + 'Ivory', + 'Thorson', + 'Corn', + 'Snook', + 'Heilman', + 'Baxley', + 'Hasty', + 'Wasserman', + 'Barringer', + 'Frankel', + 'Peltier', + 'Guarino', + 'Avina', + 'Sturdivant', + 'Lien', + 'Montemayor', + 'Giddens', + 'Valverde', + 'Burchfield', + 'Pang', + 'Holbert', + 'Rooks', + 'Erdman', + 'Mcmaster', + 'Iniguez', + 'Hartwell', + 'Menchaca', + 'Bordelon', + 'Farkas', + 'Chrisman', + 'Metzler', + 'Fredrick', + 'Porterfield', + 'Slayton', + 'Quesada', + 'Hembree', + 'Peel', + 'Woodley', + 'Mather', + 'Waltz', + 'Totten', + 'Forney', + 'Woolley', + 'Trombley', + 'Yarborough', + 'Javier', + 'Durr', + 'Macklin', + 'Macon', + 'Novotny', + 'Amundson', + 'Kidder', + 'Flagg', + 'Oxendine', + 'Arguello', + 'Marler', + 'Penrod', + 'Mallett', + 'Council', + 'Kinard', + 'Bremer', + 'Towne', + 'Harless', + 'Merkel', + 'Giese', + 'Fife', + 'Byars', + 'Grande', + 'Kuo', + 'Levi', + 'Darr', + 'Sanabria', + 'Pounds', + 'Roeder', + 'Keim', + 'Brush', + 'Dreyer', + 'Taveras', + 'Furlong', + 'Dorris', + 'Prior', + 'Musgrove', + 'Weiler', + 'Munro', + 'Leake', + 'Vollmer', + 'Musick', + 'Hetrick', + 'Perdomo', + 'Kester', + 'Lock', + 'Pine', + 'Baskin', + 'Bonham', + 'Heffernan', + 'Mandel', + 'Sarver', + 'Hamer', + 'Duckett', + 'Lozada', + 'Stocker', + 'Fulcher', + 'Damato', + 'Camargo', + 'Shephard', + 'Loftis', + 'Winfrey', + 'Rueda', + 'Ledezma', + 'Gottlieb', + 'Lamont', + 'Mackie', + 'Bowe', + 'Stockwell', + 'Groth', + 'Chavira', + 'Lohr', + 'Loftin', + 'Gilmer', + 'Cushing', + 'Brody', + 'Nowlin', + 'Holiday', + 'Shirk', + 'Archie', + 'Howerton', + 'Matthew', + 'Copley', + 'Marchese', + 'Echeverria', + 'Soper', + 'Cantwell', + 'Nelms', + 'Tuggle', + 'Dumont', + 'Bard', + 'Gower', + 'Mathes', + 'Yeung', + 'Buell', + 'Bastian', + 'Burd', + 'Broadway', + 'Peng', + 'Greenwell', + 'Vanover', + 'Correll', + 'Tindall', + 'Bill', + 'Mulcahy', + 'Dionne', + 'Rathbun', + 'Baeza', + 'Booher', + 'Fried', + 'Mcginley', + 'Lavin', + 'Atherton', + 'Donnell', + 'Bays', + 'Riedel', + 'Grenier', + 'Zachary', + 'Harold', + 'Styles', + 'Wisdom', + 'Raley', + 'Tamez', + 'Arena', + 'Morelli', + 'Hazelwood', + 'Somerville', + 'Lapp', + 'Rood', + 'Salem', + 'Pape', + 'Olivera', + 'Albritton', + 'Carvajal', + 'Zayas', + 'Myer', + 'Pohl', + 'Haynie', + 'Mariscal', + 'Wampler', + 'Rife', + 'Leeper', + 'Newhouse', + 'Rodney', + 'Vandenberg', + 'Spitzer', + 'Kingston', + 'Wessel', + 'Hartzell', + 'Durden', + 'Marques', + 'Born', + 'Scribner', + 'Rocco', + 'Germain', + 'Tinoco', + 'Valdovinos', + 'Musselman', + 'Vicente', + 'Parsley', + 'Crittenden', + 'Tibbetts', + 'Hulse', + 'Mccleary', + 'Barboza', + 'Velarde', + 'Brodie', + 'Beaudoin', + 'Moreira', + 'Maggard', + 'Jara', + 'Ferrante', + 'Overby', + 'Friesen', + 'Viola', + 'Nelsen', + 'Hash', + 'Doane', + 'Deese', + 'Messick', + 'Bay', + 'Anton', + 'Ingersoll', + 'Saucier', + 'Kwiatkowski', + 'Rawson', + 'Brophy', + 'Ladner', + 'Lehr', + 'Weil', + 'Yocum', + 'Brasher', + 'Denison', + 'Hutcherson', + 'Stowers', + 'Geller', + 'Fortenberry', + 'Stebbins', + 'Conyers', + 'Toole', + 'Stoker', + 'Roden', + 'Chitwood', + 'Beeman', + 'Fannin', + 'Strait', + 'Marlowe', + 'Greenwald', + 'Hann', + 'Stumpf', + 'Samaniego', + 'Colton', + 'Bogart', + 'Morel', + 'Montelongo', + 'Boylan', + 'Guido', + 'Wyrick', + 'Horsley', + 'Tenorio', + 'Sallee', + 'Morehouse', + 'Whyte', + 'Neilson', + 'Watanabe', + 'Magallanes', + 'Mudd', + 'Kieffer', + 'Brigham', + 'Dollar', + 'Huss', + 'Albanese', + 'Spiegel', + 'Hixson', + 'Rounds', + 'Orth', + 'Blanchette', + 'Vanderpool', + 'Pfaff', + 'Speck', + 'Shreve', + 'Sevilla', + 'Neri', + 'Rohr', + 'Ruble', + 'Vanpelt', + 'Rickman', + 'Caraway', + 'Berndt', + 'Mchale', + 'Ingalls', + 'Roybal', + 'Money', + 'Mcdougall', + 'Melancon', + 'Wellington', + 'Ingraham', + 'Ritz', + 'Lashley', + 'Marchand', + 'Schatz', + 'Heiser', + 'Eby', + 'Wimmer', + 'Orton', + 'Atchley', + 'Mumford', + 'Bahena', + 'Gammon', + 'Buehler', + 'Fike', + 'Plank', + 'Carrigan', + 'Kempf', + 'Cundiff', + 'So', + 'Sauls', + 'Mohler', + 'Grillo', + 'Prichard', + 'Pastor', + 'Prasad', + 'Babin', + 'Bontrager', + 'Weddle', + 'Alberts', + 'Theis', + 'Lemoine', + 'Hartnett', + 'Kingsbury', + 'Baran', + 'Birmingham', + 'Gault', + 'Thorp', + 'Wyant', + 'Obryan', + 'Santacruz', + 'Camara', + 'Whitehouse', + 'Evenson', + 'Halvorson', + 'Palmieri', + 'Hannan', + 'Dew', + 'Au', + 'Nolte', + 'Click', + 'Wooley', + 'Hung', + 'Eberhardt', + 'Rawlins', + 'Sadowski', + 'Sarabia', + 'Soule', + 'Millar', + 'Engstrom', + 'Cowles', + 'Runyan', + 'Mitchel', + 'Torrence', + 'Silverstein', + 'Hewett', + 'Pilgrim', + 'Yeh', + 'Rosenfeld', + 'Mulholland', + 'Hatley', + 'Fawcett', + 'Delrosario', + 'Chinn', + 'Bayless', + 'Dee', + 'Deane', + 'Arriola', + 'Duda', + 'Koster', + 'Rath', + 'Karl', + 'Weiland', + 'Lemmon', + 'Blaine', + 'Scofield', + 'Marston', + 'Gist', + 'Pinckney', + 'Moritz', + 'Mclellan', + 'Fulkerson', + 'Gaynor', + 'Pitre', + 'Warrick', + 'Cobbs', + 'Meacham', + 'Guerin', + 'Tedesco', + 'Passmore', + 'Northcutt', + 'Ison', + 'Cowell', + 'Ream', + 'Walther', + 'Meraz', + 'Tribble', + 'Bumgarner', + 'Gabbard', + 'Dawes', + 'Moncada', + 'Chilton', + 'Deweese', + 'Rigby', + 'Marte', + 'Baylor', + 'Valentino', + 'Shine', + 'August', + 'Billups', + 'Jarman', + 'Jacks', + 'Coffee', + 'Friedrich', + 'Marley', + 'Hasan', + 'Pennell', + 'Abercrombie', + 'Bazan', + 'Strickler', + 'Bruton', + 'Lamm', + 'Pender', + 'Wingfield', + 'Hoffer', + 'Zahn', + 'Chaplin', + 'Reinke', + 'Larosa', + 'Maupin', + 'Bunnell', + 'Hassell', + 'Guo', + 'Galan', + 'Paschal', + 'Browder', + 'Krantz', + 'Milne', + 'Pelayo', + 'Emanuel', + 'Mccluskey', + 'Edens', + 'Radtke', + 'Alger', + 'Duhon', + 'Probst', + 'Witmer', + 'Hoagland', + 'Saechao', + 'Pitcher', + 'Villalpando', + 'Carswell', + 'Roundtree', + 'Kuhlman', + 'Tait', + 'Shaughnessy', + 'Wei', + 'Cravens', + 'Sipe', + 'Islas', + 'Hollenbeck', + 'Lockard', + 'Perrone', + 'Tapp', + 'Santoyo', + 'Jaffe', + 'Klotz', + 'Gilpin', + 'Ehrlich', + 'Klug', + 'Stowell', + 'Ibanez', + 'Lazar', + 'Osman', + 'Larkins', + 'Donofrio', + 'Ericson', + 'Schenck', + 'Mouton', + 'Medlock', + 'Hubbell', + 'Bixler', + 'Nowicki', + 'Muro', + 'Homer', + 'Grijalva', + 'Ashmore', + 'Harbison', + 'Duffey', + 'Osgood', + 'Hardee', + 'Jain', + 'Wilber', + 'Bolling', + 'Lett', + 'Phillip', + 'Dipietro', + 'Lefebvre', + 'Batiste', + 'Mcswain', + 'Distefano', + 'Hack', + 'Strobel', + 'Kipp', + 'Doerr', + 'Radcliffe', + 'Cartagena', + 'Paradis', + 'Stilwell', + 'Mccrea', + 'Searles', + 'Frausto', + 'Hendershot', + 'Gosselin', + 'Islam', + 'Freese', + 'Stockman', + 'Burwell', + 'Vandiver', + 'Engler', + 'Geisler', + 'Barham', + 'Wiegand', + 'Goncalves', + 'Theriot', + 'Doucet', + 'Bridge', + 'Catron', + 'Blanks', + 'Rahn', + 'Schaub', + 'Hershey', + 'Strader', + 'Buckman', + 'Hartwig', + 'Campo', + 'Tsang', + 'Luck', + 'Bernardo', + 'Marker', + 'Pinkney', + 'Benefield', + 'Mcginty', + 'Bode', + 'Linden', + 'Manriquez', + 'Jaquez', + 'Bedard', + 'Flack', + 'Hesse', + 'Costanzo', + 'Boardman', + 'Carper', + 'Word', + 'Miracle', + 'Edmunds', + 'Bott', + 'Flemming', + 'Manns', + 'Kesler', + 'Piatt', + 'Tankersley', + 'Eberle', + 'Roney', + 'Belk', + 'Vansickle', + 'Varga', + 'Hillard', + 'Neubauer', + 'Quirk', + 'Chevalier', + 'Mintz', + 'Kocher', + 'Casarez', + 'Tinker', + 'Elmer', + 'Decarlo', + 'Cordes', + 'Berube', + 'Kimbrell', + 'Schick', + 'Papa', + 'Alderson', + 'Callaghan', + 'Renaud', + 'Pardue', + 'Krohn', + 'Bloomfield', + 'Coward', + 'Ligon', + 'Trask', + 'Wingo', + 'Book', + 'Crutcher', + 'Canter', + 'Teran', + 'Denman', + 'Stackhouse', + 'Chambliss', + 'Gourley', + 'Earls', + 'Frizzell', + 'Bergen', + 'Abdullah', + 'Sprinkle', + 'Fancher', + 'Urias', + 'Lavelle', + 'Baumgardner', + 'Kahler', + 'Baldridge', + 'Alejandro', + 'Plascencia', + 'Hix', + 'Rule', + 'Mix', + 'Petro', + 'Hadden', + 'Fore', + 'Humes', + 'Barnum', + 'Laing', + 'Maggio', + 'Sylvia', + 'Malinowski', + 'Fell', + 'Durst', + 'Plant', + 'Vaca', + 'Abarca', + 'Shirey', + 'Parton', + 'Ta', + 'Ramires', + 'Ochs', + 'Gaitan', + 'Ledoux', + 'Darrow', + 'Messenger', + 'Chalmers', + 'Schaller', + 'Derby', + 'Coakley', + 'Saleh', + 'Kirkman', + 'Orta', + 'Crabb', + 'Spinks', + 'Dinkins', + 'Harrigan', + 'Koller', + 'Dorr', + 'Carty', + 'Sturgis', + 'Shriver', + 'Macedo', + 'Feng', + 'Bentz', + 'Bedell', + 'Osuna', + 'Dibble', + 'Dejong', + 'Fender', + 'Parada', + 'Vanburen', + 'Chaffee', + 'Stott', + 'Sigmon', + 'Nicolas', + 'Salyers', + 'Magdaleno', + 'Deering', + 'Puentes', + 'Funderburk', + 'Jang', + 'Christopherson', + 'Sellars', + 'Marcotte', + 'Oster', + 'Liao', + 'Tudor', + 'Specht', + 'Chowdhury', + 'Landa', + 'Monge', + 'Brake', + 'Behnke', + 'Llewellyn', + 'Labelle', + 'Mangan', + 'Godsey', + 'Truax', + 'Lombard', + 'Thurmond', + 'Emerick', + 'Blume', + 'Mcginn', + 'Beer', + 'Marrs', + 'Zinn', + 'Rieger', + 'Dilley', + 'Thibault', + 'Witkowski', + 'Chi', + 'Fielding', + 'Tyrrell', + 'Peeler', + 'Northrup', + 'Augustin', + 'Toy', + 'Geist', + 'Schuman', + 'Fairley', + 'Duque', + 'Villatoro', + 'Dudek', + 'Sonnier', + 'Fritts', + 'Worsham', + 'Herold', + 'Mcgehee', + 'Caskey', + 'Boatright', + 'Lazaro', + 'Deck', + 'Palomo', + 'Cory', + 'Olivier', + 'Baines', + 'Fan', + 'Futrell', + 'Halpin', + 'Garrido', + 'Koonce', + 'Fogg', + 'Meneses', + 'Mulkey', + 'Restrepo', + 'Ducharme', + 'Slate', + 'Toussaint', + 'Sorrells', + 'Fitts', + 'Dickman', + 'Alfred', + 'Grimsley', + 'Settles', + 'Etienne', + 'Eggert', + 'Hague', + 'Caldera', + 'Hillis', + 'Hollander', + 'Haire', + 'Theriault', + 'Madigan', + 'Kiernan', + 'Parkhurst', + 'Lippert', + 'Jaynes', + 'Moniz', + 'Bost', + 'Bettis', + 'Sandy', + 'Kuhl', + 'Wilk', + 'Borrego', + 'Koon', + 'Penney', + 'Pizarro', + 'Stitt', + 'Koski', + 'Galicia', + 'Quiles', + 'Real', + 'Massa', + 'Crone', + 'Teeter', + 'Voorhees', + 'Hilbert', + 'Nabors', + 'Shupe', + 'Blood', + 'Mcauliffe', + 'Waits', + 'Blakley', + 'Stoltz', + 'Maes', + 'Munroe', + 'Rhoden', + 'Abeyta', + 'Milliken', + 'Harkness', + 'Almaraz', + 'Remington', + 'Raya', + 'Frierson', + 'Olszewski', + 'Quillen', + 'Westcott', + 'Fu', + 'Tolley', + 'Olive', + 'Mcclary', + 'Corbitt', + 'Lui', + 'Lachance', + 'Meagher', + 'Cowley', + 'Hudak', + 'Cress', + 'Mccrory', + 'Talavera', + 'Mclaren', + 'Laurent', + 'Bias', + 'Whetstone', + 'Hollister', + 'Quevedo', + 'Byerly', + 'Berryhill', + 'Folk', + 'Conners', + 'Kellum', + 'Haro', + 'Mallard', + 'Mccants', + 'Risner', + 'Barros', + 'Downes', + 'Mayers', + 'Loeffler', + 'Mink', + 'Hotchkiss', + 'Bartz', + 'Alt', + 'Hindman', + 'Bayne', + 'Bagby', + 'Colin', + 'Treadwell', + 'Hemingway', + 'Bane', + 'Heintz', + 'Fite', + 'Mccomb', + 'Carmody', + 'Kistler', + 'Olinger', + 'Vestal', + 'Byrum', + 'Seale', + 'Turnage', + 'Raber', + 'Prendergast', + 'Koons', + 'Nickell', + 'Benz', + 'Mcculley', + 'Lightner', + 'Hamill', + 'Castellon', + 'Chesser', + 'Moats', + 'Buie', + 'Svoboda', + 'Wold', + 'Macmillan', + 'Boring', + 'Terrill', + 'Loveland', + 'Gaskill', + 'Verdugo', + 'Yip', + 'Oviedo', + 'Hight', + 'Carmack', + 'Scheer', + 'Dreher', + 'Appleby', + 'Lally', + 'Kibler', + 'Marra', + 'Mcnamee', + 'Cooks', + 'Kavanaugh', + 'Carrico', + 'Alden', + 'Dillman', + 'Zamarripa', + 'Serra', + 'Gilligan', + 'Nester', + 'Sokol', + 'Latta', + 'Hanrahan', + 'Ballou', + 'Hollinger', + 'Lux', + 'Caton', + 'Hamann', + 'Sackett', + 'Leiva', + 'Emory', + 'Barden', + 'Houk', + 'Lees', + 'Deltoro', + 'Lowrey', + 'Mcevoy', + 'Hibbs', + 'Crossley', + 'Rego', + 'Melchor', + 'Tull', + 'Bramlett', + 'Hsieh', + 'Warwick', + 'Sayles', + 'Mapes', + 'Pabon', + 'Dearing', + 'Stamm', + 'Joshi', + 'Quan', + 'Larry', + 'Nordstrom', + 'Heisler', + 'Bigham', + 'Walston', + 'Solberg', + 'Bodnar', + 'Posada', + 'Mancilla', + 'Ovalle', + 'Harr', + 'Mccaskill', + 'Bromley', + 'Koerner', + 'Macpherson', + 'Trudeau', + 'Blais', + 'Kiley', + 'Lawlor', + 'Suter', + 'Rothman', + 'Oberg', + 'Seely', + 'Maxfield', + 'Truman', + 'Salvatore', + 'Fouts', + 'Goulet', + 'Munger', + 'Sikora', + 'Comeau', + 'Oliphant', + 'Baber', + 'Hensel', + 'Edelman', + 'Farina', + 'Albano', + 'Aycock', + 'Sung', + 'Deckard', + 'Steinke', + 'Silveira', + 'Servin', + 'Rex', + 'Franzen', + 'Hecker', + 'Gragg', + 'Mcgriff', + 'Ellingson', + 'Kerrigan', + 'An', + 'Bartel', + 'Priddy', + 'Hodson', + 'Tse', + 'Arbogast', + 'Arceneaux', + 'Leatherman', + 'Federico', + 'Pridgen', + 'Yim', + 'Kowalczyk', + 'Deberry', + 'Lejeune', + 'Elston', + 'Mielke', + 'Shelly', + 'Stambaugh', + 'Eagan', + 'Rivard', + 'Silvia', + 'Lawhorn', + 'Denis', + 'Hendry', + 'Wieland', + 'Levinson', + 'Marlin', + 'Gerdes', + 'Pfister', + 'Carder', + 'Pipkin', + 'Angle', + 'Hang', + 'Hagerty', + 'Rhinehart', + 'Gao', + 'Petit', + 'Mccraw', + 'Markle', + 'Lupo', + 'Busse', + 'Marble', + 'Bivins', + 'Storms', + 'Yuan', + 'Waldman', + 'Suh', + 'Wyckoff', + 'Stillman', + 'Piotrowski', + 'Abrego', + 'Gregoire', + 'Bogle', + 'Wortham', + 'Phung', + 'Brister', + 'Karnes', + 'Deming', + 'Ley', + 'Carrasquillo', + 'Curtiss', + 'Appleton', + 'Salley', + 'Borja', + 'Begum', + 'Phifer', + 'Shoup', + 'Cawley', + 'Deason', + 'Castanon', + 'Loucks', + 'Hagler', + 'Mcclinton', + 'Dulaney', + 'Hargett', + 'Mcardle', + 'Burcham', + 'Philpot', + 'Laroche', + 'Breland', + 'Hatten', + 'Karp', + 'Brummett', + 'Boatman', + 'Natale', + 'Pepe', + 'Mortimer', + 'Sink', + 'Voyles', + 'Reeve', + 'Honaker', + 'Loredo', + 'Ridgway', + 'Donner', + 'Lessard', + 'Dever', + 'Salomon', + 'Hickson', + 'Nicholls', + 'Bushey', + 'Osteen', + 'Reavis', + 'Rodman', + 'Barahona', + 'Knecht', + 'Hinman', + 'Faria', + 'Dana', + 'Bancroft', + 'Hatchett', + 'Hageman', + 'Klaus', + 'Castor', + 'Lampkin', + 'Dalessandro', + 'Riffle', + 'Korn', + 'Savoie', + 'Sandifer', + 'Mciver', + 'Magill', + 'Delafuente', + 'Widener', + 'Vermillion', + 'Dandrea', + 'Mader', + 'Woodman', + 'Milan', + 'Hollowell', + 'Schaaf', + 'Kao', + 'Nail', + 'Beaman', + 'Hawkes', + 'Mclane', + 'Marchant', + 'Scanlan', + 'Syed', + 'Peabody', + 'Uhl', + 'Schauer', + 'Azevedo', + 'Wolcott', + 'Mick', + 'Melgar', + 'Pilcher', + 'Burgin', + 'Weiser', + 'Daughtry', + 'Theisen', + 'Babbitt', + 'Petry', + 'Cotten', + 'Fick', + 'Eubank', + 'Tolson', + 'Judkins', + 'Cronk', + 'Wendel', + 'Monteiro', + 'Kissinger', + 'Banta', + 'Senn', + 'Fix', + 'Brehm', + 'Rittenhouse', + 'Banner', + 'Elwell', + 'Herd', + 'Araiza', + 'Hui', + 'Nowell', + 'Brett', + 'Hua', + 'Breeding', + 'Pawlowski', + 'Thompkins', + 'Bocanegra', + 'Bosworth', + 'Dutcher', + 'Cotto', + 'Beecher', + 'Callender', + 'Hamlett', + 'Benfield', + 'Claudio', + 'Reel', + 'Brookshire', + 'Helmick', + 'Ryals', + 'Winder', + 'Thom', + 'Robin', + 'Overman', + 'Furtado', + 'Dacosta', + 'Paddock', + 'Dancy', + 'Carpio', + 'Manzanares', + 'Zito', + 'Favela', + 'Beckley', + 'Adrian', + 'Flory', + 'Nestor', + 'Spell', + 'Speight', + 'Strawn', + 'Beckner', + 'Gause', + 'Berglund', + 'Ruppert', + 'Mincey', + 'Spinelli', + 'Suzuki', + 'Mizell', + 'Kirksey', + 'Bolduc', + 'Kilmer', + 'Wesson', + 'Brinker', + 'Urrutia', + 'Markey', + 'Brenneman', + 'Haupt', + 'Sievers', + 'Puga', + 'Halloran', + 'Birdsong', + 'Stancil', + 'Wiener', + 'Calvo', + 'Macy', + 'Cairns', + 'Kahl', + 'Vice', + 'Ordaz', + 'Grow', + 'Lafrance', + 'Dryden', + 'Studer', + 'Matney', + 'Edward', + 'Rackley', + 'Gurrola', + 'Demoss', + 'Woolard', + 'Oquinn', + 'Hambrick', + 'Christmas', + 'Robey', + 'Crayton', + 'Haber', + 'Arango', + 'Newcomer', + 'Groom', + 'Corson', + 'Harness', + 'Rossman', + 'Slaton', + 'Schutz', + 'Conant', + 'Tedder', + 'Sabin', + 'Lowder', + 'Womble', + 'Jin', + 'Monday', + 'Garmon', + 'Aronson', + 'Skeen', + 'Headrick', + 'Lefevre', + 'Whittemore', + 'Pelton', + 'Barner', + 'Hildebrandt', + 'Rick', + 'Helmer', + 'Grose', + 'Zak', + 'Schroder', + 'Mahler', + 'Keeley', + 'Flinn', + 'Jordon', + 'Ozuna', + 'Sand', + 'Henkel', + 'Turcotte', + 'Vining', + 'Bellinger', + 'Neese', + 'Hagerman', + 'Mcmillin', + 'Gaylord', + 'Harney', + 'Milano', + 'Carothers', + 'Depew', + 'Bucci', + 'Pirtle', + 'Hafner', + 'Dimas', + 'Howlett', + 'Reber', + 'Abram', + 'Davalos', + 'Zajac', + 'Pedro', + 'Goodall', + 'Kaylor', + 'Wrenn', + 'Gartner', + 'Kell', + 'Curl', + 'Leathers', + 'Spiller', + 'Beason', + 'Shattuck', + 'Brewington', + 'Pinon', + 'Nazario', + 'Wash', + 'Ruggles', + 'Matz', + 'Capers', + 'Dorsett', + 'Wilmoth', + 'Bracey', + 'Lenhart', + 'Devoe', + 'Choy', + 'Oswalt', + 'Capone', + 'Wayman', + 'Parikh', + 'Eastwood', + 'Cofield', + 'Rickert', + 'Mccandless', + 'Greenway', + 'Majewski', + 'Rigdon', + 'Armbruster', + 'Royce', + 'Sterner', + 'Swaim', + 'Flournoy', + 'Amezcua', + 'Delano', + 'Westerman', + 'Grau', + 'Claxton', + 'Veliz', + 'Haun', + 'Roscoe', + 'Mccafferty', + 'Ringer', + 'Volz', + 'Blessing', + 'Mcphail', + 'Thelen', + 'Gagliardi', + 'Scholz', + 'Genovese', + 'Boyette', + 'Squire', + 'Naughton', + 'Levitt', + 'Erskine', + 'Leffler', + 'Manchester', + 'Hallett', + 'Whitmer', + 'Gillett', + 'Groce', + 'Roos', + 'Bejarano', + 'Moskowitz', + 'Constantine', + 'Fidler', + 'Roll', + 'Schutte', + 'Ohare', + 'Warnock', + 'Wester', + 'Macgregor', + 'Golding', + 'Abner', + 'Burgett', + 'Bushnell', + 'Brazil', + 'Ascencio', + 'Hock', + 'Legrand', + 'Eversole', + 'Rome', + 'Radcliff', + 'Fuhrman', + 'Schmit', + 'Tew', + 'Caro', + 'Cowen', + 'Marriott', + 'Kephart', + 'Hartung', + 'Keil', + 'Benally', + 'Hazlett', + 'Avant', + 'Desrosiers', + 'Kwong', + 'Guyer', + 'Penner', + 'Avelar', + 'Cashman', + 'Stith', + 'Orona', + 'Rager', + 'Johanson', + 'Lanza', + 'Min', + 'Cool', + 'Heine', + 'Nissen', + 'Buenrostro', + 'Mcmullin', + 'Oropeza', + 'Hom', + 'Degroot', + 'Wescott', + 'Hulbert', + 'Shrum', + 'Muncy', + 'Littrell', + 'Forest', + 'Dyke', + 'Garces', + 'Cimino', + 'Gebhardt', + 'Hickerson', + 'Satterwhite', + 'Radke', + 'Luckey', + 'Coronel', + 'Pugliese', + 'Frazee', + 'Siddiqui', + 'Flatt', + 'Abbey', + 'Gerald', + 'Bodine', + 'Lora', + 'Youngs', + 'Catlett', + 'Alexis', + 'Luo', + 'Youmans', + 'Sherlock', + 'Kinser', + 'Wales', + 'Dinsmore', + 'Abramson', + 'Stricker', + 'Rumsey', + 'Showers', + 'Mickens', + 'Tallent', + 'Setzer', + 'Etter', + 'Allgood', + 'Pagel', + 'Jefferies', + 'Bissell', + 'Colombo', + 'Musgrave', + 'Kuehl', + 'Raab', + 'Kavanagh', + 'Beane', + 'Witcher', + 'Pattison', + 'Paulus', + 'Gong', + 'Mcgough', + 'Burkhalter', + 'Vanbuskirk', + 'Kite', + 'Sass', + 'Lalonde', + 'Gormley', + 'Baier', + 'Brauer', + 'Stricklin', + 'Napoli', + 'Brotherton', + 'Stansbury', + 'Loggins', + 'Sorrentino', + 'Poff', + 'Nieman', + 'Roebuck', + 'Reiner', + 'Hovey', + 'Walley', + 'Leech', + 'Gambino', + 'Hammack', + 'Burson', + 'Tatro', + 'Perrine', + 'Carley', + 'Stadler', + 'Nason', + 'Peckham', + 'Gervais', + 'Ables', + 'Turman', + 'Dore', + 'Peavy', + 'Addington', + 'Tobar', + 'Gilstrap', + 'Brumbaugh', + 'Gerhardt', + 'Slusher', + 'Nevins', + 'Garofalo', + 'Amick', + 'Barrick', + 'Race', + 'Daggett', + 'Manion', + 'Noah', + 'Kranz', + 'Runge', + 'Wysocki', + 'Gillum', + 'Verduzco', + 'Alvey', + 'Pettus', + 'Sim', + 'Cage', + 'Mckean', + 'Harrod', + 'Weatherspoon', + 'Takahashi', + 'Wingard', + 'Endres', + 'Skiles', + 'Wald', + 'Finger', + 'Reams', + 'Ussery', + 'Fricke', + 'Jaworski', + 'Cusick', + 'Stanek', + 'Shaner', + 'Massaro', + 'Ribeiro', + 'Eades', + 'Rue', + 'Scharf', + 'Standridge', + 'Wojciechowski', + 'Victoria', + 'Galbreath', + 'Lander', + 'Martinelli', + 'Raper', + 'Karas', + 'Tomas', + 'La', + 'Kizer', + 'Gastelum', + 'Delp', + 'Sansone', + 'Therrien', + 'Brookins', + 'Shi', + 'Hammel', + 'Polley', + 'Riddell', + 'Claiborne', + 'Lampe', + 'Benham', + 'Braddock', + 'Elwood', + 'Mcminn', + 'Amerson', + 'Leija', + 'Gambrell', + 'Nuno', + 'Mallon', + 'Gard', + 'Burford', + 'Halley', + 'Maley', + 'Eicher', + 'Caban', + 'Rubenstein', + 'Tighe', + 'Harbaugh', + 'Bergmann', + 'Runnels', + 'Carrizales', + 'Gustin', + 'Wight', + 'Dominick', + 'Cannady', + 'Brace', + 'Beauregard', + 'Weitzel', + 'Orcutt', + 'Abrahamson', + 'Jorge', + 'Mccown', + 'Harriman', + 'Nicol', + 'Gott', + 'Andino', + 'Tsosie', + 'Shumway', + 'Aucoin', + 'Bowes', + 'Hixon', + 'Broom', + 'Cate', + 'Desantiago', + 'Haug', + 'Pinedo', + 'Mowry', + 'Moyers', + 'Deangelo', + 'Mcshane', + 'Boley', + 'Tiffany', + 'Steger', + 'Woodford', + 'Whitford', + 'Collette', + 'Muth', + 'Mansour', + 'Schuh', + 'Fortney', + 'Khoury', + 'Livengood', + 'Haworth', + 'Rusk', + 'Mathieu', + 'Peppers', + 'Gehring', + 'Faris', + 'Diep', + 'Rae', + 'Hupp', + 'Escalera', + 'Gwin', + 'Engelhardt', + 'Bannon', + 'Menjivar', + 'Eberhart', + 'Kershaw', + 'Cottle', + 'Palomares', + 'Carrell', + 'Galaviz', + 'Willie', + 'Troxell', + 'Visser', + 'Xie', + 'Juan', + 'Spector', + 'Izzo', + 'Woodring', + 'Gilbreath', + 'Bey', + 'Giraldo', + 'Neary', + 'Ready', + 'Toland', + 'Benge', + 'Thrower', + 'Bemis', + 'Hostetter', + 'Dull', + 'Poulos', + 'Vanegas', + 'Abad', + 'Harker', + 'Mei', + 'Nigro', + 'Messner', + 'Peres', + 'Hardaway', + 'Crumpton', + 'Dingman', + 'Hipp', + 'Lemley', + 'Maloy', + 'Ye', + 'Neighbors', + 'Proulx', + 'Jamerson', + 'Finkelstein', + 'Payan', + 'Holler', + 'Simonds', + 'Toms', + 'Schulman', + 'Aguero', + 'Hinrichs', + 'Steffens', + 'Clapper', + 'Delao', + 'Knighton', + 'Jahn', + 'Mach', + 'Heal', + 'Detwiler', + 'Corso', + 'Toner', + 'Rook', + 'Brockway', + 'Coulson', + 'Delia', + 'Giddings', + 'Hermosillo', + 'Ballenger', + 'Persinger', + 'Delk', + 'Pedigo', + 'Burg', + 'Voelker', + 'Ecker', + 'Kile', + 'Propst', + 'Rascon', + 'Stultz', + 'Swindle', + 'Swindell', + 'Deaver', + 'Welty', + 'Sussman', + 'Southworth', + 'Child', + 'Coston', + 'Lei', + 'Spillman', + 'Hochstetler', + 'Veach', + 'Melcher', + 'Chipman', + 'Lebeau', + 'Summerville', + 'Peden', + 'Lizarraga', + 'Kingery', + 'Leos', + 'Fogel', + 'Eckman', + 'Burbank', + 'Castano', + 'Chartier', + 'Medellin', + 'Torrey', + 'Peake', + 'Swinney', + 'Aziz', + 'Reinert', + 'Borg', + 'Pires', + 'Brooke', + 'Forester', + 'Greaves', + 'Delapaz', + 'Hunnicutt', + 'Bierman', + 'Stringfellow', + 'Lavallee', + 'Farnham', + 'Gadson', + 'Gainer', + 'Kulp', + 'Liston', + 'Brooker', + 'Loudermilk', + 'Reza', + 'Henshaw', + 'Hinz', + 'Brammer', + 'Frisch', + 'Toombs', + 'Esquibel', + 'Feinberg', + 'Plaza', + 'Bly', + 'Encarnacion', + 'Cockerham', + 'Shealy', + 'Haile', + 'Nave', + 'Chenoweth', + 'Goto', + 'Ernest', + 'Staub', + 'Marty', + 'Huizar', + 'Lammers', + 'Mcavoy', + 'Dishman', + 'Giroux', + 'Dowdell', + 'Via', + 'Fenn', + 'Kain', + 'Breckenridge', + 'Egbert', + 'Steelman', + 'Gasper', + 'Riojas', + 'Parmer', + 'Creed', + 'Gillispie', + 'Edgerton', + 'Yen', + 'Calder', + 'Holmberg', + 'Kreider', + 'Landau', + 'Eley', + 'Lewallen', + 'Quimby', + 'Holladay', + 'Du', + 'Leland', + 'Hyder', + 'Omeara', + 'Acton', + 'Gaspard', + 'Kennard', + 'Renfroe', + 'Hayman', + 'Gladney', + 'Glidden', + 'Wilmot', + 'Pearsall', + 'Cahoon', + 'Hallock', + 'Grigg', + 'Boggess', + 'Lewin', + 'Doering', + 'Thach', + 'Mcatee', + 'Paulk', + 'Rusch', + 'Harrold', + 'Suttles', + 'Chiles', + 'Sawyers', + 'Roger', + 'Kwok', + 'Luevano', + 'Coelho', + 'Waldo', + 'Ewell', + 'Lagunas', + 'Rude', + 'Barrington', + 'Mccomas', + 'Whiteley', + 'Jeanbaptiste', + 'Darcy', + 'Lussier', + 'Kerley', + 'Fordham', + 'Moorehead', + 'Welton', + 'Nicely', + 'Constantino', + 'Townes', + 'Giglio', + 'Damian', + 'Mckibben', + 'Resnick', + 'Endicott', + 'Lindeman', + 'Killion', + 'Gwinn', + 'Beaumont', + 'Nord', + 'Miceli', + 'Fast', + 'Bidwell', + 'Sites', + 'Drum', + 'Maze', + 'Abshire', + 'Berner', + 'Rhyne', + 'Juliano', + 'Wortman', + 'Beggs', + 'Winchell', + 'Summerlin', + 'Thrash', + 'Biggers', + 'Buckles', + 'Barnwell', + 'Thomasson', + 'Wan', + 'Arneson', + 'Rodrigue', + 'Wroblewski', + 'Quiroga', + 'Fulk', + 'Dillingham', + 'Rone', + 'Mapp', + 'Sattler', + 'Letourneau', + 'Gaudet', + 'Mccaslin', + 'Gurule', + 'Huck', + 'Hudspeth', + 'Welter', + 'Wittman', + 'Hileman', + 'Ewald', + 'Yao', + 'Kindred', + 'Kato', + 'Nickels', + 'Tyndall', + 'Sanmiguel', + 'Mayle', + 'Alfano', + 'Eichelberger', + 'Bee', + 'Sheehy', + 'Rogan', + 'Philip', + 'Dilworth', + 'Midkiff', + 'Hudgens', + 'Killingsworth', + 'Russel', + 'Criss', + 'Liddell', + 'Eberly', + 'Khalil', + 'Lattimore', + 'Koval', + 'Maxson', + 'Schram', + 'Goodell', + 'Catlin', + 'Cofer', + 'Alva', + 'Sandler', + 'Kunkle', + 'Perron', + 'Bushman', + 'Edmonson', + 'Roa', + 'Nesbit', + 'Ahearn', + 'Garver', + 'Bible', + 'Barley', + 'Struble', + 'Oxford', + 'Wulf', + 'Marron', + 'Haught', + 'Bonnell', + 'Pigg', + 'Friel', + 'Almaguer', + 'Bowler', + 'Mitchem', + 'Fussell', + 'Lemos', + 'Savino', + 'Boisvert', + 'Torgerson', + 'Annis', + 'Dicks', + 'Ruhl', + 'Pepin', + 'Wildman', + 'Gendron', + 'Melanson', + 'Sherer', + 'Duty', + 'Cassel', + 'Croteau', + 'Rolon', + 'Staats', + 'Pass', + 'Larocca', + 'Sauter', + 'Sacks', + 'Boutwell', + 'Hunsaker', + 'Omara', + 'Mcbroom', + 'Lohman', + 'Treat', + 'Dufour', + 'Brashear', + 'Yepez', + 'Lao', + 'Telles', + 'Manis', + 'Mars', + 'Shilling', + 'Tingle', + 'Macaluso', + 'Rigney', + 'Clair', + 'Matsumoto', + 'Agosto', + 'Halbert', + 'Dabbs', + 'Eckstein', + 'Mercurio', + 'Berkley', + 'Wachter', + 'Langan', + 'Peach', + 'Carreno', + 'Lepore', + 'Howie', + 'Thaxton', + 'Arrowood', + 'Weinberger', + 'Eldred', + 'Hooten', + 'Raymer', + 'Feaster', + 'Bosco', + 'Cataldo', + 'Fears', + 'Eckhardt', + 'Mullinax', + 'Spratt', + 'Laboy', + 'Marsden', + 'Carlile', + 'Bustillos', + 'Crim', + 'Surratt', + 'Kurth', + 'Gaul', + 'Machuca', + 'Rolfe', + 'Lower', + 'Edmiston', + 'Millsap', + 'Dehaven', + 'Racine', + 'Coney', + 'Rinker', + 'Maddux', + 'Burmeister', + 'Fenwick', + 'Stocks', + 'Forde', + 'Pettway', + 'Balderrama', + 'Westover', + 'Bloch', + 'Burress', + 'Hunley', + 'Futch', + 'Chee', + 'Alvarenga', + 'Bostwick', + 'Cleaver', + 'Pelkey', + 'Bryce', + 'Pisano', + 'Qureshi', + 'Varghese', + 'Cunha', + 'Hellman', + 'Grass', + 'Luker', + 'Hazelton', + 'Cathcart', + 'Yamada', + 'Gallego', + 'Menke', + 'Yingling', + 'Merriweather', + 'Fleury', + 'Salmeron', + 'Metcalfe', + 'Brook', + 'Freitag', + 'Malek', + 'Obregon', + 'Blain', + 'Mellott', + 'Alam', + 'Bessette', + 'Moncrief', + 'Arvizu', + 'Botts', + 'Moorer', + 'Landreth', + 'Hulett', + 'Marinelli', + 'Falco', + 'Silvestri', + 'Gottschalk', + 'Thiele', + 'Kight', + 'Warrington', + 'Huckaby', + 'Ledet', + 'Charbonneau', + 'Crozier', + 'Mohan', + 'Stroh', + 'Bolinger', + 'Delvecchio', + 'Macfarlane', + 'Cribbs', + 'Mcloughlin', + 'Maynor', + 'Ming', + 'Digiovanni', + 'Truesdale', + 'Pfeffer', + 'Benn', + 'Chaparro', + 'Englert', + 'Spano', + 'Ogletree', + 'Yancy', + 'Swick', + 'Hallmark', + 'Mattern', + 'Tryon', + 'Plumb', + 'Martineau', + 'Man', + 'Grube', + 'Holst', + 'Nez', + 'Belden', + 'Aikens', + 'Litton', + 'Moorhead', + 'Dufresne', + 'Bonney', + 'Heyward', + 'Halliday', + 'Ito', + 'Crossman', + 'Gast', + 'Levan', + 'Wine', + 'Desouza', + 'Kornegay', + 'Nam', + 'Keough', + 'Stotts', + 'Dickenson', + 'Ousley', + 'Leduc', + 'Revels', + 'Dizon', + 'Arreguin', + 'Shockey', + 'Alegria', + 'Blades', + 'Ignacio', + 'Mellon', + 'Ebersole', + 'Sain', + 'Weissman', + 'Wargo', + 'Claypool', + 'Zorn', + 'Julien', + 'Hinshaw', + 'Alberto', + 'Garduno', + 'Kellar', + 'Rizo', + 'Labonte', + 'Humble', + 'Downer', + 'Lykins', + 'Tower', + 'Vanhouten', + 'Chairez', + 'Campa', + 'Blizzard', + 'Standley', + 'Reiser', + 'Whitener', + 'Menefee', + 'Nalley', + 'Lasher', + 'Strang', + 'Smock', + 'Moralez', + 'Kiel', + 'Moffatt', + 'Behm', + 'Hackworth', + 'Dirks', + 'Kratz', + 'Guillot', + 'Tittle', + 'Stlouis', + 'Seymore', + 'Searle', + 'Utter', + 'Wilborn', + 'Dortch', + 'Duron', + 'Cardinal', + 'Spikes', + 'Arambula', + 'Cutter', + 'Dibenedetto', + 'Botelho', + 'Bedwell', + 'Kilby', + 'Bottoms', + 'Cassady', + 'Rothwell', + 'Bilodeau', + 'Markowitz', + 'Baucom', + 'Valley', + 'Esqueda', + 'Depalma', + 'Laskowski', + 'Hopp', + 'Casale', + 'Perreault', + 'Shuster', + 'Wolter', + 'Raby', + 'Cyrus', + 'Tseng', + 'Georges', + 'Das', + 'Wilfong', + 'Schlueter', + 'Woolf', + 'Stickney', + 'Mcinerney', + 'Curcio', + 'Fowlkes', + 'Boldt', + 'Zander', + 'Shropshire', + 'Antonelli', + 'Froehlich', + 'Butterworth', + 'Stedman', + 'Broadnax', + 'Kroeger', + 'Kellner', + 'Monreal', + 'Armas', + 'Mcguinness', + 'Canterbury', + 'Weisman', + 'Hilburn', + 'Carruthers', + 'Pell', + 'Peele', + 'Devaney', + 'Owings', + 'Mar', + 'Liggett', + 'Breslin', + 'Soucy', + 'Aguila', + 'Weidman', + 'Mingo', + 'Tarango', + 'Winger', + 'Poteet', + 'Acree', + 'Mcnew', + 'Leatherwood', + 'Aubrey', + 'Waring', + 'Soler', + 'Roof', + 'Sunderland', + 'Blackford', + 'Rabe', + 'Hepler', + 'Leonardo', + 'Spina', + 'Smythe', + 'Alex', + 'Barta', + 'Bybee', + 'Campagna', + 'Pete', + 'Batchelder', + 'Gurney', + 'Wyche', + 'Schutt', + 'Rashid', + 'Almazan', + 'Pahl', + 'Perri', + 'Viramontes', + 'Cavender', + 'Snapp', + 'Newson', + 'Sandhu', + 'Fernando', + 'Stockdale', + 'Garfield', + 'Ealy', + 'Mcfarlin', + 'Bieber', + 'Callan', + 'Arruda', + 'Oquendo', + 'Levasseur', + 'Maple', + 'Kowal', + 'Kushner', + 'Naquin', + 'Shouse', + 'Mcquade', + 'Cai', + 'Smedley', + 'Gober', + 'Saiz', + 'Brunelle', + 'Arbuckle', + 'Landes', + 'Mak', + 'Korte', + 'Oxley', + 'Boger', + 'Mickey', + 'Lent', + 'Cureton', + 'Husted', + 'Eidson', + 'Boyett', + 'Kitts', + 'Shope', + 'Hance', + 'Jessen', + 'Litchfield', + 'Torre', + 'Cargill', + 'Herren', + 'Straight', + 'Merz', + 'Weese', + 'Sperling', + 'Lapierre', + 'Yung', + 'Doggett', + 'Cauley', + 'Hardeman', + 'Margolis', + 'Watford', + 'Seltzer', + 'Fullmer', + 'Timberlake', + 'Butz', + 'Duquette', + 'Olin', + 'Leverett', + 'Hartford', + 'Otte', + 'Beaton', + 'Grimaldi', + 'Marotta', + 'Carlsen', + 'Cullum', + 'Monte', + 'Haygood', + 'Middlebrooks', + 'Lazarus', + 'Shiver', + 'Ivie', + 'Niemi', + 'Lacombe', + 'Judson', + 'Ginsberg', + 'Firestone', + 'Izquierdo', + 'Deel', + 'Jacinto', + 'Towers', + 'Fritsch', + 'Albin', + 'Kaminsky', + 'Yin', + 'Wrobel', + 'Birdwell', + 'Krieg', + 'Danforth', + 'Florio', + 'Saito', + 'Clift', + 'Duck', + 'Matt', + 'Moxley', + 'Barbieri', + 'Klatt', + 'Saltzman', + 'Chesney', + 'Bojorquez', + 'Cosentino', + 'Lodge', + 'Converse', + 'Decastro', + 'Gerhart', + 'Music', + 'Danley', + 'Santangelo', + 'Bevins', + 'Coen', + 'Seibel', + 'Lindemann', + 'Dressler', + 'Newport', + 'Bedolla', + 'Lillie', + 'Rhone', + 'Penaloza', + 'Swart', + 'Niemeyer', + 'Pilkington', + 'Matta', + 'Hollifield', + 'Gillman', + 'Montana', + 'Maroney', + 'Stenger', + 'Loos', + 'Wert', + 'Brogdon', + 'Gandhi', + 'Bent', + 'Tabb', + 'Sikorski', + 'Hagedorn', + 'Hannigan', + 'Hoss', + 'Conlin', + 'Trott', + 'Fall', + 'Granado', + 'Bartell', + 'Rubalcava', + 'Neves', + 'Poynter', + 'Alton', + 'Paschall', + 'Waltman', + 'Parke', + 'Kittle', + 'Czarnecki', + 'Bloodworth', + 'Knorr', + 'Timms', + 'Derry', + 'Messier', + 'Saad', + 'Cozart', + 'Sutphin', + 'Puryear', + 'Gatto', + 'Whitacre', + 'Verdin', + 'Bloomer', + 'Brundage', + 'Brian', + 'Seger', + 'Clare', + 'Balch', + 'Tharpe', + 'Rayford', + 'Halter', + 'Barefoot', + 'Gonsalez', + 'Lomas', + 'Monzon', + 'Howarth', + 'Mccready', + 'Gudino', + 'Serafin', + 'Sanfilippo', + 'Minnich', + 'Eldredge', + 'Malave', + 'Greeley', + 'Sisneros', + 'Kangas', + 'Peery', + 'Lunn', + 'Lukas', + 'Bunce', + 'Riccio', + 'Thies', + 'Stivers', + 'Conard', + 'Mullaney', + 'Catalan', + 'Omar', + 'Theobald', + 'Jeffcoat', + 'Kucera', + 'Borkowski', + 'Coomer', + 'Mathison', + 'Croom', + 'Rushton', + 'Stites', + 'Pendley', + 'Till', + 'Oconner', + 'Forsberg', + 'Wages', + 'Fillmore', + 'Barcenas', + 'Gillard', + 'Leak', + 'Towle', + 'Esser', + 'Dunlop', + 'Quackenbush', + 'Archambault', + 'Buller', + 'Newlin', + 'Urquhart', + 'Shanley', + 'Mote', + 'Ippolito', + 'Rozier', + 'Reidy', + 'Gregor', + 'Swaney', + 'Bradfield', + 'Fudge', + 'More', + 'Tester', + 'Higley', + 'Dambrosio', + 'Bullington', + 'Highsmith', + 'Silas', + 'Felker', + 'Sawicki', + 'Beltz', + 'Albarran', + 'Aitken', + 'Findlay', + 'Looper', + 'Tooley', + 'Lasley', + 'Moynihan', + 'Ratcliffe', + 'Grizzle', + 'Souders', + 'Nussbaum', + 'Suber', + 'Macdougall', + 'Waddle', + 'Brawner', + 'Tucci', + 'Cosme', + 'Walk', + 'Gordy', + 'Tarrant', + 'Rosenblum', + 'Huth', + 'Bridgeman', + 'Hinkley', + 'Gehrke', + 'Boden', + 'Suazo', + 'Gambill', + 'Widner', + 'Chick', + 'Mccollough', + 'Hassler', + 'Odum', + 'Pawlak', + 'Prevost', + 'Slavin', + 'Fetters', + 'Beamon', + 'Renshaw', + 'Deng', + 'Plourde', + 'Holstein', + 'Rye', + 'Holliman', + 'Melville', + 'Messinger', + 'Turcios', + 'Garnica', + 'Feeley', + 'Mariani', + 'Otten', + 'Dorado', + 'Mortenson', + 'Meissner', + 'Scarlett', + 'Sweitzer', + 'Glisson', + 'Desjardins', + 'Penland', + 'Elledge', + 'Crumley', + 'Deen', + 'Shih', + 'Heuer', + 'Gloria', + 'Lail', + 'Mcandrew', + 'Mcnaughton', + 'Cortese', + 'Stgermain', + 'Hammon', + 'Leininger', + 'Flickinger', + 'Dement', + 'Bumgardner', + 'Tessier', + 'Fulford', + 'Cervantez', + 'Wisner', + 'Shulman', + 'Sabol', + 'Papp', + 'Strasser', + 'Sartin', + 'Rothstein', + 'Grote', + 'Beaudry', + 'Deville', + 'Roop', + 'Villar', + 'Bussell', + 'Bowyer', + 'Yoshida', + 'Hertz', + 'Countryman', + 'Hoey', + 'Roseberry', + 'Schock', + 'Boozer', + 'Mccowan', + 'Kirschner', + 'Lechner', + 'Winkelman', + 'Witham', + 'Thurber', + 'Depriest', + 'Chenault', + 'Moten', + 'Tillotson', + 'Guan', + 'Ketcham', + 'Jiles', + 'Grosso', + 'Nottingham', + 'Kellam', + 'Alejo', + 'Thoma', + 'Marchetti', + 'Holifield', + 'Fortson', + 'Leasure', + 'Mceachern', + 'Oceguera', + 'Carleton', + 'Weekley', + 'Kinsella', + 'Harvell', + 'Waldon', + 'Kean', + 'Chancellor', + 'Blosser', + 'Detweiler', + 'Presnell', + 'Beachy', + 'Lingle', + 'Plumley', + 'Knopp', + 'Gamache', + 'Atwater', + 'Caine', + 'Woodland', + 'Terwilliger', + 'Moller', + 'Cleland', + 'Cottingham', + 'Janke', + 'Willman', + 'Dann', + 'Mangrum', + 'Shuck', + 'Paden', + 'Adelman', + 'Brim', + 'Tullis', + 'Hertel', + 'Gallaher', + 'Leopold', + 'Donegan', + 'Popovich', + 'Gusman', + 'Chatham', + 'Schooley', + 'Pinder', + 'Heise', + 'Maines', + 'Nystrom', + 'Jahnke', + 'Poon', + 'Murphree', + 'Pelaez', + 'Risley', + 'Sohn', + 'Shim', + 'Armentrout', + 'Kastner', + 'Philpott', + 'Mao', + 'Pursley', + 'Mangold', + 'Mccourt', + 'Hollar', + 'Desmarais', + 'Debord', + 'Gullett', + 'Gaeta', + 'Bae', + 'Houlihan', + 'Gorton', + 'Steinman', + 'Santo', + 'Snelling', + 'Corpuz', + 'Look', + 'Scudder', + 'Treece', + 'Binns', + 'Sokolowski', + 'Harner', + 'Gallup', + 'Marti', + 'Teasley', + 'Markel', + 'Casiano', + 'Nicks', + 'Recinos', + 'Paradise', + 'Colman', + 'Orange', + 'Mele', + 'Medford', + 'Templin', + 'Zuber', + 'Mackin', + 'Brodsky', + 'Householder', + 'Wirtz', + 'Hackman', + 'Tippett', + 'Polson', + 'Colston', + 'Cerna', + 'Herald', + 'Shults', + 'Shubert', + 'Mertens', + 'Dave', + 'Duffield', + 'Vanness', + 'Mayne', + 'Driskell', + 'Percy', + 'Lauderdale', + 'Cipriano', + 'Theodore', + 'Colella', + 'Kiger', + 'Brownfield', + 'Stella', + 'Wideman', + 'Maye', + 'Chisolm', + 'Muldoon', + 'Fitzwater', + 'Harville', + 'Dixson', + 'Burkey', + 'Hartsfield', + 'Schade', + 'Brawley', + 'Pelfrey', + 'Tennyson', + 'Whitted', + 'Silvas', + 'Harbour', + 'Krupa', + 'Peraza', + 'Erdmann', + 'Halpern', + 'Finnerty', + 'Mackinnon', + 'Humbert', + 'Mccarley', + 'Doster', + 'Kugler', + 'Livesay', + 'Force', + 'Haberman', + 'Lamp', + 'Hector', + 'Charron', + 'Woosley', + 'Rein', + 'Ashburn', + 'Greenleaf', + 'Niemann', + 'Carillo', + 'Skelly', + 'Nunnally', + 'Renfrow', + 'Prickett', + 'Angus', + 'Bednar', + 'Nightingale', + 'Steinbach', + 'Warnick', + 'Jason', + 'Hans', + 'Lydon', + 'Rutland', + 'Alleman', + 'Hawn', + 'Malin', + 'Beech', + 'Auger', + 'Desilva', + 'Izaguirre', + 'Isham', + 'Mandujano', + 'Glasser', + 'Dimarco', + 'Berumen', + 'Nipper', + 'Pegram', + 'Sundberg', + 'Labbe', + 'Mcphee', + 'Crafton', + 'Agustin', + 'Cantor', + 'Beller', + 'Bang', + 'Lawyer', + 'Croy', + 'Kyles', + 'Winans', + 'Battista', + 'Jost', + 'Bakken', + 'Dandridge', + 'Mustafa', + 'Ice', + 'Eklund', + 'Montesdeoca', + 'Hermes', + 'Grimaldo', + 'Vannoy', + 'Grainger', + 'Lamas', + 'Tarantino', + 'Witter', + 'Worthen', + 'Basinger', + 'Cowden', + 'Hiles', + 'Mcanally', + 'Felipe', + 'Gallimore', + 'Kapp', + 'Makowski', + 'Copenhaver', + 'Ramer', + 'Gideon', + 'Bowker', + 'Wilkens', + 'Seeger', + 'Huntsman', + 'Palladino', + 'Jessee', + 'Kittrell', + 'Rolle', + 'Ciccone', + 'Kolar', + 'Brannen', + 'Bixby', + 'Pohlman', + 'Strachan', + 'Lesher', + 'Fleischer', + 'Umana', + 'Murphey', + 'Mcentire', + 'Rabon', + 'Mcauley', + 'Bunton', + 'Soileau', + 'Sheriff', + 'Borowski', + 'Mullens', + 'Larrabee', + 'Prouty', + 'Malley', + 'Sumrall', + 'Reisinger', + 'Surber', + 'Kasten', + 'Shoemake', + 'Yowell', + 'Bonin', + 'Bevan', + 'Bove', + 'Boe', + 'Hazard', + 'Slay', + 'Carraway', + 'Kaczmarek', + 'Armitage', + 'Lowther', + 'Sheaffer', + 'Farah', + 'Atencio', + 'Ung', + 'Kirkham', + 'Cavanagh', + 'Mccutchen', + 'Shoop', + 'Nickles', + 'Borchardt', + 'Durkee', + 'Maus', + 'Shedd', + 'Petrillo', + 'Brainard', + 'Eddings', + 'Fanelli', + 'Seo', + 'Heaney', + 'Drennan', + 'Mcgarvey', + 'Saddler', + 'Lucia', + 'Higa', + 'Gailey', + 'Groh', + 'Hinckley', + 'Griner', + 'Norfleet', + 'Caplan', + 'Rademacher', + 'Souder', + 'Autrey', + 'Eskridge', + 'Drumm', + 'Fiske', + 'Giffin', + 'Townley', + 'Derose', + 'Burrus', + 'Castrejon', + 'Emmert', + 'Cothran', + 'Hartsell', + 'Kilburn', + 'Riggle', + 'Trussell', + 'Mulvey', + 'Barto', + 'Crank', + 'Lovely', + 'Woodhouse', + 'Powe', + 'Pablo', + 'Zack', + 'Murchison', + 'Dicarlo', + 'Kessel', + 'Hagood', + 'Rost', + 'Edson', + 'Blakeney', + 'Fant', + 'Brodeur', + 'Jump', + 'Spry', + 'Laguna', + 'Lotz', + 'Bergquist', + 'Collard', + 'Mash', + 'Rideout', + 'Bilbrey', + 'Selman', + 'Fortunato', + 'Holzer', + 'Pifer', + 'Mcabee', + 'Talamantes', + 'Tollefson', + 'Pastore', + 'Crew', + 'Wilcher', + 'Kutz', + 'Stallard', + 'Ressler', + 'Fehr', + 'Piercy', + 'Lafond', + 'Digiacomo', + 'Schuck', + 'Winkle', + 'Graybill', + 'Plata', + 'Gribble', + 'Odle', + 'Fraga', + 'Bressler', + 'Moultrie', + 'Tung', + 'Charette', + 'Marvel', + 'Kerby', + 'Mori', + 'Hamman', + 'Favors', + 'Freeze', + 'Delisle', + 'Straw', + 'Dingle', + 'Elizalde', + 'Cabello', + 'Zalewski', + 'Funkhouser', + 'Abate', + 'Nero', + 'Holston', + 'Josey', + 'Schreck', + 'Shroyer', + 'Paquin', + 'Bing', + 'Chauvin', + 'Maria', + 'Melgoza', + 'Arms', + 'Caddell', + 'Pitchford', + 'Sternberg', + 'Rana', + 'Lovelady', + 'Strouse', + 'Macarthur', + 'Lechuga', + 'Wolfson', + 'Mcglynn', + 'Koo', + 'Stoops', + 'Tetreault', + 'Lepage', + 'Duren', + 'Hartz', + 'Kissel', + 'Gish', + 'Largent', + 'Henninger', + 'Janson', + 'Carrick', + 'Kenner', + 'Haack', + 'Diego', + 'Wacker', + 'Wardell', + 'Ballentine', + 'Smeltzer', + 'Bibb', + 'Winton', + 'Bibbs', + 'Reinhard', + 'Nilsen', + 'Edison', + 'Kalinowski', + 'June', + 'Hewlett', + 'Blaisdell', + 'Zeman', + 'Chon', + 'Board', + 'Nealy', + 'Moretti', + 'Wanner', + 'Bonnett', + 'Hardie', + 'Mains', + 'Cordeiro', + 'Karim', + 'Kautz', + 'Craver', + 'Colucci', + 'Congdon', + 'Mounts', + 'Kurz', + 'Eder', + 'Merryman', + 'Soles', + 'Dulin', + 'Lubin', + 'Mcgowen', + 'Hockenberry', + 'Work', + 'Mazzola', + 'Crandell', + 'Mcgrady', + 'Caruthers', + 'Govea', + 'Meng', + 'Fetter', + 'Trusty', + 'Weintraub', + 'Hurlburt', + 'Reiff', + 'Nowakowski', + 'Hoard', + 'Densmore', + 'Blumenthal', + 'Neale', + 'Schiff', + 'Raleigh', + 'Steiger', + 'Marmolejo', + 'Jessie', + 'Palafox', + 'Tutt', + 'Keister', + 'Core', + 'Im', + 'Wendell', + 'Bennet', + 'Canning', + 'Krull', + 'Patti', + 'Zucker', + 'Schlesinger', + 'Wiser', + 'Dunson', + 'Olmedo', + 'Hake', + 'Champlin', + 'Braley', + 'Wheelock', + 'Geier', + 'Janis', + 'Turek', + 'Grindstaff', + 'Schaffner', + 'Deas', + 'Sirois', + 'Polito', + 'Bergin', + 'Schall', + 'Vineyard', + 'Pellegrini', + 'Corrado', + 'Oleson', + 'List', + 'Dameron', + 'Parkin', + 'Flake', + 'Hollingshead', + 'Chancey', + 'Hufford', + 'Morell', + 'Kantor', + 'Chasteen', + 'Laborde', + 'Sessoms', + 'Hermanson', + 'Burnell', + 'Dewberry', + 'Tolman', + 'Glasscock', + 'Durfee', + 'Gilroy', + 'Wilkey', + 'Dungan', + 'Saravia', + 'Weigand', + 'Bigler', + 'Vancleave', + 'Burlingame', + 'Roseman', + 'Stiffler', + 'Gagliano', + 'Kates', + 'Awad', + 'Knepp', + 'Rondeau', + 'Bertsch', + 'Wolverton', + 'Walcott', + 'Poss', + 'Frisby', + 'Wexler', + 'Reinhold', + 'Krol', + 'Stuck', + 'Ricciardi', + 'Ardoin', + 'Michaelson', + 'Lillard', + 'Burciaga', + 'Birchfield', + 'Patch', + 'Silvey', + 'Simmonds', + 'Siu', + 'Press', + 'Deans', + 'Riegel', + 'Ismail', + 'Magallon', + 'Diller', + 'Hine', + 'Michalak', + 'Dones', + 'Deitz', + 'Gulledge', + 'Stroman', + 'Kobayashi', + 'Hafer', + 'Berk', + 'Landin', + 'Gilles', + 'Obryant', + 'Cheeks', + 'Gress', + 'Lutes', + 'Raphael', + 'Pizano', + 'Bachmann', + 'Cifuentes', + 'Earp', + 'Gilreath', + 'Peluso', + 'Hubbs', + 'Alvis', + 'Peer', + 'Dutra', + 'Stetson', + 'Constant', + 'Benford', + 'Sorto', + 'Cater', + 'Rosier', + 'Isenberg', + 'Shanklin', + 'Veloz', + 'Ramage', + 'Dunford', + 'Ku', + 'Hames', + 'Eddins', + 'Ruano', + 'Frink', + 'Flower', + 'Beadle', + 'Rochester', + 'Fontes', + 'Mefford', + 'Barwick', + 'Millen', + 'Stelly', + 'Cann', + 'Rayner', + 'Carruth', + 'Wendling', + 'Shutt', + 'Hazzard', + 'Maravilla', + 'Gregorio', + 'Pavlik', + 'Hudnall', + 'Aston', + 'Mcglothlin', + 'Weise', + 'Devereaux', + 'Belle', + 'Borst', + 'Burdett', + 'Frisbie', + 'Rummel', + 'Rentz', + 'Cobos', + 'Kimura', + 'Neu', + 'Winner', + 'Candelario', + 'Callis', + 'Basso', + 'Mckim', + 'Tai', + 'Eskew', + 'Lair', + 'Pye', + 'Knuth', + 'Scarberry', + 'Alter', + 'Mcgann', + 'Anson', + 'Drews', + 'Zuckerman', + 'Petrone', + 'Ludlow', + 'Bechtold', + 'Nair', + 'Rennie', + 'Rhine', + 'Fleetwood', + 'Sudduth', + 'Leftwich', + 'Hardiman', + 'Northrop', + 'Banker', + 'Killen', + 'Mastin', + 'Mcmurry', + 'Jasinski', + 'Taliaferro', + 'Mathers', + 'Sheikh', + 'Nuss', + 'Jesse', + 'Zabel', + 'Crotty', + 'Kamp', + 'Fleenor', + 'Halcomb', + 'Eady', + 'Vella', + 'Demars', + 'Ensley', + 'Delosreyes', + 'Zendejas', + 'Leeds', + 'Just', + 'Oday', + 'Dills', + 'Zeng', + 'Barriga', + 'Millican', + 'Cascio', + 'Eakin', + 'Argo', + 'Borland', + 'Cover', + 'Diorio', + 'Coria', + 'Lease', + 'Pinkham', + 'Reichard', + 'Guadalupe', + 'Hansel', + 'Bye', + 'Westerfield', + 'Gales', + 'Mickle', + 'Licata', + 'Cram', + 'Bracy', + 'Motta', + 'Imhoff', + 'Siegfried', + 'Merry', + 'Swiger', + 'Ton', + 'Hersey', + 'Marrone', + 'Ginter', + 'Miele', + 'Breton', + 'Scheffler', + 'Pray', + 'Stapp', + 'Bogard', + 'Towner', + 'Mcelhaney', + 'Bridgewater', + 'Waldner', + 'Quijano', + 'Galante', + 'Quesenberry', + 'Rourke', + 'Harshman', + 'Traver', + 'Alvares', + 'Mcgaha', + 'Nyberg', + 'Pharr', + 'Lerch', + 'Sok', + 'Rosson', + 'Wiggs', + 'Mcelveen', + 'Dimaggio', + 'Rettig', + 'Ahumada', + 'Hetzel', + 'Welling', + 'Chadwell', + 'Swink', + 'Mckinzie', + 'Kwak', + 'Chabot', + 'Tomaszewski', + 'Bonanno', + 'Lesko', + 'Teter', + 'Stalnaker', + 'Ober', + 'Hovis', + 'Hosey', + 'Chaudhry', + 'Fey', + 'Vital', + 'Earhart', + 'Heins', + 'Crowther', + 'Hanner', + 'Behr', + 'Billington', + 'Vogler', + 'Hersh', + 'Perlman', + 'Given', + 'Files', + 'Partain', + 'Coddington', + 'Jardine', + 'Grimmett', + 'Springs', + 'Macomber', + 'Horgan', + 'Arrieta', + 'Charley', + 'Josephson', + 'Tupper', + 'Provenzano', + 'Celaya', + 'Mcvicker', + 'Sigala', + 'Wimer', + 'Ayon', + 'Dossantos', + 'Norvell', + 'Lorenzen', + 'Pasquale', + 'Lambright', + 'Goings', + 'Defelice', + 'Wen', + 'Sigman', + 'Gaylor', + 'Rehm', + 'Carino', + 'Werth', + 'Forehand', + 'Hanke', + 'Lasalle', + 'Mitchum', + 'Priester', + 'Lefler', + 'Celis', + 'Lesser', + 'Fitz', + 'Wentzel', + 'Lavery', + 'Klassen', + 'Shiflett', + 'Hedden', + 'Henn', + 'Coursey', + 'Drain', + 'Delorenzo', + 'Haws', + 'Stansberry', + 'Trump', + 'Dantzler', + 'Chaidez', + 'Mcsweeney', + 'Griffen', + 'Trail', + 'Gandara', + 'Brunk', + 'Kennon', + 'Coss', + 'Blackmore', + 'Metts', + 'Gluck', + 'Blackshear', + 'Cogan', + 'Boney', + 'Encinas', + 'Adamski', + 'Roberge', + 'Schuette', + 'Valero', + 'Barroso', + 'Antunez', + 'Mohammad', + 'Housley', + 'Escoto', + 'Ullrich', + 'Helman', + 'Trost', + 'Lafave', + 'Faith', + 'Blaney', + 'Kershner', + 'Hoehn', + 'Roemer', + 'Isley', + 'Lipinski', + 'Claus', + 'Caulfield', + 'Paiz', + 'Leyba', + 'Robinett', + 'Lambeth', + 'Tarpley', + 'Essex', + 'Eilers', + 'Epley', + 'Murdoch', + 'Sandstrom', + 'Laux', + 'Domingue', + 'Grundy', + 'Bellows', + 'Spindler', + 'Boos', + 'Bhatt', + 'Tye', + 'Salamone', + 'Cirillo', + 'Troup', + 'Jemison', + 'Calzada', + 'Dowden', + 'Geraci', + 'Dunphy', + 'Sack', + 'Sloane', + 'Hathcock', + 'Yap', + 'Ronquillo', + 'Willette', + 'Partlow', + 'Dear', + 'Tunstall', + 'Kiss', + 'Huhn', + 'Seabolt', + 'Beene', + 'Sather', + 'Lockridge', + 'Despain', + 'Wines', + 'Mcalpine', + 'Wadley', + 'Dey', + 'Loring', + 'Meadors', + 'Buettner', + 'Lavalley', + 'Bugg', + 'Creek', + 'Millett', + 'Pumphrey', + 'Fregoso', + 'Merkle', + 'Sheffer', + 'Glassman', + 'Groover', + 'Sweatt', + 'Colunga', + 'Boykins', + 'Seng', + 'Stutz', + 'Brann', + 'Blakey', + 'Munos', + 'Geddes', + 'Avendano', + 'Molitor', + 'Diedrich', + 'Langham', + 'Kindle', + 'Lacour', + 'Buckler', + 'Corum', + 'Bakke', + 'Godin', + 'Kerner', + 'Tobey', + 'Kubiak', + 'Hoyer', + 'Hedge', + 'Priebe', + 'Callison', + 'Lahr', + 'Shears', + 'Snavely', + 'Blatt', + 'Mcpeak', + 'Tinney', + 'Sullins', + 'Bernhard', + 'Gibb', + 'Vaillancourt', + 'Paugh', + 'Funes', + 'Romans', + 'Maurice', + 'Lough', + 'Kerwin', + 'Sanger', + 'Vierra', + 'Markus', + 'Comfort', + 'Krall', + 'Spies', + 'Malcom', + 'Vizcarra', + 'Beamer', + 'Kellerman', + 'Mcroberts', + 'Waterhouse', + 'Stromberg', + 'Persons', + 'Whitesell', + 'Harty', + 'Rosenblatt', + 'Broadwater', + 'Clardy', + 'Shackleford', + 'Jacquez', + 'Brittingham', + 'Lindahl', + 'Feliz', + 'Danna', + 'Garwood', + 'Heron', + 'Southwick', + 'Dehoyos', + 'Cottrill', + 'Mellor', + 'Goldfarb', + 'Grieco', + 'Helgeson', + 'Vandusen', + 'Heinen', + 'Batt', + 'Ruch', + 'Garretson', + 'Pankey', + 'Caudillo', + 'Jakubowski', + 'Plowman', + 'Starcher', + 'Wessels', + 'Moose', + 'Rosner', + 'Louden', + 'Walczak', + 'Poulsen', + 'Mcchesney', + 'Karns', + 'Casares', + 'Cusack', + 'Cespedes', + 'Cornelison', + 'Crossland', + 'Hirst', + 'Mier', + 'Roberto', + 'Canchola', + 'Bosse', + 'Shetler', + 'Melendrez', + 'Giannini', + 'Six', + 'Traynor', + 'Knepper', + 'Lonergan', + 'Kessinger', + 'Hollon', + 'Weathersby', + 'Stouffer', + 'Gingrich', + 'Breault', + 'Pompa', + 'Vanhoose', + 'Burdine', + 'Lark', + 'Stiltner', + 'Wunderlich', + 'Yong', + 'Merrifield', + 'Willhite', + 'Geiser', + 'Lambrecht', + 'Keffer', + 'Carlo', + 'Germany', + 'Turgeon', + 'Dame', + 'Tristan', + 'Bova', + 'Doak', + 'Mannino', + 'Shotwell', + 'Bash', + 'Coots', + 'Feist', + 'Mahmood', + 'Schlabach', + 'Salzman', + 'Kass', + 'Bresnahan', + 'Stonge', + 'Tesch', + 'Grajeda', + 'Mccarron', + 'Mcelwee', + 'Spradling', + 'Mckown', + 'Colgan', + 'Piedra', + 'Collum', + 'Stoffel', + 'Won', + 'Gulick', + 'Devault', + 'Enders', + 'Yanes', + 'Lansing', + 'Ebner', + 'Deegan', + 'Boutin', + 'Fetzer', + 'Andresen', + 'Trigg', + 'Sale', + 'Polite', + 'Hummer', + 'Wille', + 'Bowerman', + 'Routh', + 'Iqbal', + 'Lakey', + 'Mcadoo', + 'Laflamme', + 'Boulware', + 'Guadarrama', + 'Campana', + 'Strayer', + 'Aho', + 'Emmett', + 'Wolters', + 'Bos', + 'Knighten', + 'Averill', + 'Bhakta', + 'Schumaker', + 'Stutts', + 'Mejias', + 'Byer', + 'Mahone', + 'Staab', + 'Riehl', + 'Briceno', + 'Zabala', + 'Lafountain', + 'Clemmer', + 'Mansell', + 'Rossetti', + 'Lafontaine', + 'Mager', + 'Adamo', + 'Bogue', + 'Northern', + 'Disney', + 'Masse', + 'Senter', + 'Yaeger', + 'Dahlberg', + 'Bisson', + 'Leitner', + 'Bolding', + 'Ormsby', + 'Berard', + 'Brazell', + 'Pickle', + 'Hord', + 'Mcguigan', + 'Glennon', + 'Aman', + 'Dearman', + 'Cauthen', + 'Rembert', + 'Delucia', + 'Enciso', + 'Slusser', + 'Kratzer', + 'Schoenfeld', + 'Gillam', + 'Rael', + 'Rhode', + 'Moton', + 'Eide', + 'Eliason', + 'Helfrich', + 'Bish', + 'Goodnight', + 'Campion', + 'Blow', + 'Gerken', + 'Goldenberg', + 'Mellinger', + 'Nations', + 'Maiden', + 'Anzalone', + 'Wagers', + 'Arguelles', + 'Christen', + 'Guth', + 'Stamey', + 'Bozarth', + 'Balogh', + 'Grammer', + 'Chafin', + 'Prine', + 'Freer', + 'Alder', + 'Latorre', + 'Zaleski', + 'Lindholm', + 'Belisle', + 'Zacharias', + 'Swinson', + 'Bazemore', + 'Glazer', + 'Acord', + 'Said', + 'Liggins', + 'Lueck', + 'Luedtke', + 'Blackstone', + 'Copper', + 'Riker', + 'Braud', + 'Demello', + 'Rode', + 'Haven', + 'Rhee', + 'Galligan', + 'Record', + 'Nilson', + 'Ansley', + 'Pera', + 'Gilliard', + 'Copp', + 'Haugh', + 'Dunigan', + 'Grinnell', + 'Garr', + 'Leonhardt', + 'Elswick', + 'Shahan', + 'Mike', + 'Boddie', + 'Casella', + 'Mauricio', + 'Millet', + 'Daye', + 'Claussen', + 'Pierrelouis', + 'Fleischman', + 'Embrey', + 'Durso', + 'Whisenant', + 'Rankins', + 'Lasky', + 'Askins', + 'Rupe', + 'Rochelle', + 'Burkes', + 'Kreger', + 'Mishler', + 'Heald', + 'Jager', + 'Player', + 'Linehan', + 'Horwitz', + 'Jacobi', + 'Maine', + 'Wiest', + 'Ostrom', + 'Sealy', + 'Jimerson', + 'Alverson', + 'Senior', + 'Hassett', + 'Colter', + 'Schleicher', + 'Marini', + 'Mcbrayer', + 'Arzola', + 'Sobel', + 'Frederickson', + 'Confer', + 'Tadlock', + 'Belmonte', + 'Lebrun', + 'Clyde', + 'Alleyne', + 'Lozoya', + 'Teller', + 'Husband', + 'Brigman', + 'Secrest', + 'Krajewski', + 'Neiman', + 'Trull', + 'Watterson', + 'Vanhook', + 'Sotomayor', + 'Woodrum', + 'Baskerville', + 'Finke', + 'Hohman', + 'Arp', + 'Hearne', + 'Mauk', + 'Danko', + 'Laurie', + 'Linderman', + 'Hutt', + 'Springfield', + 'Chmielewski', + 'Klimek', + 'Phinney', + 'Leboeuf', + 'Mcglone', + 'Holmquist', + 'Cogswell', + 'Nichol', + 'Klink', + 'Dunston', + 'Krawczyk', + 'Dart', + 'Woodside', + 'Smitherman', + 'Gasca', + 'Sala', + 'Foxworth', + 'Kammerer', + 'Auer', + 'Pegues', + 'Bukowski', + 'Koger', + 'Spitz', + 'Blomquist', + 'Creasy', + 'Bomar', + 'Holub', + 'Loney', + 'Garry', + 'Habib', + 'Chea', + 'Dupuy', + 'Seaver', + 'Sowards', + 'Julius', + 'Fulks', + 'Braithwaite', + 'Bretz', + 'Mccammon', + 'Sedillo', + 'Chiasson', + 'Oney', + 'Horstman', + 'Waites', + 'Mccusker', + 'Fenske', + 'Conwell', + 'Brokaw', + 'Cloyd', + 'Biles', + 'Aguinaga', + 'Astorga', + 'Demaio', + 'Liberty', + 'Kayser', + 'Ney', + 'Barthel', + 'Lennox', + 'Trautman', + 'Purser', + 'Pitzer', + 'Mattos', + 'Liss', + 'Clack', + 'Sias', + 'Bobb', + 'Stoller', + 'Robillard', + 'Almodovar', + 'Cribb', + 'Ebel', + 'Oyler', + 'Dail', + 'Ericksen', + 'Geis', + 'Everitt', + 'Cropper', + 'Meisner', + 'Skeens', + 'Frith', + 'Privett', + 'Braddy', + 'Bolick', + 'Severance', + 'Jeffreys', + 'Bethune', + 'Delcid', + 'Buzzard', + 'Broadbent', + 'Bono', + 'Addis', + 'Johannes', + 'Tims', + 'Castorena', + 'Simonsen', + 'Glidewell', + 'Mui', + 'Ogilvie', + 'Soukup', + 'Sunday', + 'Redwine', + 'Borton', + 'Schuyler', + 'Rudisill', + 'Beckford', + 'Pascua', + 'Garton', + 'Gilkey', + 'Applewhite', + 'Halterman', + 'Alsup', + 'Delreal', + 'Hubble', + 'Quijada', + 'Kropp', + 'Dunkle', + 'Lemire', + 'Lamontagne', + 'Dunkin', + 'Paulin', + 'Attaway', + 'Baugher', + 'Hornbeck', + 'Niehaus', + 'Nice', + 'Trimmer', + 'Canaday', + 'Maney', + 'Trexler', + 'Schmucker', + 'Edinger', + 'Massengill', + 'Rowlett', + 'Caviness', + 'Kam', + 'Chesnut', + 'Giardina', + 'Spaeth', + 'Gebhart', + 'Morano', + 'Salguero', + 'Buckland', + 'Reina', + 'Jumper', + 'Navas', + 'Thrift', + 'Spradley', + 'Bitner', + 'Ayer', + 'Harber', + 'Landaverde', + 'Mcmillion', + 'Naugle', + 'Dole', + 'Seagraves', + 'Smithers', + 'Frechette', + 'Weeden', + 'Caston', + 'Cavallaro', + 'Laureano', + 'Mandell', + 'Lowrance', + 'Baty', + 'Ronan', + 'Gigliotti', + 'Rossiter', + 'Mines', + 'Alatorre', + 'Markowski', + 'Berge', + 'Hatter', + 'Weakley', + 'Borrero', + 'Glazier', + 'Lavergne', + 'Sines', + 'Ingham', + 'Meltzer', + 'Rabinowitz', + 'Siciliano', + 'Canas', + 'Perna', + 'Struck', + 'Dare', + 'Nay', + 'Severino', + 'Mathewson', + 'Bouldin', + 'Topete', + 'Brunette', + 'Sin', + 'Hendren', + 'Brickey', + 'Ferrier', + 'Alessi', + 'Scheel', + 'Storer', + 'Matherne', + 'Mecham', + 'Spiker', + 'Hibbert', + 'Klingensmith', + 'Lefever', + 'Banning', + 'Bankhead', + 'Roan', + 'Brack', + 'Pascoe', + 'Davie', + 'Scheid', + 'Jim', + 'Tweedy', + 'Strahan', + 'Revis', + 'Fermin', + 'Obrian', + 'Motes', + 'Lobo', + 'Palmisano', + 'Faught', + 'Byington', + 'Garren', + 'Hungerford', + 'Vanzandt', + 'Gust', + 'Heater', + 'Klingler', + 'Delay', + 'Wear', + 'Hendley', + 'Threatt', + 'Gaughan', + 'Kunze', + 'Hessler', + 'Lindell', + 'Monteleone', + 'Palazzolo', + 'Shear', + 'Phares', + 'Cavalier', + 'Benning', + 'Urbanski', + 'Darrah', + 'Wager', + 'Mohn', + 'Vereen', + 'Beiler', + 'Hedlund', + 'Quade', + 'Wieczorek', + 'Cicero', + 'Hoekstra', + 'Scalf', + 'Ducote', + 'Havard', + 'Espiritu', + 'Beacham', + 'Bolger', + 'Schuller', + 'Sill', + 'Dice', + 'Lemmons', + 'Orlowski', + 'Lundeen', + 'Steck', + 'Stanfill', + 'Rakes', + 'Laine', + 'Haviland', + 'Durrett', + 'Naumann', + 'Donahoe', + 'Reif', + 'Franck', + 'Amoroso', + 'Belknap', + 'Tolle', + 'Perrotta', + 'Heyer', + 'Dougan', + 'Frakes', + 'Leath', + 'Poteat', + 'Violette', + 'Marine', + 'Zellner', + 'Granillo', + 'Fontanez', + 'Didonato', + 'Bradberry', + 'Morman', + 'Mentzer', + 'Lamoureux', + 'Sabatino', + 'Catania', + 'Wenner', + 'Pastrana', + 'Shenk', + 'Losey', + 'Hepburn', + 'Antonucci', + 'Egger', + 'Higbee', + 'Adames', + 'Reep', + 'Cavallo', + 'Bridwell', + 'Villalba', + 'Poor', + 'Peet', + 'Everette', + 'Arney', + 'Towery', + 'Sharon', + 'Trainer', + 'Marrow', + 'Cumming', + 'Rimmer', + 'Stanger', + 'Pinter', + 'Felt', + 'Parrett', + 'Garrard', + 'Benedetto', + 'Lingenfelter', + 'Resch', + 'Billy', + 'Mikesell', + 'Osterman', + 'Trueblood', + 'Redfern', + 'Calderone', + 'Placencia', + 'Wamsley', + 'Warr', + 'Varnado', + 'Harshbarger', + 'Topping', + 'Feltner', + 'Decosta', + 'Tart', + 'Blumberg', + 'Shaikh', + 'Culley', + 'Bork', + 'Thibeault', + 'Stolz', + 'Ramsdell', + 'Tedford', + 'Noto', + 'Poulson', + 'Daves', + 'Altieri', + 'Mendosa', + 'Kisner', + 'Grafton', + 'Remy', + 'Hartline', + 'Cripe', + 'Sher', + 'Mulvaney', + 'Ansari', + 'Hartfield', + 'Whitton', + 'Wathen', + 'Eisele', + 'Hinojos', + 'Backer', + 'Speaks', + 'Schuetz', + 'Novoa', + 'Marcos', + 'Mask', + 'Oboyle', + 'Kircher', + 'Stang', + 'Sibert', + 'Scala', + 'Zacarias', + 'Hendon', + 'Halvorsen', + 'Montalbano', + 'Zermeno', + 'Vancamp', + 'Grams', + 'Hornberger', + 'Binion', + 'Dewald', + 'Rives', + 'Sankey', + 'Kleinman', + 'Falconer', + 'Rumph', + 'Matus', + 'Swett', + 'Spinner', + 'Depasquale', + 'Gamino', + 'Olmsted', + 'Absher', + 'Culler', + 'Fryman', + 'Lampert', + 'Carlyle', + 'Terranova', + 'Dunagan', + 'Chouinard', + 'Wesolowski', + 'Hetherington', + 'Scalise', + 'Pendergast', + 'Marcano', + 'Joubert', + 'Scheller', + 'Whisenhunt', + 'Lenoir', + 'Mahar', + 'Vanlandingham', + 'Pecoraro', + 'You', + 'Natividad', + 'Daum', + 'Penick', + 'Eddington', + 'Deleo', + 'Soltis', + 'Santucci', + 'Costanza', + 'Hiner', + 'Farlow', + 'Hartsock', + 'Duprey', + 'Fann', + 'Safford', + 'Murtha', + 'Fessler', + 'Chien', + 'Paynter', + 'Devera', + 'Hoelscher', + 'Boltz', + 'Deacon', + 'Loo', + 'Enoch', + 'Dilorenzo', + 'Saville', + 'Mirza', + 'Takacs', + 'Drexler', + 'Lakin', + 'Geraghty', + 'Widmer', + 'Esteves', + 'Llanes', + 'Cerny', + 'Quist', + 'Hargraves', + 'Toma', + 'Tarter', + 'Chapple', + 'Alderete', + 'Michelson', + 'Clymer', + 'Batey', + 'Sealey', + 'Loughlin', + 'Preece', + 'Zurita', + 'Courville', + 'Desousa', + 'Shamblin', + 'Tingley', + 'Noles', + 'Misner', + 'Standifer', + 'Dinardo', + 'Dillow', + 'Bullis', + 'Carballo', + 'Everly', + 'Mulvihill', + 'Tincher', + 'Carle', + 'Lundin', + 'Birdsall', + 'Bainbridge', + 'Suttle', + 'Wightman', + 'Mower', + 'Mountain', + 'Bickham', + 'Durante', + 'Viveros', + 'Swinford', + 'Mcgruder', + 'Tapley', + 'Grable', + 'Gwynn', + 'Wiebe', + 'Stagg', + 'Dash', + 'Heitman', + 'Cluff', + 'Huertas', + 'Fortuna', + 'Lines', + 'Sly', + 'Halford', + 'Helsel', + 'Bicknell', + 'Blakeman', + 'Colangelo', + 'Olney', + 'Quinton', + 'Rothrock', + 'Renz', + 'Hone', + 'Prejean', + 'Oshiro', + 'Serio', + 'Latour', + 'Newbold', + 'Fitzhugh', + 'Songer', + 'Cardin', + 'Geter', + 'Barbera', + 'Abbas', + 'Caesar', + 'Blakeslee', + 'Camper', + 'Mcclurg', + 'Driskill', + 'Cancel', + 'Donelson', + 'Borrelli', + 'Donoghue', + 'Shoaf', + 'Tinajero', + 'Arzate', + 'Keesee', + 'Pasley', + 'Strode', + 'Morello', + 'Trantham', + 'Ackerson', + 'Jowers', + 'Brockington', + 'Barcia', + 'Lipp', + 'Dinger', + 'Ridings', + 'Canavan', + 'Rank', + 'Hagans', + 'Lampley', + 'Beckmann', + 'Bjork', + 'Raygoza', + 'Schirmer', + 'Longmire', + 'Schiavone', + 'Breuer', + 'Lore', + 'Stenson', + 'Koziol', + 'Channell', + 'Cale', + 'Trader', + 'Culberson', + 'Mundt', + 'Sickles', + 'Nemec', + 'Holl', + 'Stribling', + 'Berens', + 'Nauman', + 'Lehner', + 'Deem', + 'Castelli', + 'Billman', + 'Orndorff', + 'Gumm', + 'Davy', + 'Pelham', + 'Spotts', + 'Jurgens', + 'Sword', + 'Adorno', + 'Gorrell', + 'Boughton', + 'Bobadilla', + 'Mauer', + 'Moline', + 'Guay', + 'Holsinger', + 'Baranowski', + 'Gutierres', + 'Beveridge', + 'Marable', + 'Berkey', + 'Lamothe', + 'Spitler', + 'Carbaugh', + 'Hoopes', + 'Wilken', + 'Milford', + 'Bingaman', + 'Crippen', + 'Shock', + 'Yarnell', + 'Oman', + 'Wethington', + 'Kost', + 'Gaudette', + 'Spielman', + 'Foran', + 'Starke', + 'Eugene', + 'Birnbaum', + 'Navarrette', + 'Hussein', + 'Ranson', + 'Hedgepeth', + 'Doctor', + 'Higuera', + 'Brough', + 'Cookson', + 'Provencher', + 'Mendonca', + 'Gowen', + 'Summer', + 'Rutz', + 'Reader', + 'Doud', + 'Raven', + 'Toribio', + 'Peachey', + 'Gunning', + 'Bittle', + 'Vale', + 'Harnish', + 'Marano', + 'Aker', + 'Damore', + 'Utz', + 'Throckmorton', + 'Bulger', + 'Vanzant', + 'Pasillas', + 'Holmgren', + 'Corpus', + 'Longley', + 'Wetmore', + 'Carstens', + 'Line', + 'Percival', + 'Ayotte', + 'Batres', + 'Pipes', + 'Ludwick', + 'Alpert', + 'Pick', + 'Carlock', + 'Edmundson', + 'Feinstein', + 'Krouse', + 'Dahlgren', + 'Sasaki', + 'Lieb', + 'Londono', + 'Oloughlin', + 'Wardlaw', + 'Lineberry', + 'Castello', + 'Milstead', + 'Parmenter', + 'Riffe', + 'Pare', + 'Sitton', + 'Tarin', + 'Delcastillo', + 'Manor', + 'Calabro', + 'Elkin', + 'Grill', + 'Boaz', + 'Coco', + 'Chamblee', + 'Celestine', + 'Nick', + 'Stork', + 'Meekins', + 'Moise', + 'Devers', + 'Jun', + 'Kegley', + 'Brick', + 'Lobato', + 'Biggerstaff', + 'Kersten', + 'Jayne', + 'Nasser', + 'Southall', + 'Kempton', + 'Eaddy', + 'Paladino', + 'Berardi', + 'Pizzo', + 'Pulver', + 'Ohalloran', + 'Fromm', + 'Cranston', + 'Rowden', + 'Capobianco', + 'Kahle', + 'Thiessen', + 'Malott', + 'Houseman', + 'Maul', + 'Gallion', + 'Tressler', + 'Pauly', + 'Pellerin', + 'Sainz', + 'Firth', + 'Cryer', + 'Jeanlouis', + 'Mong', + 'Trawick', + 'Chronister', + 'Hayashi', + 'Posner', + 'Cueva', + 'Sherwin', + 'Lacasse', + 'Gorden', + 'Bohl', + 'Twigg', + 'Coan', + 'Hocker', + 'Goodale', + 'Urbano', + 'Loeb', + 'Perrault', + 'Frawley', + 'Carcamo', + 'Richburg', + 'Moffat', + 'Hennings', + 'Weyer', + 'Myatt', + 'Ullman', + 'Tunnell', + 'Hern', + 'Lopresti', + 'Sonnenberg', + 'Knisley', + 'Twomey', + 'Jaggers', + 'Tanksley', + 'Rachal', + 'Poppe', + 'Vos', + 'Kania', + 'Speakman', + 'Peirce', + 'Pound', + 'Romer', + 'Patty', + 'Millsaps', + 'Kyser', + 'Telford', + 'Hegarty', + 'Kellett', + 'Michaelis', + 'Halligan', + 'Maughan', + 'Herb', + 'Rainer', + 'Robichaud', + 'Fiscus', + 'Sickler', + 'Blom', + 'Lavine', + 'Medel', + 'Bolyard', + 'Secor', + 'Creekmore', + 'Magruder', + 'Haskin', + 'Laliberte', + 'Drago', + 'Bernabe', + 'Leader', + 'Cavin', + 'Lukens', + 'Vassallo', + 'Pletcher', + 'Fuson', + 'Hasson', + 'Huckabee', + 'Edington', + 'Eichler', + 'Hering', + 'Vong', + 'Mardis', + 'Gu', + 'Segarra', + 'Bilyeu', + 'Runion', + 'Fragoso', + 'Gama', + 'Dunton', + 'Frady', + 'Lewellen', + 'Crumpler', + 'Jeske', + 'Furlow', + 'Delapena', + 'Kale', + 'Massengale', + 'Hamlet', + 'Galli', + 'Esteban', + 'Greeson', + 'Shue', + 'Pollak', + 'Pinney', + 'Ruffner', + 'Maitland', + 'Steven', + 'Hockett', + 'Fraire', + 'Mulhern', + 'Elbert', + 'Hoggard', + 'Labarge', + 'Silcox', + 'Saez', + 'Sluder', + 'Stamp', + 'Darlington', + 'Mccarroll', + 'Pillow', + 'Palazzo', + 'Blaha', + 'Demaria', + 'Swanger', + 'Winningham', + 'Lippincott', + 'Dake', + 'Goldsberry', + 'Seidl', + 'Woolfolk', + 'Murawski', + 'Hobart', + 'Kimber', + 'Nilsson', + 'Stough', + 'Almendarez', + 'Nevels', + 'Fasano', + 'Salmons', + 'Denmark', + 'Lathan', + 'Mosely', + 'Stengel', + 'Mendieta', + 'Felice', + 'Drown', + 'Vidrine', + 'Callihan', + 'Polston', + 'Howze', + 'Eakins', + 'Leek', + 'Featherstone', + 'Lajoie', + 'Athey', + 'Asuncion', + 'Ashbaugh', + 'Orman', + 'Morrissette', + 'Peart', + 'Hamner', + 'Zell', + 'Dry', + 'Dieter', + 'Terrones', + 'Campuzano', + 'Reveles', + 'Bakker', + 'Banister', + 'Arceo', + 'Dhillon', + 'Normand', + 'Shavers', + 'Ginsburg', + 'Go', + 'Rubinstein', + 'Arens', + 'Clutter', + 'Jaques', + 'Traxler', + 'Hackler', + 'Cisco', + 'Starrett', + 'Ceron', + 'Gillenwater', + 'Ottinger', + 'Caster', + 'Blakemore', + 'Thorsen', + 'Molinar', + 'Baur', + 'Hower', + 'Haldeman', + 'Oliveri', + 'Mcalpin', + 'Standish', + 'Bengtson', + 'Strack', + 'Cordoba', + 'Blackstock', + 'Barna', + 'Schantz', + 'Hawkinson', + 'Breese', + 'Saba', + 'Camden', + 'Gwaltney', + 'Corliss', + 'Smit', + 'Cruise', + 'Mcneese', + 'Duggins', + 'Laub', + 'Burman', + 'Kenworthy', + 'Spohn', + 'Santini', + 'Nuttall', + 'Willison', + 'Stjean', + 'Shabazz', + 'Manes', + 'Gerry', + 'Mclamb', + 'Koepke', + 'Reeser', + 'Ogburn', + 'Wegener', + 'Risinger', + 'Carrero', + 'Livermore', + 'Brewton', + 'Harsh', + 'Utterback', + 'Lecompte', + 'Schnabel', + 'Ting', + 'Honea', + 'Stryker', + 'Foshee', + 'Baptista', + 'Gravely', + 'Courson', + 'Goyette', + 'Leitch', + 'Tasker', + 'Laurence', + 'Reneau', + 'Voight', + 'Tilson', + 'Range', + 'Hallam', + 'Dufrene', + 'Boice', + 'Shrewsbury', + 'Sturges', + 'Lenard', + 'Sistrunk', + 'Weitz', + 'Carnevale', + 'Hepner', + 'Wehner', + 'Callen', + 'Oshaughnessy', + 'Wingert', + 'Mouser', + 'Palmore', + 'Rugg', + 'Elia', + 'Alcazar', + 'Avitia', + 'Penton', + 'Brisco', + 'Ambrosio', + 'Wardlow', + 'Leaf', + 'Rowles', + 'Buggs', + 'Dittmer', + 'Schweizer', + 'Puleo', + 'Vaden', + 'Haughton', + 'Cardinale', + 'Seguin', + 'Ruddy', + 'Minard', + 'Stalker', + 'Bennington', + 'Hilt', + 'Works', + 'Broadus', + 'Engels', + 'Haddix', + 'Buster', + 'Recker', + 'Bopp', + 'Wilton', + 'Costantino', + 'Boots', + 'Falkner', + 'Tennison', + 'Mcgary', + 'Holz', + 'Lofgren', + 'Putney', + 'Christner', + 'Fruge', + 'Vassar', + 'Vankirk', + 'Spoon', + 'Pearlman', + 'Guertin', + 'Meece', + 'Sartain', + 'Petterson', + 'Primm', + 'Cardillo', + 'Dryer', + 'Hartshorn', + 'Dane', + 'Chaisson', + 'Espitia', + 'Creager', + 'Disalvo', + 'Janik', + 'Parente', + 'Paiva', + 'Slaven', + 'Tague', + 'Kujawa', + 'Gruver', + 'Foor', + 'Frampton', + 'Prokop', + 'Mettler', + 'Collis', + 'Lamkin', + 'Shuey', + 'Tepper', + 'Colyer', + 'Masi', + 'Trumble', + 'Guice', + 'Hurwitz', + 'Windle', + 'Mccully', + 'Cutting', + 'Stotler', + 'Grullon', + 'Wagstaff', + 'Morfin', + 'Dehaan', + 'Noon', + 'Flesher', + 'Ferri', + 'Covell', + 'Coll', + 'Lucy', + 'Albaugh', + 'Testerman', + 'Gordillo', + 'Jepson', + 'Brinkerhoff', + 'Calle', + 'Crowl', + 'Mcelwain', + 'Chumley', + 'Brockett', + 'Thoms', + 'Revell', + 'Garzon', + 'Polak', + 'Rothenberg', + 'Socha', + 'Vallejos', + 'Felty', + 'Peguero', + 'Ping', + 'Tso', + 'Charleston', + 'Fedor', + 'Haider', + 'Abe', + 'Enlow', + 'Fifer', + 'Bumpus', + 'Keele', + 'Mcdavid', + 'Panek', + 'Scholten', + 'Dyess', + 'Heatherly', + 'Donohoe', + 'Hoban', + 'Griffey', + 'Corry', + 'Mcclean', + 'Plyler', + 'Feathers', + 'Adkison', + 'Killeen', + 'Hoeft', + 'Myhre', + 'Fiorentino', + 'Mcbeth', + 'Erazo', + 'Madson', + 'Fulbright', + 'Wilds', + 'Petrucci', + 'Mcgaughey', + 'Monteith', + 'Murguia', + 'Hausman', + 'Zukowski', + 'Shute', + 'Brisson', + 'Lain', + 'Runkle', + 'Hickok', + 'Caffrey', + 'Million', + 'Elson', + 'Peay', + 'Haga', + 'Ancheta', + 'Cordle', + 'Blas', + 'Carmen', + 'Pettiford', + 'Dimartino', + 'Spahr', + 'Mozingo', + 'Backman', + 'Stgeorge', + 'Konrad', + 'Buhler', + 'Mcelrath', + 'Oliveros', + 'Edelstein', + 'Cadet', + 'Gilmartin', + 'Munday', + 'Roane', + 'Desalvo', + 'Lepe', + 'Symons', + 'Shearin', + 'Linkous', + 'Cheshire', + 'Klemm', + 'Beagle', + 'Pooler', + 'Dewalt', + 'Esch', + 'Finnell', + 'Sinnott', + 'Kepler', + 'Toups', + 'Riccardi', + 'Caylor', + 'Tillis', + 'Messmer', + 'Rothschild', + 'Boutte', + 'Zumwalt', + 'Bohrer', + 'Elgin', + 'Kinley', + 'Schechter', + 'Gowan', + 'Pyne', + 'Cousin', + 'Hunsinger', + 'Fishel', + 'Edenfield', + 'Nadler', + 'Warman', + 'Bruhn', + 'Swint', + 'Lizotte', + 'Nardone', + 'Troxel', + 'Grindle', + 'Labrie', + 'Tao', + 'Olea', + 'Schermerhorn', + 'Stier', + 'Hettinger', + 'Farthing', + 'Roux', + 'Max', + 'Amburgey', + 'Auerbach', + 'Janzen', + 'Ortez', + 'Alejandre', + 'Peiffer', + 'Molinaro', + 'Burleigh', + 'Benites', + 'Ringler', + 'Hou', + 'Haffner', + 'Nace', + 'Crosson', + 'Karcher', + 'Neufeld', + 'Bayles', + 'Riemer', + 'Amezquita', + 'Cadwell', + 'Petrosky', + 'Swallow', + 'Minnis', + 'Krupp', + 'Nardi', + 'Orsini', + 'Diez', + 'Updike', + 'Gasser', + 'Rogerson', + 'Speicher', + 'Dubay', + 'Hollaway', + 'Teets', + 'Keown', + 'Center', + 'Blanding', + 'Whisler', + 'Spurlin', + 'Collin', + 'Greenawalt', + 'Tomes', + 'Leister', + 'Chatfield', + 'Helwig', + 'Reimers', + 'Andress', + 'Norcross', + 'Melnick', + 'Yearwood', + 'Defazio', + 'Kubik', + 'Bhatia', + 'Uddin', + 'Belmont', + 'Haden', + 'Bench', + 'Chilson', + 'Pegg', + 'Cane', + 'Goehring', + 'Lino', + 'Tyus', + 'Furey', + 'Castleman', + 'Heywood', + 'Leedy', + 'Holleman', + 'Villeda', + 'Mcveigh', + 'Carreiro', + 'Hocking', + 'Azar', + 'Blough', + 'Lieu', + 'Marcial', + 'Coblentz', + 'Hossain', + 'Weisberg', + 'Gardea', + 'Hoyos', + 'Lipsey', + 'Reger', + 'Clouser', + 'Bewley', + 'Magness', + 'Goines', + 'Thome', + 'Odea', + 'Mannion', + 'Dansby', + 'Dipasquale', + 'Constable', + 'Truelove', + 'Hubler', + 'Ulibarri', + 'Wymer', + 'Cron', + 'Hugo', + 'Hilderbrand', + 'Milazzo', + 'Vasques', + 'Sproul', + 'Shuford', + 'Chavers', + 'Kral', + 'Vecchio', + 'Mehl', + 'Rymer', + 'Henriksen', + 'Taulbee', + 'Hagy', + 'Ammerman', + 'Kagan', + 'Galdamez', + 'Krick', + 'Owsley', + 'Mullikin', + 'Beery', + 'Eccles', + 'Kleinschmidt', + 'Kloss', + 'Oldenburg', + 'Ospina', + 'Harbert', + 'Andujar', + 'Florian', + 'Antone', + 'Mcmillon', + 'Ceniceros', + 'Rippy', + 'Adkisson', + 'Stange', + 'Balmer', + 'Mazurek', + 'Dahlke', + 'Girouard', + 'Nickelson', + 'Perera', + 'Tullos', + 'Cioffi', + 'Bogdan', + 'Olivieri', + 'Petree', + 'Speights', + 'Jantz', + 'Collings', + 'Zellers', + 'Yarber', + 'Lafollette', + 'Rink', + 'Currin', + 'Chua', + 'Hartle', + 'Larocque', + 'Cuthbertson', + 'Ehrhardt', + 'Mara', + 'Rieck', + 'Lumley', + 'Anderton', + 'Hennigan', + 'Fabrizio', + 'Hutter', + 'Bruning', + 'Korman', + 'Haring', + 'Monette', + 'Woodyard', + 'Goggins', + 'Balzer', + 'Philbrick', + 'Bruder', + 'Hansford', + 'Averett', + 'Teske', + 'Mauck', + 'Billiot', + 'Collie', + 'Caffey', + 'Manos', + 'Buchan', + 'Birk', + 'Abdallah', + 'Featherston', + 'Koh', + 'Valera', + 'Deyo', + 'Buono', + 'Aubin', + 'Doody', + 'Pigott', + 'Peloquin', + 'Maniscalco', + 'Eisenhauer', + 'Biller', + 'Farwell', + 'Hartzog', + 'Brazier', + 'Talton', + 'Mcdougald', + 'Midgett', + 'Strout', + 'Spiers', + 'Eiland', + 'Garth', + 'Sequeira', + 'Noyola', + 'Petri', + 'Goodyear', + 'Dineen', + 'Bernardi', + 'Berns', + 'Coolidge', + 'Dorfman', + 'Dittman', + 'Zeno', + 'Hauer', + 'Finlay', + 'Ziemba', + 'Spillane', + 'Kays', + 'Ekstrom', + 'Hile', + 'Mckinstry', + 'Lesley', + 'Courtright', + 'Kuhlmann', + 'Verma', + 'Cripps', + 'Wigley', + 'Nickens', + 'Petrick', + 'Delozier', + 'Hardcastle', + 'Yamaguchi', + 'Romig', + 'Venezia', + 'Reading', + 'Redford', + 'Heng', + 'Anselmo', + 'Getty', + 'Marten', + 'Badgett', + 'Eisner', + 'Holtzman', + 'Stell', + 'Hiser', + 'Dustin', + 'Bordeaux', + 'Debolt', + 'Trevizo', + 'Eckard', + 'Follett', + 'Lal', + 'Dark', + 'Buskirk', + 'Roca', + 'Todaro', + 'Campanella', + 'Lindsley', + 'Wickman', + 'Pritt', + 'Cutlip', + 'Pokorny', + 'Friedlander', + 'Saari', + 'Casias', + 'Macneil', + 'Clyburn', + 'Kravitz', + 'Edgington', + 'Portis', + 'Culbreth', + 'Cuff', + 'Brouillette', + 'Artz', + 'Trudell', + 'Pledger', + 'Markovich', + 'Pisani', + 'Faller', + 'Sergent', + 'Hail', + 'Stabile', + 'Wait', + 'Mcilwain', + 'Eriksen', + 'Nee', + 'Boll', + 'Catanzaro', + 'Giuliano', + 'Oldfield', + 'Banas', + 'Ickes', + 'Vachon', + 'Gleeson', + 'Bailes', + 'Biehl', + 'Woodham', + 'Troupe', + 'Mcgoldrick', + 'Cappello', + 'Kirkendall', + 'Baisden', + 'Joshua', + 'Nicoletti', + 'Roesch', + 'Deatherage', + 'Matter', + 'Sheth', + 'Tynes', + 'Shaheen', + 'Wilbert', + 'Toles', + 'Sanner', + 'Bury', + 'Boman', + 'Bose', + 'Millner', + 'Eisen', + 'Couto', + 'Ide', + 'Howells', + 'Jiminez', + 'Crampton', + 'Monti', + 'Jelinek', + 'Morford', + 'Yeomans', + 'Turnbow', + 'Rolland', + 'Scheetz', + 'Arends', + 'Repp', + 'Hohn', + 'Paton', + 'Govan', + 'Fabela', + 'Mroz', + 'Bourassa', + 'Rizzi', + 'Froelich', + 'Molinari', + 'Lunde', + 'Navarre', + 'Alexandre', + 'Dearborn', + 'Lakes', + 'Foxx', + 'Jerez', + 'Lamanna', + 'Talarico', + 'Butera', + 'Riner', + 'Gros', + 'Champ', + 'Phoenix', + 'Vandeventer', + 'Samora', + 'Behling', + 'Karpinski', + 'Hosier', + 'Tufts', + 'Hobby', + 'Rohrbach', + 'Youngman', + 'Yeary', + 'Paisley', + 'Ben', + 'Villalta', + 'Hempel', + 'Giblin', + 'Lunt', + 'Hagar', + 'Lapoint', + 'Singley', + 'Shows', + 'Kesterson', + 'Bollman', + 'Stansell', + 'Yon', + 'Gabaldon', + 'Simental', + 'Zastrow', + 'Enloe', + 'Sasso', + 'Harkey', + 'Sansom', + 'Twyman', + 'Haslam', + 'Sowa', + 'Hunsberger', + 'Norberg', + 'Hornback', + 'Hanshaw', + 'Axtell', + 'Hoge', + 'Gantz', + 'Mccullum', + 'Blazek', + 'Scher', + 'Carlucci', + 'Jeong', + 'Tillett', + 'Woolridge', + 'Carberry', + 'Reck', + 'Nevin', + 'Armes', + 'Sidhu', + 'Wiesner', + 'Auman', + 'Teeters', + 'Rigg', + 'Moloney', + 'Feld', + 'Lucier', + 'Cardone', + 'Kilian', + 'Conder', + 'Horta', + 'Murakami', + 'Schaff', + 'Dresser', + 'Spray', + 'Hott', + 'Capuano', + 'Englund', + 'Rothe', + 'Ferree', + 'Nolt', + 'Triana', + 'Sanjuan', + 'Oller', + 'Brathwaite', + 'Richert', + 'Holdren', + 'Goree', + 'Branstetter', + 'Schimmel', + 'Jessop', + 'Nellis', + 'Sevier', + 'Rabb', + 'Mcmorris', + 'Lindo', + 'Littles', + 'Polzin', + 'Ranieri', + 'Reale', + 'Sturtevant', + 'Arnone', + 'Zamorano', + 'Keever', + 'Clow', + 'Corr', + 'Blaser', + 'Sheetz', + 'Llanos', + 'Belew', + 'Rusnak', + 'Brandes', + 'Eichhorn', + 'Guida', + 'Pucci', + 'Streit', + 'Renn', + 'Partee', + 'Rappaport', + 'Rosso', + 'Defeo', + 'Greve', + 'Schoch', + 'Langevin', + 'Manna', + 'Towe', + 'Scoville', + 'Marco', + 'Gove', + 'Mckissick', + 'Dangerfield', + 'Mcwhirter', + 'Port', + 'Marrufo', + 'Nicosia', + 'Farren', + 'Kinsley', + 'Pearman', + 'Porch', + 'Mooneyham', + 'Buff', + 'Ruben', + 'Blanc', + 'Mellen', + 'Heiman', + 'Novack', + 'Heston', + 'Huie', + 'Justin', + 'Kincade', + 'Laverty', + 'Villavicencio', + 'Burkart', + 'Offutt', + 'Halliburton', + 'Polo', + 'Barbara', + 'Trammel', + 'Rosati', + 'Sakamoto', + 'Salo', + 'Heyman', + 'Rooker', + 'Sarno', + 'Leroux', + 'Virgen', + 'Collison', + 'Branum', + 'Mcmasters', + 'Divine', + 'Mcnatt', + 'Threadgill', + 'Desir', + 'Borchers', + 'Walkup', + 'Sy', + 'Greenbaum', + 'Vidales', + 'Mercedes', + 'Selph', + 'Bardwell', + 'Whorton', + 'Demartino', + 'Endsley', + 'Verner', + 'Hillier', + 'Mancha', + 'Ricard', + 'Postell', + 'Kummer', + 'Welsch', + 'Almanzar', + 'Brunet', + 'Deeds', + 'Romanowski', + 'Ocallaghan', + 'Cueto', + 'Terhune', + 'Truesdell', + 'Whisnant', + 'Lingo', + 'Aden', + 'Labrecque', + 'Braga', + 'Iles', + 'Garrick', + 'Knickerbocker', + 'Rasberry', + 'Hervey', + 'Schill', + 'Kiely', + 'Liddle', + 'Blakeley', + 'Marez', + 'Schoonmaker', + 'Swinton', + 'Fryar', + 'Exum', + 'Gouge', + 'Hoskinson', + 'Lupton', + 'Guild', + 'Davisson', + 'Chidester', + 'Gravitt', + 'Lenox', + 'Pyatt', + 'Moberg', + 'Overholt', + 'Whiddon', + 'Foti', + 'Lipps', + 'Shankle', + 'Xiao', + 'Balentine', + 'Cesar', + 'Barreras', + 'Schroer', + 'Ram', + 'Eames', + 'Gutman', + 'Pardee', + 'Damiano', + 'Houchin', + 'Porto', + 'Leclerc', + 'Mahaney', + 'Deardorff', + 'Garey', + 'Trotta', + 'Lachapelle', + 'Suiter', + 'Ewert', + 'Costner', + 'Bever', + 'Charpentier', + 'Milewski', + 'Coffelt', + 'Schorr', + 'Leis', + 'Dasher', + 'Cullins', + 'Eveland', + 'Hornung', + 'Swingle', + 'Eudy', + 'Motter', + 'Silk', + 'Gadd', + 'Sidwell', + 'Sandusky', + 'Auld', + 'Mazariegos', + 'Hirt', + 'Zane', + 'Rickett', + 'Ritenour', + 'Goin', + 'Dipaolo', + 'Wolfgang', + 'Inouye', + 'Branton', + 'Rakestraw', + 'Kimbro', + 'Craighead', + 'Sandefur', + 'Foerster', + 'Wipf', + 'Wilkin', + 'Shoffner', + 'Overcash', + 'Simonetti', + 'Toomer', + 'Albino', + 'Eshelman', + 'Rockwood', + 'Pineiro', + 'Reames', + 'Cray', + 'Wulff', + 'Heider', + 'Bath', + 'Colletti', + 'Fiala', + 'Greenstein', + 'Moles', + 'Bashaw', + 'Adamczyk', + 'Finkel', + 'Kistner', + 'Manzi', + 'Ferretti', + 'Demarest', + 'Ahlers', + 'Lack', + 'Wedel', + 'Kinzer', + 'Sechrist', + 'Stickler', + 'Easterday', + 'Mallette', + 'Loehr', + 'Gessner', + 'Croce', + 'Stanko', + 'Innes', + 'Farfan', + 'Heady', + 'Chambless', + 'Balbuena', + 'Decicco', + 'Winsor', + 'Pereyra', + 'Zoller', + 'Ingles', + 'Churchwell', + 'Westlake', + 'Villagran', + 'Soderberg', + 'Thill', + 'Timmer', + 'Mccaleb', + 'Mckernan', + 'Vandergriff', + 'Yoho', + 'Crispin', + 'Dorton', + 'Fults', + 'Borne', + 'Maxie', + 'Bloomquist', + 'Kung', + 'Budde', + 'Weinstock', + 'Honey', + 'Diener', + 'Horak', + 'Tsui', + 'Zirkle', + 'Plum', + 'Heitz', + 'Manrique', + 'Balcom', + 'Napper', + 'Boese', + 'Stefan', + 'Kime', + 'Gautreaux', + 'Leverette', + 'Lemaire', + 'Danford', + 'Hollman', + 'Kuzma', + 'Swinehart', + 'Merriam', + 'Novick', + 'Stankiewicz', + 'Parkes', + 'Englehart', + 'Polansky', + 'Leclaire', + 'Magner', + 'Masson', + 'Mass', + 'Coogan', + 'Jepsen', + 'Pittenger', + 'Bump', + 'Hain', + 'Burchell', + 'Chesley', + 'Cawthon', + 'Dance', + 'Piccolo', + 'Lucey', + 'Ordway', + 'Recio', + 'Ginther', + 'Hauge', + 'Lesperance', + 'Suhr', + 'Ding', + 'Ogg', + 'Skiba', + 'Scannell', + 'Gillies', + 'Brame', + 'Schipper', + 'Brune', + 'Stuber', + 'Pesce', + 'Stead', + 'Bushong', + 'Juneau', + 'Mccalla', + 'Feder', + 'Plaisance', + 'Tweed', + 'Hashimoto', + 'Mounce', + 'Diana', + 'Savala', + 'Vanek', + 'Lamson', + 'Dubin', + 'Killebrew', + 'Kan', + 'Nault', + 'Mulford', + 'Salamanca', + 'Linker', + 'Penrose', + 'Kowalewski', + 'Platz', + 'Kogan', + 'Martucci', + 'Gutowski', + 'Mattes', + 'Haigh', + 'Merida', + 'Ashman', + 'Batton', + 'Biondo', + 'Sweigart', + 'Sorg', + 'Barrier', + 'Gatling', + 'Geib', + 'Henrich', + 'Dabrowski', + 'Vara', + 'Weikel', + 'Jarosz', + 'Mummert', + 'Uriarte', + 'Fifield', + 'Locker', + 'Merlo', + 'Lasater', + 'Ripple', + 'Hopwood', + 'Sherrell', + 'Ruark', + 'Litz', + 'Kinkade', + 'Simkins', + 'Grandy', + 'Lemasters', + 'Wehr', + 'Jinks', + 'Alas', + 'Bale', + 'Stimpson', + 'Glickman', + 'Hage', + 'Seabrook', + 'Stirling', + 'Rozell', + 'Woodburn', + 'Braaten', + 'Sugg', + 'Linde', + 'Castille', + 'Grewal', + 'Blackwelder', + 'Hover', + 'Spurling', + 'Mckellar', + 'Muench', + 'Bovee', + 'Amado', + 'Yau', + 'Harger', + 'Lederer', + 'Seda', + 'Doney', + 'Kimes', + 'Western', + 'Foret', + 'Luera', + 'Warnke', + 'Bussard', + 'Cartier', + 'Andreasen', + 'Lagasse', + 'Topper', + 'Nyman', + 'Hallberg', + 'Whisman', + 'Cremeans', + 'Dewar', + 'Garrow', + 'Odaniel', + 'Stabler', + 'Bourg', + 'Appling', + 'Dahlstrom', + 'Fujimoto', + 'Prudhomme', + 'Gum', + 'Nau', + 'Hiers', + 'Rockett', + 'Sobczak', + 'Traub', + 'Bevis', + 'Tilghman', + 'Plasencia', + 'Sison', + 'Blau', + 'Abbate', + 'Sisler', + 'Rudder', + 'Trotman', + 'Brust', + 'Lederman', + 'Frahm', + 'Fredette', + 'Parise', + 'Urso', + 'Amann', + 'Kaul', + 'Woolery', + 'Thielen', + 'Symonds', + 'Marcy', + 'Wiltshire', + 'Sustaita', + 'Botkin', + 'Kernan', + 'Doolin', + 'Babineaux', + 'Greenspan', + 'Delacerda', + 'Kinnard', + 'Twitty', + 'Augustus', + 'Corriveau', + 'Stults', + 'Toman', + 'Sklar', + 'Leber', + 'Considine', + 'Ohearn', + 'Deforest', + 'Mcmann', + 'Farquhar', + 'Ferrel', + 'Bickley', + 'Manno', + 'Vreeland', + 'Berthiaume', + 'Mcentee', + 'Summerfield', + 'Woodrow', + 'Reynaga', + 'Soltero', + 'Tomko', + 'Jarboe', + 'Allmon', + 'Duplessis', + 'Sydnor', + 'Diallo', + 'Cogar', + 'Mandeville', + 'Shimizu', + 'Aubuchon', + 'Gabbert', + 'Ashlock', + 'Macri', + 'Weng', + 'Walser', + 'Teng', + 'Bailon', + 'Steeves', + 'Perillo', + 'Quattlebaum', + 'Knipp', + 'Delavega', + 'Kirtley', + 'Bramble', + 'Sublett', + 'Borchert', + 'Doria', + 'Session', + 'Merced', + 'Lundstrom', + 'Bluhm', + 'Cortinas', + 'Proper', + 'Sieber', + 'Mccay', + 'Wilford', + 'Asberry', + 'Muldrow', + 'Berning', + 'Hemenway', + 'Millman', + 'Ewers', + 'Timko', + 'Reding', + 'Sayer', + 'Pickel', + 'Cogburn', + 'Chappel', + 'Custodio', + 'Reichel', + 'Robeson', + 'Waid', + 'Wagler', + 'Sappington', + 'Bart', + 'Zazueta', + 'Najar', + 'Marko', + 'Nally', + 'States', + 'Bellard', + 'Marciano', + 'Killough', + 'Cosper', + 'Sangster', + 'Heinze', + 'Bortz', + 'Matamoros', + 'Nuckols', + 'Townsley', + 'Bak', + 'Ralls', + 'Ferrin', + 'Villela', + 'Siegrist', + 'Arora', + 'Collinsworth', + 'Masten', + 'Deer', + 'Balog', + 'Buchman', + 'Scaggs', + 'Holeman', + 'Lefkowitz', + 'Santora', + 'Funke', + 'Redfield', + 'Douthit', + 'Marciniak', + 'Twitchell', + 'Sheahan', + 'Dai', + 'Demuth', + 'Ganz', + 'Bruckner', + 'Wier', + 'Alamo', + 'Aultman', + 'Chubb', + 'Branco', + 'Courter', + 'Vivian', + 'Guin', + 'Witten', + 'Glen', + 'Hyer', + 'Crowson', + 'Arendt', + 'Cipolla', + 'Prochaska', + 'Schober', + 'Harte', + 'Arciniega', + 'Beier', + 'Middlebrook', + 'Dennard', + 'Vantassel', + 'Weekes', + 'Penley', + 'Lozier', + 'Lamberson', + 'Broomfield', + 'Nygaard', + 'Pascale', + 'Hyden', + 'Mundell', + 'Kamara', + 'Ehlert', + 'Mangus', + 'Bornstein', + 'Benedetti', + 'Erikson', + 'Quint', + 'Westman', + 'Basler', + 'Smoak', + 'Leavell', + 'Kerber', + 'Kopec', + 'Emrick', + 'Mattice', + 'Render', + 'Mccree', + 'Feldmann', + 'Cutright', + 'Randell', + 'Drucker', + 'Gilmour', + 'Marconi', + 'Stripling', + 'Mucha', + 'Shipe', + 'Chalk', + 'Martone', + 'Lema', + 'Ricardo', + 'Cobian', + 'Laufer', + 'Mistretta', + 'Shortt', + 'Menzel', + 'Wickline', + 'Oddo', + 'Chai', + 'Rabideau', + 'Stogner', + 'Mckie', + 'Luongo', + 'Trieu', + 'Breshears', + 'Sturdevant', + 'Abernethy', + 'Rohan', + 'Bonnette', + 'Steffes', + 'Straka', + 'Lawhon', + 'Shawver', + 'Guilford', + 'Wiltz', + 'Digregorio', + 'Warburton', + 'Fleshman', + 'Kerstetter', + 'Byram', + 'Obannon', + 'Dalessio', + 'Gatti', + 'Kalb', + 'Boris', + 'Graver', + 'Parkins', + 'Kollar', + 'Crothers', + 'Patin', + 'Cutshall', + 'Fern', + 'Derosier', + 'Goodrum', + 'Kaelin', + 'Baynes', + 'Beesley', + 'Macintyre', + 'Butters', + 'Kinsman', + 'Huffer', + 'Eslinger', + 'Prunty', + 'Boehmer', + 'Nusbaum', + 'Gouveia', + 'Mire', + 'Mccary', + 'Mikell', + 'Petrovich', + 'Melillo', + 'Kennelly', + 'Howley', + 'Merwin', + 'Cotner', + 'Kanter', + 'Sahagun', + 'Bodden', + 'Mcconville', + 'Leddy', + 'Auten', + 'Downie', + 'Armistead', + 'Goudy', + 'Gerhard', + 'Theiss', + 'Lauria', + 'Tuthill', + 'Ammon', + 'Ikeda', + 'Schultheis', + 'Zhong', + 'Pearcy', + 'Vass', + 'Essary', + 'Wendland', + 'Zehr', + 'Hartigan', + 'Ugalde', + 'Mossman', + 'Hartwick', + 'Joaquin', + 'Andreas', + 'Bartee', + 'Gajewski', + 'Gallaway', + 'Comerford', + 'Lieber', + 'Wireman', + 'Damm', + 'Yousif', + 'Kosinski', + 'Kelm', + 'Durrant', + 'Derouen', + 'Bonk', + 'Rubalcaba', + 'Opperman', + 'Decamp', + 'Fairfield', + 'Pauls', + 'Dicicco', + 'Northup', + 'Woerner', + 'Stegman', + 'Ritch', + 'Bedoya', + 'Jeanpierre', + 'Rioux', + 'Strohl', + 'Herrell', + 'Simonton', + 'Carriere', + 'Pridemore', + 'Karam', + 'Marple', + 'Topp', + 'Heiden', + 'Leibowitz', + 'Morabito', + 'Junker', + 'Calixto', + 'Hardt', + 'Silverio', + 'Swords', + 'Rickey', + 'Roussel', + 'Earles', + 'Bastien', + 'Defilippo', + 'Bigley', + 'Mosteller', + 'Issa', + 'Prout', + 'Grossi', + 'Bartos', + 'Lipman', + 'Colegrove', + 'Stpeter', + 'Vanfleet', + 'Fordyce', + 'Risher', + 'Royston', + 'Shoulders', + 'Mendel', + 'Statler', + 'Dantonio', + 'Inglis', + 'Fogleman', + 'Loveday', + 'Straus', + 'Luft', + 'Dam', + 'Chewning', + 'Winkel', + 'Bousquet', + 'Eckhart', + 'Dillinger', + 'Locascio', + 'Shellenberger', + 'Duerr', + 'Alcocer', + 'Licht', + 'Gingras', + 'Grassi', + 'Gately', + 'Padula', + 'Brien', + 'Nimmo', + 'Nell', + 'Bondurant', + 'Hughley', + 'Schalk', + 'Cabrales', + 'Heinemann', + 'Meunier', + 'Maddock', + 'Noone', + 'Brackin', + 'Dunnigan', + 'Sargeant', + 'Kinchen', + 'Veras', + 'Gile', + 'Bacchus', + 'Ang', + 'Cowgill', + 'Currey', + 'Garlick', + 'Manus', + 'Ballance', + 'Robitaille', + 'Begin', + 'Mijares', + 'Keogh', + 'Wicklund', + 'Mccurley', + 'Truett', + 'Pullin', + 'Alkire', + 'Loughran', + 'Mort', + 'Tatman', + 'Wanamaker', + 'Haralson', + 'Harrah', + 'Stucker', + 'Reda', + 'Pascal', + 'Holter', + 'Solares', + 'Bruck', + 'Mah', + 'Didomenico', + 'Korth', + 'Virgil', + 'Nishimura', + 'Vacca', + 'Stenberg', + 'Tomczak', + 'Sayler', + 'Chasse', + 'Blazer', + 'Sleeper', + 'Doiron', + 'Nunnery', + 'Ortman', + 'Maag', + 'Cali', + 'Ferrera', + 'Hotaling', + 'Festa', + 'Murr', + 'Sterrett', + 'Cuthbert', + 'Clayborn', + 'Pendergraft', + 'Yoakum', + 'Baily', + 'Overbey', + 'Warne', + 'Hokanson', + 'Tafolla', + 'Puglisi', + 'Wooster', + 'Nassar', + 'Lesniak', + 'Noack', + 'Beres', + 'Liberatore', + 'Guyette', + 'Duffin', + 'Ishmael', + 'Dolezal', + 'Larimer', + 'Musso', + 'Borman', + 'Deemer', + 'Hobgood', + 'Triggs', + 'Mau', + 'Wainscott', + 'Seth', + 'Hodnett', + 'Mckeehan', + 'Toon', + 'Evens', + 'Drost', + 'Roehl', + 'Trapani', + 'Bains', + 'Modica', + 'Arcos', + 'Knopf', + 'Salvo', + 'Garlock', + 'Lounsbury', + 'Hennen', + 'Drescher', + 'Morgenstern', + 'Studebaker', + 'Nordin', + 'Madore', + 'Joslyn', + 'Brousseau', + 'Addy', + 'Audette', + 'Santibanez', + 'Sauers', + 'Engelman', + 'Mauney', + 'Arechiga', + 'Eckel', + 'Jerry', + 'Pernell', + 'Sedlacek', + 'Mcnary', + 'Loewen', + 'Eyler', + 'Feather', + 'Mckinnie', + 'Bowersox', + 'Laclair', + 'Melby', + 'Thoman', + 'Hose', + 'Carmon', + 'Bartram', + 'Berggren', + 'Rogge', + 'Seto', + 'Court', + 'Deskins', + 'Barcus', + 'Putt', + 'Minick', + 'Durgin', + 'Hockman', + 'Keltner', + 'Legaspi', + 'Wallach', + 'Ranney', + 'Borger', + 'Wakeman', + 'Schoolcraft', + 'Souther', + 'Villani', + 'Sauder', + 'Chupp', + 'Slover', + 'Faul', + 'Degroat', + 'Hakim', + 'Brucker', + 'Moylan', + 'Castilleja', + 'Whetzel', + 'Eanes', + 'Brouwer', + 'Okelley', + 'Crimmins', + 'Bargas', + 'Jo', + 'Clover', + 'Adan', + 'Domingues', + 'Yelton', + 'Lobdell', + 'Mattis', + 'Escudero', + 'Pentecost', + 'Riser', + 'Lorentz', + 'Neace', + 'Caplinger', + 'Lipe', + 'Satterlee', + 'Labarbera', + 'Cullison', + 'Goggin', + 'Coke', + 'Keo', + 'Buckmaster', + 'Holtzclaw', + 'Lustig', + 'Ellinger', + 'Lollar', + 'Cork', + 'Mccrae', + 'Hilario', + 'Yawn', + 'Arnette', + 'Yuhas', + 'Wardle', + 'Pixley', + 'Leflore', + 'Fluker', + 'Krier', + 'Wind', + 'Ditto', + 'Rorie', + 'Ensminger', + 'Hunsucker', + 'Levenson', + 'Millington', + 'Gorsuch', + 'Willems', + 'Fredricks', + 'Agarwal', + 'Lariviere', + 'Don', + 'Chery', + 'Pfeil', + 'Wurtz', + 'Remillard', + 'Cozad', + 'Hodgkins', + 'Cohan', + 'Nurse', + 'Espana', + 'Giguere', + 'Hoskin', + 'Pettaway', + 'Keifer', + 'Yandell', + 'Frandsen', + 'Nawrocki', + 'Vila', + 'Pouliot', + 'Boulanger', + 'Pruden', + 'Strauch', + 'Lua', + 'Rohn', + 'Greig', + 'Lightsey', + 'Etheredge', + 'Hara', + 'Ensign', + 'Ruckman', + 'Senecal', + 'Sedgwick', + 'Maciejewski', + 'Morningstar', + 'Creswell', + 'Britten', + 'Godley', + 'Laubach', + 'Schwenk', + 'Hayhurst', + 'Cammarata', + 'Paxson', + 'Mcmurtry', + 'Marasco', + 'Weatherby', + 'Fales', + 'Fondren', + 'Deherrera', + 'Gaydos', + 'Defranco', + 'Bjorklund', + 'Silberman', + 'Maxon', + 'Rockey', + 'Brass', + 'Marcoux', + 'Marquette', + 'Marcello', + 'Veit', + 'Debose', + 'Cloninger', + 'Puccio', + 'Greenman', + 'Bross', + 'Lile', + 'Behan', + 'Plumlee', + 'Hampson', + 'Steverson', + 'Wininger', + 'Mcmullan', + 'Jude', + 'Sharif', + 'Rothermel', + 'Becher', + 'Keithley', + 'Gargano', + 'Morillo', + 'Dumond', + 'Johannsen', + 'Baney', + 'Lipton', + 'Railey', + 'Clowers', + 'Rotondo', + 'Simeone', + 'Hatt', + 'Schexnayder', + 'Snoddy', + 'Gelinas', + 'Mendelson', + 'Matherly', + 'Klock', + 'Clubb', + 'Dunkley', + 'Rosenzweig', + 'Chuang', + 'Gines', + 'Galasso', + 'Helland', + 'Rohrbaugh', + 'Avilez', + 'Czajkowski', + 'Olsson', + 'Lumsden', + 'Birt', + 'Ortego', + 'Acuff', + 'Yetter', + 'Tichenor', + 'Mork', + 'Skillman', + 'Row', + 'Lollis', + 'Wolk', + 'Demott', + 'Lazenby', + 'Bellew', + 'Brickner', + 'Ragusa', + 'Stice', + 'Herlihy', + 'Guillermo', + 'Estabrook', + 'Montijo', + 'Jenner', + 'Rayfield', + 'Donlon', + 'Greenhalgh', + 'Alberti', + 'Rix', + 'Holthaus', + 'Mistry', + 'Ruzicka', + 'Sievert', + 'Koopman', + 'Kalish', + 'Kehl', + 'Ponte', + 'Varnell', + 'Guss', + 'Kovac', + 'Hosmer', + 'Scrivner', + 'Tomblin', + 'Villafuerte', + 'Branscum', + 'Nitz', + 'Reider', + 'Gaunt', + 'Richerson', + 'Hemmer', + 'Vinyard', + 'Barrie', + 'Manalo', + 'Flynt', + 'Cadle', + 'Hau', + 'Uy', + 'Manfredi', + 'Deeter', + 'Resto', + 'Carnell', + 'Drane', + 'Cusumano', + 'Fein', + 'Schneck', + 'Stucky', + 'Heid', + 'Bruggeman', + 'Schweiger', + 'Vanetten', + 'Munsey', + 'Kiker', + 'Whittier', + 'Seeman', + 'Zerbe', + 'Hillyer', + 'Burkhead', + 'Gafford', + 'Gephart', + 'Braman', + 'Plott', + 'Henriques', + 'Coppock', + 'Mcandrews', + 'Valtierra', + 'Dileo', + 'Stiner', + 'Mikel', + 'Owensby', + 'Gupton', + 'Scurlock', + 'Gittens', + 'Degnan', + 'Guillaume', + 'Helmuth', + 'Nolin', + 'Mair', + 'Bergeson', + 'Paik', + 'Kinne', + 'Goodloe', + 'Nakagawa', + 'Raposo', + 'Defreitas', + 'Korb', + 'Hinkel', + 'Magers', + 'Althoff', + 'Rafael', + 'Akhtar', + 'Cashion', + 'Mcquillan', + 'Patricio', + 'Sweeny', + 'Meaux', + 'Tyre', + 'Demeo', + 'Trivedi', + 'Goodfellow', + 'Dunleavy', + 'Middaugh', + 'Barbato', + 'Pasco', + 'Harland', + 'Shorts', + 'Mowrey', + 'Dempster', + 'Knuckles', + 'Luebke', + 'Petrella', + 'Retana', + 'Licea', + 'Rundle', + 'Cape', + 'Lou', + 'Mcconkey', + 'Leeman', + 'Cabe', + 'Timothy', + 'Crochet', + 'Fulgham', + 'Glasco', + 'Backes', + 'Konopka', + 'Mcquaid', + 'Schley', + 'Abrahams', + 'Dahlin', + 'Iversen', + 'Chico', + 'Huffaker', + 'Modlin', + 'Laduke', + 'Marquart', + 'Motz', + 'Keech', + 'Louviere', + 'Como', + 'Fye', + 'Brightwell', + 'Yamashita', + 'Desrochers', + 'Richer', + 'Bourke', + 'Broadhead', + 'Pink', + 'Okamoto', + 'Chicas', + 'Vanatta', + 'Shick', + 'Furst', + 'Layfield', + 'Mcewan', + 'Baumgart', + 'Kappel', + 'Kucharski', + 'Quam', + 'Taub', + 'Houghtaling', + 'Sundquist', + 'Monks', + 'Wake', + 'Quiros', + 'Pursell', + 'Johansson', + 'Talkington', + 'Bast', + 'Stimson', + 'Hakes', + 'Loe', + 'Caggiano', + 'Schaper', + 'Chandra', + 'Tuma', + 'Arledge', + 'Romain', + 'Hornick', + 'Bridgman', + 'Livingstone', + 'Potvin', + 'Sparling', + 'Hause', + 'Trosclair', + 'Pless', + 'Szeto', + 'Clontz', + 'Lauber', + 'Detrick', + 'Dominique', + 'Mosser', + 'Degraff', + 'Liner', + 'Fleet', + 'Czerwinski', + 'Kopf', + 'Kovar', + 'Sheedy', + 'Zaremba', + 'Mina', + 'Sweeten', + 'Ou', + 'Musto', + 'Hennig', + 'Bangs', + 'Pasternak', + 'Berrier', + 'Smidt', + 'Brayton', + 'Claytor', + 'Ellerbe', + 'Reiman', + 'Larimore', + 'Ratzlaff', + 'Mudge', + 'Ni', + 'Spillers', + 'Cuomo', + 'Gerke', + 'Polizzi', + 'Harmer', + 'Apperson', + 'Regis', + 'Ugarte', + 'Paull', + 'Lagrange', + 'Dinwiddie', + 'Becton', + 'Gadsden', + 'Conforti', + 'Desoto', + 'Orme', + 'Filer', + 'Viers', + 'Lares', + 'Stair', + 'Hipps', + 'Kaneshiro', + 'Ladson', + 'Altizer', + 'Montejano', + 'Scalzo', + 'Sowder', + 'Ebeling', + 'Faucher', + 'Dicken', + 'Sartor', + 'Mcnerney', + 'Stage', + 'Mika', + 'Hice', + 'Grinstead', + 'Bartsch', + 'Mccumber', + 'Lenahan', + 'Liska', + 'Tietz', + 'Gauna', + 'Janda', + 'Bellis', + 'Shew', + 'Kelton', + 'Doby', + 'Golson', + 'Plaster', + 'Gonsales', + 'Krone', + 'Lape', + 'Lowrie', + 'Polly', + 'Gerardi', + 'Lamoreaux', + 'Bhatti', + 'Kimsey', + 'Buhl', + 'Arvin', + 'Gillian', + 'Benbow', + 'Roesler', + 'Stlaurent', + 'Canon', + 'Swihart', + 'Corea', + 'Petitt', + 'Spates', + 'Nappi', + 'Sebring', + 'Smelser', + 'Eckenrode', + 'Palos', + 'Disanto', + 'Tabares', + 'Okane', + 'Easterly', + 'Dendy', + 'Whigham', + 'Bednarz', + 'Wedge', + 'Edelen', + 'Stiff', + 'Borjas', + 'Obando', + 'Mcspadden', + 'Breed', + 'Dismuke', + 'Jarmon', + 'Serpa', + 'Lucky', + 'Cournoyer', + 'Hedberg', + 'Martine', + 'Michell', + 'Wittig', + 'Clodfelter', + 'Davids', + 'Gattis', + 'Kull', + 'Mascorro', + 'Schad', + 'Rine', + 'Bradburn', + 'Marie', + 'Czech', + 'Sunderman', + 'Wickersham', + 'Toohey', + 'Capozzi', + 'Poplin', + 'Markland', + 'Brosnan', + 'Fetterman', + 'Heiss', + 'Haglund', + 'Jourdan', + 'Turnipseed', + 'Tiernan', + 'Horrocks', + 'Barnhardt', + 'Sing', + 'Belford', + 'Baumgarten', + 'Klee', + 'Degeorge', + 'Caulder', + 'Gladstone', + 'Dancer', + 'Satchell', + 'Vento', + 'Larock', + 'Kimberly', + 'Hunn', + 'Harvin', + 'Krahn', + 'Ogorman', + 'Storch', + 'Coomes', + 'Bevilacqua', + 'Crotts', + 'Schillinger', + 'Morelock', + 'Hayworth', + 'Avis', + 'Cranmer', + 'Getchell', + 'Tena', + 'Buzzell', + 'Widman', + 'Barter', + 'Lafayette', + 'Asencio', + 'Embree', + 'Krell', + 'Siders', + 'Fuselier', + 'Whitby', + 'Elsner', + 'Pando', + 'Surface', + 'Rolf', + 'Highland', + 'Bufford', + 'Scheidt', + 'Defrancesco', + 'Fellers', + 'Carrol', + 'Germano', + 'Licon', + 'Hilty', + 'Ringo', + 'Dowler', + 'Glowacki', + 'Slabaugh', + 'Tomasello', + 'Messing', + 'Lavalle', + 'Milo', + 'Frerichs', + 'Plotkin', + 'Ziolkowski', + 'Gentle', + 'Knobloch', + 'Larochelle', + 'Duell', + 'Hurdle', + 'Speller', + 'Ceasar', + 'Vinci', + 'Mosquera', + 'Wyse', + 'Towler', + 'Ayoub', + 'Gullickson', + 'Spade', + 'Forshee', + 'Cliff', + 'Gholson', + 'Reichenbach', + 'Lockman', + 'Morones', + 'Storie', + 'Bissett', + 'Janney', + 'Durocher', + 'Fentress', + 'Troiano', + 'Boes', + 'Rouleau', + 'Rall', + 'Sultan', + 'Braggs', + 'Bethke', + 'Schacht', + 'Straley', + 'Mcfalls', + 'Fahy', + 'Winegar', + 'Gorecki', + 'Rudnick', + 'Wigginton', + 'Dedrick', + 'Sthilaire', + 'Lovette', + 'Hanneman', + 'Loch', + 'Moores', + 'Polen', + 'Anchondo', + 'Rosato', + 'Tindell', + 'Hunsicker', + 'Penna', + 'Privette', + 'Gayton', + 'Sliger', + 'Wink', + 'Brummer', + 'Crown', + 'Sommerville', + 'Mastrangelo', + 'Latimore', + 'Merlino', + 'Thoreson', + 'Kleiner', + 'Able', + 'Boose', + 'Loyola', + 'Jimenes', + 'Lapham', + 'Srinivasan', + 'Hammers', + 'Mo', + 'Evert', + 'Vanslyke', + 'Caywood', + 'Gremillion', + 'Rauscher', + 'Eckhoff', + 'Dearth', + 'Sinha', + 'Becerril', + 'Tuten', + 'Greenwalt', + 'Curlee', + 'Burgan', + 'Feagin', + 'Gallman', + 'Germann', + 'Swensen', + 'Vanallen', + 'Bissonnette', + 'Stoudt', + 'Handler', + 'Tanguay', + 'Lovins', + 'Smotherman', + 'Cutts', + 'Herod', + 'Maclin', + 'Arcuri', + 'Hackbarth', + 'Breazeale', + 'Rainville', + 'Crick', + 'Macintosh', + 'Bloss', + 'Fridley', + 'Stefanski', + 'Beauvais', + 'Koop', + 'Andes', + 'Blomberg', + 'Vallee', + 'Lanigan', + 'Blouin', + 'Rochon', + 'Dorazio', + 'Drouin', + 'Lamonica', + 'Wilbourn', + 'Spraggins', + 'Rieder', + 'Shugart', + 'Chacko', + 'Rutan', + 'Nutting', + 'Lawley', + 'Landy', + 'January', + 'Blowers', + 'Handel', + 'Doman', + 'Swiney', + 'Ettinger', + 'Jellison', + 'Veilleux', + 'Wiens', + 'Raimondi', + 'Spink', + 'Emond', + 'Yale', + 'Rachel', + 'Alldredge', + 'Lach', + 'Morlan', + 'Wayland', + 'Colquitt', + 'Gabrielson', + 'Mccarver', + 'Frances', + 'Granville', + 'Costigan', + 'Preuss', + 'Lentini', + 'Vansant', + 'Mosca', + 'Connally', + 'Frei', + 'Laplant', + 'Lago', + 'Leiter', + 'Trumbull', + 'Shaeffer', + 'Gongora', + 'Coady', + 'Fyffe', + 'Mance', + 'Worcester', + 'Zehner', + 'Bodie', + 'Burnes', + 'Pompey', + 'Teitelbaum', + 'Beaupre', + 'Visconti', + 'Mumma', + 'Markiewicz', + 'Piscitelli', + 'Moak', + 'Bourland', + 'Pennock', + 'Hannum', + 'Robichaux', + 'Folks', + 'Coppage', + 'Heffron', + 'Mullet', + 'Kimberlin', + 'Breneman', + 'Blandford', + 'Matthias', + 'Engebretson', + 'Roessler', + 'Allee', + 'Parkman', + 'Barge', + 'Ren', + 'Backstrom', + 'Bullen', + 'Lampman', + 'Loesch', + 'Echavarria', + 'Haman', + 'Cortina', + 'Elms', + 'Gordan', + 'Pabst', + 'Snelson', + 'Vanarsdale', + 'Pecora', + 'Rabago', + 'Enger', + 'Senger', + 'Dewees', + 'Semple', + 'Howey', + 'Westlund', + 'Daw', + 'Hagemann', + 'Mcpeek', + 'Vanderhoof', + 'Ohler', + 'Bohm', + 'Mazzone', + 'Arnott', + 'Bouton', + 'Fackler', + 'Giunta', + 'Stagner', + 'Tavera', + 'Poorman', + 'Buch', + 'Mangano', + 'Bonar', + 'Gerson', + 'Ranger', + 'Mccullar', + 'Wunder', + 'Bade', + 'Armand', + 'Chalfant', + 'Lichtenstein', + 'Turco', + 'Degraw', + 'Few', + 'Haigler', + 'Lis', + 'Bittinger', + 'Morrone', + 'Hodgdon', + 'Wittenberg', + 'Imes', + 'Dreiling', + 'Landwehr', + 'Maly', + 'Warlick', + 'Terpstra', + 'Bolte', + 'Stiller', + 'Stmartin', + 'Pankratz', + 'Albee', + 'Victory', + 'Lezama', + 'Brecht', + 'Monarrez', + 'Thurlow', + 'Laskey', + 'Bothwell', + 'Candler', + 'Esh', + 'Kalman', + 'Samano', + 'Yohe', + 'Regnier', + 'Leite', + 'Ballantyne', + 'Dan', + 'Fikes', + 'Cendejas', + 'Mikula', + 'Fairman', + 'Dragon', + 'Manzella', + 'Renninger', + 'Leaman', + 'Godbey', + 'Current', + 'Mirabal', + 'Boerner', + 'Depaz', + 'Birge', + 'Westberry', + 'Severin', + 'Weddington', + 'Longenecker', + 'Mccreery', + 'Lebel', + 'Nader', + 'Gan', + 'Auguste', + 'Colonna', + 'Paramo', + 'Minyard', + 'Duley', + 'Beil', + 'Salters', + 'Brindley', + 'Simmers', + 'Lumpkins', + 'Crisman', + 'Raulerson', + 'Lanz', + 'Deroche', + 'Kemmerer', + 'Bogner', + 'Mahn', + 'Willer', + 'Gunnels', + 'Warford', + 'Reason', + 'Scherr', + 'Digirolamo', + 'Hallowell', + 'Wilcoxson', + 'Gaillard', + 'Deshields', + 'Hively', + 'Sakai', + 'Creason', + 'Jaber', + 'Lapinski', + 'Bolivar', + 'Millwood', + 'Shumpert', + 'Fujii', + 'Plemmons', + 'Lamere', + 'Cleghorn', + 'Mccaw', + 'Seavey', + 'Zwick', + 'Hosler', + 'Lepley', + 'Marden', + 'Cornwall', + 'Gauger', + 'Hofmeister', + 'Bugarin', + 'Loose', + 'Guardiola', + 'Hertzog', + 'Bigger', + 'Heineman', + 'Retzlaff', + 'Rizzuto', + 'Flannigan', + 'Rathburn', + 'Moulder', + 'Town', + 'Gautier', + 'Hamid', + 'Torrance', + 'Walthall', + 'Windom', + 'Kleckner', + 'Kirwan', + 'Gasaway', + 'Pinkard', + 'Concannon', + 'Mcquiston', + 'Yow', + 'Eshleman', + 'Riggleman', + 'Foulk', + 'Bolles', + 'Craine', + 'Hinnant', + 'Gholston', + 'Lebo', + 'Torkelson', + 'Mancia', + 'Canale', + 'Celestin', + 'Neubert', + 'Schmaltz', + 'Highfill', + 'Fisch', + 'Matte', + 'Hoefer', + 'Flippin', + 'Mclin', + 'Mikkelson', + 'Gump', + 'Kilroy', + 'Ensor', + 'Klosterman', + 'Ruppel', + 'Steffey', + 'Sauve', + 'Cessna', + 'Apgar', + 'Jacobus', + 'Pettyjohn', + 'Northington', + 'Smithey', + 'Moro', + 'Dossett', + 'Mccroskey', + 'Yelverton', + 'Mascarenas', + 'Hebb', + 'Quinteros', + 'Giang', + 'Pontius', + 'Sipple', + 'Atkin', + 'Howington', + 'Hiebert', + 'Lingerfelt', + 'Schueler', + 'Sailer', + 'Smits', + 'Keeter', + 'Macrae', + 'Mease', + 'Shortridge', + 'Scates', + 'Amstutz', + 'Kuebler', + 'Cambron', + 'Eaker', + 'Finlayson', + 'Bookout', + 'Mullett', + 'Bank', + 'Schlenker', + 'Morlock', + 'Haskett', + 'Dade', + 'Gallucci', + 'Lahey', + 'Ryerson', + 'Crownover', + 'Banfield', + 'Mcclay', + 'Diggins', + 'Conerly', + 'Primus', + 'Syverson', + 'Prindle', + 'Blasingame', + 'Deford', + 'Garnes', + 'Hoisington', + 'Glasper', + 'Lorusso', + 'Hesson', + 'Youssef', + 'Threlkeld', + 'Talmadge', + 'Winfree', + 'Heacock', + 'Rawlinson', + 'Burse', + 'Diederich', + 'Niemiec', + 'Norby', + 'Bauder', + 'Scranton', + 'Prentiss', + 'Towles', + 'Henton', + 'Purifoy', + 'Pinzon', + 'Edler', + 'Ragin', + 'Albarado', + 'Cuadra', + 'Hoadley', + 'Devita', + 'Pavon', + 'Alday', + 'Goulding', + 'Millis', + 'Dalley', + 'Kolodziej', + 'Kropf', + 'Kuiper', + 'Crespin', + 'Xavier', + 'Sailor', + 'Lagrone', + 'Boehme', + 'Tidd', + 'Wilmore', + 'Ziemer', + 'Ropp', + 'Kettler', + 'Pilon', + 'Miron', + 'Salsbury', + 'Job', + 'Sensenig', + 'Cayton', + 'Nanney', + 'Rasch', + 'Silvestre', + 'Ladue', + 'Dampier', + 'Ackermann', + 'Friedel', + 'Kleiman', + 'Geronimo', + 'Ezzell', + 'Duclos', + 'Moor', + 'Neuhaus', + 'Lan', + 'Allender', + 'Tedeschi', + 'Langton', + 'Dawley', + 'Kearse', + 'Godina', + 'Guernsey', + 'Kober', + 'Bisbee', + 'Lamphere', + 'Kinman', + 'Wesner', + 'Malo', + 'Stroupe', + 'Millette', + 'Yeoman', + 'Baig', + 'Kirchoff', + 'Tsao', + 'Cristobal', + 'Mucci', + 'Pair', + 'Barefield', + 'Dewolf', + 'Fitzmaurice', + 'Mcaleer', + 'Natal', + 'Bara', + 'Macey', + 'Mclennan', + 'Fabre', + 'Vieyra', + 'Magno', + 'Eyre', + 'Chatterton', + 'Gilland', + 'Hurlbut', + 'Umberger', + 'Roloff', + 'Brambila', + 'Mazzeo', + 'Letson', + 'Norsworthy', + 'Bier', + 'Gioia', + 'Kapoor', + 'Marlatt', + 'Flippo', + 'Houde', + 'Baughn', + 'Blackledge', + 'Fly', + 'Dinkel', + 'Rathbone', + 'Bober', + 'Boydston', + 'Ferdinand', + 'Coletti', + 'Cuenca', + 'Deters', + 'Blagg', + 'Timmins', + 'Boyden', + 'Meads', + 'Narcisse', + 'Saelee', + 'Cosner', + 'Strawser', + 'Amico', + 'Dowdle', + 'Golub', + 'Silverberg', + 'Riles', + 'Balk', + 'Buhr', + 'Feltman', + 'Stickel', + 'Zapien', + 'Cargile', + 'Kulik', + 'Lazzaro', + 'Oberle', + 'Wickstrom', + 'Maeda', + 'Cockrum', + 'Boulton', + 'Sandford', + 'Culbert', + 'Dula', + 'Ament', + 'Chunn', + 'Owenby', + 'Wasilewski', + 'Wichman', + 'Oestreich', + 'Klos', + 'Orchard', + 'Hogge', + 'Presson', + 'Cordon', + 'Gans', + 'Leonardi', + 'Manjarrez', + 'Olander', + 'Drennen', + 'Wirt', + 'Tiger', + 'Dolce', + 'Hagstrom', + 'Hirsh', + 'Tally', + 'Crumbley', + 'Mcgreevy', + 'Amidon', + 'Olague', + 'Lint', + 'Poche', + 'Lipford', + 'Engen', + 'Mcelfresh', + 'Cuneo', + 'Krumm', + 'Haak', + 'Arocho', + 'Longworth', + 'Seamon', + 'Bronner', + 'Swartzentruber', + 'Chand', + 'Wilhoit', + 'Chapel', + 'Hitchens', + 'Brzezinski', + 'Heidenreich', + 'Ellenberger', + 'Gamblin', + 'Ormond', + 'Burchard', + 'Dibella', + 'Nicoll', + 'Simcox', + 'Strohm', + 'Dittmar', + 'Wycoff', + 'Grays', + 'Spero', + 'Vess', + 'Picone', + 'Greening', + 'Maynes', + 'Knauss', + 'Wojtowicz', + 'Chaput', + 'Soliman', + 'Ponton', + 'Carlino', + 'Kestner', + 'Kelch', + 'Dimauro', + 'Iorio', + 'Parenteau', + 'Pesina', + 'Clauson', + 'Stigall', + 'Keels', + 'Waldrep', + 'Wix', + 'Draeger', + 'Ertel', + 'Starner', + 'Charest', + 'Simoneaux', + 'Ivanov', + 'Thor', + 'Gravel', + 'Trottier', + 'Clendenin', + 'Kromer', + 'Benda', + 'Touchet', + 'Hornbuckle', + 'Avent', + 'Dombroski', + 'Friedland', + 'Radabaugh', + 'Vesely', + 'Wike', + 'Lax', + 'Messersmith', + 'Deoliveira', + 'Brey', + 'Cogdill', + 'Overturf', + 'Sova', + 'Pero', + 'Beaird', + 'Cevallos', + 'Defalco', + 'Taormina', + 'Thornberry', + 'Westervelt', + 'Macaulay', + 'Hajek', + 'Brugger', + 'Leff', + 'Ketterer', + 'Ono', + 'Mullenix', + 'Frison', + 'Gullo', + 'Calhoon', + 'Summey', + 'Hockaday', + 'Dimatteo', + 'Agan', + 'Patenaude', + 'Mary', + 'Tanis', + 'Obert', + 'Elton', + 'Randles', + 'Migliore', + 'Schmalz', + 'Vanvalkenburg', + 'Quinto', + 'Palmquist', + 'Hoops', + 'Naples', + 'Orear', + 'Eberhard', + 'Fitzgibbons', + 'Adkinson', + 'Gerace', + 'Elie', + 'Dressel', + 'Silber', + 'Otey', + 'Hsiao', + 'Kreutzer', + 'Tutor', + 'Roundy', + 'Haddox', + 'Bridgers', + 'Leto', + 'Daniell', + 'Pollitt', + 'Freda', + 'Mraz', + 'Engelbrecht', + 'Ariza', + 'Grand', + 'Pavone', + 'Everts', + 'Benes', + 'Reamer', + 'Faucett', + 'Eatmon', + 'Raymundo', + 'Zaman', + 'Devitt', + 'Master', + 'Carron', + 'Hoffner', + 'Sciortino', + 'Stringham', + 'Bookman', + 'Westberg', + 'Spahn', + 'Hise', + 'Waterbury', + 'Buckwalter', + 'Hug', + 'Overly', + 'Dingus', + 'Ince', + 'Haar', + 'Shain', + 'Heaps', + 'Oppenheimer', + 'Miyamoto', + 'Schreier', + 'Martello', + 'Atteberry', + 'Folger', + 'Macke', + 'Pal', + 'Lucchesi', + 'Osterhout', + 'Liriano', + 'Legge', + 'Barra', + 'Crumb', + 'Gwyn', + 'Forst', + 'Axelrod', + 'Samayoa', + 'Edgell', + 'Purkey', + 'Lannon', + 'Branam', + 'Yeo', + 'Hatmaker', + 'Borum', + 'Villagrana', + 'Lawing', + 'Bark', + 'Muirhead', + 'Eckles', + 'Weight', + 'Surles', + 'Cullinan', + 'Lagos', + 'Naber', + 'Sloat', + 'Foos', + 'Vine', + 'Milliner', + 'Reliford', + 'Dahlquist', + 'Gibney', + 'Moroney', + 'Stecker', + 'Bella', + 'Brickhouse', + 'Canela', + 'Kula', + 'Tartaglia', + 'Siewert', + 'Hitch', + 'Brickman', + 'Cheeseman', + 'Carollo', + 'Geissler', + 'Jiron', + 'Cossey', + 'Sroka', + 'Border', + 'Brownlow', + 'Ellenburg', + 'Cella', + 'Brinton', + 'Scurry', + 'Behrendt', + 'Carstensen', + 'Schendel', + 'Bodner', + 'Eddleman', + 'Stec', + 'Capasso', + 'Leu', + 'Kennett', + 'Ruane', + 'Critchfield', + 'Carbonell', + 'Mitcham', + 'Troncoso', + 'Mckeen', + 'Cammack', + 'Broach', + 'Culbreath', + 'Callejas', + 'Wurst', + 'Brookman', + 'Guerrier', + 'Seese', + 'Kitzmiller', + 'Graybeal', + 'Yardley', + 'Cheever', + 'Virgin', + 'Brimmer', + 'Swoboda', + 'Pandya', + 'Canton', + 'Magnus', + 'Draughn', + 'Dilts', + 'Tauber', + 'Vandegrift', + 'Rene', + 'Cousineau', + 'Joo', + 'Pimental', + 'Carpentier', + 'Eager', + 'Cumberland', + 'Eastridge', + 'Moberly', + 'Erhardt', + 'Meldrum', + 'Degennaro', + 'Desanto', + 'Manahan', + 'Gowdy', + 'Popham', + 'Mee', + 'Kinslow', + 'Harned', + 'Cartee', + 'Raiford', + 'Henrichs', + 'Maffei', + 'Seamans', + 'Heckel', + 'Toll', + 'Milian', + 'Mabrey', + 'Dall', + 'Lanford', + 'Carew', + 'Bascom', + 'Christofferson', + 'Hadfield', + 'Ferber', + 'Mestas', + 'Leith', + 'Abston', + 'Cuddy', + 'Svendsen', + 'Cowling', + 'Segars', + 'Nalls', + 'Hofstetter', + 'Badgley', + 'Mccaffery', + 'Burner', + 'Laymon', + 'Pinion', + 'Schooler', + 'Brun', + 'Aldaco', + 'Savarese', + 'Gravelle', + 'Belvin', + 'Brekke', + 'Dekker', + 'Ellefson', + 'Lurie', + 'Cassity', + 'Epperly', + 'Genova', + 'Dehn', + 'Fargo', + 'Vanderford', + 'Sine', + 'Horrell', + 'Napoleon', + 'Kamm', + 'Riel', + 'Gerena', + 'Check', + 'Devane', + 'Grissett', + 'Brendel', + 'Weyant', + 'Basurto', + 'Coppinger', + 'Grosse', + 'Saeed', + 'Lunceford', + 'Washam', + 'Benard', + 'Eastham', + 'Holleran', + 'Kiesel', + 'Risch', + 'Mccullen', + 'Vizcaino', + 'Fullen', + 'Westbrooks', + 'Babich', + 'Mauch', + 'Hensler', + 'Bryner', + 'Phillippi', + 'Santistevan', + 'Jalbert', + 'Vanorden', + 'Brantner', + 'Mcgrail', + 'Rustin', + 'Lebaron', + 'Genao', + 'Quast', + 'Hamburg', + 'Mensah', + 'Heckler', + 'Popa', + 'Mantooth', + 'Hargreaves', + 'Jury', + 'Seiber', + 'Calton', + 'Lafreniere', + 'Starbuck', + 'Gow', + 'Veazey', + 'Kneeland', + 'Woodberry', + 'Vallone', + 'Sutcliffe', + 'Loh', + 'Wiltse', + 'Choudhury', + 'Rollo', + 'Bjerke', + 'Huffstetler', + 'Ogren', + 'Legere', + 'Wilmer', + 'Conboy', + 'Pressler', + 'Hon', + 'Monger', + 'Devos', + 'Houtz', + 'Shurtleff', + 'Sedlak', + 'Carolan', + 'Luc', + 'Immel', + 'Guizar', + 'Kron', + 'Lusby', + 'Whitsett', + 'Pryce', + 'Mengel', + 'Youngberg', + 'Kluge', + 'Thrush', + 'Wilsey', + 'Santee', + 'Braham', + 'Palmeri', + 'Cousino', + 'Willits', + 'Gram', + 'Dearmond', + 'Fonville', + 'Sabatini', + 'Nehring', + 'Henne', + 'Prager', + 'Mederos', + 'Schuldt', + 'Weisz', + 'Mccart', + 'Warriner', + 'Bartelt', + 'Dimond', + 'Mccubbin', + 'Say', + 'Mickel', + 'Bracamonte', + 'Volkman', + 'Brindle', + 'Bitter', + 'Dickie', + 'Inge', + 'Brinegar', + 'Lerman', + 'Bohan', + 'Rondon', + 'Dilbeck', + 'Rumbaugh', + 'Simard', + 'Berke', + 'Ealey', + 'Knauer', + 'Michalek', + 'Smolinski', + 'Wurster', + 'Zullo', + 'Nott', + 'Claar', + 'Mayor', + 'Moir', + 'Hubbert', + 'Hankerson', + 'Mok', + 'Simko', + 'Mumm', + 'Sheely', + 'Abramowitz', + 'Pusateri', + 'Boomer', + 'Chappelle', + 'Demery', + 'Coniglio', + 'Asay', + 'Nova', + 'Biel', + 'Delancey', + 'Tocco', + 'Tant', + 'Melin', + 'Lacoste', + 'Derrico', + 'Stacks', + 'Watley', + 'Stoneking', + 'Westrick', + 'Pons', + 'Malm', + 'Parekh', + 'Loop', + 'Kitt', + 'Crisostomo', + 'Ecklund', + 'Tollison', + 'Dziedzic', + 'Pillsbury', + 'Baumer', + 'Matsuda', + 'Jeon', + 'Foye', + 'Peltz', + 'Candela', + 'Levey', + 'Organ', + 'Hathorn', + 'Galeano', + 'Nies', + 'Cabezas', + 'Barras', + 'Pier', + 'Truss', + 'Leist', + 'Lheureux', + 'Nakano', + 'Ladwig', + 'Grunwald', + 'Centers', + 'Sherrard', + 'Morais', + 'Juhl', + 'Ivers', + 'Dunfee', + 'Jolliff', + 'Breeze', + 'Tapper', + 'Goodridge', + 'Kelliher', + 'Finck', + 'Roose', + 'Gauvin', + 'Coil', + 'Pounders', + 'Lobb', + 'Stalcup', + 'Swanner', + 'Boivin', + 'Neer', + 'Laxton', + 'Pai', + 'Postma', + 'Janus', + 'Didier', + 'Engleman', + 'League', + 'Fray', + 'Aguillon', + 'Richins', + 'Tolar', + 'Criner', + 'Rowlands', + 'Verdi', + 'Utt', + 'Winders', + 'Turbeville', + 'Rada', + 'Mcnichols', + 'Boddy', + 'Binford', + 'Amey', + 'Schultze', + 'Sontag', + 'Saleem', + 'Przybylski', + 'Vanderlinden', + 'Vanfossen', + 'Longacre', + 'Heasley', + 'Southwell', + 'Decesare', + 'Munch', + 'Minix', + 'Hymes', + 'Klopp', + 'Militello', + 'Schuessler', + 'Velazco', + 'Jurek', + 'Claycomb', + 'Diemer', + 'Roser', + 'Huse', + 'Perkinson', + 'Musa', + 'Leavy', + 'Seidman', + 'Vroman', + 'Stalter', + 'Grieve', + 'Aron', + 'Purdie', + 'Dusek', + 'Rago', + 'Shepler', + 'Leopard', + 'Araya', + 'Rutt', + 'Voth', + 'Hittle', + 'Husain', + 'Gratton', + 'Seigler', + 'Coppedge', + 'Nicastro', + 'Fitzgibbon', + 'Sosebee', + 'Tank', + 'Troche', + 'Delph', + 'Ryland', + 'Mazzella', + 'Rai', + 'Strecker', + 'Epp', + 'Clower', + 'Porche', + 'Gelman', + 'Herrman', + 'Balser', + 'Tosh', + 'Bonn', + 'Cerrato', + 'Varley', + 'Dingess', + 'Goodspeed', + 'Boller', + 'Heimann', + 'Gottfried', + 'Super', + 'Falzone', + 'Bizzell', + 'Litwin', + 'Ji', + 'Rogowski', + 'Tindle', + 'Hoye', + 'Balfour', + 'Focht', + 'Manz', + 'Stender', + 'Sutterfield', + 'Bayes', + 'Mullings', + 'Dockter', + 'Figueiredo', + 'Kepner', + 'Posadas', + 'Nettleton', + 'Ruder', + 'Younce', + 'Flanary', + 'Scotti', + 'Bayliss', + 'Tandy', + 'Henrickson', + 'Volker', + 'Letts', + 'Joines', + 'Fewell', + 'Wherry', + 'Stelzer', + 'Stever', + 'Viator', + 'Catt', + 'Jeffords', + 'Guerriero', + 'Milby', + 'Jozwiak', + 'Slawson', + 'Portwood', + 'Billie', + 'Borunda', + 'Chinchilla', + 'Papadopoulos', + 'Lohse', + 'Mantz', + 'Gabriele', + 'Hosford', + 'Kohut', + 'Tardiff', + 'Puma', + 'Bodin', + 'Hodgins', + 'Boon', + 'Golightly', + 'Bogert', + 'Abdi', + 'Wigfall', + 'Fleischmann', + 'Nease', + 'Rayborn', + 'Zigler', + 'Reimann', + 'Malagon', + 'Puls', + 'Grogg', + 'Drinkwater', + 'Dacus', + 'Mcfee', + 'Domino', + 'Harjo', + 'Pascarella', + 'Spengler', + 'Copple', + 'Rollings', + 'Brew', + 'Brabham', + 'Nordquist', + 'Emig', + 'Riggio', + 'Sanson', + 'Gerardo', + 'Pereda', + 'Renken', + 'Stickley', + 'Milliron', + 'Rolling', + 'Hollie', + 'Biondi', + 'Fluharty', + 'Magyar', + 'Balsamo', + 'Imler', + 'Hanlin', + 'Dycus', + 'Kirkley', + 'Wimberley', + 'Finan', + 'Kulkarni', + 'Morreale', + 'Briner', + 'Pelzer', + 'Bouie', + 'Fenstermaker', + 'Gimenez', + 'Labella', + 'Scherrer', + 'Holzman', + 'Winer', + 'Wrigley', + 'Leighty', + 'Liptak', + 'Chamness', + 'Franko', + 'Arwood', + 'Tiner', + 'Schoenberger', + 'Gear', + 'Hereford', + 'Slezak', + 'Longfellow', + 'Cull', + 'Brashears', + 'Clear', + 'Zielke', + 'Arden', + 'Bonneau', + 'Muck', + 'Tarvin', + 'Beran', + 'Coulombe', + 'Toothman', + 'Ghosh', + 'Mcguirk', + 'Pinero', + 'Ruan', + 'Gartman', + 'Peed', + 'Cassano', + 'Forcier', + 'Haque', + 'Veatch', + 'Fodor', + 'Wetherington', + 'Barrette', + 'Bottorff', + 'Holmstrom', + 'Honda', + 'Kopecky', + 'Loaiza', + 'Castelan', + 'Haydon', + 'Lamotte', + 'Mutchler', + 'Mahmoud', + 'Gleaton', + 'Rebollar', + 'Moctezuma', + 'Tannehill', + 'Bernardino', + 'Walrath', + 'Adcox', + 'Heidt', + 'Rakowski', + 'Soza', + 'Limas', + 'Wysong', + 'Mannix', + 'Pattillo', + 'Corner', + 'Kuang', + 'Loflin', + 'Ledger', + 'Ivery', + 'Likens', + 'Mctaggart', + 'Hartin', + 'Prange', + 'Stenzel', + 'Shadle', + 'Karn', + 'Duplantis', + 'Garibaldi', + 'Batty', + 'Goulart', + 'Ranck', + 'Beekman', + 'Nicolosi', + 'Arizmendi', + 'Donoho', + 'Drewry', + 'Lenihan', + 'Spatz', + 'Wible', + 'Dimmick', + 'Stelter', + 'Seyler', + 'Stringfield', + 'Bonaparte', + 'Dematteo', + 'Petrey', + 'Bellino', + 'Cavaliere', + 'Thaler', + 'Heiner', + 'Lillis', + 'Hammes', + 'Rainbolt', + 'Hillyard', + 'Farnum', + 'Overmyer', + 'Replogle', + 'Sclafani', + 'Audet', + 'Santa', + 'Hollen', + 'Lineberger', + 'Bonnet', + 'Caples', + 'Dahlen', + 'Ruggieri', + 'Keppler', + 'Ryman', + 'Copas', + 'Lyda', + 'Pusey', + 'Bostrom', + 'Patnode', + 'Richeson', + 'Hamil', + 'Wyss', + 'Mcadam', + 'Dennett', + 'Lever', + 'Drinkard', + 'Ohl', + 'Restivo', + 'Vyas', + 'Moyle', + 'Blauvelt', + 'Gregson', + 'Scull', + 'Verret', + 'Stines', + 'Forsman', + 'Gehman', + 'Watrous', + 'Gunnell', + 'Choice', + 'Castaldo', + 'Pietrzak', + 'Goodsell', + 'Klima', + 'Stratman', + 'Foutz', + 'Massingill', + 'Huneycutt', + 'Zellmer', + 'Tefft', + 'Hamblen', + 'Baggs', + 'Mcgarity', + 'Alfieri', + 'Stetler', + 'Hershman', + 'Fuerst', + 'Granda', + 'Villafane', + 'Stocking', + 'Laguerre', + 'Salvato', + 'Mcniel', + 'Trim', + 'Goldston', + 'Tannenbaum', + 'Laforge', + 'Hawker', + 'Innis', + 'Rasheed', + 'Marbury', + 'Jules', + 'Harpster', + 'Hruska', + 'Mancillas', + 'Ruck', + 'Schloss', + 'Shy', + 'Leming', + 'Eich', + 'Allain', + 'Premo', + 'Goodner', + 'Karlin', + 'Natoli', + 'Sinn', + 'Althouse', + 'Bodiford', + 'Krishnan', + 'Snedeker', + 'Weigle', + 'Blohm', + 'Renwick', + 'Menzies', + 'Stonebraker', + 'Brunetti', + 'Crompton', + 'Hucks', + 'Maharaj', + 'Bangert', + 'Hepp', + 'Kammer', + 'Sutliff', + 'Doyon', + 'Hutsell', + 'Cumbie', + 'Dibiase', + 'Linke', + 'Sapienza', + 'Sprayberry', + 'Sundstrom', + 'Vanbeek', + 'Ewart', + 'Erlandson', + 'Knutsen', + 'Nicolai', + 'Oros', + 'Almquist', + 'Tedrow', + 'Diebold', + 'Bellman', + 'Sherrer', + 'Ehret', + 'Ota', + 'Seman', + 'Folse', + 'Amy', + 'Mcateer', + 'Steinhauer', + 'Vannatta', + 'Holle', + 'Carreras', + 'Anger', + 'Clinkscales', + 'Castiglione', + 'Zakrzewski', + 'Principe', + 'Artman', + 'Waiters', + 'Tarbox', + 'Sippel', + 'Belz', + 'Joachim', + 'Pipkins', + 'Peterkin', + 'Abalos', + 'Flock', + 'Brochu', + 'Tobler', + 'Mckinnis', + 'Gatson', + 'Cronan', + 'Manthey', + 'Oberholtzer', + 'Schiltz', + 'Skowronski', + 'Matute', + 'Castonguay', + 'Bechard', + 'Drees', + 'Carte', + 'Baysinger', + 'Kees', + 'Steve', + 'Ratchford', + 'Clopton', + 'Heimbach', + 'Selig', + 'Peavey', + 'Sidney', + 'Hilliker', + 'Oehler', + 'Essig', + 'Ownby', + 'Huling', + 'Aylward', + 'Matzke', + 'Mikkelsen', + 'Vandam', + 'Rodden', + 'Plunk', + 'Mcdonell', + 'Buechler', + 'Dahm', + 'Tarlton', + 'Funches', + 'Alvidrez', + 'Padua', + 'Pingel', + 'Cid', + 'Mcburney', + 'Brunton', + 'Dwight', + 'Bucio', + 'Schiffer', + 'Dyal', + 'Cyphers', + 'Gildea', + 'Wengerd', + 'Lappin', + 'Longwell', + 'Basil', + 'Acklin', + 'Cancino', + 'Kalina', + 'Tynan', + 'Raasch', + 'Fleener', + 'Dunmire', + 'Gent', + 'Cruickshank', + 'Baltimore', + 'Shum', + 'Vanpatten', + 'Costilla', + 'Grimshaw', + 'Loar', + 'Royse', + 'Amon', + 'Amendola', + 'Mcgonagle', + 'Alm', + 'Hausmann', + 'Heitzman', + 'Mailloux', + 'Brault', + 'Capra', + 'Levis', + 'Barillas', + 'Quandt', + 'Fedele', + 'Chittenden', + 'Cheesman', + 'Wildes', + 'Bolan', + 'Metoyer', + 'Ciccarelli', + 'Melara', + 'Gano', + 'Janowski', + 'Magoon', + 'Kuster', + 'Ofarrell', + 'Joplin', + 'Cannella', + 'Middendorf', + 'Putz', + 'Saephan', + 'Sieg', + 'Lainez', + 'Roten', + 'Buras', + 'Nock', + 'Manke', + 'Hymel', + 'Devaughn', + 'Braverman', + 'Fleisher', + 'Persson', + 'Sandidge', + 'Corsi', + 'Torok', + 'Steinhoff', + 'Corby', + 'Shorey', + 'Wooton', + 'Estell', + 'Bolander', + 'Vivar', + 'Cuesta', + 'Renick', + 'Isler', + 'Caprio', + 'Crissman', + 'Wann', + 'Matchett', + 'Calahan', + 'Escareno', + 'Liguori', + 'Helt', + 'Boner', + 'Luper', + 'Hoppes', + 'Ingold', + 'Gilleland', + 'Saathoff', + 'Szczepanski', + 'Yockey', + 'Veith', + 'Wasser', + 'Denniston', + 'Fretwell', + 'Goetsch', + 'Havel', + 'Banach', + 'Schaal', + 'Nisbet', + 'Depaul', + 'Escalona', + 'Gammons', + 'Schmelzer', + 'Wehrle', + 'Guglielmo', + 'Oberlander', + 'Wolski', + 'Dimick', + 'Rebello', + 'Braunstein', + 'Vanderveen', + 'Saini', + 'Meiners', + 'Metheny', + 'Schommer', + 'Kissell', + 'Burgoyne', + 'Walmsley', + 'Parmley', + 'Arthurs', + 'Worsley', + 'Hulme', + 'Campisi', + 'Parvin', + 'Ogawa', + 'Coder', + 'Gardener', + 'Taplin', + 'Nuzzo', + 'Linthicum', + 'Rosenstein', + 'Simoneau', + 'Preble', + 'Chae', + 'Nealon', + 'Stonecipher', + 'Medders', + 'Bencomo', + 'Durazo', + 'Scotto', + 'Klem', + 'Corman', + 'Byard', + 'Evan', + 'Dengler', + 'Kohls', + 'Seidler', + 'Clute', + 'Nebel', + 'Hohl', + 'Younker', + 'Parkerson', + 'Pullins', + 'Sweeting', + 'Wiersma', + 'Callanan', + 'Lisk', + 'Fassett', + 'Alloway', + 'Lafever', + 'Ollis', + 'Gracey', + 'Tune', + 'Ester', + 'Weingarten', + 'Swigart', + 'Frew', + 'Conkle', + 'Mendelsohn', + 'Belliveau', + 'Bacher', + 'Coto', + 'Ro', + 'Lipson', + 'Standard', + 'Hoerner', + 'Moldenhauer', + 'Trivette', + 'Colligan', + 'Cacho', + 'Emrich', + 'Condit', + 'Styer', + 'Paramore', + 'Cheramie', + 'Sprenger', + 'Kreps', + 'Curd', + 'Josephs', + 'Bruch', + 'Villano', + 'Banh', + 'Kennison', + 'Hilson', + 'Gathers', + 'Weinman', + 'Brickley', + 'Jetton', + 'Munford', + 'Charboneau', + 'Dittrich', + 'Boysen', + 'Newbury', + 'Hayner', + 'Pfau', + 'Wegman', + 'Eure', + 'Heinrichs', + 'Kresge', + 'Klepper', + 'Yohn', + 'Bergan', + 'Spells', + 'Reisman', + 'Schiffman', + 'Napoles', + 'Banegas', + 'Landman', + 'Hallenbeck', + 'Sever', + 'Hole', + 'Bown', + 'Barnaby', + 'Junior', + 'Deloatch', + 'Secrist', + 'Steigerwald', + 'Kallas', + 'Littell', + 'Clinger', + 'Rehman', + 'Cothern', + 'Class', + 'Sabino', + 'Mckain', + 'Werts', + 'Asmus', + 'Fierros', + 'Heffelfinger', + 'Henthorn', + 'Weirich', + 'Ashbrook', + 'Alber', + 'Calles', + 'Bragdon', + 'Gerow', + 'Hanger', + 'Machen', + 'Patt', + 'Harada', + 'Parmelee', + 'Decaro', + 'Sons', + 'Tindal', + 'Lubbers', + 'Ferland', + 'Bruni', + 'Boyes', + 'Danis', + 'Tigner', + 'Anzaldua', + 'Gaxiola', + 'Iacono', + 'Lizama', + 'Forbis', + 'Mcguffin', + 'Greenhill', + 'Baity', + 'Welcome', + 'Lauzon', + 'Nicodemus', + 'Rabin', + 'Teegarden', + 'Yunker', + 'Salim', + 'Dews', + 'Schueller', + 'Stogsdill', + 'Minch', + 'Ellett', + 'Villafana', + 'Shan', + 'Boler', + 'Kast', + 'Shrout', + 'Taff', + 'Willcox', + 'Kahan', + 'Gerth', + 'Sabella', + 'Procopio', + 'Vedder', + 'Heeter', + 'Banes', + 'Alaimo', + 'Raza', + 'Starkweather', + 'Mutter', + 'Manners', + 'Bohanan', + 'Virden', + 'Booze', + 'Wimbush', + 'Eickhoff', + 'Hankinson', + 'Swilley', + 'Killinger', + 'Labar', + 'Tallant', + 'Rosin', + 'Hillhouse', + 'Labarre', + 'Ryans', + 'Heintzelman', + 'Cottone', + 'Bickerstaff', + 'Westley', + 'Rotter', + 'Hey', + 'Dinapoli', + 'Lohmann', + 'Reetz', + 'Vences', + 'Mckiernan', + 'Thornsberry', + 'Hofman', + 'Murrieta', + 'Vanwormer', + 'Sen', + 'Pinheiro', + 'Jaco', + 'Maner', + 'Crosley', + 'Rogalski', + 'Hollandsworth', + 'Hinze', + 'Seawright', + 'Brosius', + 'Keehn', + 'Sweetman', + 'Vicknair', + 'Casler', + 'Hagopian', + 'Westhoff', + 'Lipari', + 'Poll', + 'Lintz', + 'Rosinski', + 'Henrie', + 'Crystal', + 'Wroten', + 'Perla', + 'Zawacki', + 'Mckillip', + 'Dorantes', + 'Wallick', + 'Hoots', + 'Witty', + 'Granata', + 'Janicki', + 'Petroff', + 'Emert', + 'Raskin', + 'Picou', + 'Caple', + 'Mcelyea', + 'Blackmer', + 'Busbee', + 'Pettengill', + 'Newberg', + 'Nickle', + 'Hedman', + 'Flavin', + 'Forgione', + 'Wachtel', + 'Meader', + 'Nale', + 'Westby', + 'Pulaski', + 'Schupp', + 'Troutt', + 'Fishburn', + 'Laprade', + 'Dealba', + 'Waymire', + 'Stiefel', + 'Carner', + 'Fallin', + 'Belin', + 'Anand', + 'Lesh', + 'Okada', + 'Whipkey', + 'Mang', + 'Harvill', + 'Caver', + 'Moskal', + 'Schaible', + 'Vandeusen', + 'Boyko', + 'Matteo', + 'Crisler', + 'Capehart', + 'Heide', + 'Holdsworth', + 'Mcdonagh', + 'Burlison', + 'Beshears', + 'Gills', + 'Cowger', + 'Gendreau', + 'Goering', + 'Hewes', + 'Whelchel', + 'Kier', + 'Tramel', + 'Mcsherry', + 'Morita', + 'Cissell', + 'Knaus', + 'Vangilder', + 'Karsten', + 'Linscott', + 'Ratner', + 'Catoe', + 'Scriven', + 'Gerstner', + 'Brobst', + 'Normandin', + 'Piasecki', + 'Tamura', + 'Balboa', + 'Nathanson', + 'Huizenga', + 'Renard', + 'Deshazo', + 'Ethier', + 'Fabiano', + 'Quisenberry', + 'Mcbryde', + 'Palencia', + 'Scaglione', + 'Friese', + 'Laughter', + 'Houchins', + 'Loman', + 'Garden', + 'Cromartie', + 'Borgman', + 'Hoffpauir', + 'Choquette', + 'Jarrard', + 'Fernald', + 'Barranco', + 'Levering', + 'Ansell', + 'Perl', + 'Caudell', + 'Ewen', + 'Ohanlon', + 'Swofford', + 'Reasoner', + 'Grout', + 'Rising', + 'Buttram', + 'Vandenheuvel', + 'Imel', + 'Rearick', + 'Harn', + 'Sorrels', + 'Biggins', + 'Renda', + 'Norden', + 'Matula', + 'Walch', + 'Broad', + 'Stokley', + 'Gully', + 'Barrientes', + 'Chilcote', + 'Freel', + 'Lage', + 'Farner', + 'Rubel', + 'Demko', + 'Shao', + 'Cupples', + 'Holderman', + 'Dunnam', + 'Hughs', + 'Foskey', + 'Darst', + 'Greenblatt', + 'Shiner', + 'Brasfield', + 'Simeon', + 'Maser', + 'Lacayo', + 'Priestley', + 'Pleasants', + 'Howse', + 'Iyer', + 'Perreira', + 'Baillargeon', + 'Revilla', + 'Yarger', + 'Gries', + 'Sheeley', + 'Prim', + 'Picazo', + 'Heinlein', + 'Merola', + 'Malhotra', + 'Wein', + 'Mchone', + 'Valliere', + 'Minner', + 'Blumer', + 'Hasse', + 'Kuester', + 'Landi', + 'Suits', + 'Primeaux', + 'Jarnagin', + 'Galle', + 'Greenlaw', + 'Qiu', + 'Lamarche', + 'Acheson', + 'Gothard', + 'Mendivil', + 'Bombard', + 'Mcquillen', + 'Munden', + 'Herzberg', + 'Ros', + 'Umstead', + 'Levins', + 'Pellegrin', + 'Castagna', + 'Alvord', + 'Huckins', + 'Wagnon', + 'Plemons', + 'Dolin', + 'Garica', + 'Lyttle', + 'Bazile', + 'Astudillo', + 'Gover', + 'Galati', + 'Seager', + 'Girardi', + 'Freels', + 'Bramblett', + 'Brancato', + 'Reppert', + 'Saetern', + 'Puig', + 'Prettyman', + 'Chagnon', + 'Heavner', + 'Schlichting', + 'Saladino', + 'Stall', + 'Loiselle', + 'Sedano', + 'Panos', + 'Heilig', + 'Ridgley', + 'Basilio', + 'Rapoza', + 'Furrow', + 'Oliveras', + 'Cordray', + 'Strausbaugh', + 'Culhane', + 'Iraheta', + 'Lamantia', + 'Shires', + 'Wilding', + 'Obanion', + 'Easterwood', + 'Hearns', + 'Manske', + 'Spiess', + 'Eckley', + 'Wootton', + 'Enochs', + 'Cheatwood', + 'Woodfin', + 'Akridge', + 'Mattocks', + 'Mcdougle', + 'Legette', + 'Neher', + 'Rhoton', + 'Vartanian', + 'Dunkel', + 'Wehmeyer', + 'Foutch', + 'Dille', + 'Halle', + 'Lowden', + 'Olesen', + 'Chace', + 'Hasbrouck', + 'Lesage', + 'Pappalardo', + 'Shinkle', + 'Ishii', + 'Peralez', + 'Gabler', + 'Fichter', + 'Mcnicholas', + 'Moshier', + 'Barbeau', + 'Bossert', + 'Trivett', + 'Bamford', + 'Lauterbach', + 'Gossman', + 'Epling', + 'Welk', + 'Daub', + 'Squier', + 'Dicus', + 'Siller', + 'Romaine', + 'Meriwether', + 'Bordner', + 'Baden', + 'Hagins', + 'Sica', + 'Mullane', + 'Jurgensen', + 'Tien', + 'Gertz', + 'Touchstone', + 'Bones', + 'Kimmons', + 'Prisco', + 'Kaser', + 'Drysdale', + 'Jelks', + 'Cerrone', + 'Wolfenbarger', + 'Deckert', + 'Ganley', + 'Fleeman', + 'Cubbage', + 'Woodie', + 'Schwan', + 'Siefert', + 'Rizvi', + 'Heier', + 'Khanna', + 'Leet', + 'Gratz', + 'Mullan', + 'Moorefield', + 'Fishback', + 'Whittenburg', + 'Casson', + 'Statham', + 'Red', + 'Coldiron', + 'Keplinger', + 'Reichman', + 'Brier', + 'Vavra', + 'Housman', + 'Kitson', + 'Fekete', + 'Rotella', + 'Onofre', + 'Orvis', + 'Beutler', + 'Cadwallader', + 'Gabor', + 'Emmanuel', + 'Moretz', + 'Suniga', + 'Mcmath', + 'Kinlaw', + 'Beringer', + 'Gaudreau', + 'Lirette', + 'Drye', + 'Oubre', + 'Gardella', + 'Reigle', + 'Zubia', + 'Mccardle', + 'Ambler', + 'Lucius', + 'Fizer', + 'Hilley', + 'Fischbach', + 'Borelli', + 'Gies', + 'Barks', + 'Sheard', + 'Hammontree', + 'Hogle', + 'Fagg', + 'Buitron', + 'Eiler', + 'Grandstaff', + 'Hank', + 'Wark', + 'Decoteau', + 'Depina', + 'Clabaugh', + 'Desiderio', + 'Kuchta', + 'Trang', + 'Abril', + 'Smathers', + 'Kaspar', + 'Melia', + 'Sandman', + 'Maltese', + 'Mccasland', + 'Rayl', + 'Meche', + 'Wiggin', + 'Saint', + 'Dorner', + 'Columbus', + 'Boatner', + 'Fresquez', + 'Sykora', + 'Shriner', + 'Drumheller', + 'Mahony', + 'Redinger', + 'Radloff', + 'Mitts', + 'Casperson', + 'Gammill', + 'Moraga', + 'Baratta', + 'Tow', + 'Ocon', + 'Cruce', + 'Bohannan', + 'Hurtt', + 'Mose', + 'Caines', + 'Heisey', + 'Pitcock', + 'Swiderski', + 'Shu', + 'Buda', + 'Whidden', + 'Busick', + 'Simas', + 'Croley', + 'Morrisey', + 'Saulsberry', + 'Crudup', + 'Bongiorno', + 'Beem', + 'Bunner', + 'Rosemond', + 'Freire', + 'Casado', + 'Merideth', + 'Selden', + 'Lamarre', + 'Fullwood', + 'Hartig', + 'Kerlin', + 'Lebowitz', + 'Kibbe', + 'Fannon', + 'Hotz', + 'Yerkes', + 'Re', + 'Waddington', + 'Akbar', + 'Baek', + 'Closson', + 'Miers', + 'Bonomo', + 'Wetherbee', + 'Taranto', + 'Henslee', + 'Bartle', + 'Hilger', + 'Asaro', + 'Mahr', + 'Strozier', + 'Agudelo', + 'Kulick', + 'Skoglund', + 'Yamasaki', + 'Schlemmer', + 'Hefley', + 'Waxman', + 'Radley', + 'Sanderlin', + 'Arispe', + 'Galang', + 'Morejon', + 'Stich', + 'Cesario', + 'Silvis', + 'Gurganus', + 'Shofner', + 'Funderburg', + 'Reddish', + 'Rybak', + 'Dingler', + 'Mankin', + 'Renna', + 'Alban', + 'Mckittrick', + 'Lippman', + 'Brenton', + 'Liebman', + 'Santillo', + 'Crigger', + 'Riney', + 'Mccraney', + 'Kluck', + 'Sosnowski', + 'Anspach', + 'Bourdon', + 'Modi', + 'Heer', + 'Mastroianni', + 'Musial', + 'Whiteaker', + 'Summa', + 'Herber', + 'Roselli', + 'Orris', + 'Bert', + 'Dedmon', + 'Kelson', + 'Paone', + 'Barstow', + 'Gerst', + 'Bettinger', + 'Castner', + 'Penman', + 'Broaddus', + 'Ohman', + 'Villalon', + 'Carwile', + 'Fluellen', + 'Ort', + 'Bommarito', + 'Shuff', + 'Cannata', + 'Westgate', + 'Bien', + 'Driggs', + 'Maisonet', + 'Costin', + 'Raine', + 'Banton', + 'Buterbaugh', + 'Katzman', + 'Coreas', + 'Rosalez', + 'Gose', + 'Robie', + 'Winburn', + 'Glancy', + 'Hild', + 'Strock', + 'Umanzor', + 'Hoglund', + 'Kesner', + 'Lynam', + 'Swayze', + 'Grizzard', + 'Fettig', + 'Macko', + 'Schrum', + 'Sours', + 'Yonker', + 'Ebanks', + 'Chiodo', + 'Meaney', + 'Paras', + 'Struthers', + 'Sicard', + 'Leveille', + 'Beckstead', + 'Calero', + 'Fuhrmann', + 'Lybarger', + 'Capo', + 'Adolph', + 'Raabe', + 'Gran', + 'Borel', + 'Ary', + 'Charland', + 'Huh', + 'Steinert', + 'Stemple', + 'Groat', + 'Zang', + 'Nath', + 'Ogara', + 'Pecina', + 'Simoes', + 'Breece', + 'Nascimento', + 'Usry', + 'Gain', + 'Brassfield', + 'Lochner', + 'Pietsch', + 'Wechsler', + 'Sum', + 'Teneyck', + 'Pelt', + 'Burnley', + 'Renzi', + 'Mujica', + 'Profitt', + 'Body', + 'Debusk', + 'Robidoux', + 'Pruneda', + 'Pomerantz', + 'Gonyea', + 'Crosier', + 'Currence', + 'Newborn', + 'Tolleson', + 'Conlan', + 'Dunsmore', + 'Tansey', + 'Clinard', + 'Staudt', + 'Oppenheim', + 'Gossard', + 'Osbourne', + 'Gilyard', + 'Lucido', + 'Tonkin', + 'Mitzel', + 'Sola', + 'Palombo', + 'Duane', + 'Mac', + 'Kerry', + 'Stills', + 'Viveiros', + 'Stallman', + 'Moos', + 'Follis', + 'Maris', + 'Hollier', + 'Gundlach', + 'Moler', + 'Schweigert', + 'Chartrand', + 'Finkle', + 'Meese', + 'Nigh', + 'Amundsen', + 'Brocato', + 'Dreier', + 'Glessner', + 'Weibel', + 'Fritch', + 'Retherford', + 'Rahim', + 'Markert', + 'Ronk', + 'Olmeda', + 'Gosney', + 'Keathley', + 'Luby', + 'Harrill', + 'Dinges', + 'Rocheleau', + 'Meisel', + 'Farrer', + 'Lute', + 'Apel', + 'Pincus', + 'Maida', + 'Jimmerson', + 'Baltz', + 'Cuccia', + 'Heenan', + 'Thieme', + 'Zoeller', + 'Larocco', + 'Abdalla', + 'Classen', + 'Hassinger', + 'Filler', + 'Pidgeon', + 'Hanford', + 'Espy', + 'Goodlett', + 'Jone', + 'Ruggeri', + 'Lisi', + 'Spada', + 'Gerrard', + 'Allbritton', + 'Brazelton', + 'Boggan', + 'Dufault', + 'Espejo', + 'Bodkin', + 'Penix', + 'Dockins', + 'Rascoe', + 'Swarthout', + 'Tritt', + 'Gouin', + 'Lamberth', + 'Bourn', + 'Barnhouse', + 'Guzzo', + 'Netherton', + 'Zamarron', + 'Rosenberry', + 'Dahms', + 'Anwar', + 'Whitesides', + 'Tidmore', + 'Longstreet', + 'Claunch', + 'Ehrhart', + 'Hullinger', + 'Xia', + 'Heideman', + 'Nicklas', + 'Prins', + 'Soni', + 'Dominquez', + 'Vogelsang', + 'Pew', + 'Chess', + 'Simmerman', + 'Brunell', + 'Matthes', + 'Kinnison', + 'Cansler', + 'Weekly', + 'Eger', + 'Garabedian', + 'Milliman', + 'Severns', + 'Magnusson', + 'Fossum', + 'Salamon', + 'Vandoren', + 'Gillingham', + 'Charney', + 'Nokes', + 'Lamon', + 'Irick', + 'Okeeffe', + 'Zou', + 'Kott', + 'Quillin', + 'Friar', + 'Drummer', + 'Catchings', + 'Hamada', + 'Scheck', + 'Setser', + 'Gobble', + 'Condra', + 'Bowley', + 'Deschamps', + 'Sylva', + 'Bartolome', + 'Warfel', + 'Veltri', + 'Speers', + 'Butner', + 'Delorme', + 'Giesler', + 'Sonntag', + 'Wetherell', + 'Ohagan', + 'Torbert', + 'Grandberry', + 'Ronning', + 'Howser', + 'Soden', + 'Rasco', + 'Clauss', + 'Beland', + 'Nicola', + 'Justiniano', + 'Varnum', + 'Fergus', + 'Lazcano', + 'Sartori', + 'Carnley', + 'Lucarelli', + 'Bergh', + 'Wellborn', + 'Bow', + 'Longshore', + 'Marcel', + 'Sumlin', + 'Atilano', + 'Dostal', + 'Westendorf', + 'Stiver', + 'Morency', + 'Herrod', + 'Bologna', + 'Valiente', + 'Weinert', + 'Gaertner', + 'Prock', + 'Spangenberg', + 'Tineo', + 'Cosio', + 'Maass', + 'Rist', + 'Oatman', + 'Waguespack', + 'Cardiel', + 'Grate', + 'Behrends', + 'Linger', + 'Pozo', + 'Scoggin', + 'Jenkinson', + 'Ake', + 'Redick', + 'Bonacci', + 'Rivet', + 'Declue', + 'Swing', + 'Chopra', + 'Leib', + 'Wallner', + 'Grimmer', + 'Wilmes', + 'Pirkle', + 'Stanhope', + 'Knop', + 'Culotta', + 'Dipaola', + 'Hipolito', + 'Gerling', + 'Sennett', + 'Fulghum', + 'Grothe', + 'Krout', + 'Onorato', + 'Donis', + 'Winbush', + 'Aoki', + 'Buscher', + 'Jarquin', + 'Lemanski', + 'Mcgrane', + 'Tardif', + 'Segundo', + 'Caba', + 'Sease', + 'Blinn', + 'Losee', + 'Kirschbaum', + 'Baskett', + 'Knights', + 'Goudeau', + 'Grondin', + 'Harting', + 'Szewczyk', + 'Wieder', + 'Conatser', + 'Romanelli', + 'Freshour', + 'Brizendine', + 'Rolen', + 'Guynn', + 'Laforest', + 'Doris', + 'Sandridge', + 'Dublin', + 'Blancas', + 'Duryea', + 'Naik', + 'Paradiso', + 'Scheele', + 'Westra', + 'Hassel', + 'Bertucci', + 'Fansler', + 'Flohr', + 'Solt', + 'Suess', + 'Keiper', + 'Downard', + 'Ivester', + 'Darley', + 'Seales', + 'Kolesar', + 'Overbeck', + 'Subramanian', + 'Panter', + 'Parshall', + 'Stannard', + 'Gravley', + 'Dhaliwal', + 'Shippy', + 'Dolphin', + 'Lepper', + 'Gorby', + 'Delmonte', + 'Piccirillo', + 'Besaw', + 'Alligood', + 'Rhymes', + 'Eisenman', + 'Deveau', + 'Tilden', + 'Girton', + 'Buser', + 'Rentschler', + 'Sopko', + 'Uriostegui', + 'Wasko', + 'Noffsinger', + 'Barkman', + 'Dyck', + 'Ferrero', + 'Kiehl', + 'Leffel', + 'Rybicki', + 'Hedstrom', + 'Bracamontes', + 'Zebrowski', + 'Blundell', + 'Brightman', + 'Hegwood', + 'Beecham', + 'Kolbe', + 'Bucy', + 'Bondi', + 'Borgen', + 'Gibbens', + 'Pullman', + 'Letcher', + 'Ferebee', + 'Kitterman', + 'Seefeldt', + 'Upham', + 'Thiede', + 'Bolster', + 'Bastin', + 'Bondy', + 'Mershon', + 'Nickson', + 'Drozd', + 'Schroyer', + 'Mcmenamin', + 'Reith', + 'Lovin', + 'San', + 'Henegar', + 'Haislip', + 'Barco', + 'Arter', + 'Malecki', + 'Teeple', + 'Walpole', + 'Feil', + 'Neitzel', + 'Ostler', + 'Parmar', + 'Vinton', + 'Jan', + 'Weldy', + 'Etherton', + 'Joya', + 'Saliba', + 'Schnur', + 'Belles', + 'Mcgeorge', + 'Olden', + 'Rarick', + 'Worrall', + 'Degen', + 'Froman', + 'Odowd', + 'Einhorn', + 'Fimbres', + 'Maresca', + 'Rocker', + 'Arend', + 'Biermann', + 'Guimond', + 'Mcgurk', + 'Goll', + 'Santilli', + 'Hadlock', + 'Teer', + 'Dillion', + 'Jorden', + 'Honore', + 'Bromberg', + 'Stoneman', + 'Blossom', + 'Guzik', + 'Stockstill', + 'Wax', + 'Anello', + 'Blasko', + 'Frese', + 'Berthold', + 'Morefield', + 'Baptist', + 'Legault', + 'Bouffard', + 'Bebout', + 'Darnall', + 'Buscemi', + 'Buentello', + 'Scroggs', + 'Gatton', + 'Turnquist', + 'Lucht', + 'Remick', + 'Godlewski', + 'Bradt', + 'Waldorf', + 'Zeringue', + 'Rowen', + 'Mowbray', + 'Parkey', + 'Engram', + 'Mazzarella', + 'Kirkbride', + 'Gridley', + 'Kaster', + 'Lorenzana', + 'Wareham', + 'Star', + 'Marshburn', + 'Everman', + 'Wolfram', + 'Zick', + 'Hyun', + 'Yerger', + 'Baham', + 'Gebhard', + 'Ruf', + 'Suchy', + 'Tieman', + 'Wenz', + 'Schiro', + 'Fout', + 'Abdo', + 'Hayter', + 'Cleaves', + 'Fritsche', + 'Meurer', + 'Riendeau', + 'Ventimiglia', + 'Cervera', + 'Mallow', + 'Allie', + 'Hanscom', + 'Viloria', + 'Dubon', + 'Leeson', + 'Ruffing', + 'Jonson', + 'Fenimore', + 'Gonzaga', + 'Schriver', + 'Traina', + 'Mecca', + 'Lantigua', + 'Baril', + 'Harford', + 'Bartow', + 'Asbell', + 'Rumley', + 'Brogden', + 'Derryberry', + 'Ketner', + 'Dakin', + 'Wass', + 'Fallis', + 'Wada', + 'Studdard', + 'Lecroy', + 'Fetty', + 'Nass', + 'Chute', + 'Parman', + 'Bevans', + 'Headen', + 'Hysell', + 'Merten', + 'Most', + 'Fuss', + 'Schrank', + 'Last', + 'Even', + 'Vaz', + 'Sifford', + 'Streets', + 'Claude', + 'Bronstein', + 'Sherburne', + 'Wadkins', + 'Gascon', + 'Seiter', + 'Steffan', + 'Cardozo', + 'Henricks', + 'Claflin', + 'Etzel', + 'Kulas', + 'Trinkle', + 'Ortegon', + 'Phaneuf', + 'Langworthy', + 'Barb', + 'Mazon', + 'Veney', + 'Redondo', + 'Tieu', + 'Laursen', + 'Nanez', + 'Votaw', + 'Walraven', + 'Abella', + 'Dsouza', + 'Bayley', + 'Townson', + 'Applebaum', + 'Mazzei', + 'Piche', + 'Rivenbark', + 'Urrea', + 'Dolph', + 'Bonifacio', + 'Shehan', + 'Glascock', + 'Verde', + 'Gadberry', + 'Trimm', + 'Dowe', + 'Khang', + 'Mulhall', + 'Selzer', + 'Raub', + 'Ore', + 'Copes', + 'Masuda', + 'Moscoso', + 'Zeitler', + 'Mollica', + 'Iler', + 'Leventhal', + 'Manders', + 'Prue', + 'Fergerson', + 'Brose', + 'Phu', + 'Debellis', + 'Haan', + 'Schoening', + 'Stager', + 'Demos', + 'Rumble', + 'Brunt', + 'Nivens', + 'Manigault', + 'Buendia', + 'Deschenes', + 'Wittmer', + 'Hamon', + 'Hentz', + 'Loud', + 'Oseguera', + 'Rayo', + 'Macfarland', + 'Mimms', + 'Grunewald', + 'Hartness', + 'Wynkoop', + 'Wallingford', + 'Juergens', + 'Meszaros', + 'Riehle', + 'Trego', + 'Neece', + 'Coggin', + 'Burrill', + 'Laurel', + 'Routt', + 'Rodger', + 'Krum', + 'Faulkenberry', + 'Labadie', + 'Hemming', + 'Fulp', + 'Jamal', + 'Deloney', + 'Fells', + 'Bohnert', + 'Kapadia', + 'Guill', + 'Coop', + 'Broadhurst', + 'Mccrimmon', + 'Bonfiglio', + 'Capetillo', + 'Chamorro', + 'Gargiulo', + 'Stoehr', + 'Schlecht', + 'Karlson', + 'Garten', + 'Remer', + 'Mebane', + 'Finnigan', + 'Bourdeau', + 'Espindola', + 'Shukla', + 'Petras', + 'Steinberger', + 'Casner', + 'Carico', + 'Seevers', + 'Westwood', + 'Hosea', + 'Mcphillips', + 'Nygren', + 'Wagaman', + 'Coghlan', + 'Sutherlin', + 'Sellman', + 'Bashore', + 'Mullican', + 'Stoneburner', + 'Montag', + 'Karst', + 'Murch', + 'Puffer', + 'Sabala', + 'Pauli', + 'Odonoghue', + 'Lassen', + 'Mattera', + 'Mcaninch', + 'Portugal', + 'Clingan', + 'Michener', + 'Munsell', + 'Streetman', + 'Harton', + 'Swarts', + 'Honig', + 'Jesus', + 'Rentas', + 'Trosper', + 'Coffield', + 'Burket', + 'Donaghy', + 'Byun', + 'Riess', + 'Mcqueary', + 'Stayton', + 'Ferron', + 'Wedding', + 'Tibbitts', + 'Frisbee', + 'Reinoso', + 'Lama', + 'Allyn', + 'Sheen', + 'Tyra', + 'Golder', + 'Veasey', + 'Schroth', + 'Kukla', + 'Narayan', + 'Vandemark', + 'Horace', + 'Kadlec', + 'Portnoy', + 'Reynosa', + 'Surprenant', + 'Savell', + 'Seagle', + 'Vandervort', + 'Eye', + 'Eccleston', + 'Blaise', + 'Glaspie', + 'Cressman', + 'Lahti', + 'Yocom', + 'Leppert', + 'Brendle', + 'Greenough', + 'Relyea', + 'Marinez', + 'Bouley', + 'Fincham', + 'Highley', + 'Goza', + 'Norrell', + 'Yusuf', + 'Ohm', + 'Thakkar', + 'Cosenza', + 'Efird', + 'Heger', + 'Dysart', + 'Mango', + 'Fitchett', + 'Kring', + 'Paolucci', + 'Menges', + 'Layden', + 'Mccleery', + 'Benko', + 'Sandor', + 'Blakney', + 'Zanders', + 'Gengler', + 'Fujita', + 'Huls', + 'Basquez', + 'Trepanier', + 'Spadaro', + 'Ankney', + 'Damiani', + 'Games', + 'Cherney', + 'Fitzsimons', + 'Dearmas', + 'Bonet', + 'Diem', + 'Shimp', + 'Agrawal', + 'Gaw', + 'Gahagan', + 'Fossett', + 'Kafka', + 'Dedios', + 'Coryell', + 'Bahe', + 'Wurm', + 'Wishart', + 'Dray', + 'Armer', + 'Khalid', + 'Gassaway', + 'Vawter', + 'Loew', + 'Coello', + 'Curren', + 'Gilder', + 'Letendre', + 'Sprecher', + 'Rexrode', + 'Minich', + 'Koepp', + 'Mulloy', + 'Bohman', + 'Gambrel', + 'Hackley', + 'Demasi', + 'Hoffert', + 'Kittredge', + 'Maltby', + 'Nyquist', + 'Schieber', + 'Kennell', + 'Calderwood', + 'Compean', + 'Romines', + 'Simonelli', + 'Pico', + 'Oda', + 'Holte', + 'Bate', + 'Learn', + 'Lowenstein', + 'Holtman', + 'Mingus', + 'Sessa', + 'Legendre', + 'Gerrish', + 'Schoenberg', + 'Liberman', + 'Mclachlan', + 'Higginson', + 'Vince', + 'Mallery', + 'Delamora', + 'Difranco', + 'Lein', + 'Haltom', + 'Dority', + 'Marcellus', + 'Heskett', + 'Harward', + 'Spinney', + 'Darwin', + 'Baylis', + 'Amodeo', + 'Schwandt', + 'Mcmorrow', + 'Foraker', + 'Fyfe', + 'Shingleton', + 'Blandon', + 'Waddy', + 'Ricca', + 'Scheffer', + 'Balliet', + 'Philipp', + 'Rish', + 'Hattaway', + 'Krejci', + 'Orduno', + 'Passarelli', + 'Skala', + 'Oram', + 'Raynes', + 'Hiett', + 'Tolan', + 'Kimbell', + 'Delara', + 'Farhat', + 'Kamps', + 'Mohney', + 'Escarcega', + 'Mell', + 'Mcquay', + 'Cannizzaro', + 'Deuel', + 'Losoya', + 'Goldin', + 'Zaidi', + 'Gillmore', + 'Buelow', + 'Maust', + 'Guerrera', + 'Bouck', + 'Bick', + 'Kelty', + 'Pines', + 'Braziel', + 'Bruening', + 'Frenzel', + 'Kenna', + 'Loria', + 'Koren', + 'Cornelio', + 'Poisson', + 'Raker', + 'Ptak', + 'Bohr', + 'Coury', + 'Failla', + 'Cipriani', + 'Delany', + 'Marmon', + 'Kinch', + 'Figgins', + 'Delfino', + 'Risser', + 'Hickox', + 'Fager', + 'Turpen', + 'Dalzell', + 'Falvey', + 'Leiker', + 'Mcgonigal', + 'Vaquera', + 'Weisser', + 'Viviano', + 'Shrock', + 'Minaya', + 'Chitty', + 'Costley', + 'Granberry', + 'Dimaria', + 'Roma', + 'Ortis', + 'Burnam', + 'Burruss', + 'Stoughton', + 'Cales', + 'Burrage', + 'Vanwagner', + 'Espada', + 'Mccuen', + 'Baize', + 'Pullum', + 'Gerrity', + 'Vicari', + 'Heuser', + 'Semler', + 'Fear', + 'Havener', + 'Kash', + 'Thibodaux', + 'Hadaway', + 'Smithwick', + 'Eisenhart', + 'Hodgin', + 'Cluck', + 'Godby', + 'Belli', + 'Demaree', + 'Beyers', + 'Jared', + 'Mall', + 'Defoe', + 'Chmura', + 'Hepworth', + 'Hintze', + 'Luk', + 'Vanriper', + 'Solari', + 'Atlas', + 'Outland', + 'Hanselman', + 'Scharff', + 'Rhein', + 'Milone', + 'Rochford', + 'Mynatt', + 'Lambdin', + 'Sandell', + 'Grounds', + 'Tabler', + 'Smartt', + 'Dejean', + 'Clayborne', + 'Vangorder', + 'Eastin', + 'Hiler', + 'Lisle', + 'Gramling', + 'Degarmo', + 'Malec', + 'Tinkham', + 'Vanauken', + 'Andrzejewski', + 'Rundell', + 'Happel', + 'Strine', + 'Koerber', + 'Haner', + 'Ashcroft', + 'Hille', + 'Cairo', + 'Upson', + 'Mooring', + 'Koury', + 'Vito', + 'Oberlin', + 'Christiano', + 'Redfearn', + 'Trower', + 'Hibbler', + 'Sumter', + 'Raftery', + 'Geise', + 'Wohl', + 'Gorney', + 'Peasley', + 'Heap', + 'Brazeal', + 'Mccleskey', + 'Yard', + 'Mcroy', + 'Amend', + 'Cutshaw', + 'Kazmierczak', + 'Strandberg', + 'Lasko', + 'Newlon', + 'File', + 'Bevill', + 'Silvera', + 'Arakaki', + 'Kelsch', + 'Ostendorf', + 'Cowie', + 'Hove', + 'Doles', + 'Bouvier', + 'Fecteau', + 'Hasegawa', + 'Paschke', + 'Taing', + 'Heldt', + 'Allaire', + 'Ochsner', + 'Giusti', + 'Reisner', + 'Swim', + 'Laidlaw', + 'Vanderbilt', + 'Atterberry', + 'Barthelemy', + 'Chalker', + 'Degregorio', + 'Mastro', + 'Patlan', + 'Gipe', + 'Roosa', + 'Filkins', + 'Styron', + 'Bryer', + 'Blackston', + 'Hagel', + 'Fralick', + 'Linhart', + 'Moura', + 'Pavia', + 'Pavao', + 'Furry', + 'Petrus', + 'Fairweather', + 'Blystone', + 'Co', + 'Divito', + 'Villicana', + 'Winch', + 'Tome', + 'Lanoue', + 'Biron', + 'Noell', + 'Mckeel', + 'Worthey', + 'Aten', + 'Eyer', + 'Zhen', + 'Tischler', + 'Luoma', + 'Opp', + 'Riggin', + 'Furness', + 'Wolbert', + 'Penning', + 'Draves', + 'Whitehill', + 'Dudgeon', + 'Kinkead', + 'Luca', + 'Rosell', + 'Macauley', + 'Goldner', + 'Ishikawa', + 'Kirchhoff', + 'Lamarca', + 'Miyashiro', + 'Weger', + 'Wuest', + 'Kreis', + 'Urbanek', + 'Palko', + 'Victorino', + 'Morado', + 'Burchette', + 'Holyfield', + 'Tulloch', + 'Twombly', + 'Munk', + 'Woolford', + 'Knisely', + 'Locher', + 'Eckart', + 'Rancourt', + 'Pyron', + 'Edney', + 'Besser', + 'Truex', + 'Monterroso', + 'Bruneau', + 'Province', + 'Permenter', + 'Nims', + 'Rollison', + 'Cabell', + 'Sylvain', + 'Salman', + 'Signorelli', + 'Vegas', + 'Maddy', + 'Bachelder', + 'Sevigny', + 'Stolte', + 'Chavarin', + 'Lukes', + 'Rather', + 'Gartland', + 'Kurek', + 'Nantz', + 'Savard', + 'Finegan', + 'No', + 'Chichester', + 'Newbill', + 'Mahnke', + 'Sax', + 'Sowinski', + 'Wendler', + 'Cadiz', + 'Male', + 'Mealey', + 'Brookes', + 'Enderle', + 'Valenta', + 'Tooker', + 'Whitbeck', + 'Threet', + 'Cavitt', + 'Murtagh', + 'Phalen', + 'Errico', + 'Merkley', + 'Ju', + 'Zachery', + 'Bramer', + 'Henline', + 'Noga', + 'Woelfel', + 'Deras', + 'Amen', + 'Aldape', + 'Bartling', + 'Claros', + 'Spurrier', + 'Ginder', + 'Fred', + 'Giberson', + 'Ryba', + 'Sommerfeld', + 'Dahle', + 'Endo', + 'Haddon', + 'Bowlby', + 'Wagener', + 'Ketter', + 'Balint', + 'Goheen', + 'Motsinger', + 'Celentano', + 'Drawdy', + 'Dennehy', + 'Mcelligott', + 'Nakamoto', + 'Deines', + 'Goldsby', + 'Drakeford', + 'Steffy', + 'Streich', + 'Villasana', + 'Cermak', + 'Prill', + 'Ellzey', + 'Gartrell', + 'Duffie', + 'Rother', + 'Buse', + 'Luz', + 'Groen', + 'Laviolette', + 'Roles', + 'Days', + 'Eash', + 'Haefner', + 'Font', + 'Mcree', + 'Bustillo', + 'Coughlan', + 'Bax', + 'Hoxie', + 'Barre', + 'Scaife', + 'Nowacki', + 'Reichardt', + 'Rogel', + 'Ivins', + 'Vanderburg', + 'Etchison', + 'Chesson', + 'Molden', + 'Giuliani', + 'Goodpaster', + 'Kriner', + 'Sturtz', + 'Tschida', + 'Henschel', + 'Asselin', + 'Kocsis', + 'Kroger', + 'Swayne', + 'Gallop', + 'Fraker', + 'Lauro', + 'Tuohy', + 'Scholes', + 'Croxton', + 'Fertig', + 'Gregerson', + 'Gundersen', + 'Lehrer', + 'Monsivais', + 'Pilla', + 'Weishaar', + 'Gutshall', + 'Winget', + 'Human', + 'Oberry', + 'Learned', + 'Marburger', + 'Teed', + 'Parrilla', + 'Due', + 'Hartzler', + 'Cieslak', + 'Feltz', + 'Geren', + 'Wile', + 'Waldrip', + 'Clore', + 'Stutler', + 'Feehan', + 'Lacher', + 'Felter', + 'Barakat', + 'Flippen', + 'Holsey', + 'Finkbeiner', + 'Istre', + 'Lengyel', + 'Lupercio', + 'Beegle', + 'Habel', + 'Hammill', + 'Kifer', + 'Buswell', + 'Deboard', + 'Guilliams', + 'Ahlstrom', + 'Beliveau', + 'Sasse', + 'Delker', + 'Letterman', + 'Avey', + 'Bohlen', + 'Piner', + 'Folmar', + 'Barile', + 'Komar', + 'Bonelli', + 'Lamay', + 'Cora', + 'Deere', + 'Sanon', + 'Deppe', + 'Emmerich', + 'Giannone', + 'Navarra', + 'Hudock', + 'Seaborn', + 'Burda', + 'Faz', + 'Stefani', + 'Beemer', + 'Vose', + 'Calandra', + 'Eno', + 'Figueredo', + 'Lauck', + 'Schwindt', + 'Dumais', + 'Hedger', + 'Capp', + 'Barreiro', + 'Buker', + 'Spruell', + 'Bertolini', + 'Hoar', + 'Tiemann', + 'Vandenbosch', + 'Winebrenner', + 'Maio', + 'Winship', + 'Brissette', + 'Hansell', + 'Elsey', + 'Hansard', + 'Gildersleeve', + 'Hambright', + 'Borba', + 'Konieczny', + 'Lundell', + 'Tiedemann', + 'Siegler', + 'Ying', + 'Mckinsey', + 'Olah', + 'Boersma', + 'Younkin', + 'Evanoff', + 'Nakashima', + 'Scalia', + 'Piro', + 'Colorado', + 'Felan', + 'Fuentez', + 'Blea', + 'Gowin', + 'Hanning', + 'Byrom', + 'Morant', + 'Bachand', + 'Mcsorley', + 'Peaslee', + 'Bardsley', + 'Stilson', + 'Severs', + 'Kincheloe', + 'Kyler', + 'Aurand', + 'Bento', + 'Hoeppner', + 'Mertes', + 'Pickrell', + 'Rustad', + 'Millikan', + 'Celestino', + 'Hovland', + 'Kurowski', + 'Zollinger', + 'Tallon', + 'Junkins', + 'Mizrahi', + 'Bomberger', + 'Farrand', + 'Curto', + 'Bona', + 'Donatelli', + 'Eppley', + 'Schurman', + 'Henao', + 'Tomberlin', + 'Provencio', + 'Speidel', + 'Cree', + 'Inskeep', + 'Yeates', + 'Hoggatt', + 'Hinkson', + 'Ficklin', + 'Mcnealy', + 'Cabanas', + 'Laycock', + 'Theroux', + 'Weymouth', + 'Mabie', + 'Hatchell', + 'Bohanon', + 'Bilger', + 'Nazarian', + 'Weist', + 'Depue', + 'Mangini', + 'Gelb', + 'Luman', + 'Blass', + 'Desroches', + 'Hearon', + 'Mcmiller', + 'Stoltenberg', + 'Parenti', + 'Daulton', + 'Smail', + 'Chisum', + 'Benefiel', + 'Tetrault', + 'Foland', + 'Reddington', + 'Mattei', + 'Custis', + 'Fransen', + 'Zylstra', + 'Salvaggio', + 'Factor', + 'Deshong', + 'Biederman', + 'Sirianni', + 'Steckler', + 'Thrall', + 'Dorsch', + 'Harpe', + 'Tell', + 'Galusha', + 'Guttman', + 'Raposa', + 'Jaros', + 'Lipka', + 'Shive', + 'Shand', + 'Brizuela', + 'Horvat', + 'Pisciotta', + 'Sorge', + 'Riebe', + 'Vanderlaan', + 'Isenhour', + 'Franson', + 'Goslin', + 'Amore', + 'Leachman', + 'Foulks', + 'Alamillo', + 'Scarpa', + 'Tickle', + 'Pettitt', + 'Orrell', + 'Fleckenstein', + 'Sapien', + 'Roye', + 'Mcmeans', + 'Sligh', + 'Landgraf', + 'Cecere', + 'Aune', + 'Ketron', + 'Welcher', + 'Tilford', + 'Maston', + 'Overall', + 'Fails', + 'Bah', + 'Ketterman', + 'Lindauer', + 'Saxe', + 'Majka', + 'Goodenough', + 'Panella', + 'Ramm', + 'Caley', + 'Christine', + 'Kinsler', + 'Pippen', + 'Murph', + 'Ammann', + 'Falkowski', + 'Madonna', + 'Seligman', + 'Rommel', + 'Lareau', + 'Melone', + 'Frasure', + 'Joyal', + 'Piekarski', + 'Porcelli', + 'Kennington', + 'Pica', + 'Ankrom', + 'Capron', + 'Chatmon', + 'Horrigan', + 'Morelos', + 'Noren', + 'Paolini', + 'Wildermuth', + 'Rossow', + 'Dorgan', + 'Pawlik', + 'Reiber', + 'Rothenberger', + 'Mcgonigle', + 'Oren', + 'Jeans', + 'Vivas', + 'Gerner', + 'Brzozowski', + 'Croyle', + 'Klick', + 'Vidaurri', + 'Wollman', + 'Brouillard', + 'Dejohn', + 'Meikle', + 'Grochowski', + 'Kaczor', + 'Philbin', + 'Sperber', + 'Vancil', + 'Zornes', + 'Strope', + 'Housel', + 'Minks', + 'Dike', + 'Jasmin', + 'Denicola', + 'Gokey', + 'Dominy', + 'Gillham', + 'Viray', + 'Herz', + 'Hursh', + 'Koeller', + 'Caicedo', + 'Near', + 'Harrel', + 'Veale', + 'Gustavson', + 'Lopiccolo', + 'Goldschmidt', + 'Loder', + 'Vannorman', + 'Maske', + 'Randel', + 'Pinner', + 'Buntin', + 'Roache', + 'Pinnock', + 'Dimaio', + 'Heckert', + 'Perrigo', + 'Schank', + 'Lisowski', + 'Brownstein', + 'Sharer', + 'Hambleton', + 'Maker', + 'Hursey', + 'Aguado', + 'Tian', + 'Rheaume', + 'Becraft', + 'Sowders', + 'Bratt', + 'Tebo', + 'Eid', + 'Reinecke', + 'Storck', + 'Pech', + 'Alspaugh', + 'Grell', + 'Purdue', + 'Jennette', + 'Pauling', + 'Wint', + 'Knupp', + 'Madewell', + 'Schwanke', + 'Tellier', + 'Washer', + 'Staff', + 'Keely', + 'Lisenby', + 'Walder', + 'Kennerly', + 'Ip', + 'Michalik', + 'Eichner', + 'Disbrow', + 'Bellomy', + 'Boesch', + 'Chirico', + 'Lietz', + 'Ploof', + 'Dyar', + 'Bai', + 'Lary', + 'Corbo', + 'Danaher', + 'Schiavo', + 'Giacalone', + 'Pentz', + 'Studley', + 'Doyal', + 'Edie', + 'Nathaniel', + 'Cambra', + 'Fenstermacher', + 'Garst', + 'Gaudio', + 'Zavaleta', + 'Castilla', + 'Griffeth', + 'Warthen', + 'Derringer', + 'Samsel', + 'Mattia', + 'Boelter', + 'Mathieson', + 'Estelle', + 'Frisk', + 'Hipple', + 'Garceau', + 'Ehrman', + 'Buchner', + 'Frailey', + 'Ganey', + 'Belser', + 'Leiby', + 'Schwind', + 'Hagberg', + 'Hooley', + 'Rafter', + 'Hasting', + 'Mcnab', + 'Piggott', + 'Millhouse', + 'Brescia', + 'Giancola', + 'Grob', + 'Uresti', + 'Tawney', + 'Huot', + 'Mizer', + 'Storrs', + 'Shobe', + 'Blade', + 'Baumbach', + 'Eppler', + 'Henningsen', + 'Kmetz', + 'Sepeda', + 'Pangburn', + 'Falgout', + 'Hurn', + 'Sholar', + 'Kendricks', + 'Brimhall', + 'Bucklin', + 'Hruby', + 'Hunziker', + 'Krenz', + 'Schwager', + 'Murley', + 'Crittendon', + 'Broady', + 'Kintz', + 'Entrekin', + 'Estey', + 'Sharrow', + 'Quarterman', + 'Gumbs', + 'Steely', + 'Machin', + 'Difiore', + 'Desch', + 'Wiedemann', + 'Tonn', + 'Villines', + 'Mcdole', + 'Bashir', + 'Beauford', + 'Crary', + 'Gallina', + 'Wolak', + 'Aburto', + 'Hasler', + 'Gullion', + 'Bracewell', + 'Rusher', + 'Sarvis', + 'Dargan', + 'Garbarino', + 'Pigeon', + 'Blasi', + 'Viens', + 'Reising', + 'Vosburgh', + 'Canipe', + 'Mcnett', + 'Bruss', + 'Shiflet', + 'Pinard', + 'Lattin', + 'Armbrust', + 'Peffer', + 'Shotts', + 'Arbaugh', + 'Hux', + 'First', + 'Bolds', + 'Ceaser', + 'Cephas', + 'Bormann', + 'Broadwell', + 'Qian', + 'Talamantez', + 'Vandermolen', + 'Maza', + 'Kinnear', + 'Bullins', + 'Arant', + 'Brodbeck', + 'Rolfes', + 'Wisneski', + 'Dague', + 'Dudas', + 'Greener', + 'Noguera', + 'Greeno', + 'Daddario', + 'Giambrone', + 'Menon', + 'Sherrick', + 'Spier', + 'Semon', + 'Fendley', + 'Crichton', + 'Moree', + 'Stratford', + 'Zobel', + 'Halladay', + 'Keesler', + 'Prewett', + 'Deavers', + 'Kamal', + 'Bottom', + 'Caves', + 'Harshaw', + 'Fretz', + 'Secord', + 'Seibold', + 'Pantaleon', + 'Greek', + 'Baumeister', + 'Kleven', + 'Kos', + 'Orban', + 'Papke', + 'Shatto', + 'Cui', + 'Boan', + 'Nevitt', + 'Hultgren', + 'Kreiser', + 'Veres', + 'Jent', + 'Merck', + 'Gibby', + 'Hosch', + 'Mallet', + 'Dock', + 'Dallman', + 'Loiacono', + 'Tetzlaff', + 'Arboleda', + 'Mclelland', + 'Willing', + 'Coonrod', + 'Cappiello', + 'Courtemanche', + 'Halperin', + 'Odegard', + 'Hornyak', + 'Stem', + 'Doner', + 'Saffold', + 'Hochman', + 'Ing', + 'Knudtson', + 'Laabs', + 'Selleck', + 'Bassler', + 'Kamin', + 'Hur', + 'Forward', + 'Finnie', + 'Blubaugh', + 'Hitz', + 'Litteral', + 'Mansur', + 'Rosenow', + 'Vermeulen', + 'Markarian', + 'Marceau', + 'Weisner', + 'Sharpless', + 'Cunniff', + 'Guilfoyle', + 'Lauver', + 'Lukasik', + 'Ripp', + 'Wierzbicki', + 'Wunsch', + 'Boothby', + 'Selfridge', + 'Mckey', + 'Vandermeer', + 'Vanhoy', + 'Edlund', + 'Eggen', + 'Bickett', + 'Hallum', + 'Brow', + 'Rhymer', + 'Buckalew', + 'Haughey', + 'Hentges', + 'Matthies', + 'Mccloy', + 'Simmon', + 'Concha', + 'Feingold', + 'Maglio', + 'Olaughlin', + 'Tassone', + 'Abbasi', + 'Oyola', + 'Mook', + 'Makin', + 'Carnegie', + 'Yue', + 'Sethi', + 'Duchene', + 'Mcnear', + 'Bartolo', + 'Hegedus', + 'Knoblauch', + 'Orner', + 'Hottinger', + 'Lovitt', + 'Harkless', + 'Anastasio', + 'Hohmann', + 'Mangione', + 'Dalby', + 'Urich', + 'Shuttleworth', + 'Guilbeau', + 'Bausch', + 'Demartini', + 'Difrancesco', + 'Schwalm', + 'Steere', + 'Guel', + 'Blanford', + 'Flax', + 'Fearon', + 'Severe', + 'Canto', + 'Krogh', + 'Meola', + 'Dykema', + 'Angelini', + 'Pooley', + 'Raff', + 'Rister', + 'Baehr', + 'Daubert', + 'Dechant', + 'Kliewer', + 'Hamdan', + 'Gaiser', + 'Lichty', + 'Pomerleau', + 'Uhler', + 'Membreno', + 'Printz', + 'Worman', + 'Thornley', + 'Burbridge', + 'Burdge', + 'Schnitzer', + 'Swanberg', + 'Steinkamp', + 'Heidel', + 'Karch', + 'Igo', + 'Mccausland', + 'Huskins', + 'Kuss', + 'Newbern', + 'Peete', + 'Godbolt', + 'Climer', + 'Neuenschwander', + 'Then', + 'Tietjen', + 'Trombetta', + 'Hawke', + 'Hazlewood', + 'Mayse', + 'Patillo', + 'Banos', + 'Kuck', + 'Lashbrook', + 'Sarkisian', + 'Goldberger', + 'Moravec', + 'Arey', + 'Crosswhite', + 'Elders', + 'Fricks', + 'Hercules', + 'Bester', + 'Erhart', + 'Kuper', + 'Sickels', + 'Mun', + 'Beddingfield', + 'Panetta', + 'Poplawski', + 'Lansford', + 'Negri', + 'Dawe', + 'Belair', + 'Lattimer', + 'Betty', + 'Raye', + 'Gobert', + 'Dragoo', + 'Horney', + 'Strawbridge', + 'Howery', + 'Bosarge', + 'Panzer', + 'Labrador', + 'Ransdell', + 'Trumbo', + 'Aubry', + 'Fenderson', + 'Fukuda', + 'Grosz', + 'Jacome', + 'Slick', + 'Kogut', + 'Haig', + 'Fouse', + 'Hufnagel', + 'Kehr', + 'Musselwhite', + 'Otwell', + 'Raddatz', + 'Oliverio', + 'Sluss', + 'Crossen', + 'Guidroz', + 'Mollett', + 'Sumler', + 'Chmiel', + 'Guinan', + 'Vita', + 'Wieser', + 'Ohlson', + 'Bubb', + 'Stennett', + 'Bugbee', + 'Minchew', + 'Grado', + 'Calcagno', + 'Losh', + 'Witzel', + 'Brandl', + 'Geoghegan', + 'Vanbrunt', + 'Smalling', + 'Carignan', + 'Schuelke', + 'Sienkiewicz', + 'Sollars', + 'Dames', + 'Malkin', + 'Rodriges', + 'Rozanski', + 'Tews', + 'Aust', + 'Bardin', + 'Voorhies', + 'Rines', + 'Courts', + 'Bannerman', + 'Martinsen', + 'Malick', + 'Collar', + 'Twilley', + 'Freiberg', + 'Latiolais', + 'Zehnder', + 'Mannon', + 'Becnel', + 'Cowans', + 'Arrigo', + 'Crago', + 'Curtsinger', + 'Gassman', + 'Marcelo', + 'Rosendahl', + 'Benito', + 'Cortright', + 'Carlon', + 'Kenton', + 'Hemminger', + 'Martinek', + 'Galeana', + 'Cobble', + 'Ruffino', + 'Wittrock', + 'Aberle', + 'Catanese', + 'Huezo', + 'Soules', + 'Ashraf', + 'Mera', + 'Gash', + 'Agnello', + 'Hauk', + 'Hayek', + 'Rahm', + 'Higham', + 'Kondo', + 'Almon', + 'Earwood', + 'Kriebel', + 'Philbrook', + 'Rimer', + 'Cuffee', + 'Wolfgram', + 'Wardwell', + 'Ridder', + 'Runner', + 'Houchens', + 'Vasser', + 'Charlesworth', + 'Dierks', + 'Molter', + 'Orosz', + 'Roudebush', + 'Coca', + 'Brost', + 'Lovern', + 'Brott', + 'Baudoin', + 'Prophet', + 'Bermea', + 'Ulm', + 'Bahl', + 'Ulery', + 'Caraveo', + 'Maez', + 'Corchado', + 'Baillie', + 'Colmenero', + 'Rebolledo', + 'Shevlin', + 'Mehaffey', + 'Hedin', + 'Pickell', + 'Spiro', + 'Coatney', + 'Gentner', + 'Fuhr', + 'Zeh', + 'Fuerte', + 'Knerr', + 'Nakata', + 'Voll', + 'Zach', + 'Gatica', + 'Rabalais', + 'Macek', + 'Petti', + 'Dickison', + 'Sheley', + 'Kinner', + 'Effinger', + 'Axelson', + 'Overbay', + 'Vancleve', + 'Speegle', + 'Muntz', + 'Sang', + 'Mcleroy', + 'Aleshire', + 'Holdridge', + 'Knouse', + 'Saling', + 'Zacher', + 'Zambrana', + 'Neblett', + 'Cichon', + 'Herdman', + 'Poli', + 'Schisler', + 'Antrim', + 'Babineau', + 'Coplin', + 'Straughn', + 'Watlington', + 'Burbach', + 'Campanelli', + 'Coletta', + 'Tennis', + 'Dymond', + 'Darosa', + 'Chard', + 'Delcampo', + 'Lyden', + 'Piland', + 'Eslick', + 'Beets', + 'Ransome', + 'Schuett', + 'Styers', + 'Fegley', + 'Corning', + 'Crume', + 'Villeneuve', + 'Schmeling', + 'Zeiger', + 'Blaker', + 'Ramsden', + 'Carol', + 'Roseboro', + 'Egner', + 'Filip', + 'Poitras', + 'Flanery', + 'Cothren', + 'Bridger', + 'Hoose', + 'Demas', + 'Kozel', + 'Marzano', + 'Penwell', + 'Rast', + 'Whicker', + 'Haslett', + 'Bibby', + 'Keese', + 'Montilla', + 'Sultana', + 'Resendes', + 'Vanscoy', + 'Dinan', + 'Bala', + 'Dirksen', + 'Ek', + 'Shimer', + 'Doshi', + 'Mayeux', + 'Streater', + 'Dattilo', + 'Marlar', + 'Senft', + 'Vanalstine', + 'Rehberg', + 'Vanderhoff', + 'Brenes', + 'Motto', + 'Sproles', + 'Toone', + 'Royall', + 'Beaudette', + 'Belding', + 'Berta', + 'Carmean', + 'Simonian', + 'Avera', + 'Martina', + 'Kind', + 'Buchheit', + 'Corrao', + 'Crumrine', + 'Wertman', + 'Lininger', + 'Pressman', + 'Slane', + 'Manges', + 'Theus', + 'Canizales', + 'Eugenio', + 'Ferrigno', + 'Ellard', + 'Stilley', + 'Crabbe', + 'Procter', + 'Baccus', + 'Hellmann', + 'Risk', + 'Schild', + 'Tostado', + 'Fessenden', + 'Glines', + 'Perone', + 'Carns', + 'Belote', + 'Deshotel', + 'Bottomley', + 'Delbosque', + 'Dubinsky', + 'Flinchum', + 'Berlanga', + 'Darland', + 'Daniele', + 'Jess', + 'Mungia', + 'Harlin', + 'Rocca', + 'Saltsman', + 'Trovato', + 'Dionisio', + 'Erbe', + 'Dauzat', + 'Laferriere', + 'Kear', + 'Brannigan', + 'Guard', + 'Roquemore', + 'Brehmer', + 'Kappes', + 'Kepley', + 'Labounty', + 'Sudol', + 'Walburn', + 'Bibeau', + 'Euler', + 'Brawn', + 'Pilot', + 'Bunger', + 'Earnhardt', + 'Fischetti', + 'Buitrago', + 'Calo', + 'Surette', + 'Martyn', + 'Tollett', + 'Tuller', + 'Noakes', + 'Marson', + 'Bongiovanni', + 'Novello', + 'Werling', + 'Wyland', + 'Palen', + 'Sigmund', + 'Salzer', + 'Abels', + 'Penson', + 'Cazarez', + 'Diblasi', + 'Jantzen', + 'Kittleson', + 'Hurlbert', + 'Shepardson', + 'Munz', + 'Bozek', + 'Woll', + 'Forth', + 'Colvard', + 'Baginski', + 'Beirne', + 'Lemmer', + 'Shover', + 'Lucci', + 'Hockensmith', + 'Mayhall', + 'Faucette', + 'Soloman', + 'Lembo', + 'Tarnowski', + 'Westerlund', + 'Gossage', + 'Bold', + 'Davi', + 'Crater', + 'Saia', + 'Spisak', + 'Zerr', + 'Penate', + 'Piel', + 'Raja', + 'Farney', + 'Cutrer', + 'Liverman', + 'Brar', + 'Nocera', + 'Coutu', + 'Rishel', + 'Spurr', + 'Kail', + 'Molino', + 'Favreau', + 'Mullinix', + 'Pospisil', + 'Rohloff', + 'Slavens', + 'Stumbo', + 'Ahl', + 'Hosking', + 'Speaker', + 'Tarkington', + 'Majeski', + 'Skoog', + 'Kirch', + 'Vannostrand', + 'Olmo', + 'Dorrell', + 'Newcombe', + 'Halls', + 'Riffel', + 'Luque', + 'Rolston', + 'Lokey', + 'Nicholes', + 'Gula', + 'Schrage', + 'Goshorn', + 'Woodell', + 'Ahmadi', + 'Austria', + 'Shaul', + 'Berwick', + 'Graczyk', + 'Lacourse', + 'Porcaro', + 'Rexroad', + 'Chrzanowski', + 'Abele', + 'Woodin', + 'Gillan', + 'Lone', + 'Orzechowski', + 'Fader', + 'Regina', + 'Ban', + 'Morriss', + 'Rickards', + 'Gannaway', + 'Tassin', + 'Accardi', + 'Engelke', + 'Kruk', + 'Mantilla', + 'Soderstrom', + 'Kriz', + 'Cantley', + 'Cangelosi', + 'Kalin', + 'Sobolewski', + 'Prinz', + 'Bessey', + 'Chittum', + 'Marcucci', + 'Annunziata', + 'Hegg', + 'Mishra', + 'Heppner', + 'Benningfield', + 'Rhoten', + 'Smolen', + 'Lewellyn', + 'Tall', + 'Comiskey', + 'Gobel', + 'Klump', + 'Stauber', + 'Tocci', + 'Gosser', + 'Tussey', + 'Summitt', + 'Ottman', + 'Vester', + 'Pasko', + 'Latshaw', + 'Kies', + 'Valderrama', + 'Leese', + 'Orduna', + 'Gilcrease', + 'Alli', + 'Berberich', + 'Delariva', + 'Harb', + 'Schmuck', + 'Spang', + 'Uecker', + 'Garfinkel', + 'Mcalexander', + 'Monty', + 'Leonetti', + 'Knipe', + 'Loudon', + 'Leisure', + 'Swearengin', + 'Tinnin', + 'Engelmann', + 'Noblitt', + 'Ruhland', + 'Shewmaker', + 'Smetana', + 'Vangundy', + 'Yzaguirre', + 'Nehls', + 'Sullens', + 'Mahurin', + 'Ferman', + 'Lenhardt', + 'Littman', + 'Udell', + 'Coutts', + 'Mcginness', + 'Nakayama', + 'Goguen', + 'Lass', + 'Tibbits', + 'Pafford', + 'Fett', + 'Ruis', + 'Trogdon', + 'Tarleton', + 'Isabell', + 'Paylor', + 'Grandison', + 'Bejar', + 'Highfield', + 'Peplinski', + 'Hammitt', + 'Mitton', + 'Dashiell', + 'Turrentine', + 'Rusin', + 'Sheeran', + 'Barrs', + 'Grund', + 'Kowalsky', + 'Mccaughey', + 'Orantes', + 'Oshields', + 'Tourville', + 'Szymczak', + 'Gagner', + 'Kemble', + 'Delangel', + 'Kaler', + 'Treanor', + 'Deems', + 'Ours', + 'Loss', + 'Remley', + 'Welles', + 'Bogardus', + 'Feher', + 'Grzybowski', + 'Meinert', + 'Mickelsen', + 'Opitz', + 'Osowski', + 'Paglia', + 'Srivastava', + 'Hirata', + 'Vandermark', + 'Maggi', + 'Gautreau', + 'Fonte', + 'Meck', + 'Mcquinn', + 'Criddle', + 'Hulin', + 'Fulmore', + 'Baldino', + 'Neugebauer', + 'Sletten', + 'Talcott', + 'Tessmer', + 'Vrooman', + 'Whitlatch', + 'Miano', + 'Arauz', + 'Lafon', + 'Cashin', + 'Carrow', + 'Feely', + 'Provo', + 'Botsford', + 'Chojnacki', + 'Pritts', + 'Duby', + 'Danos', + 'Mundo', + 'Strum', + 'Bealer', + 'Barmore', + 'Birkholz', + 'Hedgecock', + 'Vides', + 'Mcjunkin', + 'Paley', + 'Dennie', + 'Cosey', + 'Trombly', + 'Wagar', + 'Tope', + 'Venters', + 'Neptune', + 'Allshouse', + 'Kuczynski', + 'Beams', + 'Kilbourne', + 'Troxler', + 'Mcgahee', + 'Latson', + 'Miraglia', + 'Suda', + 'Prall', + 'Searls', + 'Tevis', + 'Vales', + 'Coberly', + 'Eichman', + 'Hiltz', + 'Mancera', + 'Mrozek', + 'Obermeyer', + 'Wiedeman', + 'Yoshimura', + 'Pascucci', + 'Denk', + 'Pita', + 'Abdul', + 'Schurr', + 'Huntoon', + 'Sund', + 'Blose', + 'Agostini', + 'Cogdell', + 'Hamburger', + 'Orwig', + 'Pelley', + 'Mcnelly', + 'Litten', + 'Osterberg', + 'Zepp', + 'Mathur', + 'Ardon', + 'Petre', + 'Schroeter', + 'Christoff', + 'Ridenhour', + 'Hibler', + 'Coachman', + 'Tadeo', + 'Vanderploeg', + 'Ference', + 'Connery', + 'Albro', + 'Bublitz', + 'Fagundes', + 'Purpura', + 'Deeb', + 'Melzer', + 'Haus', + 'Huffine', + 'Groner', + 'Laforce', + 'Burriss', + 'Longino', + 'Seldon', + 'Chicoine', + 'Neira', + 'Pintor', + 'Trager', + 'Garg', + 'Camilleri', + 'Limbaugh', + 'Marinello', + 'Sanz', + 'Hankey', + 'Aylor', + 'Homes', + 'Marro', + 'Stalder', + 'Creasey', + 'Blankinship', + 'Waldrup', + 'Aubert', + 'Quintanar', + 'Tarbell', + 'Mayton', + 'Baba', + 'Voltz', + 'Cuba', + 'Bracco', + 'Dimeo', + 'Cauble', + 'Rodela', + 'Sambrano', + 'Doten', + 'Jobes', + 'Laura', + 'Farrier', + 'Mixson', + 'Bassi', + 'Kroening', + 'Papineau', + 'Scheuerman', + 'Zertuche', + 'Cardella', + 'Taube', + 'Bazzi', + 'Sautter', + 'Tobon', + 'Venditti', + 'Nordman', + 'Loken', + 'Fortino', + 'Godbout', + 'Knaub', + 'Larabee', + 'Meserve', + 'Slama', + 'Junge', + 'Stamand', + 'Daigneault', + 'Fredericksen', + 'Loveall', + 'Clothier', + 'Kuehne', + 'Delahoussaye', + 'Bosquez', + 'Hildenbrand', + 'Muto', + 'Vanvliet', + 'Frederiksen', + 'Mero', + 'Rapier', + 'Feldt', + 'Mcpartland', + 'Stegner', + 'Veenstra', + 'Yeater', + 'Yeatts', + 'Rosenbloom', + 'Shepperd', + 'Marchbanks', + 'Tapscott', + 'Baynard', + 'Osby', + 'Cumberbatch', + 'Brassard', + 'Dahlman', + 'Doi', + 'Katona', + 'Niesen', + 'Slavik', + 'Macneill', + 'Marsala', + 'Fazekas', + 'Cudd', + 'Ocana', + 'Brimer', + 'Lachman', + 'Balla', + 'Shahid', + 'Gammage', + 'Canez', + 'Fickes', + 'Goldblatt', + 'Mcgeehan', + 'Westerberg', + 'Legler', + 'Stanberry', + 'Hillery', + 'Colosimo', + 'Florek', + 'Heckathorn', + 'Lenart', + 'Mcneilly', + 'Viles', + 'Davin', + 'Pierro', + 'Edman', + 'Patron', + 'Tipps', + 'Ardis', + 'Hassen', + 'Crase', + 'Gebert', + 'Predmore', + 'Entwistle', + 'Lourenco', + 'Snively', + 'Chivers', + 'Byas', + 'Edsall', + 'Sneddon', + 'Kloster', + 'Luedke', + 'Barcelo', + 'Corns', + 'Paula', + 'Tacker', + 'Marton', + 'Lyke', + 'Huitt', + 'Tinch', + 'Tagle', + 'Linnell', + 'Loden', + 'Witman', + 'Condrey', + 'Swindler', + 'Denby', + 'Mcdow', + 'Bennion', + 'Berkman', + 'Esguerra', + 'Kohli', + 'Leicht', + 'Platero', + 'Purtell', + 'Sarro', + 'Spera', + 'Wasielewski', + 'Nold', + 'Gander', + 'Coster', + 'Burn', + 'Sindelar', + 'Spivak', + 'Stangl', + 'Eakes', + 'Host', + 'Raybon', + 'Stickle', + 'Vitiello', + 'Borntrager', + 'Glorioso', + 'Winnie', + 'Blocher', + 'Che', + 'Godbold', + 'Blumenfeld', + 'Hallford', + 'Nuckolls', + 'Rasor', + 'Tardy', + 'Hayslett', + 'Kivett', + 'Pettry', + 'Klopfenstein', + 'Martelli', + 'Dunker', + 'Klass', + 'Denn', + 'Vessels', + 'Stukes', + 'Iannone', + 'Kovarik', + 'Perlmutter', + 'Som', + 'Kump', + 'Tack', + 'Warf', + 'Coffer', + 'Baas', + 'Balli', + 'Fleishman', + 'Lyall', + 'Meli', + 'Petrovic', + 'Sego', + 'Tignor', + 'Maule', + 'Stinchcomb', + 'Doxey', + 'Garbutt', + 'Drewes', + 'Prestridge', + 'Vivanco', + 'Weinmann', + 'Amrhein', + 'Schluter', + 'Cleek', + 'Rossignol', + 'Rezendes', + 'Marone', + 'Sloss', + 'Weary', + 'Leishman', + 'Searfoss', + 'Springman', + 'Wolfer', + 'Hires', + 'Mccampbell', + 'Casselman', + 'Frasca', + 'Lintner', + 'Preiss', + 'Neilsen', + 'Twiss', + 'Boughner', + 'Donnellan', + 'Rech', + 'Mccaulley', + 'Massenburg', + 'Dermody', + 'Neuberger', + 'Rifkin', + 'Ullom', + 'Marth', + 'Blacker', + 'Kase', + 'Garon', + 'Calaway', + 'Grange', + 'Yopp', + 'Service', + 'Blassingame', + 'Lockley', + 'Straughter', + 'Porath', + 'Situ', + 'Stansfield', + 'Eves', + 'Cianci', + 'Colindres', + 'Killam', + 'Luiz', + 'Stahlman', + 'Silvernail', + 'Moorhouse', + 'Langner', + 'Soucie', + 'Lucke', + 'Manly', + 'Huggard', + 'Higareda', + 'Matarazzo', + 'Jusino', + 'Winnett', + 'Matheney', + 'Bufkin', + 'Bilbo', + 'Levingston', + 'Auxier', + 'Guevarra', + 'Triolo', + 'Roder', + 'Clever', + 'Moodie', + 'Cabana', + 'Kiesling', + 'Lindblom', + 'Reuther', + 'Rubi', + 'Brinkmann', + 'Donati', + 'Cresswell', + 'Fortes', + 'Bayard', + 'Grayer', + 'Malveaux', + 'Hauger', + 'Hirschman', + 'Soroka', + 'Witek', + 'Pugsley', + 'Eoff', + 'Alewine', + 'Hastie', + 'Budzinski', + 'Burgard', + 'Hebel', + 'Kleist', + 'Lawhead', + 'Saporito', + 'Sugarman', + 'Sechler', + 'Cohoon', + 'Treadaway', + 'Silliman', + 'Horsey', + 'Chauhan', + 'Jovel', + 'Giorgio', + 'Waltrip', + 'Templeman', + 'Morning', + 'Fava', + 'Mcinturff', + 'Migliaccio', + 'Moncayo', + 'Pesek', + 'Olivero', + 'Devall', + 'Dauphin', + 'Banerjee', + 'Benway', + 'Bermejo', + 'Dacey', + 'Pilarski', + 'Pinnell', + 'Chia', + 'Pung', + 'Rahe', + 'Greenhaw', + 'Byrns', + 'Ancona', + 'Granato', + 'Luciani', + 'Shryock', + 'Sloop', + 'Murcia', + 'Croll', + 'Congleton', + 'Okelly', + 'Norville', + 'Flesch', + 'Murad', + 'Seddon', + 'Waybright', + 'Cremer', + 'Hagman', + 'Largo', + 'Solar', + 'Costales', + 'Gier', + 'Tober', + 'Reeb', + 'Lands', + 'Hoback', + 'Ingrassia', + 'Youngquist', + 'Tyrell', + 'Profit', + 'Collura', + 'Oldaker', + 'Vogl', + 'Spafford', + 'Laughman', + 'Goris', + 'Coghill', + 'Sweatman', + 'Rozelle', + 'Chatelain', + 'Fouch', + 'Legros', + 'Koza', + 'Vialpando', + 'Subia', + 'Danz', + 'Dosch', + 'Debruin', + 'Stefanik', + 'Gamber', + 'Saylors', + 'Cost', + 'Bernat', + 'Eastburn', + 'Getman', + 'Maillet', + 'Dogan', + 'Finklea', + 'Alongi', + 'Ballas', + 'Konkel', + 'Ryu', + 'Scoles', + 'Oles', + 'Algarin', + 'Seago', + 'Delaune', + 'Pettey', + 'Gettys', + 'Blanch', + 'Kea', + 'Cambridge', + 'Ciesielski', + 'Pribble', + 'Mayhugh', + 'Dery', + 'Allsup', + 'Hauptman', + 'Shoff', + 'Spath', + 'Lipsky', + 'Lakhani', + 'Lona', + 'Andrea', + 'Heist', + 'Herzig', + 'Insley', + 'Frasher', + 'Muise', + 'Kettle', + 'Catano', + 'Harkleroad', + 'Rominger', + 'Schreffler', + 'Bielecki', + 'Knarr', + 'Arvidson', + 'Harnden', + 'Galyon', + 'Rando', + 'Delima', + 'Constance', + 'Bosman', + 'Meinke', + 'Rosenquist', + 'Stickles', + 'Batz', + 'Eitel', + 'Kouba', + 'Marmol', + 'Rini', + 'Kinyon', + 'Munns', + 'Hilts', + 'Verrett', + 'Shead', + 'Staggers', + 'Naccarato', + 'Shupp', + 'Willeford', + 'Gayer', + 'Bran', + 'Krider', + 'Cue', + 'Dubiel', + 'Kawamoto', + 'Quayle', + 'Meckley', + 'Weingart', + 'Ivan', + 'Aller', + 'Pattee', + 'Pile', + 'Shinault', + 'Alzate', + 'Goudreau', + 'Weitzman', + 'Zurek', + 'Portman', + 'Tellis', + 'Achenbach', + 'Cranfill', + 'Scheib', + 'Rud', + 'Forgey', + 'Sardina', + 'Hayslip', + 'Fadden', + 'Ethington', + 'Jette', + 'Maberry', + 'Stecher', + 'Mcgahan', + 'Buffa', + 'Lehto', + 'Lesch', + 'Minier', + 'Niblett', + 'Behar', + 'Gochenour', + 'Thole', + 'Woodmansee', + 'Guse', + 'Breunig', + 'Deibert', + 'Levario', + 'Liming', + 'Oltman', + 'Vought', + 'Higby', + 'Lummus', + 'Casimir', + 'Grabow', + 'Helzer', + 'Madero', + 'Panico', + 'Ruud', + 'Beas', + 'Knebel', + 'Lorence', + 'Sizer', + 'Goodwill', + 'Darrell', + 'Dismukes', + 'Wimbish', + 'Kleine', + 'Prohaska', + 'Freeborn', + 'Caso', + 'Meis', + 'Bise', + 'Maxim', + 'Chumbley', + 'Eaglin', + 'Bergey', + 'Hillenbrand', + 'Pacifico', + 'Plath', + 'Rio', + 'Ristau', + 'Zych', + 'Whang', + 'Fister', + 'Forbush', + 'Lagarde', + 'Atha', + 'Hallinan', + 'Hesser', + 'Hoak', + 'Kohr', + 'Longnecker', + 'Nomura', + 'Raia', + 'Seybold', + 'Spagnola', + 'Majano', + 'Sanmartin', + 'Mangual', + 'Stanback', + 'Gangi', + 'Lauritzen', + 'Seeber', + 'Disla', + 'Frain', + 'Besse', + 'Makris', + 'Ducker', + 'Demps', + 'Laporta', + 'Pavey', + 'Reineke', + 'Najjar', + 'Mcclaskey', + 'Luff', + 'Vanderveer', + 'Mccoll', + 'Leamon', + 'Meinhardt', + 'Dinatale', + 'Laffoon', + 'Jenny', + 'Skipworth', + 'Folds', + 'Burstein', + 'Freas', + 'Lizardo', + 'Selle', + 'Vrabel', + 'Beranek', + 'Hakala', + 'Spataro', + 'Prahl', + 'Meas', + 'Haston', + 'Croker', + 'Carmouche', + 'Doolan', + 'Guerrieri', + 'Poulton', + 'Mauger', + 'Klose', + 'Husk', + 'Pharis', + 'Dipalma', + 'Hamaker', + 'Simek', + 'Strube', + 'Corl', + 'Bence', + 'Meigs', + 'Gillaspie', + 'Moring', + 'Eli', + 'Mccullers', + 'Erving', + 'Dopp', + 'Falbo', + 'Gensler', + 'Heroux', + 'Hertzler', + 'Muscarella', + 'Wittmann', + 'Willner', + 'Howton', + 'Brummitt', + 'Demar', + 'Hardrick', + 'Benavente', + 'Choo', + 'Tiscareno', + 'Bunge', + 'Helle', + 'Ogan', + 'Allbright', + 'Jervis', + 'Tompson', + 'Sheats', + 'Hebron', + 'Esters', + 'Fiorillo', + 'Narciso', + 'Slowik', + 'Kush', + 'Sole', + 'Bitting', + 'Bradham', + 'Goggans', + 'Rushin', + 'Huguley', + 'Kittelson', + 'Nadel', + 'Noggle', + 'Xue', + 'Alameda', + 'Hege', + 'Liberto', + 'Maron', + 'Aber', + 'Brodersen', + 'Clasen', + 'Couturier', + 'Godines', + 'Ozment', + 'Parga', + 'Rohm', + 'Voris', + 'Leaver', + 'Newhart', + 'Sabourin', + 'Kelling', + 'Repass', + 'Wigington', + 'Prioleau', + 'Antle', + 'Goucher', + 'Kreitzer', + 'Reuss', + 'Rosenfield', + 'Sliva', + 'Nolting', + 'Radel', + 'Quintal', + 'Lisa', + 'Temples', + 'Cavins', + 'Gazaway', + 'Hopewell', + 'Albury', + 'Broberg', + 'Khuu', + 'Zelinski', + 'Kurian', + 'Treacy', + 'Rake', + 'Tirrell', + 'Macdowell', + 'Smead', + 'Edgerly', + 'Fowles', + 'Yorke', + 'Goodwyn', + 'Sciacca', + 'Breitenbach', + 'Charity', + 'Greenidge', + 'Kendig', + 'Navarette', + 'Doremus', + 'Marcelino', + 'Ribera', + 'Luse', + 'Hasley', + 'Halton', + 'Jakes', + 'Balas', + 'Cheema', + 'Dettman', + 'Schachter', + 'Weisenberger', + 'Lehn', + 'Sailors', + 'Alcott', + 'Mancino', + 'Mineo', + 'Montz', + 'Stettler', + 'Brannock', + 'Shumake', + 'Blunk', + 'Feuerstein', + 'Mangino', + 'Bitzer', + 'Padden', + 'Wetter', + 'Blase', + 'Helvey', + 'Sabia', + 'Folden', + 'Wyllie', + 'Hoosier', + 'Gehringer', + 'Peifer', + 'Schneiderman', + 'Raj', + 'Gift', + 'Sue', + 'Wedgeworth', + 'Bischof', + 'Coviello', + 'Flor', + 'Barrentine', + 'Ells', + 'Dundas', + 'Baine', + 'Bouknight', + 'Koning', + 'Mallari', + 'Monje', + 'Wingler', + 'Stainbrook', + 'Mari', + 'Hemby', + 'Boateng', + 'Enfinger', + 'Esquer', + 'Salvatierra', + 'Tercero', + 'Porta', + 'Speth', + 'Plate', + 'Rockhold', + 'Hampshire', + 'Stipe', + 'Buescher', + 'Denault', + 'Fahnestock', + 'Vandehey', + 'Brouse', + 'Ciaccio', + 'Hund', + 'Wire', + 'Sherron', + 'Fairfax', + 'Owusu', + 'Cuervo', + 'Minjarez', + 'Zarco', + 'Vandyne', + 'Gedeon', + 'Kegler', + 'Ebron', + 'Murtaugh', + 'Pariseau', + 'Morvant', + 'Ellwood', + 'Beazley', + 'Farrelly', + 'Mccollom', + 'Alegre', + 'Dussault', + 'Goulette', + 'Hession', + 'Regier', + 'Speranza', + 'Spinella', + 'Maloof', + 'Nogueira', + 'Beaudin', + 'Sable', + 'Samford', + 'Marchan', + 'Rodriques', + 'Rhines', + 'Aldrete', + 'Creedon', + 'Laberge', + 'Sandel', + 'Spady', + 'Horsman', + 'Schimpf', + 'Sottile', + 'Than', + 'Ybanez', + 'Sagastume', + 'Vosburg', + 'Langlais', + 'Windley', + 'Bielski', + 'Meyerson', + 'Rizk', + 'Sparacino', + 'Winebarger', + 'Helsley', + 'Alward', + 'Wilker', + 'Clyne', + 'Bergren', + 'Gin', + 'Heberling', + 'Noh', + 'Rotz', + 'Laffey', + 'Zurawski', + 'Aliff', + 'Coover', + 'Steves', + 'Brain', + 'Greggs', + 'Burts', + 'Culwell', + 'Halbrook', + 'Marcantel', + 'Alsip', + 'Esslinger', + 'Kinnaird', + 'Rew', + 'Wimbley', + 'Dalal', + 'Litke', + 'Ostlund', + 'Petersheim', + 'Vezina', + 'Vickrey', + 'Vida', + 'Stachowiak', + 'Santizo', + 'Stow', + 'Hoel', + 'Parrino', + 'Elsberry', + 'Pharris', + 'Chiarello', + 'Konen', + 'Ogata', + 'Tousignant', + 'Turano', + 'Zoll', + 'Reser', + 'Ribble', + 'Dally', + 'Kersh', + 'Crivello', + 'Glantz', + 'Vanvleet', + 'Dy', + 'Woolwine', + 'Ager', + 'Romney', + 'Dedeaux', + 'Ringgold', + 'Mir', + 'Rexford', + 'Whitehair', + 'Wilczynski', + 'Kleinsasser', + 'Siemens', + 'Kindig', + 'Kemmer', + 'Fonda', + 'Litt', + 'Mcferrin', + 'Riche', + 'Beaudet', + 'Lasala', + 'Maglione', + 'Milani', + 'Moscato', + 'Pangilinan', + 'Haycraft', + 'Camilo', + 'Trafton', + 'Stroble', + 'Dollard', + 'Consiglio', + 'Kinnaman', + 'Mumaw', + 'Mustard', + 'Nees', + 'Rupprecht', + 'Gimbel', + 'Chamberland', + 'Lish', + 'Beedle', + 'Minder', + 'Broxton', + 'Cocco', + 'Vore', + 'Slough', + 'Pehrson', + 'Graney', + 'Reade', + 'Cozzi', + 'Mowrer', + 'Necaise', + 'Notaro', + 'Vanderwall', + 'Jeffs', + 'Lynd', + 'Perino', + 'Poyner', + 'Oscar', + 'Mihalik', + 'Coscia', + 'Zoellner', + 'Shippee', + 'Casimiro', + 'Phillippe', + 'Bartolotta', + 'Graciano', + 'Schnoor', + 'Aube', + 'Duguay', + 'Dickerman', + 'Santi', + 'Cude', + 'Haver', + 'Heidelberg', + 'Farquharson', + 'Bianchini', + 'Kasprzak', + 'Pizzi', + 'Urquiza', + 'Knee', + 'Lust', + 'Strayhorn', + 'Ader', + 'Canup', + 'Mira', + 'Saulnier', + 'Stalvey', + 'Takeuchi', + 'Updegraff', + 'Barletta', + 'Mikhail', + 'Abadie', + 'Cohee', + 'Sones', + 'Hird', + 'Mizelle', + 'Graddy', + 'Demay', + 'Escandon', + 'Kozar', + 'Lecuyer', + 'Tredway', + 'Danks', + 'Pry', + 'Mathena', + 'Gomer', + 'Moussa', + 'Journey', + 'Brison', + 'Denardo', + 'Digiorgio', + 'Worster', + 'Kottke', + 'Sayegh', + 'Aday', + 'Chain', + 'Digby', + 'Beeks', + 'Malpass', + 'Toft', + 'Fucci', + 'Stam', + 'Smoker', + 'Willms', + 'Bohner', + 'Sugar', + 'Tay', + 'Faye', + 'Melnik', + 'Pankow', + 'Stehle', + 'Vecchione', + 'Weatherwax', + 'Monterrosa', + 'Bodily', + 'Serino', + 'Jerkins', + 'Bosma', + 'Luczak', + 'Serafini', + 'Baze', + 'Hemmings', + 'Darrington', + 'Fraizer', + 'Henrikson', + 'Kok', + 'Larrison', + 'Mirabella', + 'Newhall', + 'Hollenbach', + 'Formica', + 'Haake', + 'Seim', + 'Zeledon', + 'Crabill', + 'Mensch', + 'Prevatt', + 'Riggan', + 'Gallien', + 'Erby', + 'Running', + 'Shisler', + 'Sidebottom', + 'Sladek', + 'Alejos', + 'Momin', + 'Bickers', + 'Smither', + 'Ahart', + 'Huseman', + 'Cantero', + 'Reiley', + 'Mcneeley', + 'Quill', + 'Binger', + 'Ellerbee', + 'Cearley', + 'Guilmette', + 'Helbig', + 'Nuzum', + 'Gravatt', + 'Turlington', + 'Deramus', + 'Casados', + 'Harrop', + 'Kardos', + 'Krehbiel', + 'Homa', + 'Agostino', + 'Candia', + 'Byerley', + 'Kincer', + 'Vitello', + 'Backhaus', + 'Burzynski', + 'Zaborowski', + 'Puebla', + 'Pedrick', + 'Hyson', + 'Mazyck', + 'Deno', + 'Yutzy', + 'Dubbs', + 'Shimek', + 'Saha', + 'Philipps', + 'Chretien', + 'Bramwell', + 'Mccalister', + 'Ebright', + 'Parkhill', + 'Rieke', + 'Karras', + 'Mcbain', + 'Gibbon', + 'Beckler', + 'Nordby', + 'Sipos', + 'Swider', + 'Treiber', + 'Weakland', + 'Zagorski', + 'Peavler', + 'Cirino', + 'Corzine', + 'Barbier', + 'Dolby', + 'Sheperd', + 'Vanderhorst', + 'Cornman', + 'Dippel', + 'Gramlich', + 'Hoffmeister', + 'Markwell', + 'Milks', + 'Schriner', + 'Cusimano', + 'Emberton', + 'Kimbler', + 'Merrow', + 'Huard', + 'Paulo', + 'Durrance', + 'Faherty', + 'Palmatier', + 'Rezac', + 'Speir', + 'Streicher', + 'Ackman', + 'Veitch', + 'Bedgood', + 'Pantano', + 'Raman', + 'Eusebio', + 'Coldwell', + 'Omer', + 'Swanigan', + 'Stepney', + 'Breiner', + 'Casebolt', + 'Deblasio', + 'Mascaro', + 'Maselli', + 'Overfield', + 'Enyart', + 'Litman', + 'Borer', + 'Dudash', + 'Mcniff', + 'Cherian', + 'Scearce', + 'Brakefield', + 'Hamed', + 'Cooperman', + 'Kinzel', + 'Mchargue', + 'Schiefelbein', + 'Varughese', + 'Brumm', + 'Novy', + 'Vicars', + 'Barratt', + 'Titsworth', + 'Mole', + 'Crisafulli', + 'Deitch', + 'Slager', + 'Tokarz', + 'Speelman', + 'Tunney', + 'Peal', + 'Chenevert', + 'Haggins', + 'Heitmann', + 'Scheuer', + 'Stuhr', + 'Zenner', + 'Wishon', + 'Arno', + 'Lauder', + 'Goertz', + 'Jew', + 'Knapik', + 'Lococo', + 'Murnane', + 'Pawloski', + 'Contino', + 'Holbrooks', + 'Carlstrom', + 'Heitkamp', + 'Muszynski', + 'Shelnutt', + 'Tortora', + 'Dietrick', + 'Kyzer', + 'Colt', + 'Propes', + 'Caffee', + 'Fankhauser', + 'Liotta', + 'Patil', + 'Broder', + 'Disher', + 'Telfer', + 'Lampkins', + 'Bartman', + 'Beauchemin', + 'Gatz', + 'Pedrosa', + 'Schuch', + 'Zorrilla', + 'Capote', + 'Vanderslice', + 'Boulden', + 'Kirkendoll', + 'Fausto', + 'Krom', + 'Ngai', + 'Sepe', + 'Domenech', + 'Dines', + 'Aschenbrenner', + 'Carias', + 'Inoue', + 'Montagna', + 'Pulsifer', + 'Rieman', + 'Seelye', + 'Yochum', + 'Defilippis', + 'Lacross', + 'Betances', + 'Jenne', + 'Rousey', + 'Brunswick', + 'Wadlington', + 'Brainerd', + 'Dauria', + 'Dinicola', + 'Fath', + 'Gemmell', + 'Rudman', + 'Urbaniak', + 'Fillion', + 'Brandel', + 'Devin', + 'Derrickson', + 'Jenkin', + 'Ebling', + 'Ferranti', + 'Lueders', + 'Alvear', + 'Gero', + 'Maury', + 'Estill', + 'Beadles', + 'Philyaw', + 'Tann', + 'Bednarski', + 'Nagata', + 'Partington', + 'Sobol', + 'Soohoo', + 'Welliver', + 'Yam', + 'Popejoy', + 'Berthelot', + 'Manwaring', + 'Cahn', + 'Layer', + 'Poarch', + 'Tee', + 'Arellanes', + 'Ehler', + 'Montalto', + 'Pavlick', + 'Rauh', + 'Mcnees', + 'Balke', + 'Alles', + 'Caperton', + 'Frier', + 'Thweatt', + 'Whitely', + 'Demby', + 'Kowalik', + 'Loffredo', + 'Solem', + 'Clampitt', + 'Dossey', + 'Fauver', + 'Toto', + 'Corlett', + 'Nickols', + 'Golston', + 'Graef', + 'Salsman', + 'Hartl', + 'Towell', + 'Lasseter', + 'Arata', + 'Diver', + 'Malan', + 'Lanter', + 'Justis', + 'Prime', + 'Ditzler', + 'Engelhart', + 'Plouffe', + 'Zaldivar', + 'Elser', + 'Witherow', + 'Mateer', + 'Rikard', + 'Dolson', + 'Mariner', + 'Amis', + 'Toby', + 'Evins', + 'Midgette', + 'Pinnix', + 'Blackard', + 'Huisman', + 'Lager', + 'Deloera', + 'Dutt', + 'Goodrow', + 'Morphis', + 'Quin', + 'Frankenfield', + 'Craycraft', + 'Mazer', + 'Meloy', + 'Lebouef', + 'Beresford', + 'Spiva', + 'Michie', + 'Jarreau', + 'Vallier', + 'Dunmore', + 'Cerra', + 'Ciulla', + 'Dauer', + 'Helling', + 'Jackowski', + 'Taboada', + 'Balistreri', + 'Blattner', + 'Cabot', + 'Lawver', + 'Cornette', + 'Arline', + 'Amsden', + 'Degner', + 'Ungar', + 'Birney', + 'Goldie', + 'Croston', + 'Wixon', + 'Alan', + 'Garneau', + 'Kolakowski', + 'Vitek', + 'Witherell', + 'Licari', + 'Badeaux', + 'Sammon', + 'Greenland', + 'Corlew', + 'Cashwell', + 'Aldinger', + 'Bilderback', + 'Kleeman', + 'Sisto', + 'Menz', + 'Bakos', + 'Ebbert', + 'Berliner', + 'Kin', + 'Cabaniss', + 'Ouzts', + 'Mccook', + 'Campfield', + 'Gulino', + 'Odriscoll', + 'Weyand', + 'Mcguckin', + 'Crean', + 'Boyington', + 'Bracero', + 'Carini', + 'Chawla', + 'Chaudhary', + 'Koehl', + 'Wahlstrom', + 'Francoeur', + 'Leveque', + 'Ledgerwood', + 'Paluch', + 'Wyble', + 'Latif', + 'Koen', + 'Eddie', + 'Mcgirt', + 'Boxley', + 'Exline', + 'Lujano', + 'Michalowski', + 'Rottman', + 'Throop', + 'Zech', + 'Baros', + 'Bohne', + 'Mule', + 'Monica', + 'Lasiter', + 'Alsop', + 'Pittard', + 'Whitefield', + 'Mccaskey', + 'Paek', + 'Reilley', + 'Wasik', + 'Bouma', + 'Garrigan', + 'Nett', + 'Mclarty', + 'Flemings', + 'Alcorta', + 'Spoor', + 'Mccranie', + 'Coverdale', + 'Guaman', + 'Jenness', + 'Knoop', + 'Scarpelli', + 'Schrecengost', + 'Toews', + 'Caughey', + 'Laska', + 'Helfer', + 'Bevers', + 'Forbus', + 'Mccrady', + 'Reasor', + 'Aggarwal', + 'Locicero', + 'Uber', + 'Vadnais', + 'Budnick', + 'Duhamel', + 'Stelling', + 'Kicklighter', + 'Basco', + 'Otts', + 'Tippins', + 'Bliven', + 'Gayheart', + 'Knauf', + 'Lalli', + 'Quigg', + 'Kingman', + 'Boros', + 'Henneman', + 'Lofland', + 'Pendarvis', + 'Keitt', + 'Gelfand', + 'Greaney', + 'Kindt', + 'Stimac', + 'Kirn', + 'Tokar', + 'Miura', + 'Wendorf', + 'Vigue', + 'Dorey', + 'Fegan', + 'Meares', + 'Thierry', + 'Ambrosino', + 'Coenen', + 'Kersting', + 'Leas', + 'Millward', + 'Petzold', + 'Morphew', + 'Filippone', + 'Stoffer', + 'Mani', + 'Clairmont', + 'Mccreight', + 'Cully', + 'Bissonette', + 'Kochan', + 'Linneman', + 'Parlier', + 'Bergner', + 'Sterns', + 'Steveson', + 'Clingerman', + 'Karg', + 'Medved', + 'Prakash', + 'Ulman', + 'Petroski', + 'Hagaman', + 'Huddle', + 'Auclair', + 'Shives', + 'Dunavant', + 'Glade', + 'Chauncey', + 'Pough', + 'Burgoon', + 'Pluta', + 'Couey', + 'Punch', + 'Colmenares', + 'Fosdick', + 'Henze', + 'Kaczynski', + 'Lomonaco', + 'Roepke', + 'Schenkel', + 'Schlatter', + 'Schoenherr', + 'Tripodi', + 'Zeiler', + 'Bunt', + 'Dolly', + 'Boyland', + 'Bickle', + 'Cincotta', + 'Crull', + 'Enfield', + 'Saltz', + 'Skelley', + 'Younts', + 'Bussiere', + 'Latona', + 'Sensabaugh', + 'Grosvenor', + 'Woolbright', + 'Shorty', + 'Brungardt', + 'Cardon', + 'Carlberg', + 'Clevinger', + 'Rucinski', + 'Vanhooser', + 'Westling', + 'Imperial', + 'Tyer', + 'Elzey', + 'Aslam', + 'Fesler', + 'Leiser', + 'Smitley', + 'Orgeron', + 'Scuderi', + 'Flatley', + 'Whiteford', + 'Tison', + 'Laurin', + 'Fortman', + 'Whitty', + 'Kirton', + 'Cassella', + 'Flom', + 'Seigel', + 'Cossette', + 'Bryden', + 'Gobin', + 'Hieb', + 'Marzullo', + 'Matuszak', + 'Rolph', + 'Spilman', + 'Vanvoorhis', + 'Sande', + 'Suydam', + 'Gledhill', + 'Krill', + 'Mackiewicz', + 'Templet', + 'Friedrichs', + 'Ruddell', + 'Kats', + 'Nourse', + 'Millender', + 'Wafer', + 'Fauntleroy', + 'Archibeque', + 'Maslowski', + 'Metzgar', + 'Pizana', + 'Mcguffey', + 'Estridge', + 'Vanalstyne', + 'Decuir', + 'Mcbean', + 'Hardnett', + 'Avilla', + 'Spadafora', + 'Weisel', + 'Kann', + 'Leyden', + 'Purdom', + 'Tappan', + 'Gunnells', + 'Slaten', + 'Hansley', + 'Chiappetta', + 'Rozek', + 'Tiede', + 'Winland', + 'Dubuque', + 'Heslin', + 'Bradway', + 'Eckels', + 'Saffell', + 'Germaine', + 'Apolinar', + 'Coloma', + 'Gawlik', + 'Chipps', + 'Hicklin', + 'Glanton', + 'Dalke', + 'Denlinger', + 'Kuipers', + 'Houpt', + 'Parcell', + 'Claeys', + 'Ferreri', + 'Greif', + 'Lucente', + 'Siems', + 'Yousef', + 'Llerena', + 'Rote', + 'Suero', + 'Malmberg', + 'Touchette', + 'Luton', + 'Wess', + 'Height', + 'Stampley', + 'Anastasi', + 'Bulman', + 'Deharo', + 'Laube', + 'Severt', + 'Midgley', + 'Colling', + 'Ell', + 'Burbage', + 'Commander', + 'Hubner', + 'Zurcher', + 'Arocha', + 'Nobile', + 'Tingler', + 'Ellman', + 'Lolley', + 'Pewitt', + 'Mcduff', + 'Hyler', + 'Goltz', + 'Kubota', + 'Lamberti', + 'Ohern', + 'Uhrig', + 'Dummer', + 'Keesling', + 'Litzinger', + 'Moriarity', + 'Servantes', + 'Rohe', + 'Stokely', + 'Weedon', + 'Pippins', + 'Dehner', + 'Krogman', + 'Luecke', + 'Rosete', + 'Zona', + 'Lowy', + 'Applebee', + 'Heather', + 'Cruikshank', + 'Linson', + 'Brandy', + 'Koser', + 'Ruel', + 'Ruppe', + 'Saeteurn', + 'Dewolfe', + 'Sawtelle', + 'Rudin', + 'Raver', + 'Bassham', + 'Yaw', + 'Segrest', + 'Belfiore', + 'Heeren', + 'Kotowski', + 'Luken', + 'Makela', + 'Ranallo', + 'Schug', + 'Seery', + 'Payson', + 'Caufield', + 'Lacefield', + 'Bratten', + 'Jr', + 'Buske', + 'Ternes', + 'Bivona', + 'Felber', + 'Rott', + 'Pitkin', + 'Pridmore', + 'Oyer', + 'Astle', + 'Jeppesen', + 'Shimabukuro', + 'Soltys', + 'Vieth', + 'Rasnick', + 'Calfee', + 'Brignac', + 'Lamy', + 'Facey', + 'Alper', + 'Borquez', + 'Cavalieri', + 'Niswonger', + 'Pajak', + 'Schwabe', + 'Ringel', + 'Abbe', + 'Fenley', + 'Churchman', + 'Haydel', + 'Stockard', + 'Adamek', + 'Ellerman', + 'Torpey', + 'Waldroup', + 'Hunte', + 'Bienaime', + 'Lazzara', + 'Nemitz', + 'Wingerter', + 'Boer', + 'Franken', + 'Lebow', + 'Manger', + 'Baisley', + 'Pane', + 'Gayden', + 'Bertelsen', + 'Curfman', + 'Leanos', + 'Nissley', + 'Odwyer', + 'Manzer', + 'Kollman', + 'Quon', + 'Holgate', + 'Cola', + 'Mckissack', + 'Cousar', + 'Bilski', + 'Boehler', + 'Kawamura', + 'April', + 'Mckelvy', + 'Lanni', + 'Roehm', + 'Salva', + 'Stackpole', + 'Stracener', + 'Masiello', + 'Barrus', + 'Tubb', + 'Brummel', + 'Devereux', + 'Foushee', + 'Corado', + 'Gladfelter', + 'Grewe', + 'Hodapp', + 'Swartwood', + 'Vacek', + 'Wrona', + 'Shaffner', + 'Ullah', + 'Heslop', + 'Mungo', + 'Haymon', + 'Behrend', + 'Falter', + 'Feola', + 'Gruner', + 'Picklesimer', + 'Riedl', + 'Stegeman', + 'Harpole', + 'Moyes', + 'Boulay', + 'Brighton', + 'Guise', + 'Laury', + 'Badilla', + 'Cypher', + 'Houdek', + 'Juhasz', + 'Klingbeil', + 'Pinales', + 'Fellman', + 'Daher', + 'Allmond', + 'Bal', + 'Crager', + 'Hillebrand', + 'Menezes', + 'Serpas', + 'Zager', + 'Alvardo', + 'Summerford', + 'Stillings', + 'Vandergrift', + 'Hanchett', + 'Minto', + 'Daughtery', + 'Gillon', + 'Rajan', + 'Vasko', + 'Wirick', + 'Woolever', + 'Caserta', + 'Welle', + 'Kimbrel', + 'Traywick', + 'Hands', + 'Spratley', + 'Iannuzzi', + 'Krikorian', + 'Runk', + 'Sood', + 'Riese', + 'Antunes', + 'Winsett', + 'Mans', + 'Capel', + 'Condron', + 'Nilles', + 'Petz', + 'Salemi', + 'Bainter', + 'Patchett', + 'Hirschfeld', + 'Murrin', + 'Lamey', + 'Mcglothin', + 'Hodo', + 'Hirth', + 'Kaltenbach', + 'Kensinger', + 'Leidy', + 'Shurtz', + 'Braatz', + 'Brafford', + 'Willet', + 'Clendening', + 'Basch', + 'Brockwell', + 'Oberman', + 'Palmateer', + 'Osornio', + 'Gehl', + 'Staker', + 'Mattila', + 'Dawn', + 'Cowherd', + 'Appleman', + 'Carbonaro', + 'Castruita', + 'Pilling', + 'Wenrich', + 'Christoffersen', + 'Hinzman', + 'Kaup', + 'Pettersen', + 'Jue', + 'Khalsa', + 'Mutz', + 'Remus', + 'Arch', + 'Shands', + 'Borek', + 'Buresh', + 'Egli', + 'Feldkamp', + 'Hampel', + 'Lichtenberg', + 'Morimoto', + 'Brasel', + 'Demelo', + 'Royalty', + 'Averitt', + 'Metivier', + 'Bradsher', + 'Avallone', + 'Demeter', + 'Masucci', + 'Musil', + 'Wichmann', + 'Broman', + 'Taunton', + 'Blewett', + 'Duhart', + 'Goo', + 'Hanus', + 'Mathai', + 'Shutts', + 'Taniguchi', + 'Vanleeuwen', + 'Delvillar', + 'Hane', + 'Givan', + 'Croskey', + 'Elamin', + 'Deffenbaugh', + 'Miklos', + 'Passalacqua', + 'Woessner', + 'Lapan', + 'Miah', + 'Coty', + 'Baksh', + 'Beehler', + 'Goel', + 'Wolfinger', + 'Goodhue', + 'Toal', + 'Mattoon', + 'Haq', + 'Nida', + 'Dant', + 'Varnadore', + 'Tippit', + 'Every', + 'Bohling', + 'Lichtenberger', + 'Louk', + 'Soderquist', + 'Werkheiser', + 'Willbanks', + 'Whitis', + 'Millikin', + 'Dietzel', + 'Frase', + 'Ishida', + 'Pilger', + 'Grajales', + 'Kole', + 'Roff', + 'Ballantine', + 'Basden', + 'Cadenas', + 'Caliendo', + 'Hotard', + 'Vidrio', + 'Lichtman', + 'Devinney', + 'Fugitt', + 'Proud', + 'Hults', + 'Galey', + 'Verna', + 'Newburn', + 'Lafortune', + 'Fobbs', + 'Azure', + 'Cheong', + 'Heft', + 'Aispuro', + 'Longstreth', + 'Lajeunesse', + 'Howle', + 'Galley', + 'Lovan', + 'Convery', + 'Malatesta', + 'Warnecke', + 'Glavin', + 'Reil', + 'Filson', + 'Poage', + 'Fountaine', + 'Nolley', + 'Raglin', + 'Backlund', + 'Doerfler', + 'Faunce', + 'Hooton', + 'Lightcap', + 'Stepanek', + 'Grosser', + 'Weld', + 'Filippi', + 'Youn', + 'Matis', + 'Harnett', + 'Ferrill', + 'Segers', + 'Ponds', + 'Cuyler', + 'Faile', + 'Flaugher', + 'Kuehner', + 'Giorgi', + 'Eckler', + 'Sergeant', + 'Twiggs', + 'Boeck', + 'Flach', + 'Iliff', + 'Mcmurtrey', + 'Mcnelis', + 'Steckel', + 'Rouillard', + 'Folkerts', + 'Mechling', + 'Whitcher', + 'Daws', + 'Joly', + 'Abt', + 'Eells', + 'Niccum', + 'Twining', + 'Grinder', + 'Melrose', + 'Yarbro', + 'Degenhardt', + 'Dimeglio', + 'Okamura', + 'Kriss', + 'Payette', + 'Chui', + 'Mowers', + 'Foose', + 'Kinzie', + 'Blick', + 'Rizer', + 'Alcock', + 'Sirmans', + 'Behrman', + 'Carsten', + 'Kopacz', + 'Randhawa', + 'Schwing', + 'Burkhard', + 'Cunanan', + 'Exley', + 'Balducci', + 'Leman', + 'Hyslop', + 'Burtch', + 'Hadnot', + 'Lanphear', + 'Finchum', + 'Voit', + 'Jock', + 'Wilhoite', + 'Officer', + 'Mayweather', + 'Ravenell', + 'Arehart', + 'Bonetti', + 'Cloer', + 'Galliher', + 'Niven', + 'Uyeda', + 'Coughenour', + 'Siddiqi', + 'Karimi', + 'Cupit', + 'Loupe', + 'Hammell', + 'Antley', + 'Ally', + 'Southers', + 'Haymond', + 'Hosley', + 'Broz', + 'Kinoshita', + 'Kohout', + 'Lipke', + 'Ostrow', + 'Teves', + 'Gaus', + 'Meiser', + 'Cravey', + 'Noss', + 'Drayer', + 'Crooms', + 'Carrano', + 'Mckechnie', + 'Uhrich', + 'Villalva', + 'Wilkening', + 'Benevides', + 'Kepple', + 'Pon', + 'Randol', + 'Leadbetter', + 'Russom', + 'Locklin', + 'Battiste', + 'Abundis', + 'Agosta', + 'Bartek', + 'Brillhart', + 'Hoffmaster', + 'Mehr', + 'Spanos', + 'Denker', + 'Kimberling', + 'Schon', + 'Felten', + 'Lightle', + 'Ramseur', + 'Branning', + 'Deblois', + 'Inocencio', + 'Maricle', + 'Nishimoto', + 'Oviatt', + 'Shunk', + 'Taddeo', + 'Villarruel', + 'Otterson', + 'Clune', + 'Seamster', + 'Dandy', + 'Cybulski', + 'Daza', + 'Eastep', + 'Faulhaber', + 'Friedberg', + 'Gentz', + 'Scola', + 'Sebesta', + 'Glinski', + 'Schoon', + 'Graeber', + 'Sinks', + 'Wee', + 'Summerall', + 'Deets', + 'Furnish', + 'Kelemen', + 'Maiorano', + 'Teachout', + 'Paquet', + 'Mcgahey', + 'Kill', + 'Horman', + 'Selders', + 'Cottman', + 'Delfin', + 'Fronk', + 'Seelig', + 'Visco', + 'Briles', + 'Castillon', + 'Suire', + 'Havey', + 'Arner', + 'Farver', + 'Marts', + 'Gean', + 'Hugh', + 'Stoney', + 'Townsel', + 'Sandquist', + 'Neidig', + 'Miser', + 'Leeth', + 'Hocutt', + 'Balcazar', + 'Caporale', + 'Guymon', + 'Horstmann', + 'Miedema', + 'Zickefoose', + 'Casterline', + 'Pfannenstiel', + 'Becht', + 'Myres', + 'Ried', + 'Vallery', + 'Bator', + 'Calise', + 'Cotterman', + 'Desautels', + 'Hinchey', + 'Kostka', + 'Orenstein', + 'Rosenau', + 'Skow', + 'Cuello', + 'Herder', + 'Cure', + 'Eadie', + 'Claggett', + 'Batie', + 'Kirwin', + 'Troia', + 'Sinnett', + 'Books', + 'Maize', + 'Tremble', + 'Sinkler', + 'Gallon', + 'Winkles', + 'Zion', + 'Walt', + 'Pearse', + 'Gathright', + 'Isakson', + 'Saeger', + 'Siegle', + 'Wittwer', + 'Modesto', + 'Bensen', + 'Royals', + 'Mccane', + 'Begaye', + 'Matuszewski', + 'Schrier', + 'Shimko', + 'Torchia', + 'Ausmus', + 'Casazza', + 'Mealer', + 'Yant', + 'Amar', + 'Callas', + 'Depaola', + 'Kintner', + 'Lech', + 'Marsico', + 'Boerger', + 'Rak', + 'Kellen', + 'Kennemer', + 'Carbo', + 'Rennick', + 'Brennen', + 'Dorrough', + 'Shealey', + 'Breyer', + 'Dilks', + 'Geske', + 'Hundt', + 'Occhipinti', + 'Strauser', + 'Schult', + 'Transue', + 'Holding', + 'Vanhorne', + 'Critchlow', + 'Steptoe', + 'Buerger', + 'Claassen', + 'Farinas', + 'Ruland', + 'Holsapple', + 'Mcclintic', + 'Bendel', + 'Muriel', + 'Mckeithan', + 'Shellman', + 'Balzano', + 'Bement', + 'Montesinos', + 'Ringle', + 'Sobotka', + 'Donahoo', + 'Dicker', + 'Harling', + 'Burkley', + 'Browner', + 'Iovino', + 'Kubala', + 'Labriola', + 'Morra', + 'Orloff', + 'Patchen', + 'Recchia', + 'Budge', + 'Glendenning', + 'Nethery', + 'Scholtz', + 'Aybar', + 'Buis', + 'Mattie', + 'Bonsall', + 'Conine', + 'Dettmer', + 'Gerding', + 'Plantz', + 'Vandorn', + 'Tremaine', + 'Ruddick', + 'Murrow', + 'Mceachin', + 'Bridgeforth', + 'Docherty', + 'Hultman', + 'Liechty', + 'Touchton', + 'Yokoyama', + 'Borth', + 'Daoud', + 'Mealy', + 'Hearst', + 'Stalling', + 'Drapeau', + 'Hellwig', + 'Longtin', + 'Rappa', + 'Tormey', + 'Vanantwerp', + 'Sabel', + 'Neagle', + 'Duet', + 'Liebert', + 'Lush', + 'Aly', + 'Behn', + 'Brereton', + 'Atienza', + 'Dubey', + 'Gennaro', + 'Miltenberger', + 'Nitschke', + 'Ragle', + 'Schumm', + 'Tangen', + 'Waibel', + 'Whitham', + 'Stallone', + 'Perritt', + 'Coody', + 'Hinch', + 'Depuy', + 'Dunkelberger', + 'Texeira', + 'Tomita', + 'Diers', + 'Elsasser', + 'Neve', + 'Clendenen', + 'Pettibone', + 'Dobyns', + 'Ciotti', + 'Dodrill', + 'Fridman', + 'Lepine', + 'Nygard', + 'Shreves', + 'Sollenberger', + 'Leinbach', + 'Diazdeleon', + 'Bourget', + 'Ramadan', + 'Allensworth', + 'Scarboro', + 'Prowell', + 'Ghee', + 'Edouard', + 'Duca', + 'Ziebell', + 'Kercher', + 'Greger', + 'Mas', + 'Shier', + 'Branca', + 'Melchior', + 'Cast', + 'Saner', + 'Beswick', + 'Carone', + 'Sobieski', + 'Zweifel', + 'Beahm', + 'Defrank', + 'Krebsbach', + 'Mericle', + 'Mcinnes', + 'Lown', + 'Brumback', + 'Clause', + 'Claborn', + 'Rollin', + 'Montford', + 'Beckles', + 'Grebe', + 'Groesbeck', + 'Guidi', + 'Mathisen', + 'Mukherjee', + 'Rotolo', + 'Seybert', + 'Odegaard', + 'Mackley', + 'Glatt', + 'Going', + 'Perks', + 'Sansbury', + 'Prude', + 'Bequette', + 'Difilippo', + 'Dodgen', + 'Terpening', + 'Vanepps', + 'Poncedeleon', + 'Qu', + 'Ullery', + 'Wisener', + 'Lok', + 'Lutton', + 'Bellah', + 'Kinsel', + 'Tone', + 'Carabajal', + 'Koll', + 'Shankar', + 'Edick', + 'Donathan', + 'Andree', + 'Perrino', + 'Moffit', + 'Gaddie', + 'Breidenbach', + 'Jespersen', + 'Larrick', + 'Mauriello', + 'Morgado', + 'Roh', + 'Svec', + 'Tebbe', + 'Thieman', + 'Cerezo', + 'Perkowski', + 'Colville', + 'Yarnall', + 'Chason', + 'Brach', + 'Meller', + 'Brayboy', + 'Salaam', + 'Keleher', + 'Kilbourn', + 'Lowenthal', + 'Rispoli', + 'Vanzee', + 'Vlahos', + 'Trojan', + 'Birdsell', + 'Defoor', + 'Mcclusky', + 'Barret', + 'Smoke', + 'Berkeley', + 'Cuadrado', + 'Galyean', + 'Gruen', + 'Gualtieri', + 'Kurland', + 'Sposato', + 'Stieber', + 'Weatherman', + 'Strausser', + 'Miera', + 'Edlin', + 'Gilford', + 'Mouzon', + 'Buczek', + 'Krapf', + 'Lucatero', + 'Amburn', + 'Peddicord', + 'Forero', + 'Domer', + 'Farish', + 'Segraves', + 'Sant', + 'Engles', + 'Douthitt', + 'Lall', + 'Wormley', + 'Geisel', + 'Hao', + 'Polhemus', + 'Slifer', + 'Mowen', + 'Markin', + 'Rape', + 'Bollin', + 'Bulloch', + 'Pouncey', + 'Rufus', + 'Goodlow', + 'Dammann', + 'Delgrosso', + 'Gadbois', + 'Leap', + 'Lorentzen', + 'Sprankle', + 'Stucki', + 'Vitela', + 'Walck', + 'Winkelmann', + 'Mund', + 'Bley', + 'Channel', + 'Griebel', + 'Nordberg', + 'Slinkard', + 'Orrick', + 'Crooker', + 'Groll', + 'Maradiaga', + 'Jolin', + 'Boni', + 'Prom', + 'Reder', + 'Easler', + 'Totty', + 'Arnaud', + 'Bohler', + 'Heikkila', + 'Kehler', + 'Klingenberg', + 'Matera', + 'Striegel', + 'Urzua', + 'Baldi', + 'Burling', + 'Osmond', + 'Rucks', + 'Diel', + 'Kassel', + 'Schewe', + 'Conkling', + 'Ricke', + 'Schack', + 'Shirah', + 'Brauner', + 'Carriker', + 'Mcduffy', + 'Bieker', + 'Credeur', + 'Fabry', + 'Holdeman', + 'Jeansonne', + 'Klett', + 'Kolstad', + 'Mustain', + 'Strub', + 'Ricketson', + 'Fairbairn', + 'Langel', + 'Fenster', + 'Slatton', + 'Ehrenberg', + 'Espinola', + 'Hannaford', + 'Hinderliter', + 'Siqueiros', + 'Ange', + 'Gillin', + 'Battin', + 'Belue', + 'Spigner', + 'Simien', + 'Gervasi', + 'Pallares', + 'Plotner', + 'Puri', + 'Swiatek', + 'Vanmatre', + 'Corp', + 'Devillier', + 'Bucholtz', + 'Bremner', + 'Jen', + 'Evanson', + 'Ghent', + 'Eastland', + 'Kappler', + 'Grahn', + 'Shadrick', + 'Kibby', + 'Chaires', + 'Kontos', + 'Petrov', + 'Pillai', + 'Chadbourne', + 'Sotolongo', + 'Allende', + 'Kells', + 'Hayford', + 'Hempstead', + 'Livers', + 'Farrior', + 'Authement', + 'Bitz', + 'Corkery', + 'Klawitter', + 'Mongold', + 'Somma', + 'Topham', + 'Defrancisco', + 'Noda', + 'Breon', + 'Thetford', + 'Rod', + 'Kisling', + 'Drouillard', + 'Dotts', + 'Gramajo', + 'Masek', + 'Volkert', + 'Vora', + 'Pietras', + 'Sheffler', + 'Shrestha', + 'Kono', + 'Panza', + 'Brunn', + 'Tatom', + 'Nasir', + 'Barris', + 'Bursey', + 'Elsea', + 'Kettner', + 'Martorana', + 'Lindow', + 'Chevez', + 'Pater', + 'Hennis', + 'Iman', + 'Stembridge', + 'Satcher', + 'Britz', + 'Hommel', + 'Llanas', + 'Pathak', + 'Schwartzman', + 'Janz', + 'Hickle', + 'Deakins', + 'Mantle', + 'Billing', + 'Veiga', + 'Darbonne', + 'Angelle', + 'Granderson', + 'Odoms', + 'Mondesir', + 'Ducksworth', + 'Anker', + 'Deneen', + 'Follmer', + 'Norred', + 'Whitecotton', + 'Halsted', + 'Schiele', + 'Reddin', + 'Pichon', + 'Eustice', + 'Finelli', + 'Kawasaki', + 'Kerekes', + 'Surrett', + 'Divers', + 'Kerney', + 'Bohlman', + 'Oberst', + 'Prough', + 'Tarwater', + 'Wangler', + 'Piceno', + 'Persico', + 'Lastra', + 'Fillman', + 'Barlett', + 'Cort', + 'Kuchar', + 'Plaisted', + 'Rufo', + 'Whitmarsh', + 'Fusaro', + 'Bajwa', + 'Belter', + 'Aldama', + 'Conlee', + 'Tweedie', + 'Greear', + 'Riviera', + 'Stormer', + 'Flannagan', + 'Heatley', + 'Feazell', + 'Bastidas', + 'Benninger', + 'Canseco', + 'Hanners', + 'Kreiner', + 'Pestana', + 'Simerly', + 'Such', + 'Tiedeman', + 'Weible', + 'Zawadzki', + 'Rayman', + 'Crose', + 'Sheeler', + 'Kirven', + 'Winford', + 'Mackall', + 'Balderson', + 'Calleja', + 'Klinefelter', + 'Lauffer', + 'Probert', + 'Melero', + 'Ravelo', + 'Degroff', + 'Pylant', + 'Ricco', + 'Varona', + 'Pickney', + 'Bachmeier', + 'Dulay', + 'Hanover', + 'Virgilio', + 'Spino', + 'Bohon', + 'Cantin', + 'Pettijohn', + 'Branigan', + 'Duhe', + 'Perine', + 'Thedford', + 'Shamburger', + 'Guarnieri', + 'Guptill', + 'Nyland', + 'Setliff', + 'Shreffler', + 'Viggiano', + 'Pries', + 'Sunde', + 'Bulmer', + 'Platts', + 'Jeremiah', + 'Fawley', + 'Jansson', + 'Rebelo', + 'Prochnow', + 'Waldeck', + 'Citron', + 'Roughton', + 'Ryckman', + 'Molano', + 'Cannaday', + 'Ned', + 'Beckerman', + 'Galaz', + 'Graziani', + 'Kawakami', + 'Limones', + 'Mousseau', + 'Riha', + 'Huser', + 'Casady', + 'Kirker', + 'Benish', + 'Tomczyk', + 'Hallahan', + 'Kue', + 'Siple', + 'Kandel', + 'Maring', + 'Bosak', + 'Gandolfo', + 'Reichart', + 'Robarge', + 'Shufelt', + 'Forry', + 'Richart', + 'Shireman', + 'Tozzi', + 'Trudel', + 'Tat', + 'Maday', + 'Faw', + 'Lawrie', + 'Mingle', + 'Yasin', + 'Cutrone', + 'Fairbrother', + 'Ficken', + 'Kluesner', + 'Lagana', + 'Schoenborn', + 'Greb', + 'Stromain', + 'Mcpeters', + 'Toepfer', + 'Wehrman', + 'Kozma', + 'Rohner', + 'Kittel', + 'Louderback', + 'Daughtrey', + 'Philippe', + 'Bargo', + 'Cullinane', + 'Fama', + 'Fredenburg', + 'Pedone', + 'Santillanes', + 'Zahner', + 'Zupan', + 'Dundon', + 'Gilfillan', + 'Grego', + 'Otter', + 'Jamil', + 'Beaubien', + 'Collingwood', + 'Quinney', + 'Botero', + 'Edstrom', + 'Flink', + 'Ortner', + 'Schmidtke', + 'Reichle', + 'Leder', + 'Pelosi', + 'Fiorito', + 'Berber', + 'Hislop', + 'Dunstan', + 'Favorite', + 'Wooding', + 'Gariepy', + 'Gottesman', + 'Guercio', + 'Konz', + 'Kothari', + 'Laguardia', + 'Lamphier', + 'Puetz', + 'Casagrande', + 'Quay', + 'Rieth', + 'Vowell', + 'Mcanulty', + 'Mian', + 'Lucus', + 'Alvizo', + 'Domanski', + 'Elling', + 'Maniaci', + 'Neumeyer', + 'Piraino', + 'Schroll', + 'Willsey', + 'Avellaneda', + 'Wilcoxen', + 'Murrey', + 'Bennette', + 'Boyajian', + 'Distler', + 'Lindamood', + 'Maclaren', + 'Onken', + 'Stefano', + 'Uselton', + 'Wilgus', + 'Rardin', + 'Boen', + 'Stillwagon', + 'Satter', + 'Allis', + 'Capell', + 'Nedd', + 'Arcand', + 'Breit', + 'Horwath', + 'Lakatos', + 'Roling', + 'Hessel', + 'Cusson', + 'Rockefeller', + 'Shiffer', + 'Briney', + 'Celeste', + 'Sayed', + 'Revelle', + 'Corker', + 'Baldonado', + 'Lokken', + 'Plymale', + 'Sugden', + 'Twist', + 'Parten', + 'Geil', + 'Sime', + 'Grisby', + 'Jeanty', + 'Baroni', + 'Ditullio', + 'Domenico', + 'Geiss', + 'Gemmill', + 'Leng', + 'Lewicki', + 'Weyandt', + 'Haycock', + 'Coonce', + 'Pillar', + 'Medcalf', + 'Sall', + 'Goldsborough', + 'Bergerson', + 'Daffron', + 'Hinchman', + 'Leibold', + 'Sarkissian', + 'Serratos', + 'Uhlig', + 'Wurth', + 'Ost', + 'Steinmann', + 'Saum', + 'Bullion', + 'Dejonge', + 'Assad', + 'Adelson', + 'Sholes', + 'Clermont', + 'Tabron', + 'Kilduff', + 'Millspaugh', + 'Partyka', + 'Santore', + 'Wensel', + 'Zima', + 'Raschke', + 'Simonis', + 'Tuell', + 'Obriant', + 'Lewter', + 'Nealey', + 'Baranski', + 'Bloomberg', + 'Franchi', + 'Klemme', + 'Raborn', + 'Wohlgemuth', + 'Basta', + 'Bernardini', + 'Canlas', + 'Yeargin', + 'Stingley', + 'Crosland', + 'Bob', + 'Ascher', + 'Dibona', + 'Farabaugh', + 'Kilcoyne', + 'Poblete', + 'Beato', + 'Teasdale', + 'Rossell', + 'Lawhorne', + 'Jama', + 'Behringer', + 'Hallstrom', + 'Kitzman', + 'Klenk', + 'Mctigue', + 'Onate', + 'Rodda', + 'Siegal', + 'Pepple', + 'Tash', + 'Gager', + 'Hing', + 'Yokley', + 'Epting', + 'Mangham', + 'Zackery', + 'Blackerby', + 'Canedo', + 'Glatz', + 'Hilker', + 'Hummell', + 'Mangels', + 'Gamel', + 'Gang', + 'Hooser', + 'Moates', + 'Mutch', + 'Lyerly', + 'Vesey', + 'Satterthwaite', + 'Calcote', + 'Saulsbury', + 'Averette', + 'Ates', + 'Rita', + 'Vicencio', + 'Wismer', + 'Mayoral', + 'Crader', + 'Levens', + 'Joel', + 'Haye', + 'Drager', + 'Eiden', + 'Escutia', + 'Inzunza', + 'Moroz', + 'Sepulvado', + 'Tomaselli', + 'Zartman', + 'Isaak', + 'Philippi', + 'Mcgeary', + 'Taha', + 'Buttler', + 'Crisci', + 'Kot', + 'Micek', + 'Mondello', + 'Petrarca', + 'Rossini', + 'Villalvazo', + 'Weedman', + 'Mitten', + 'Favre', + 'Varnes', + 'Betancur', + 'Bevington', + 'Bockman', + 'Feldstein', + 'Kujawski', + 'Siemer', + 'Soderlund', + 'Fricker', + 'Gerstein', + 'Kick', + 'Haff', + 'Brackman', + 'Hulen', + 'Nephew', + 'Birkett', + 'Gardenhire', + 'Garn', + 'Kellenberger', + 'Mogensen', + 'Murata', + 'Weisbrod', + 'Vilchis', + 'Meder', + 'Akey', + 'Mcmanis', + 'Delatte', + 'Guiles', + 'Turnbough', + 'Murrah', + 'Kilgo', + 'Marcelin', + 'Cecchini', + 'Chrysler', + 'Eick', + 'Fletes', + 'Luevanos', + 'Kurt', + 'Firman', + 'Hensen', + 'Champine', + 'Holford', + 'Appelbaum', + 'Ciampa', + 'Florentino', + 'Lorton', + 'Lubinski', + 'Moquin', + 'Welke', + 'Grinberg', + 'Bolstad', + 'Ade', + 'Outten', + 'Grear', + 'Haith', + 'Borntreger', + 'Steinhauser', + 'Facio', + 'Preslar', + 'Speirs', + 'Grasser', + 'Zuck', + 'Deslauriers', + 'Frates', + 'Mayville', + 'Suddeth', + 'Littlepage', + 'Aversa', + 'Chagolla', + 'Godshall', + 'Jordahl', + 'Oakland', + 'Monsen', + 'Rudolf', + 'Mccollister', + 'Mickles', + 'Flaig', + 'Friberg', + 'Grubaugh', + 'Sliwinski', + 'Stach', + 'Bechtol', + 'Pasch', + 'Keebler', + 'Fagin', + 'Mister', + 'Wynter', + 'Bednarek', + 'Blansett', + 'Crossett', + 'Kettering', + 'Lafata', + 'Raffa', + 'Roig', + 'Schopp', + 'Voegele', + 'Waldschmidt', + 'Clatterbuck', + 'Amer', + 'Kraut', + 'Furniss', + 'Edgecomb', + 'Aspinwall', + 'Buckelew', + 'Loranger', + 'Koppel', + 'Vernier', + 'Latino', + 'Hayton', + 'Girod', + 'Primrose', + 'Jetter', + 'Hyche', + 'Ottley', + 'Isidro', + 'Kort', + 'Mulroy', + 'Reznik', + 'Tozer', + 'Vanderheyden', + 'Kassab', + 'Paro', + 'Belen', + 'Vandever', + 'Harsch', + 'Rawley', + 'Gonder', + 'Delbridge', + 'Alumbaugh', + 'Basulto', + 'Hoehne', + 'Mccaig', + 'Qin', + 'Rasnake', + 'Tewksbury', + 'Ratajczak', + 'Reinbold', + 'Mcgillivray', + 'Nuccio', + 'Steinbeck', + 'Deland', + 'Callow', + 'Wootten', + 'Lytton', + 'Calix', + 'Stinger', + 'Slider', + 'Cadman', + 'Faulconer', + 'Higashi', + 'Lamping', + 'Sellner', + 'Walko', + 'Kilkenny', + 'Charter', + 'Gauntt', + 'Bronk', + 'Legare', + 'Hukill', + 'Kulikowski', + 'Kunde', + 'Michelsen', + 'Mottola', + 'Pasion', + 'Stimmel', + 'Deavila', + 'Lian', + 'Koga', + 'Kitchin', + 'Whitner', + 'Bucholz', + 'Kilbride', + 'Klumpp', + 'Osinski', + 'Petrich', + 'Saar', + 'Robards', + 'Flakes', + 'Accardo', + 'Gebauer', + 'Matyas', + 'Montesano', + 'Schiefer', + 'Zuehlke', + 'Swartout', + 'Gidley', + 'Burghardt', + 'Delcambre', + 'Jerman', + 'Laufenberg', + 'Paterno', + 'Piccione', + 'Wenning', + 'Wilhelmi', + 'Rathjen', + 'Bauch', + 'Hiott', + 'Bagnall', + 'Miskell', + 'Snellings', + 'Sally', + 'Bjornson', + 'Din', + 'Kroeker', + 'Mitra', + 'Saxena', + 'Hausler', + 'Scogin', + 'Jeronimo', + 'Holderfield', + 'Cruze', + 'Christina', + 'Beville', + 'Whitehorn', + 'Bembry', + 'Fludd', + 'Abboud', + 'Blomgren', + 'Friddle', + 'Jarvi', + 'Nastasi', + 'Tomich', + 'Peinado', + 'Rinaldo', + 'Proudfoot', + 'Down', + 'Lawry', + 'Noor', + 'Bachelor', + 'Mullenax', + 'Pocock', + 'Resler', + 'Sprunger', + 'Wiegel', + 'Wohlers', + 'Niedzwiecki', + 'Bourgoin', + 'Grist', + 'Nora', + 'Gude', + 'Mcgaughy', + 'Borror', + 'Bushee', + 'Crego', + 'Engberg', + 'Karle', + 'Raso', + 'Rayas', + 'Roehrig', + 'Villamil', + 'Croucher', + 'Candido', + 'Rockhill', + 'Dahn', + 'Philp', + 'Grasty', + 'Basnight', + 'Cacioppo', + 'Heavener', + 'Hoenig', + 'Janisch', + 'Labombard', + 'Sheng', + 'Wettstein', + 'Wymore', + 'Zuluaga', + 'Canova', + 'Maclennan', + 'Tuley', + 'Geddings', + 'Cayetano', + 'Bogar', + 'Malbrough', + 'Bradish', + 'Chiaramonte', + 'Eguia', + 'Loux', + 'Nemecek', + 'Ouimet', + 'Roxas', + 'Yoshioka', + 'Cossio', + 'Sleight', + 'Walla', + 'Younan', + 'Hee', + 'Bartlow', + 'Parchman', + 'Leaks', + 'Folz', + 'Knittel', + 'Lovvorn', + 'Melick', + 'Weingartner', + 'Eustace', + 'Robbs', + 'Jacquet', + 'Direnzo', + 'Domke', + 'Kestler', + 'Pavelka', + 'Pileggi', + 'Silvestro', + 'Leedom', + 'Kyte', + 'Espey', + 'Kincannon', + 'Robicheaux', + 'Lard', + 'Falkenstein', + 'Fino', + 'Kotz', + 'Lammert', + 'Markovic', + 'Mcwaters', + 'Shibata', + 'Garoutte', + 'Brum', + 'Hora', + 'Gundrum', + 'Leer', + 'Coller', + 'Delsignore', + 'Ebarb', + 'Heras', + 'Skolnick', + 'Sponseller', + 'Baltes', + 'Rabinovich', + 'Welden', + 'Papas', + 'Bingman', + 'Neto', + 'Burrough', + 'Ollie', + 'Deitrick', + 'Hermansen', + 'Datta', + 'Gebo', + 'Bulla', + 'Rippey', + 'Solon', + 'Draughon', + 'Sylvestre', + 'Outen', + 'Westfield', + 'Daoust', + 'Kuan', + 'Kubat', + 'Labuda', + 'Olejniczak', + 'Radomski', + 'Scheuermann', + 'Schunk', + 'Tuazon', + 'Wineland', + 'Gizzi', + 'Millay', + 'Hamp', + 'Murdaugh', + 'Hayles', + 'Plowden', + 'Lesure', + 'Artrip', + 'Kenneally', + 'Piehl', + 'Vandermeulen', + 'Camberos', + 'Hochberg', + 'Sinner', + 'Crass', + 'Gade', + 'Tedrick', + 'Nicholl', + 'Speece', + 'Chatterjee', + 'Gillihan', + 'Luzzi', + 'Obyrne', + 'Uchida', + 'Kidney', + 'Dorough', + 'Dangler', + 'Mcneel', + 'Ruley', + 'Mcloud', + 'Smarr', + 'Gayles', + 'Janiszewski', + 'Kubo', + 'Mckibbin', + 'Szatkowski', + 'Lehnert', + 'Mcilvain', + 'Mcclish', + 'Mcentyre', + 'Strawder', + 'Briere', + 'Headlee', + 'Leszczynski', + 'Mauser', + 'Rask', + 'Wisler', + 'Burba', + 'Shaulis', + 'Showman', + 'Proto', + 'Creasman', + 'Slye', + 'Dunwoody', + 'Ellingsworth', + 'Linebaugh', + 'Riva', + 'Um', + 'Muldowney', + 'Burlew', + 'Gettings', + 'Clingman', + 'Shield', + 'Trollinger', + 'Stiger', + 'Kellman', + 'Arviso', + 'Boettger', + 'Deak', + 'Deiter', + 'Hackenberg', + 'Langone', + 'Lichter', + 'Siano', + 'Wrinkle', + 'Dickert', + 'Boor', + 'Ludington', + 'Griffing', + 'Perin', + 'Woodby', + 'Quail', + 'Harriss', + 'Bilotta', + 'Chino', + 'Cocke', + 'Corbell', + 'Dearden', + 'Facundo', + 'Gaskell', + 'Grieser', + 'Houts', + 'Zuk', + 'Yamauchi', + 'Caouette', + 'Perham', + 'Hewson', + 'Keppel', + 'Artiaga', + 'Sa', + 'Ginger', + 'Goosby', + 'Bollig', + 'Grippo', + 'Hoffmeyer', + 'Klaas', + 'Rohlfing', + 'Stolp', + 'Vielma', + 'Gresh', + 'Mignone', + 'Parsell', + 'Sprout', + 'Hase', + 'Nadal', + 'Joye', + 'Butkus', + 'Donlan', + 'Fuhrer', + 'Grobe', + 'Haverkamp', + 'Janecek', + 'Pancoast', + 'Rathke', + 'Scheibe', + 'Schneller', + 'Scally', + 'Valeriano', + 'Fail', + 'Everage', + 'Murff', + 'Demayo', + 'Dieterich', + 'Kramp', + 'Macchia', + 'Ruyle', + 'Zuidema', + 'Tischer', + 'Palo', + 'Bahn', + 'Hartson', + 'Rosborough', + 'Hartke', + 'Hixenbaugh', + 'Matlack', + 'Hoefler', + 'Hsia', + 'Cech', + 'Donham', + 'Szafranski', + 'Jennison', + 'Emmer', + 'Christians', + 'Swigert', + 'Mclawhorn', + 'Costas', + 'Culligan', + 'Eisenstein', + 'Joos', + 'Villacorta', + 'Majerus', + 'Lukowski', + 'Byford', + 'Canepa', + 'Jeppson', + 'Larison', + 'Waechter', + 'Bleich', + 'Trigo', + 'Lill', + 'Mcisaac', + 'Oflaherty', + 'Dedman', + 'Lynes', + 'Everidge', + 'Armfield', + 'Cadieux', + 'Dembowski', + 'Flewelling', + 'Guadagno', + 'Lamendola', + 'Meidinger', + 'Muzzy', + 'Pacelli', + 'Pangle', + 'Denzer', + 'Sharman', + 'Venzor', + 'Shadwick', + 'Saine', + 'Lighty', + 'Twine', + 'Buehner', + 'Caruana', + 'Filipiak', + 'Fiori', + 'Kellison', + 'Odonovan', + 'Ragone', + 'Enyeart', + 'Coale', + 'Coombes', + 'Yarrington', + 'Leno', + 'Coad', + 'Well', + 'Labranche', + 'Banaszak', + 'Jovanovic', + 'Junk', + 'Kratochvil', + 'Marchi', + 'Mcnitt', + 'Monnin', + 'Portales', + 'Nazzaro', + 'Laramie', + 'Kohlman', + 'Pinette', + 'Craw', + 'Aldred', + 'Jolicoeur', + 'Nevers', + 'Boseman', + 'Apostol', + 'Barbaro', + 'Dirienzo', + 'Kimrey', + 'Knaack', + 'Marenco', + 'Meixner', + 'Placek', + 'Prigge', + 'Sablan', + 'Stoecker', + 'Ulrey', + 'Madonia', + 'Mariotti', + 'Hypes', + 'Teti', + 'Pothier', + 'Duer', + 'Reay', + 'Charlie', + 'Alix', + 'Cropp', + 'Wellons', + 'Haugland', + 'Malkowski', + 'Powley', + 'Query', + 'Stolle', + 'Twedt', + 'Grech', + 'Musson', + 'Larrimore', + 'Esper', + 'Suleiman', + 'Gillie', + 'Aaronson', + 'Brueggeman', + 'Kupfer', + 'Orf', + 'Pozzi', + 'Rayos', + 'Scheiner', + 'Schmoll', + 'Sirota', + 'Trickey', + 'Ahuja', + 'Halm', + 'Jaycox', + 'Carithers', + 'Bjorkman', + 'Klar', + 'Lembke', + 'Nordyke', + 'Primeau', + 'Wachs', + 'Wissinger', + 'Doonan', + 'Mikulski', + 'Murthy', + 'Raju', + 'Thrailkill', + 'Splawn', + 'Lockamy', + 'Brassell', + 'Mcshan', + 'Hawbaker', + 'Kracht', + 'Lahman', + 'Lauritsen', + 'Metzner', + 'Presser', + 'Rapoport', + 'Romani', + 'Wolken', + 'Bertone', + 'Bhat', + 'Lenzi', + 'Lefort', + 'Makar', + 'Melnyk', + 'Siguenza', + 'Ristow', + 'Piller', + 'Mcgaugh', + 'Lampton', + 'Delva', + 'Gethers', + 'Leday', + 'Bateson', + 'Beckstrom', + 'Bedsole', + 'Hauber', + 'Hodgkinson', + 'Croghan', + 'Glanz', + 'Gaver', + 'Pinkley', + 'Traynham', + 'Heffley', + 'Indelicato', + 'Lindblad', + 'Petrik', + 'Ptacek', + 'Capen', + 'Carrara', + 'Ortuno', + 'Lobue', + 'Corella', + 'Lybrand', + 'Myler', + 'Steer', + 'Mckamey', + 'Coman', + 'Auker', + 'Escue', + 'Knell', + 'Mahood', + 'Tillinghast', + 'Deremer', + 'Janak', + 'Naegele', + 'Patnaude', + 'Leahey', + 'Pupo', + 'Bouse', + 'Bradstreet', + 'Symes', + 'Callies', + 'Duncanson', + 'Blanche', + 'Span', + 'Shakir', + 'Finneran', + 'Lenker', + 'Mendola', + 'Navin', + 'Palka', + 'Spanier', + 'Stahler', + 'Vannatter', + 'Botta', + 'Gonser', + 'Edelson', + 'Brashier', + 'Golla', + 'Parramore', + 'Bigby', + 'El', + 'Habeck', + 'Kleinhans', + 'Knobel', + 'Pekar', + 'Remmers', + 'Dea', + 'Foo', + 'Plumer', + 'Combest', + 'Godbee', + 'Hilaire', + 'Lepak', + 'Sgro', + 'Vierling', + 'Harm', + 'Holtsclaw', + 'Gaetano', + 'Kindler', + 'Sabbagh', + 'Politte', + 'Amor', + 'Tilly', + 'Trone', + 'Callaham', + 'Roussell', + 'Asplund', + 'Cacciatore', + 'Dries', + 'Friedl', + 'Hartranft', + 'Kimmell', + 'Lengacher', + 'Scardino', + 'Werley', + 'Zappa', + 'Hust', + 'Seiden', + 'Bultman', + 'Withey', + 'Brandow', + 'Oler', + 'Ladouceur', + 'Celli', + 'Condie', + 'Egge', + 'Kleman', + 'Krafft', + 'Margulies', + 'Weier', + 'Mikels', + 'Pavel', + 'Sigel', + 'Foulke', + 'Kluttz', + 'Mcgown', + 'Acero', + 'Gering', + 'Knauff', + 'Ruesch', + 'Rydberg', + 'Shonk', + 'Weisgerber', + 'Wieber', + 'Zinser', + 'Lilienthal', + 'Crosbie', + 'Luckie', + 'Chenier', + 'Aceto', + 'Atnip', + 'Hisey', + 'Imhof', + 'Klocke', + 'Renderos', + 'Schaad', + 'Shoults', + 'Slevin', + 'Tenenbaum', + 'Vrana', + 'Dicesare', + 'Colarusso', + 'Killgore', + 'Courtois', + 'Tysinger', + 'Agard', + 'Brutus', + 'Woodfork', + 'Boeckman', + 'Breitenstein', + 'Downen', + 'Franzese', + 'Garbe', + 'Iannucci', + 'Kist', + 'Mccolgan', + 'Seib', + 'Sereno', + 'Varma', + 'Fought', + 'Barcomb', + 'Happ', + 'Yeaton', + 'Sharples', + 'Huson', + 'Askin', + 'Elliston', + 'Birks', + 'Allums', + 'Richarson', + 'Arterburn', + 'Auyeung', + 'Engman', + 'Segall', + 'Sjoberg', + 'Sturman', + 'Buys', + 'Basford', + 'Gaut', + 'Hollomon', + 'Antal', + 'Groseclose', + 'Motyka', + 'Reddell', + 'Ansel', + 'Fausett', + 'Girgis', + 'Brownson', + 'Pouncy', + 'Behler', + 'Ciesla', + 'Dewall', + 'Helmers', + 'Pizzuto', + 'Sao', + 'Hourigan', + 'Novelli', + 'Kuta', + 'Gau', + 'Verville', + 'Parkison', + 'Souter', + 'Whitelaw', + 'Vercher', + 'Coger', + 'Issac', + 'Cardamone', + 'Heneghan', + 'Herrero', + 'Plancarte', + 'Reach', + 'Sarinana', + 'Zweig', + 'Berkheimer', + 'Brosseau', + 'Angstadt', + 'Popoca', + 'Brode', + 'Presswood', + 'Hannibal', + 'Pigford', + 'Argento', + 'Dieringer', + 'Kinnett', + 'Maclachlan', + 'Perko', + 'Rosenkranz', + 'Kobus', + 'Merk', + 'Prevatte', + 'Kaya', + 'Didio', + 'Thong', + 'Cowin', + 'Tumlin', + 'Lopp', + 'Callier', + 'Sesay', + 'Beerman', + 'Creger', + 'Eyster', + 'Libbey', + 'Minear', + 'Pontious', + 'Stemen', + 'Strahl', + 'Trillo', + 'Dively', + 'Lackner', + 'Welte', + 'Likes', + 'Mazzoni', + 'Resh', + 'Oser', + 'Dilday', + 'Requena', + 'Bail', + 'Ellen', + 'Buchanon', + 'Almeda', + 'Dimino', + 'Griess', + 'Wetzler', + 'Kriegel', + 'Attanasio', + 'Reighard', + 'Alling', + 'Wiginton', + 'Penfield', + 'Barbe', + 'Alred', + 'Ridout', + 'Lucien', + 'Cerullo', + 'Esterline', + 'Garriott', + 'Hendershott', + 'Kaczmarczyk', + 'Pazos', + 'Racicot', + 'Kowaleski', + 'Lippold', + 'Bankert', + 'Emigh', + 'Cupps', + 'Jagger', + 'Leavens', + 'Lies', + 'Ater', + 'Bleau', + 'Pellot', + 'Crosslin', + 'Faulks', + 'Antwine', + 'Calixte', + 'Brod', + 'Hamad', + 'Junkin', + 'Koeppel', + 'Leifer', + 'Vannest', + 'Olcott', + 'Delange', + 'Hillen', + 'Merlin', + 'Gundy', + 'Hogans', + 'Arseneau', + 'Buzard', + 'Ewalt', + 'Persing', + 'Pursel', + 'Rohrs', + 'Sisemore', + 'Vilchez', + 'Bernath', + 'Rosenbalm', + 'Woolverton', + 'Gibbins', + 'Like', + 'Larsson', + 'Savidge', + 'Strohmeyer', + 'Trentham', + 'Wotring', + 'Boster', + 'Sewall', + 'Glore', + 'Burtis', + 'Marchman', + 'Fouche', + 'Okafor', + 'Khatri', + 'Lengel', + 'Pribyl', + 'Rodewald', + 'Cafaro', + 'Mattix', + 'Shingler', + 'Seawell', + 'Square', + 'Belnap', + 'Heidemann', + 'Kretz', + 'Nebeker', + 'Zemke', + 'Reiners', + 'Cassels', + 'Hout', + 'Favor', + 'Rattray', + 'Custard', + 'Bellucci', + 'Bucklew', + 'Casavant', + 'Davanzo', + 'Kleber', + 'Koeppen', + 'Kulpa', + 'Ledonne', + 'Scarano', + 'Schaar', + 'Staiger', + 'Trigueros', + 'Trobaugh', + 'Tufano', + 'Tschetter', + 'Labra', + 'Beverage', + 'Hulet', + 'Stairs', + 'Waggener', + 'Candy', + 'Kaba', + 'Feiner', + 'Ipock', + 'Nelligan', + 'Pottorff', + 'Beno', + 'Beausoleil', + 'Mayen', + 'Kalil', + 'Deller', + 'Cormack', + 'Hayne', + 'Below', + 'Bundick', + 'Avakian', + 'Desmet', + 'Dobler', + 'Dykeman', + 'Eckstrom', + 'Mahle', + 'Meers', + 'Bortner', + 'Kroon', + 'Lindenmuth', + 'Mcnichol', + 'Sechrest', + 'Abdulla', + 'Gaudin', + 'Lamers', + 'Luffman', + 'Marchione', + 'Paredez', + 'Polster', + 'Maresh', + 'Kristoff', + 'Rickel', + 'Frary', + 'Lorance', + 'Round', + 'Toye', + 'Claybrook', + 'Senegal', + 'Gayhart', + 'Mcmackin', + 'Sagan', + 'Sarkar', + 'Whistler', + 'Stutsman', + 'Alderfer', + 'Spainhour', + 'Karol', + 'Ke', + 'Mifflin', + 'Salah', + 'Alberty', + 'Hynson', + 'Beisel', + 'Castelo', + 'Dau', + 'Diliberto', + 'Dollins', + 'Fiorini', + 'Fritzler', + 'Hanan', + 'Hauschild', + 'Overholser', + 'Wrobleski', + 'Peil', + 'Bellon', + 'Buice', + 'Rolls', + 'Shack', + 'Arakelian', + 'Carpino', + 'Liou', + 'Lydick', + 'Supple', + 'Tammaro', + 'Walbridge', + 'Jandreau', + 'Riter', + 'Roeser', + 'Merson', + 'Bole', + 'Franey', + 'Berrett', + 'Carton', + 'Mcnish', + 'Earnhart', + 'Lehrman', + 'Lipski', + 'Mandelbaum', + 'Tanabe', + 'Mirabile', + 'Ocegueda', + 'Clementi', + 'Shake', + 'Buckle', + 'Rowsey', + 'Eifert', + 'Giesen', + 'Standiford', + 'Vallecillo', + 'Walworth', + 'Berkshire', + 'Feit', + 'Lande', + 'Fiddler', + 'Deputy', + 'Feemster', + 'Evelyn', + 'Bocchino', + 'Cozza', + 'Dirocco', + 'Kock', + 'Luisi', + 'Marcantonio', + 'Presti', + 'Rahimi', + 'Ridinger', + 'Sergi', + 'Viana', + 'Kabat', + 'Suriel', + 'Mester', + 'Bozman', + 'Huffines', + 'Linck', + 'Lodato', + 'Ownbey', + 'Pietz', + 'Rudnicki', + 'Schoener', + 'Schrag', + 'Spicher', + 'Sze', + 'Villella', + 'Steinle', + 'Seaberg', + 'Derks', + 'Mavis', + 'Luellen', + 'Garlington', + 'Nimmons', + 'Brevard', + 'Seabrooks', + 'Ahlquist', + 'Golembiewski', + 'Kochis', + 'Popov', + 'Poulter', + 'Redington', + 'Wingrove', + 'Krepps', + 'Viars', + 'Gallatin', + 'Gilham', + 'Jimison', + 'Glosson', + 'Campeau', + 'Goodhart', + 'Koth', + 'Lettieri', + 'Siragusa', + 'Sojka', + 'Tichy', + 'Viar', + 'Carrozza', + 'Chaffins', + 'Eagleson', + 'Prestwood', + 'Deshazer', + 'Ike', + 'Kubacki', + 'Minogue', + 'Sunseri', + 'Turnbaugh', + 'Heminger', + 'Delira', + 'Jani', + 'Platte', + 'Waterson', + 'Keeble', + 'Kiper', + 'Crigler', + 'Swaby', + 'Brisbin', + 'Galiano', + 'Negley', + 'Regal', + 'Stottlemyer', + 'Volkmann', + 'Herrold', + 'Cypert', + 'Markman', + 'Laman', + 'Williard', + 'Terrio', + 'Raulston', + 'Harrow', + 'Humiston', + 'Kantner', + 'Mcmonagle', + 'Polasek', + 'Ruocco', + 'Schelling', + 'Seip', + 'Woller', + 'Despres', + 'Melius', + 'Keiffer', + 'Voges', + 'Figg', + 'Judice', + 'Henery', + 'Dejarnette', + 'Prosper', + 'Duenez', + 'Frenette', + 'Jaimez', + 'Krist', + 'Kuch', + 'Schlachter', + 'Traeger', + 'Mrozinski', + 'Colberg', + 'Lade', + 'Been', + 'Revere', + 'Greely', + 'Belizaire', + 'Amberg', + 'Cerniglia', + 'Lattanzio', + 'Leitz', + 'Ocker', + 'Ratto', + 'Thornburgh', + 'Yule', + 'Hibner', + 'Puerto', + 'Shoultz', + 'Baley', + 'Linley', + 'Alfrey', + 'Bazaldua', + 'Deniz', + 'Lohnes', + 'Marder', + 'Pelland', + 'Urick', + 'Loberg', + 'Rempel', + 'Faux', + 'Tomkins', + 'Gail', + 'Mccardell', + 'Reuben', + 'Brabant', + 'Hutzler', + 'Liedtke', + 'Nowack', + 'Pittsley', + 'Pelc', + 'Darragh', + 'Pae', + 'Blanke', + 'Brinks', + 'Delap', + 'Brea', + 'Milsap', + 'Borneman', + 'Crofts', + 'Nakai', + 'Silguero', + 'Speciale', + 'Martindelcampo', + 'Vandenburg', + 'Wimsatt', + 'Harbor', + 'Mccorvey', + 'Bensinger', + 'Carhart', + 'Condo', + 'Lemen', + 'Malchow', + 'Vandewater', + 'Ventresca', + 'Morena', + 'Mendell', + 'Faustino', + 'Kleiber', + 'Alberson', + 'Lamonte', + 'Kiner', + 'Belgrave', + 'Blitz', + 'Dildine', + 'Gosch', + 'Grabill', + 'Klemp', + 'Larrea', + 'Pallas', + 'Leonhard', + 'Littler', + 'Dilling', + 'Weatherbee', + 'Robnett', + 'Lacount', + 'Brackins', + 'Counterman', + 'Divincenzo', + 'Dobrowolski', + 'Eppard', + 'Estepp', + 'Gahan', + 'Steininger', + 'Tancredi', + 'Wixom', + 'Combes', + 'Dena', + 'Warn', + 'Teems', + 'Askey', + 'Delmar', + 'Ogles', + 'Herriott', + 'Aguinaldo', + 'In', + 'Kinter', + 'Moul', + 'Santaniello', + 'Tringali', + 'Vanasse', + 'Vanwagoner', + 'Whitesel', + 'Vanderwal', + 'Friedmann', + 'Kalis', + 'Cayer', + 'Para', + 'Wander', + 'Cothron', + 'Betters', + 'Cloward', + 'Cusano', + 'Encinias', + 'Imai', + 'Lalone', + 'Saks', + 'Nosal', + 'Crossan', + 'Caverly', + 'Tewell', + 'Lowney', + 'Merle', + 'Meighan', + 'Labat', + 'Pou', + 'Linsey', + 'Gaviria', + 'Manthei', + 'Marquina', + 'Siegert', + 'Blondin', + 'Maskell', + 'Kimpel', + 'Cappel', + 'Tootle', + 'Folkes', + 'Mainor', + 'Offord', + 'Clagg', + 'Minshew', + 'Niebuhr', + 'Schanz', + 'Stotz', + 'Takeda', + 'Huelsman', + 'Madril', + 'Monico', + 'Stradley', + 'Thein', + 'Cannell', + 'Malson', + 'Ludden', + 'Couts', + 'Mishoe', + 'Dales', + 'Slemp', + 'Stueve', + 'Ziemann', + 'Fluke', + 'Vitali', + 'Monn', + 'Dooling', + 'Lambe', + 'Cail', + 'Louder', + 'Lotts', + 'Augusta', + 'Ando', + 'Depaolo', + 'Egolf', + 'Hibdon', + 'Marzan', + 'Mccawley', + 'Mcgivern', + 'Minjares', + 'Mullally', + 'Portner', + 'Vinciguerra', + 'Wolpert', + 'Yingst', + 'Checo', + 'Starck', + 'Ra', + 'Credle', + 'Baldauf', + 'Bamberger', + 'Besch', + 'Caulkins', + 'Huyck', + 'Portela', + 'Walberg', + 'Kutcher', + 'Hunger', + 'Trant', + 'Cumbee', + 'Cheadle', + 'Drewery', + 'Andrada', + 'Dollinger', + 'Dondero', + 'Salvati', + 'Sefton', + 'Siemers', + 'Sitz', + 'Smale', + 'Wenk', + 'Reschke', + 'Puglia', + 'Koob', + 'Overland', + 'Furrer', + 'Gohl', + 'Hegge', + 'Hentschel', + 'Huberty', + 'Krise', + 'Stasiak', + 'Tripoli', + 'Palomera', + 'Norling', + 'Smucker', + 'Hennes', + 'Metro', + 'Himmel', + 'Paolino', + 'Prato', + 'Wommack', + 'Mcpheeters', + 'Ronald', + 'Eppinger', + 'Cantey', + 'Appell', + 'Capellan', + 'Fielden', + 'Garfias', + 'Heit', + 'Janusz', + 'Pagliaro', + 'Pitz', + 'Winegardner', + 'Gregorich', + 'Schlager', + 'Selvidge', + 'Shultis', + 'Severn', + 'Buffum', + 'Crafts', + 'Antony', + 'Timpson', + 'Deveaux', + 'Maese', + 'Merlos', + 'Mojarro', + 'Policastro', + 'Tawil', + 'Flamm', + 'Aasen', + 'Lipkin', + 'Dyches', + 'Caulk', + 'Rampersad', + 'Pettie', + 'Hagwood', + 'Jedlicka', + 'Paoli', + 'Perkey', + 'Shaub', + 'Vires', + 'Glad', + 'Mandrell', + 'Angeli', + 'Antuna', + 'Bessler', + 'Cebula', + 'Heagy', + 'Mankowski', + 'Sitler', + 'Vanleuven', + 'Blanck', + 'Dannenberg', + 'Moren', + 'Hites', + 'Leckie', + 'Tham', + 'Dower', + 'Beans', + 'Alls', + 'Sipp', + 'Dygert', + 'Kubicek', + 'Matsumura', + 'Shiroma', + 'Smiddy', + 'Szilagyi', + 'Winkleman', + 'Zentz', + 'Niehoff', + 'Boedeker', + 'Dimmitt', + 'Trew', + 'Wilner', + 'Traughber', + 'Bardales', + 'Borbon', + 'Bramhall', + 'Crofoot', + 'Desilets', + 'Disch', + 'Kehrer', + 'Leffingwell', + 'Olalde', + 'Wawrzyniak', + 'Jagodzinski', + 'Schwerin', + 'Heiney', + 'Hirano', + 'Rueter', + 'Sarris', + 'Magnan', + 'Rigsbee', + 'Blay', + 'Edgeworth', + 'Hafford', + 'Legrande', + 'Netter', + 'Dulac', + 'Etherington', + 'Gaede', + 'Matranga', + 'Misch', + 'Gryder', + 'Kolman', + 'Reyer', + 'Landsman', + 'Huppert', + 'Steagall', + 'Heims', + 'Baldini', + 'Breithaupt', + 'Claypoole', + 'Feuer', + 'Heishman', + 'Pallotta', + 'Sponaugle', + 'Pershing', + 'Spaid', + 'Salt', + 'Giger', + 'Whetsel', + 'Balaban', + 'Baus', + 'Croke', + 'Heimer', + 'Milnes', + 'Onstott', + 'Wagman', + 'Magro', + 'Havlik', + 'Menge', + 'Talmage', + 'Aungst', + 'Dichiara', + 'Kuhr', + 'Milstein', + 'Sinatra', + 'Speiser', + 'Vise', + 'Panther', + 'Phair', + 'Commons', + 'Mincy', + 'Ashline', + 'Eagen', + 'Enns', + 'Epler', + 'Giltner', + 'Rexroat', + 'Schein', + 'Wellner', + 'Wickert', + 'Ardito', + 'Ihrig', + 'Schuerman', + 'Wentland', + 'Wohlford', + 'Stoy', + 'Kohan', + 'Ratley', + 'Hazell', + 'Coppin', + 'Blackshire', + 'Coolbaugh', + 'Essman', + 'Gandee', + 'Moccia', + 'Mullarkey', + 'Sugrue', + 'Woomer', + 'Arriaza', + 'Pipitone', + 'Heart', + 'Prothro', + 'Connaughton', + 'Covelli', + 'Lunger', + 'Mcilroy', + 'Morataya', + 'Swedberg', + 'Trembley', + 'Wiederhold', + 'Zappia', + 'Perret', + 'Glander', + 'Snedden', + 'Stonestreet', + 'Archey', + 'Arbour', + 'Cordaro', + 'Diskin', + 'Dumlao', + 'Fravel', + 'Spagnuolo', + 'Derossett', + 'Grigorian', + 'Mercadante', + 'Harcourt', + 'Norgaard', + 'Terhaar', + 'Touch', + 'Mccubbins', + 'Tadros', + 'Zabriskie', + 'Fontanilla', + 'Ruse', + 'Springsteen', + 'Getter', + 'Berrian', + 'Louissaint', + 'Cobbins', + 'Dorney', + 'Kugel', + 'Luth', + 'Poffenberger', + 'Sidoti', + 'Steinfeld', + 'Poley', + 'Dreger', + 'Ertl', + 'Capper', + 'Laswell', + 'Spragg', + 'Coltrane', + 'Winborne', + 'Langhorne', + 'Fambro', + 'Berkebile', + 'Bosserman', + 'Cygan', + 'Debonis', + 'Munsch', + 'Pflug', + 'Skowron', + 'Ediger', + 'Bosler', + 'Morden', + 'Virtue', + 'Orso', + 'Claire', + 'Damas', + 'Eichenlaub', + 'Gatchell', + 'Mikus', + 'Tjaden', + 'Tremper', + 'Tusing', + 'Longest', + 'Baires', + 'Dobos', + 'Deforge', + 'Kawa', + 'Hodder', + 'Thornell', + 'Mcgarrity', + 'Gotcher', + 'Judah', + 'Busey', + 'Perrier', + 'Hawthorn', + 'Captain', + 'Costlow', + 'Frohlich', + 'Gulla', + 'Hildebrant', + 'Hilgendorf', + 'Ramachandran', + 'Reaume', + 'Vollrath', + 'Lambertson', + 'Wyer', + 'Coit', + 'Dietsch', + 'Struve', + 'Vicario', + 'Ahlberg', + 'Warshaw', + 'Ryon', + 'Evatt', + 'Mobbs', + 'Gartin', + 'Kenley', + 'Marcell', + 'Bumpers', + 'Jans', + 'Karczewski', + 'Mazurkiewicz', + 'Nadolny', + 'Verrill', + 'Sitter', + 'Freyer', + 'Hindle', + 'Hergert', + 'Inda', + 'Magwood', + 'Basa', + 'Covello', + 'Pacini', + 'Ruoff', + 'Schenker', + 'Zwicker', + 'Popovic', + 'Augustyn', + 'Sutera', + 'Almy', + 'Keisler', + 'Vowels', + 'Lemond', + 'Abila', + 'Beardslee', + 'Benvenuto', + 'Deschaine', + 'Hodel', + 'Turbyfill', + 'Vejar', + 'Iddings', + 'Labrada', + 'Bowne', + 'Seel', + 'Stretch', + 'Haswell', + 'Rickerson', + 'Speas', + 'Southward', + 'Tony', + 'Burrier', + 'Casco', + 'Lorch', + 'Pietrowski', + 'Rabbitt', + 'Sefcik', + 'Trenary', + 'Trisler', + 'Zarazua', + 'Kube', + 'Riera', + 'Stmarie', + 'Starns', + 'Carmel', + 'Shire', + 'Britto', + 'Lacks', + 'Cifelli', + 'Dusenberry', + 'Lusher', + 'Mattioli', + 'Quiring', + 'Regner', + 'Shetty', + 'Stober', + 'Winemiller', + 'Zinke', + 'Heffington', + 'Santelli', + 'Figeroa', + 'Dishon', + 'Doble', + 'Canino', + 'Tahir', + 'Stamant', + 'Sharpton', + 'Sancho', + 'Linzy', + 'Ba', + 'Bonebrake', + 'Frenkel', + 'Irion', + 'Marines', + 'Lacava', + 'Drennon', + 'Fallen', + 'Whiten', + 'Bielawski', + 'Brasch', + 'Eichorn', + 'Gattuso', + 'Neis', + 'Tkach', + 'Usrey', + 'Walkowiak', + 'Dorame', + 'Orem', + 'Crombie', + 'Lowes', + 'Truscott', + 'Marlette', + 'Bushell', + 'Gosa', + 'Hillary', + 'Byfield', + 'Engdahl', + 'Ganser', + 'Hollars', + 'Lambros', + 'Matzen', + 'Moldovan', + 'Najarian', + 'Schoff', + 'Soo', + 'Spargo', + 'Wierenga', + 'Maysonet', + 'Dewan', + 'Bardo', + 'Figgs', + 'Bostian', + 'Graser', + 'Pecor', + 'Rodrigo', + 'Spilker', + 'Suen', + 'Nafziger', + 'Khouri', + 'Milling', + 'Benke', + 'Chapdelaine', + 'Darwish', + 'Merrigan', + 'Narayanan', + 'Neuner', + 'Wallman', + 'Caracciolo', + 'Uren', + 'Borge', + 'Garside', + 'Veasley', + 'Arquette', + 'Gastineau', + 'Helbling', + 'Maggiore', + 'Prell', + 'Vangelder', + 'Giaquinto', + 'Macha', + 'Jonsson', + 'Febus', + 'Lady', + 'Hughson', + 'Wickliffe', + 'Archila', + 'Bearce', + 'Harstad', + 'Krein', + 'Kulesza', + 'Levitan', + 'Nakasone', + 'Saraceno', + 'Stankus', + 'Shelden', + 'Hopping', + 'Diab', + 'Agar', + 'Mcpike', + 'Betterton', + 'Buzbee', + 'Dieguez', + 'Lins', + 'Phuong', + 'Pinegar', + 'Postel', + 'Beatrice', + 'Biddy', + 'Over', + 'Riding', + 'Rials', + 'Rance', + 'Simington', + 'Degraffenreid', + 'Sherard', + 'Clum', + 'Harkin', + 'Mallen', + 'Messerschmidt', + 'Patz', + 'Shatzer', + 'Stetz', + 'Beckert', + 'Worm', + 'Belmontes', + 'Narron', + 'Lyne', + 'Mckendrick', + 'Rester', + 'Archbold', + 'Whorley', + 'Monts', + 'Crapo', + 'Gribbin', + 'Lamborn', + 'Leverenz', + 'Mccarville', + 'Nishida', + 'Ryberg', + 'Smeal', + 'Piontek', + 'Routhier', + 'Willmon', + 'Proffit', + 'Sharrock', + 'Gasque', + 'Minott', + 'Corpening', + 'Capizzi', + 'Dubuc', + 'Gurevich', + 'Hohenstein', + 'Kotch', + 'Peper', + 'Rehbein', + 'Stortz', + 'Corvin', + 'Savant', + 'Ryle', + 'Madere', + 'Firmin', + 'Bitterman', + 'Bruso', + 'Guzzi', + 'Hefty', + 'Almada', + 'Mcninch', + 'Mangin', + 'On', + 'Hardage', + 'Garson', + 'Hisle', + 'Dease', + 'Critelli', + 'Digennaro', + 'Ehle', + 'Freestone', + 'Grieb', + 'Haubert', + 'Kelsay', + 'Loughman', + 'Neth', + 'Pen', + 'Ranta', + 'Sater', + 'Tomei', + 'Castiglia', + 'Kosek', + 'Zentner', + 'Nowland', + 'Klinedinst', + 'Karls', + 'Charon', + 'Cart', + 'Umphrey', + 'Laramore', + 'Mckenny', + 'Hamler', + 'Stoudemire', + 'Diercks', + 'Hodzic', + 'Huntzinger', + 'Runde', + 'Scavone', + 'Halbach', + 'Banales', + 'Thiry', + 'Waterfield', + 'Bebee', + 'Dass', + 'Caughman', + 'Admire', + 'Attebery', + 'Faubion', + 'Friess', + 'Goldsworthy', + 'Raburn', + 'Vantine', + 'Newswanger', + 'Manhart', + 'Grecco', + 'Meany', + 'Rumpf', + 'Dunlevy', + 'Franceschi', + 'Romanski', + 'Alwine', + 'Cahall', + 'Czaja', + 'Krawiec', + 'Mikolajczyk', + 'Neyman', + 'Perrotti', + 'Weideman', + 'Coppa', + 'Ingerson', + 'Avena', + 'Crunk', + 'Cadenhead', + 'Gittings', + 'Gloss', + 'Trowell', + 'Denard', + 'Funchess', + 'Kinnamon', + 'Mailhot', + 'Mollohan', + 'Polacek', + 'Pozos', + 'Rempe', + 'Schutter', + 'Shimkus', + 'Bedrosian', + 'Beede', + 'Conry', + 'Legan', + 'Pickford', + 'Chamblin', + 'Depinto', + 'Geibel', + 'Gilpatrick', + 'Hashmi', + 'Hermsen', + 'Petruzzi', + 'Robben', + 'Sorkin', + 'Gambardella', + 'Podgorski', + 'Langenfeld', + 'Yanke', + 'Zipperer', + 'Tillson', + 'Ariola', + 'Kelman', + 'Hert', + 'Fearn', + 'Goods', + 'Cervenka', + 'Kreft', + 'Kreidler', + 'Kuhar', + 'Leffew', + 'Maziarz', + 'Vollmar', + 'Zmuda', + 'Eisenhower', + 'Yelle', + 'Bhagat', + 'Kirst', + 'Gilkerson', + 'Kindel', + 'Argyle', + 'Bedingfield', + 'Manney', + 'Guion', + 'Rencher', + 'Plater', + 'Beitzel', + 'Camero', + 'Delaluz', + 'Fennelly', + 'Keenum', + 'Kingrey', + 'Mckillop', + 'Munyon', + 'Rorick', + 'Schrimsher', + 'Sohl', + 'Torbett', + 'Lynde', + 'Reiland', + 'Shepley', + 'Cudney', + 'Cather', + 'Abed', + 'Holen', + 'Jobson', + 'Husbands', + 'Marc', + 'Blatz', + 'Feucht', + 'Gunkel', + 'Margolin', + 'Messerly', + 'Womer', + 'Teston', + 'Ditch', + 'Marta', + 'Osier', + 'Awan', + 'Marcella', + 'Silvester', + 'Baugus', + 'Wilcoxon', + 'Nowling', + 'Torain', + 'Badalamenti', + 'Bartosh', + 'Czajka', + 'Savedra', + 'Shaker', + 'Shambaugh', + 'Stapley', + 'Goeke', + 'Schepers', + 'Tyo', + 'Rhodus', + 'Arencibia', + 'Kara', + 'Aitchison', + 'Parlin', + 'Benny', + 'Shakespeare', + 'Altomare', + 'Axe', + 'Bednarczyk', + 'Feasel', + 'Heikkinen', + 'Heyl', + 'Konecny', + 'Montalbo', + 'Semones', + 'Zuercher', + 'Dorrance', + 'Gehrig', + 'Kretzer', + 'Puchalski', + 'Asche', + 'Astacio', + 'Steers', + 'Jeanes', + 'Bamberg', + 'Matthis', + 'Maultsby', + 'Bunkley', + 'Afonso', + 'Danielsen', + 'Freier', + 'Graeff', + 'Gutknecht', + 'Jansky', + 'Lindenberg', + 'Macphee', + 'Pequeno', + 'Petrocelli', + 'Petrowski', + 'Prete', + 'Igoe', + 'Demonte', + 'Khatib', + 'Agin', + 'Siddall', + 'Mcdill', + 'Higginbottom', + 'Gallow', + 'Inniss', + 'Ballman', + 'Bieniek', + 'Casino', + 'Garringer', + 'Griese', + 'Heritage', + 'Zeitz', + 'Montanaro', + 'Qi', + 'Belcastro', + 'Brautigam', + 'Wakeland', + 'Keasler', + 'Oglesbee', + 'Saye', + 'Steppe', + 'Cichocki', + 'Melgarejo', + 'Primavera', + 'Rippe', + 'Sieger', + 'Stutes', + 'Tustin', + 'Vanloon', + 'Konkol', + 'Altmann', + 'Anderegg', + 'Bun', + 'Mcduffee', + 'Deo', + 'Persad', + 'Kindell', + 'Antillon', + 'Ast', + 'Kumm', + 'Lauricella', + 'Minkler', + 'Pilch', + 'Porreca', + 'Shoopman', + 'Skeels', + 'Chanthavong', + 'Hounshell', + 'Pitner', + 'Space', + 'Blackley', + 'Groomes', + 'Bleeker', + 'Duddy', + 'Inlow', + 'Knabe', + 'Lehmkuhl', + 'Salais', + 'Statz', + 'Sundin', + 'Woolston', + 'Hojnacki', + 'Drolet', + 'Gallivan', + 'Viner', + 'Hafley', + 'Hollan', + 'Phillis', + 'Montrose', + 'Colclough', + 'Coaxum', + 'Basel', + 'Campoverde', + 'Cirelli', + 'Delmonico', + 'Goh', + 'Goyal', + 'Hungate', + 'Lufkin', + 'Passaro', + 'Penta', + 'Quispe', + 'Ovalles', + 'Bulkley', + 'Show', + 'Purington', + 'Sockwell', + 'Mccluney', + 'Asato', + 'Buchta', + 'Cassara', + 'Cesena', + 'Empey', + 'Fass', + 'Gazda', + 'Giannetti', + 'Giuffre', + 'Jahns', + 'Jong', + 'Ruh', + 'Schmieder', + 'Sheerin', + 'Weinheimer', + 'Iwamoto', + 'Ouyang', + 'Uranga', + 'Ranalli', + 'Woolum', + 'Calabria', + 'Arrowsmith', + 'Cashen', + 'Vogan', + 'Giffen', + 'Sherk', + 'Denner', + 'Lanclos', + 'Whittlesey', + 'Dora', + 'Plain', + 'Bransford', + 'Bradwell', + 'Davitt', + 'Dehoff', + 'Lotito', + 'Roell', + 'Satterly', + 'Stahr', + 'Thiem', + 'Helberg', + 'Vause', + 'Willmore', + 'Seid', + 'Linebarger', + 'Geddis', + 'Bringhurst', + 'Damelio', + 'Fetterolf', + 'Galban', + 'Henkle', + 'Kamen', + 'Kaneko', + 'Kissane', + 'Rua', + 'Tehrani', + 'Tingey', + 'Lizardi', + 'Strick', + 'Halper', + 'Striker', + 'Amason', + 'Lesueur', + 'Tatem', + 'Bulluck', + 'Hobdy', + 'Flythe', + 'Brookover', + 'Fishbein', + 'Hartless', + 'Snelgrove', + 'Weikert', + 'Wissman', + 'Bourbeau', + 'Colclasure', + 'Sampley', + 'Shubin', + 'Rhoda', + 'Mcclane', + 'Meals', + 'Peets', + 'Anding', + 'Clewis', + 'Gaymon', + 'Bierly', + 'Brockmeyer', + 'Burnworth', + 'Dierking', + 'Patzer', + 'Seipel', + 'Shieh', + 'Pazmino', + 'Bailie', + 'Ducey', + 'Sessler', + 'Hornaday', + 'Andry', + 'Mowatt', + 'Charlot', + 'Buchholtz', + 'Gaulke', + 'Gondek', + 'Grossmann', + 'Hammerschmidt', + 'Heinle', + 'Huckabay', + 'Neathery', + 'Vanzile', + 'Vossler', + 'Schillaci', + 'Lem', + 'Paff', + 'Oja', + 'Broker', + 'Marlett', + 'Innocent', + 'Adsit', + 'Begg', + 'Kocian', + 'Maddalena', + 'Melamed', + 'Mikos', + 'Pio', + 'Poth', + 'Richwine', + 'Ruda', + 'Sackman', + 'Querry', + 'Padro', + 'Sober', + 'Ayscue', + 'Puff', + 'Hunton', + 'Woltz', + 'Alsobrook', + 'Baskins', + 'Daggs', + 'Brands', + 'Buechel', + 'Gonda', + 'Haberkorn', + 'Hartel', + 'Hazeltine', + 'Lantrip', + 'Leoni', + 'Licona', + 'Stanke', + 'Zwart', + 'Aplin', + 'Leatham', + 'Ace', + 'Ganter', + 'Bartolomeo', + 'Colgrove', + 'Halling', + 'Hesler', + 'Hainline', + 'Susi', + 'Kroner', + 'Sanden', + 'Rylander', + 'Basaldua', + 'Fujiwara', + 'Hengst', + 'Kapur', + 'Kienzle', + 'Miao', + 'Mutschler', + 'Orsi', + 'Pais', + 'Termini', + 'Yamane', + 'Zipp', + 'Wildey', + 'Bauerle', + 'Rehn', + 'Hipsher', + 'Staubin', + 'Esquilin', + 'Goley', + 'Buenaventura', + 'Frutos', + 'Gaugler', + 'Maclellan', + 'Mehring', + 'Stiers', + 'Gearheart', + 'Bong', + 'Maddocks', + 'Canary', + 'Urie', + 'Skillings', + 'Amir', + 'Bogus', + 'Oakman', + 'Barresi', + 'Cappelli', + 'Clausing', + 'Genest', + 'Grella', + 'Mulherin', + 'Roettger', + 'Corle', + 'Mantel', + 'Mody', + 'Delapp', + 'Dunnington', + 'Harvard', + 'Berquist', + 'Foglia', + 'Gilbride', + 'Krenek', + 'Gagnier', + 'Berney', + 'Bazzell', + 'Selvage', + 'Gullette', + 'Lavan', + 'Gunderman', + 'Holaday', + 'Horine', + 'Salata', + 'Slaybaugh', + 'Tobia', + 'Knick', + 'Tinkle', + 'Calcaterra', + 'Fauth', + 'Helmke', + 'Margiotta', + 'Mejorado', + 'Salomone', + 'Sevy', + 'Suri', + 'Vasconcellos', + 'Vetrano', + 'Flaten', + 'Sweetser', + 'Logston', + 'Varon', + 'Allsop', + 'Mickler', + 'Swails', + 'Conejo', + 'Derosia', + 'Hamre', + 'Hanvey', + 'Holscher', + 'Interiano', + 'Kleinberg', + 'Kravetz', + 'Reinking', + 'Schow', + 'Schur', + 'Vanbrocklin', + 'Yinger', + 'Zelenka', + 'Chagoya', + 'Sieben', + 'Devora', + 'Archambeau', + 'Burpee', + 'Shamp', + 'Stander', + 'Weaks', + 'Viney', + 'Halloway', + 'Artiga', + 'Clinkenbeard', + 'Kenison', + 'Loeza', + 'Schaap', + 'Simoni', + 'Frock', + 'Galea', + 'Graven', + 'Brookhart', + 'Gurr', + 'Mackintosh', + 'Arjona', + 'Busche', + 'Salvi', + 'Bedenbaugh', + 'Duan', + 'Clara', + 'Brundidge', + 'Akhter', + 'Amsler', + 'Bolz', + 'Bonura', + 'Brumbelow', + 'Droste', + 'Lohmeyer', + 'Lorah', + 'Louthan', + 'Botti', + 'Feigenbaum', + 'Thon', + 'Osbourn', + 'Peugh', + 'Viau', + 'Elsayed', + 'Hilyard', + 'Coram', + 'Alvin', + 'Milbourne', + 'Hickmon', + 'Basu', + 'Fasnacht', + 'Heathcock', + 'Matsui', + 'Oyama', + 'Stransky', + 'Blakesley', + 'Antes', + 'Flury', + 'Lacrosse', + 'Lull', + 'Clelland', + 'Rugh', + 'Hamelin', + 'Reta', + 'Barnet', + 'Ballow', + 'Pyburn', + 'Slayden', + 'Freshwater', + 'Fomby', + 'Bourquin', + 'Bowersock', + 'Calleros', + 'Dallmann', + 'Gootee', + 'Koelling', + 'Parfitt', + 'Pruss', + 'Tretter', + 'Bellini', + 'Gulden', + 'Pett', + 'Mcglasson', + 'Yerby', + 'Buth', + 'Curnow', + 'Goller', + 'Halderman', + 'Kulig', + 'Laue', + 'Roesner', + 'Samra', + 'Sorrow', + 'Vanbibber', + 'Mellin', + 'Villacis', + 'Hilborn', + 'Ditty', + 'Vasey', + 'Crall', + 'Sera', + 'Honeywell', + 'Blanchet', + 'Halim', + 'Nevius', + 'Ines', + 'Stuard', + 'Birr', + 'Curnutt', + 'Deibler', + 'Jaster', + 'Ouk', + 'Poppell', + 'Provence', + 'Rebman', + 'Schmick', + 'Terra', + 'Zea', + 'Hoven', + 'Loth', + 'Arreaga', + 'Cambre', + 'Roots', + 'Gains', + 'Jeancharles', + 'Cerritos', + 'Ihle', + 'Zambito', + 'Brueggemann', + 'Kluth', + 'Schwartzkopf', + 'Shott', + 'Mcglaughlin', + 'Decoster', + 'Northam', + 'Esau', + 'Fling', + 'Castile', + 'Milledge', + 'Desjarlais', + 'Laframboise', + 'Remigio', + 'Rudloff', + 'Utecht', + 'Enrique', + 'Wygant', + 'Fairbank', + 'Behl', + 'Meuse', + 'Pyke', + 'Fury', + 'Chowning', + 'Hyndman', + 'Donat', + 'Nuckles', + 'Cartledge', + 'Bilal', + 'Antonacci', + 'Huether', + 'Kha', + 'Mascia', + 'Rothberg', + 'Sieck', + 'Younes', + 'Sassaman', + 'Amparan', + 'Benesh', + 'Faraci', + 'Gaber', + 'Lehew', + 'Belzer', + 'Segoviano', + 'Teagle', + 'Burian', + 'Menne', + 'Niemeier', + 'Old', + 'Olenick', + 'Takemoto', + 'Tepe', + 'Test', + 'Zahler', + 'Matsuoka', + 'Hopf', + 'Misenheimer', + 'Mings', + 'Hullett', + 'Beutel', + 'Criscuolo', + 'Fedak', + 'Holtkamp', + 'Kretschmer', + 'Mongillo', + 'Mulrooney', + 'Panganiban', + 'Pollick', + 'Sgroi', + 'Shirkey', + 'Stodola', + 'Tozier', + 'Weidler', + 'Puskar', + 'Fiorello', + 'Stille', + 'Pomales', + 'Gladding', + 'Griffie', + 'Warmack', + 'Uzzell', + 'Stennis', + 'Buttrey', + 'Ekberg', + 'Harmsen', + 'Lieske', + 'Madriz', + 'Mohs', + 'Reininger', + 'Edgin', + 'Galla', + 'Chattin', + 'Frayer', + 'Brents', + 'Lasker', + 'Angelone', + 'Boulter', + 'Burritt', + 'Choudhry', + 'Claffey', + 'Elizarraras', + 'Gaumer', + 'Gawronski', + 'Henwood', + 'Lapine', + 'Bitar', + 'Himel', + 'Almand', + 'Brase', + 'Lala', + 'Salama', + 'Essick', + 'Longman', + 'Mone', + 'Reynard', + 'Brackney', + 'Cottam', + 'Donadio', + 'Geesey', + 'Laudenslager', + 'Mcgilvray', + 'Yano', + 'Bueche', + 'Irey', + 'Carneal', + 'Tinder', + 'Walke', + 'Baston', + 'Segar', + 'Brisbane', + 'Venson', + 'Arguijo', + 'Beitler', + 'Burek', + 'Burgener', + 'Collyer', + 'Donlin', + 'Duhaime', + 'Dworak', + 'Frech', + 'Kozik', + 'Montejo', + 'Nhan', + 'Quirarte', + 'Tram', + 'Deshpande', + 'Silverthorn', + 'Leard', + 'Sheller', + 'Alphin', + 'Boxer', + 'Shawn', + 'Pinnick', + 'Stigler', + 'Arpin', + 'Falkenberg', + 'Gerig', + 'Lemonds', + 'Salm', + 'Sarkis', + 'Paprocki', + 'Probasco', + 'Haithcock', + 'Carn', + 'Farrish', + 'Haliburton', + 'Copen', + 'Pieri', + 'Slaymaker', + 'Cardarelli', + 'Veneziano', + 'Melfi', + 'Solley', + 'Hymer', + 'Pleitez', + 'Hinsley', + 'Bruen', + 'Arita', + 'Dreisbach', + 'Fichtner', + 'Keckler', + 'Slaby', + 'Tanguma', + 'Wiberg', + 'Ferrucci', + 'Lick', + 'Maginnis', + 'Quaranta', + 'Bera', + 'Maybee', + 'Hennessee', + 'Kerrick', + 'Kabir', + 'Branscome', + 'Fullington', + 'Menser', + 'Brooking', + 'Patridge', + 'Gue', + 'Gowens', + 'Redus', + 'Ector', + 'Distasio', + 'Kissner', + 'Prada', + 'Sponsler', + 'Tempel', + 'Wedemeyer', + 'Degler', + 'Bodenhamer', + 'Sherbert', + 'Jefferis', + 'Belgarde', + 'Bevel', + 'Figaro', + 'Bertino', + 'Fabbri', + 'Kovacevic', + 'Kunst', + 'Leja', + 'Ruffo', + 'Stearman', + 'Trickett', + 'Zafar', + 'Valdivieso', + 'Curbelo', + 'Mabee', + 'Emma', + 'Arman', + 'Swasey', + 'Lyday', + 'Muff', + 'Rideaux', + 'Ahlgren', + 'Cobo', + 'Hanratty', + 'Litwiller', + 'Mallonee', + 'Glunt', + 'Moudy', + 'Hickam', + 'Mahmud', + 'Fate', + 'Hemsley', + 'Biery', + 'Buechner', + 'Fragale', + 'Hornbaker', + 'Lacorte', + 'Mateos', + 'Mickley', + 'Reusch', + 'Sabado', + 'Schnurr', + 'Gasior', + 'Konkle', + 'Okazaki', + 'Doubleday', + 'Couvillion', + 'Lupien', + 'Oder', + 'Ohair', + 'Win', + 'Quaintance', + 'Diltz', + 'Poythress', + 'Percell', + 'Weatherall', + 'Ainslie', + 'Brandner', + 'Byrge', + 'Cawood', + 'Heatwole', + 'Kerschner', + 'Looker', + 'Racz', + 'Skirvin', + 'Steitz', + 'Svenson', + 'Vermette', + 'Zupancic', + 'Monnier', + 'Scafidi', + 'Trousdale', + 'Bares', + 'Costantini', + 'Frees', + 'Kallio', + 'Methvin', + 'Prudencio', + 'Hayse', + 'Mahabir', + 'Wafford', + 'Borgmann', + 'Cogley', + 'Gigante', + 'Kurkowski', + 'Lavoy', + 'Wertheimer', + 'Wienke', + 'Goodling', + 'Danek', + 'Brinley', + 'Charlson', + 'Whitsell', + 'Lowen', + 'Minnix', + 'Lowers', + 'Palin', + 'Burgher', + 'Lorick', + 'Sobers', + 'Gavigan', + 'Italiano', + 'Liebl', + 'Prevette', + 'Wehunt', + 'Radin', + 'Guillotte', + 'Mode', + 'Halfacre', + 'Stjames', + 'Isabelle', + 'Meggs', + 'Burkard', + 'Giannotti', + 'Justo', + 'Kasprzyk', + 'Kuba', + 'Mino', + 'Morganti', + 'Schnelle', + 'Serfass', + 'Yacoub', + 'Thode', + 'Wykoff', + 'Macbeth', + 'Oxner', + 'Mayhue', + 'Saulter', + 'Budnik', + 'Gandarilla', + 'Michalec', + 'Eisel', + 'Newmark', + 'Placido', + 'Bellar', + 'Dollarhide', + 'Huett', + 'Copher', + 'Lacaze', + 'Dominic', + 'Bibler', + 'Boydstun', + 'Faas', + 'Grana', + 'Guardino', + 'Illig', + 'Luebbert', + 'Lyford', + 'Mcgettigan', + 'Repko', + 'Widmann', + 'Trevathan', + 'Ewan', + 'Mcray', + 'Footman', + 'Kerchner', + 'Leggio', + 'Bullinger', + 'Rushford', + 'Edel', + 'Leandro', + 'Burkman', + 'Grattan', + 'Tench', + 'Dartez', + 'Lemar', + 'Fane', + 'Zenon', + 'Sabb', + 'Blatchford', + 'Chilcoat', + 'Hahne', + 'Hanssen', + 'Mawhinney', + 'Pflueger', + 'Pol', + 'Vitelli', + 'Brierley', + 'Zundel', + 'Mcgillicuddy', + 'Adriano', + 'Mate', + 'Wilkison', + 'Ramnarine', + 'Peaks', + 'Bacote', + 'Barretto', + 'Benevento', + 'Gubler', + 'Koelsch', + 'Naas', + 'Patane', + 'Schnitzler', + 'Sprenkle', + 'Ulbrich', + 'Violante', + 'Rench', + 'Najarro', + 'Kristensen', + 'Poma', + 'Sara', + 'Jerrell', + 'Sarratt', + 'Mondy', + 'Antill', + 'Belleville', + 'Dworkin', + 'Holdaway', + 'Lenderman', + 'Murga', + 'Reiling', + 'Stasko', + 'Topel', + 'Verity', + 'Vinas', + 'Ziebarth', + 'Vanguilder', + 'Stoots', + 'Yantis', + 'Faries', + 'Tulley', + 'Baucum', + 'Fugett', + 'Harring', + 'Semien', + 'Dauphinais', + 'Furukawa', + 'Grilli', + 'Ohanian', + 'Ormiston', + 'Osegueda', + 'Wiegert', + 'Zier', + 'Chiesa', + 'Radecki', + 'Mongeon', + 'Stake', + 'Sweetland', + 'Shearon', + 'Lamore', + 'Mccuiston', + 'Minson', + 'Burditt', + 'Mcferren', + 'Covin', + 'Straker', + 'Elzy', + 'Althaus', + 'Anzures', + 'Glaeser', + 'Huseby', + 'Nitta', + 'Ribaudo', + 'Sobota', + 'Spieker', + 'Stefaniak', + 'Valois', + 'Vanwie', + 'Venturini', + 'Beltre', + 'Ewer', + 'Hartt', + 'Keaney', + 'Throne', + 'Edrington', + 'Inmon', + 'Isabel', + 'Brayman', + 'Devilbiss', + 'Krasner', + 'Malak', + 'Tito', + 'Vermeer', + 'Benigno', + 'Bosque', + 'Berridge', + 'Clines', + 'Brite', + 'Mcbeath', + 'Gleaves', + 'Koenen', + 'Kubicki', + 'Kudla', + 'Seiple', + 'Warkentin', + 'Choiniere', + 'Nassif', + 'Banko', + 'Muncie', + 'Garling', + 'Causby', + 'Mcgaw', + 'Burkeen', + 'Balan', + 'Georgia', + 'Hick', + 'Tumblin', + 'Badon', + 'Warrior', + 'Yearby', + 'Hiestand', + 'Hughart', + 'Proffer', + 'Sult', + 'Yepes', + 'Zachman', + 'Beddow', + 'Molyneux', + 'Camejo', + 'Stephany', + 'Cadogan', + 'Gosha', + 'Goodwine', + 'Harewood', + 'Burnsed', + 'Frappier', + 'Minardi', + 'Rieser', + 'Tabbert', + 'Marietta', + 'Butch', + 'Steil', + 'Canal', + 'Brundige', + 'Comas', + 'Hopkinson', + 'Shomo', + 'Kendle', + 'Bowsher', + 'Illingworth', + 'Kampa', + 'Manasco', + 'Mcdorman', + 'Theurer', + 'Widger', + 'Carbonneau', + 'Stachura', + 'Eriksson', + 'Trostle', + 'Foxworthy', + 'Lex', + 'Belman', + 'Isola', + 'Mckane', + 'Gearing', + 'Rimes', + 'Couillard', + 'Emanuele', + 'Pho', + 'Scimeca', + 'Skaar', + 'Vibbert', + 'Bilby', + 'Hink', + 'Gohn', + 'Nguy', + 'Perrett', + 'Bowland', + 'Comes', + 'Moffet', + 'Pauline', + 'Donalson', + 'Tilman', + 'Hansberry', + 'Acedo', + 'Camarda', + 'Devivo', + 'Eurich', + 'Jojola', + 'Railsback', + 'Rumfelt', + 'Stastny', + 'Strittmatter', + 'Houseknecht', + 'Rynearson', + 'Weinrich', + 'Kinghorn', + 'Astin', + 'Aguillard', + 'Hameed', + 'Drone', + 'Lonon', + 'Burgio', + 'Klimas', + 'Riegler', + 'Schiano', + 'Slonaker', + 'Deery', + 'Weissinger', + 'Cea', + 'Grenz', + 'Arent', + 'Sopher', + 'Jarratt', + 'Mitchener', + 'Conigliaro', + 'Dohm', + 'Feenstra', + 'Meiers', + 'Hetland', + 'Kinsinger', + 'Kmiec', + 'Teich', + 'Fukushima', + 'Kerins', + 'Cienfuegos', + 'Orlandi', + 'Bonser', + 'Okun', + 'Coate', + 'Rittenberry', + 'Mcclaine', + 'Dunklin', + 'Citizen', + 'Danzy', + 'Geers', + 'Georgeson', + 'Kikuchi', + 'Macinnis', + 'Malizia', + 'Mukai', + 'Plants', + 'Ehmann', + 'Haren', + 'Lachney', + 'Duchesne', + 'Collinson', + 'Connett', + 'Hostler', + 'Farnell', + 'Osler', + 'Triche', + 'Ballweg', + 'Bansal', + 'Galo', + 'Hollabaugh', + 'Hultquist', + 'Mcbrien', + 'Pelz', + 'Picciano', + 'Tashjian', + 'Thresher', + 'Uphoff', + 'Shawley', + 'Tomasek', + 'Aldaz', + 'Harig', + 'Kullman', + 'Vaness', + 'Isabella', + 'Munley', + 'Bissette', + 'Thackston', + 'Borgia', + 'Camire', + 'Charters', + 'Feiler', + 'Geisinger', + 'Racca', + 'Rasmusson', + 'Stonesifer', + 'Vidmar', + 'Arciga', + 'Bialek', + 'Baruch', + 'Kornfeld', + 'Harmeyer', + 'Picon', + 'Suppa', + 'Strate', + 'Hyre', + 'Verdon', + 'Reily', + 'Castell', + 'Foard', + 'Exner', + 'Furnari', + 'Guereca', + 'Hallgren', + 'Holsclaw', + 'Ketelsen', + 'Magnani', + 'Mehling', + 'Naser', + 'Seder', + 'Sparr', + 'Strnad', + 'Tatar', + 'Crecelius', + 'Knicely', + 'Vantassell', + 'Balsley', + 'Babbs', + 'Gowans', + 'Mcclam', + 'Batdorf', + 'Belsky', + 'Gull', + 'Letizia', + 'Ludlum', + 'Mascari', + 'Scheffel', + 'Spurgin', + 'Dignan', + 'Steffensen', + 'Freeberg', + 'Honan', + 'Hamric', + 'Woolman', + 'Valeri', + 'Saab', + 'Boyers', + 'Pardon', + 'Deasy', + 'Forshey', + 'Juntunen', + 'Kamel', + 'Macisaac', + 'Marinaro', + 'Milroy', + 'Parillo', + 'Rappold', + 'Schippers', + 'Smola', + 'Staniszewski', + 'Strasburg', + 'Epple', + 'Dewitte', + 'Hubley', + 'Queener', + 'Stoddart', + 'Briant', + 'Mcclurkin', + 'Binkowski', + 'Eberts', + 'Kilbane', + 'Kiraly', + 'Monsalve', + 'Othman', + 'Pasek', + 'Rinke', + 'Steinbrecher', + 'Trees', + 'Winther', + 'Boal', + 'Eber', + 'Funez', + 'Harryman', + 'Boyter', + 'Rill', + 'Jolliffe', + 'Dorian', + 'Demore', + 'Sebree', + 'Jeff', + 'Jolivette', + 'Elko', + 'Jividen', + 'Lenzen', + 'Marsee', + 'Milbrandt', + 'Orihuela', + 'Osterhoudt', + 'Parras', + 'Schnepp', + 'Tenaglia', + 'Thoren', + 'Diosdado', + 'Pingree', + 'Rutigliano', + 'Filbert', + 'Babel', + 'Stollings', + 'Hopes', + 'Bynes', + 'Brockmann', + 'Carta', + 'Deleeuw', + 'Demo', + 'Margeson', + 'Mckitrick', + 'Reyez', + 'Sidor', + 'Strehlow', + 'Timlin', + 'Wegrzyn', + 'Burgdorf', + 'Benzing', + 'Bonneville', + 'Clonts', + 'Camps', + 'Graydon', + 'Pasha', + 'Andreoli', + 'Cockerill', + 'Covino', + 'Hajjar', + 'Korpi', + 'Pohlmann', + 'Wente', + 'Wickwire', + 'Schaber', + 'Vonderhaar', + 'Manser', + 'Fitton', + 'Galindez', + 'Ares', + 'Longmore', + 'Buchert', + 'Delisi', + 'Gaulin', + 'Genco', + 'Helgerson', + 'Khawaja', + 'Radosevich', + 'Sannicolas', + 'Sterk', + 'Theberge', + 'Voiles', + 'Warchol', + 'Potthoff', + 'Runkel', + 'Stachowski', + 'Snay', + 'Share', + 'Conkey', + 'Pontes', + 'Mathies', + 'Brittian', + 'Allgeier', + 'Daughenbaugh', + 'Glock', + 'Meisinger', + 'Pantaleo', + 'Saitta', + 'Weick', + 'Burak', + 'Borda', + 'Rim', + 'Bunyard', + 'Neaves', + 'Mcilvaine', + 'Zee', + 'Buskey', + 'Roseborough', + 'Bellin', + 'Fasulo', + 'Grab', + 'Jia', + 'Knab', + 'Skalski', + 'Stensland', + 'Zajicek', + 'Echeverry', + 'Kolenda', + 'Cadden', + 'Delawder', + 'Propp', + 'Scheeler', + 'Clukey', + 'Loven', + 'Bogen', + 'Whittingham', + 'Barcelona', + 'Braasch', + 'Haubrich', + 'Kolberg', + 'Vendetti', + 'Sheesley', + 'Bartoli', + 'Knierim', + 'Amparo', + 'Lauth', + 'Rosero', + 'Burry', + 'Guynes', + 'Cumbo', + 'Pridgeon', + 'Aarons', + 'Alarid', + 'Arakawa', + 'Benzel', + 'Bywater', + 'Grosch', + 'Heth', + 'Logiudice', + 'Maisel', + 'Morquecho', + 'Wahlberg', + 'Teigen', + 'Bockelman', + 'Rehak', + 'Bitler', + 'Brion', + 'Niece', + 'Selvey', + 'Sudderth', + 'Ruddock', + 'Sandiford', + 'Aguas', + 'Folan', + 'Herwig', + 'Krupinski', + 'Mccarrick', + 'Mudgett', + 'Pancake', + 'Redner', + 'Wentzell', + 'Soliday', + 'Marschall', + 'Krakowski', + 'Rebholz', + 'Dold', + 'Giller', + 'Gassett', + 'Brazzell', + 'Bellow', + 'Tolen', + 'Gloster', + 'Gagliardo', + 'Harbuck', + 'Lorber', + 'Natarajan', + 'Sarna', + 'Schrack', + 'Vena', + 'Witzke', + 'Minassian', + 'Loi', + 'Rogue', + 'Trace', + 'Bomba', + 'Cozzens', + 'Evett', + 'Boze', + 'Petros', + 'Cotta', + 'Eisenmann', + 'Florea', + 'Hammersley', + 'Keohane', + 'Necessary', + 'Nodine', + 'Pekarek', + 'Sjogren', + 'Ruybal', + 'Arabie', + 'Huntsinger', + 'Eiseman', + 'Mehler', + 'Craner', + 'Vandine', + 'Gaffey', + 'Menna', + 'Royle', + 'Cordrey', + 'Gala', + 'Gauss', + 'Dacruz', + 'Cardell', + 'Devan', + 'Calmes', + 'Humber', + 'Stoute', + 'Balko', + 'Cera', + 'Griesbach', + 'Kissick', + 'Kloos', + 'Oertel', + 'Sedlock', + 'Stellato', + 'Tuite', + 'Bero', + 'Rinard', + 'Dambra', + 'Cinelli', + 'Tea', + 'Hicken', + 'Linch', + 'Dials', + 'Bennefield', + 'Hillsman', + 'Flemister', + 'Alvaro', + 'Goranson', + 'Henk', + 'Ryden', + 'Verhagen', + 'Wessling', + 'Willetts', + 'Neidlinger', + 'Pereida', + 'Lainhart', + 'Nemes', + 'Rudzinski', + 'Sward', + 'Rom', + 'Rosko', + 'Runions', + 'Henney', + 'Ridgely', + 'Tomson', + 'Arballo', + 'Bohorquez', + 'Brixey', + 'Durling', + 'Espina', + 'Esquivias', + 'Nungaray', + 'Ovando', + 'Zapf', + 'Pizza', + 'Arel', + 'Ballin', + 'Heathman', + 'Morison', + 'Troop', + 'Monfort', + 'Copland', + 'Harriott', + 'Mcwhite', + 'Amini', + 'Cirilo', + 'Gassner', + 'Gulbranson', + 'Kovatch', + 'Venne', + 'Terriquez', + 'Savin', + 'Amo', + 'Moris', + 'Crable', + 'Delaughter', + 'Greenhouse', + 'Eckardt', + 'Hendrixson', + 'Manansala', + 'Mongeau', + 'Panko', + 'Pichette', + 'Sliwa', + 'Tabak', + 'Determan', + 'Freeburg', + 'Portell', + 'Steller', + 'Buffkin', + 'Righter', + 'Mcguinn', + 'Corrie', + 'Tatham', + 'Smelley', + 'Terrel', + 'Selmon', + 'Blecha', + 'Eisler', + 'Engelking', + 'Goen', + 'Krey', + 'Mceldowney', + 'Plamondon', + 'Slovak', + 'Sorce', + 'Spagnolo', + 'Wambold', + 'Colborn', + 'Englander', + 'Monsour', + 'Pait', + 'Perricone', + 'Loveridge', + 'Cragg', + 'Dies', + 'Holsten', + 'Dagley', + 'Beverley', + 'Bayona', + 'Cam', + 'Chock', + 'Coppersmith', + 'Donath', + 'Guillemette', + 'Iannelli', + 'Potratz', + 'Selander', + 'Suk', + 'Waldvogel', + 'Olberding', + 'Giaimo', + 'Spoto', + 'Crocco', + 'Waskiewicz', + 'Krizan', + 'Vigo', + 'Boarman', + 'Ron', + 'Facer', + 'Garlow', + 'Filsaime', + 'Andersson', + 'Demski', + 'Derouin', + 'Diegel', + 'Feria', + 'Foth', + 'Hertzberg', + 'Jillson', + 'Kram', + 'Mammen', + 'Melhorn', + 'Monjaras', + 'Oslund', + 'Petrin', + 'Pinho', + 'Scheerer', + 'Shadden', + 'Sitzman', + 'Stumbaugh', + 'Wengert', + 'Gershon', + 'Mcelhinney', + 'Batterson', + 'Macqueen', + 'Janas', + 'Gladson', + 'Aull', + 'Wasinger', + 'Shemwell', + 'Seats', + 'Colas', + 'Allbee', + 'Fithian', + 'Fonner', + 'Gergen', + 'Lubrano', + 'Mannarino', + 'Piscopo', + 'Sydow', + 'Werle', + 'Aumiller', + 'Coplen', + 'Dardar', + 'Morrisette', + 'Mchaney', + 'Simes', + 'Gillison', + 'Emmel', + 'Klunk', + 'Luber', + 'Madeira', + 'Schlicht', + 'Tremain', + 'Cleaveland', + 'Boulet', + 'Golladay', + 'Enck', + 'Fera', + 'Hammar', + 'Hebner', + 'Ishee', + 'Nanni', + 'Palomar', + 'Pangborn', + 'Rogala', + 'Rushlow', + 'Wiedman', + 'Laber', + 'Schoenfelder', + 'Sonner', + 'Duffer', + 'Granier', + 'Sawin', + 'Dwiggins', + 'Jaso', + 'Popplewell', + 'Loren', + 'Ord', + 'Dearmon', + 'Hammen', + 'Misra', + 'Reindl', + 'Siordia', + 'Woodhead', + 'Yasuda', + 'Dockstader', + 'Kobs', + 'Tokarski', + 'Villers', + 'Mase', + 'Arrant', + 'Hedgpeth', + 'Eggleton', + 'Frederic', + 'Victorian', + 'Akerman', + 'Balazs', + 'Brandau', + 'Depietro', + 'Dillenbeck', + 'Goodnow', + 'Larner', + 'Mcmurtrie', + 'Salameh', + 'Swicegood', + 'Koshy', + 'Stdenis', + 'Deakin', + 'Izzi', + 'Teater', + 'Gramm', + 'Doig', + 'Blacklock', + 'Haymore', + 'Heggie', + 'Kirklin', + 'Kassa', + 'Ryles', + 'Tenner', + 'Ndiaye', + 'Burrola', + 'Faires', + 'Grega', + 'Krentz', + 'Needles', + 'Portz', + 'Ruedas', + 'Sitko', + 'Viernes', + 'Setter', + 'Tricarico', + 'Prest', + 'Olivar', + 'Whitsitt', + 'Labossiere', + 'Bellomo', + 'Burgeson', + 'Capriotti', + 'Drinnon', + 'Gulati', + 'Haffey', + 'Lasota', + 'Laughery', + 'Mees', + 'Melander', + 'Paoletti', + 'Petermann', + 'Zerby', + 'Burhans', + 'Lasseigne', + 'Vannote', + 'Wai', + 'Berson', + 'Gritton', + 'Searl', + 'Toller', + 'Brackeen', + 'Screws', + 'Hagens', + 'Billingslea', + 'Hyppolite', + 'Asmussen', + 'Bitton', + 'Diiorio', + 'Grigoryan', + 'Hauenstein', + 'Krukowski', + 'Mulcahey', + 'Perras', + 'Prak', + 'Reitzel', + 'Spackman', + 'Valenciano', + 'Wieck', + 'Yeagley', + 'Zanetti', + 'Goeller', + 'Azizi', + 'Grise', + 'Mogan', + 'Traverso', + 'Nangle', + 'Saladin', + 'Hardgrove', + 'Osei', + 'Fehrenbach', + 'Giesbrecht', + 'Halas', + 'Hetzler', + 'Orsak', + 'Salaz', + 'Surace', + 'Whipp', + 'Charlebois', + 'Stayer', + 'Stelmach', + 'Hitchings', + 'Senters', + 'Mcnaught', + 'Cordier', + 'Dawsey', + 'Barhorst', + 'Clauser', + 'Dibernardo', + 'Hawkey', + 'Hritz', + 'Patchin', + 'Raatz', + 'Seubert', + 'Slingerland', + 'Vanderwoude', + 'Aquilino', + 'Goertzen', + 'Navratil', + 'Mccuistion', + 'Vallin', + 'Moors', + 'Connely', + 'Fedrick', + 'Bontempo', + 'Dishong', + 'Felch', + 'Laino', + 'Minshall', + 'Montroy', + 'Plotts', + 'Radice', + 'Sachse', + 'Safran', + 'Schecter', + 'Traut', + 'Vasile', + 'Yadon', + 'Gorka', + 'Roelofs', + 'Suit', + 'Asbill', + 'Torrens', + 'Kimmey', + 'Ruger', + 'Vinzant', + 'Watkin', + 'Rawles', + 'Cubero', + 'Duch', + 'Endress', + 'Fangman', + 'Holben', + 'Holzapfel', + 'Karner', + 'Otteson', + 'Stangel', + 'Terrebonne', + 'Wagley', + 'Wisecup', + 'Bengston', + 'Leck', + 'Coalson', + 'Farooq', + 'Safi', + 'Smyers', + 'All', + 'Else', + 'Wason', + 'Nairn', + 'Panton', + 'Ahrendt', + 'Arvizo', + 'Klahn', + 'Robak', + 'Schier', + 'Start', + 'Tiano', + 'Kraatz', + 'Corzo', + 'Maranto', + 'Elm', + 'Eagles', + 'Acres', + 'Schoolfield', + 'Ancrum', + 'Ahner', + 'Augsburger', + 'Berna', + 'Danh', + 'Fruth', + 'Galluzzo', + 'Racette', + 'Selva', + 'Szekely', + 'Zirbel', + 'Hauff', + 'Markgraf', + 'Wonderly', + 'Rydell', + 'Julia', + 'Chris', + 'Simson', + 'Bridgeford', + 'Jeffress', + 'Brailsford', + 'Bluford', + 'Boser', + 'Fichera', + 'Meininger', + 'Meyerhoff', + 'Modzelewski', + 'Niese', + 'Pavlovich', + 'Radovich', + 'Ratz', + 'Frankowski', + 'Berti', + 'Geno', + 'Fares', + 'Marney', + 'Harwick', + 'Tata', + 'Bobby', + 'Dobbin', + 'Roosevelt', + 'Greenaway', + 'Janvier', + 'Oatis', + 'Beilke', + 'Brelsford', + 'Dowty', + 'Giudice', + 'Hetzer', + 'Imboden', + 'Irelan', + 'Nie', + 'Ramberg', + 'Rega', + 'Sproat', + 'Sytsma', + 'Unrein', + 'Davignon', + 'Ganoe', + 'Leinweber', + 'Mantell', + 'Troisi', + 'Sahr', + 'Esperanza', + 'Asper', + 'Lathem', + 'Eagleton', + 'Lamons', + 'Gaulden', + 'Bloodgood', + 'Cerone', + 'Claro', + 'Durfey', + 'Enamorado', + 'Herrada', + 'Maw', + 'Schlagel', + 'Signor', + 'Reisch', + 'Gruenwald', + 'Helbert', + 'Lorenzi', + 'Woodlief', + 'Huval', + 'Batman', + 'Meadow', + 'Croswell', + 'Bordonaro', + 'Earnshaw', + 'Freiburger', + 'Gunnoe', + 'Lamberton', + 'Martella', + 'Mischke', + 'Shelor', + 'Venuti', + 'Bilek', + 'Mcmains', + 'Balding', + 'Mestre', + 'Mcconnaughey', + 'Manso', + 'Decoste', + 'Egerton', + 'Alvino', + 'Arizpe', + 'Blaschke', + 'Foglesong', + 'Heyn', + 'Irigoyen', + 'Komorowski', + 'Lesinski', + 'Nghiem', + 'Rund', + 'Santiesteban', + 'Strahm', + 'Hendel', + 'Capes', + 'Carls', + 'Bon', + 'Sires', + 'Nichelson', + 'Brimm', + 'Aikins', + 'Berra', + 'Brazee', + 'Burkert', + 'Capalbo', + 'Criscione', + 'Feddersen', + 'Hofbauer', + 'Jacobowitz', + 'Mackowiak', + 'Mcenroe', + 'Philbeck', + 'Shimada', + 'Ticknor', + 'Wozny', + 'Biernacki', + 'Hirschi', + 'Polich', + 'Sokoloski', + 'Dolores', + 'Knoch', + 'Ge', + 'Groome', + 'Markell', + 'Fearing', + 'Mcclaren', + 'Hadsell', + 'Rumple', + 'Samudio', + 'Scardina', + 'Spinosa', + 'Abramov', + 'Siracusa', + 'Goren', + 'Rocchio', + 'Bibi', + 'Lamer', + 'Liddy', + 'Anna', + 'Coxe', + 'De', + 'Rodes', + 'Cheshier', + 'Coulon', + 'Closs', + 'Tigue', + 'Seville', + 'Hopkin', + 'Rodwell', + 'Bibbins', + 'Baldree', + 'Bawden', + 'Bishoff', + 'Costabile', + 'Dec', + 'Hillegass', + 'Infantino', + 'Mantia', + 'Mcamis', + 'Northcott', + 'Ruprecht', + 'Sanpedro', + 'Campione', + 'Muchow', + 'Ostby', + 'Mohl', + 'Pulice', + 'Vigna', + 'Thomann', + 'Lillibridge', + 'Manville', + 'Vives', + 'Bellanger', + 'Desormeaux', + 'Lovingood', + 'Stjulien', + 'Echeverri', + 'Florey', + 'Gieseke', + 'Maeder', + 'Marcinko', + 'Nuncio', + 'Quirino', + 'Versteeg', + 'Voelkel', + 'Wanless', + 'Morocho', + 'Monteagudo', + 'Aikin', + 'Bramley', + 'Bartleson', + 'Skeete', + 'Batra', + 'Dolloff', + 'Gehr', + 'Hellyer', + 'Hersch', + 'Hier', + 'Lannan', + 'Reffitt', + 'Carboni', + 'Schouten', + 'Burkle', + 'Riches', + 'Busa', + 'Rademaker', + 'Hult', + 'Synder', + 'Bossard', + 'Tunis', + 'Pamplin', + 'Oats', + 'Mcphaul', + 'Baik', + 'Kieser', + 'Pareja', + 'Raffaele', + 'Erhard', + 'Iwasaki', + 'Tonelli', + 'Mabey', + 'Debruyn', + 'Carrel', + 'Myron', + 'Arai', + 'Vallo', + 'Points', + 'Buteau', + 'Becknell', + 'Lue', + 'Antos', + 'Folkers', + 'Galletta', + 'Hissong', + 'Knoche', + 'Kundert', + 'Larussa', + 'Lobos', + 'Poitra', + 'Rinn', + 'Seamons', + 'Senko', + 'Villaverde', + 'Weatherholt', + 'Maliszewski', + 'Jurkowski', + 'Scism', + 'Hallas', + 'Collet', + 'Capello', + 'Lena', + 'Popper', + 'Aikman', + 'Blakes', + 'Cadigan', + 'Dupler', + 'Kazi', + 'Masri', + 'Matejka', + 'Mcgirr', + 'Pistone', + 'Prenger', + 'Ranes', + 'Thiemann', + 'Voeller', + 'Cockman', + 'Burtt', + 'Looby', + 'Bonnie', + 'Mcclenny', + 'Ridgell', + 'Nails', + 'Lesane', + 'Bertolino', + 'Doheny', + 'Fechter', + 'Holshouser', + 'Kierstead', + 'Krewson', + 'Lanahan', + 'Vig', + 'Wiswell', + 'Freytag', + 'Haselden', + 'Kuras', + 'Navar', + 'Raisor', + 'Finamore', + 'Kipper', + 'Morissette', + 'Laughton', + 'Awe', + 'Manier', + 'Cumby', + 'Cabada', + 'Hafen', + 'Kojima', + 'Massari', + 'Mctague', + 'Stehr', + 'Vandevelde', + 'Voong', + 'Wisely', + 'Girardin', + 'Bies', + 'Demaris', + 'Galles', + 'Goldstone', + 'Kai', + 'Cord', + 'Brigance', + 'Gomillion', + 'Drakes', + 'Bartkowiak', + 'Chica', + 'Draheim', + 'Honeyman', + 'Klapper', + 'Kniffen', + 'Knoblock', + 'Scherzer', + 'Tougas', + 'Toyama', + 'Urbach', + 'Walia', + 'Wattenbarger', + 'Marz', + 'Cesare', + 'Miro', + 'Kervin', + 'Godard', + 'Beiter', + 'Betcher', + 'Evarts', + 'Evensen', + 'Gaff', + 'Griffitts', + 'Grunden', + 'Hoffart', + 'Kroupa', + 'Maiers', + 'Mckendry', + 'Puett', + 'Shoe', + 'Stermer', + 'Wineinger', + 'Brocious', + 'Chudy', + 'Spofford', + 'Wessinger', + 'Weich', + 'Croff', + 'Ephraim', + 'Sallis', + 'Blasco', + 'Burningham', + 'Buschmann', + 'Forget', + 'Kulak', + 'Panozzo', + 'Pierpont', + 'Priolo', + 'Puhl', + 'Ruffolo', + 'Voisine', + 'Mancinelli', + 'Santacroce', + 'Vanvalkenburgh', + 'Veverka', + 'Desena', + 'Agner', + 'Boron', + 'Wheeling', + 'Plato', + 'Tonge', + 'Deibel', + 'Herriman', + 'Holroyd', + 'Huitron', + 'Hum', + 'Kreamer', + 'Lada', + 'Lucena', + 'Pao', + 'Planck', + 'Vanroekel', + 'Bodell', + 'Francia', + 'Anastasia', + 'Haxton', + 'Maile', + 'Warning', + 'Labeau', + 'Pujol', + 'Done', + 'Minney', + 'Hogsett', + 'Tayler', + 'Delancy', + 'Philson', + 'Allemand', + 'Buhrman', + 'Diefenbach', + 'Gawel', + 'Kovacic', + 'Kralik', + 'Lazor', + 'Mcnemar', + 'Warth', + 'Glanzer', + 'Keep', + 'Hochstein', + 'Febles', + 'Morneau', + 'Agostinelli', + 'Galeas', + 'Landen', + 'Lion', + 'Attwood', + 'Capshaw', + 'Willy', + 'Dekle', + 'Murrill', + 'Coby', + 'Falvo', + 'Kanagy', + 'Mihalko', + 'Schellenberg', + 'Sugimoto', + 'Lippard', + 'Sardo', + 'Suckow', + 'Demichele', + 'Kath', + 'Lappe', + 'Lego', + 'Schleifer', + 'Vold', + 'Kingsland', + 'Mitch', + 'Manlove', + 'Cuozzo', + 'Dauber', + 'Deininger', + 'Goldbach', + 'Halfmann', + 'Kazarian', + 'Marksberry', + 'Marzec', + 'Mcmurphy', + 'Oregan', + 'Paczkowski', + 'Pinsky', + 'Poynor', + 'Schertz', + 'Tetrick', + 'Umali', + 'Valenza', + 'Witherington', + 'Kesselring', + 'Nylund', + 'Cinnamon', + 'Rielly', + 'Surman', + 'Fowle', + 'Hains', + 'Sharlow', + 'Lones', + 'Durgan', + 'Savory', + 'Minger', + 'Okon', + 'Berends', + 'Binning', + 'Malina', + 'Loeser', + 'Marthaler', + 'Pacella', + 'Vasta', + 'Hinerman', + 'Goodchild', + 'Chuck', + 'Linney', + 'Beckworth', + 'Carrie', + 'Lovings', + 'Ginyard', + 'Bredeson', + 'Debiase', + 'Gorder', + 'Noce', + 'Redlin', + 'Schwinn', + 'Zins', + 'Burtner', + 'Kosakowski', + 'Erler', + 'Altom', + 'Husman', + 'Markos', + 'Thorman', + 'Fagen', + 'Voisin', + 'Gauldin', + 'Pressey', + 'Calbert', + 'Holness', + 'Alspach', + 'Broeker', + 'Danziger', + 'Klenke', + 'Popescu', + 'Schoenrock', + 'Schreckengost', + 'Syme', + 'Trick', + 'Plautz', + 'Beckel', + 'Dealmeida', + 'Winne', + 'Moron', + 'Seed', + 'Capozzoli', + 'Gawron', + 'Kobel', + 'Kouns', + 'Nunemaker', + 'Steinbacher', + 'Stookey', + 'Vidana', + 'Zoch', + 'Ohlinger', + 'Hudkins', + 'Ferren', + 'Gille', + 'Sheckler', + 'Kittell', + 'Roath', + 'Ziglar', + 'Brecher', + 'Coldren', + 'Degraaf', + 'Eddinger', + 'Joffe', + 'Luthy', + 'Metzinger', + 'Nayak', + 'Paule', + 'Prudente', + 'Wooddell', + 'Zuccaro', + 'Rineer', + 'Soos', + 'Manka', + 'Vandervoort', + 'Kitchell', + 'Casserly', + 'Watchman', + 'Poteete', + 'Dopson', + 'Mathurin', + 'Cataldi', + 'Crepeau', + 'Fackrell', + 'Goben', + 'Macinnes', + 'Scherf', + 'Shaddix', + 'Sorber', + 'Teichman', + 'Wydra', + 'Holzworth', + 'Baade', + 'Tinnell', + 'Tinkler', + 'Mauzy', + 'Alphonse', + 'Fullard', + 'Adger', + 'Akiyama', + 'Bloxham', + 'Coultas', + 'Esler', + 'Giebel', + 'Goswick', + 'Heikes', + 'Javed', + 'Linan', + 'Mooers', + 'Nemetz', + 'Pradhan', + 'Rainone', + 'Romito', + 'Treichel', + 'Vohs', + 'Grosskopf', + 'Weisinger', + 'Ruple', + 'Naff', + 'Meaders', + 'Lamarr', + 'Toppin', + 'Apicella', + 'Beecroft', + 'Boshears', + 'Breier', + 'Cuadros', + 'Umbarger', + 'Alioto', + 'Ravenscroft', + 'Vesper', + 'Oak', + 'Tigges', + 'Simmer', + 'Hanby', + 'Webre', + 'Lenk', + 'Mcelvain', + 'Boy', + 'Debarros', + 'Hickenbottom', + 'Quincy', + 'Billips', + 'Ollison', + 'Barbuto', + 'Clearwater', + 'Cronkhite', + 'Groleau', + 'Mehra', + 'Tessler', + 'Kegel', + 'Borenstein', + 'Newnam', + 'Crofton', + 'Phenix', + 'Dankert', + 'Hymas', + 'Lobel', + 'Marszalek', + 'Moceri', + 'Ottaviano', + 'Papazian', + 'Roedel', + 'Jochum', + 'Urquidez', + 'Lapin', + 'Garro', + 'Lamond', + 'Sessums', + 'Tooke', + 'Steadham', + 'Azam', + 'Bleier', + 'Buelna', + 'Bupp', + 'Burridge', + 'Derderian', + 'Derstine', + 'Halberg', + 'Katzer', + 'Meegan', + 'Ortmann', + 'Herschberger', + 'Sanroman', + 'Winiarski', + 'Alcon', + 'Picker', + 'Demille', + 'Huron', + 'Hankin', + 'Dahmen', + 'Fronczak', + 'Klingman', + 'Perugini', + 'Pettinato', + 'Powelson', + 'Saffer', + 'Schwenke', + 'Pals', + 'Estremera', + 'Sofia', + 'Arvelo', + 'Terrero', + 'Bankes', + 'Sais', + 'Netherland', + 'Odeh', + 'Sutphen', + 'Caddy', + 'Dorval', + 'Glaude', + 'Mcadory', + 'Eichinger', + 'Lesniewski', + 'Petito', + 'Pfohl', + 'Presler', + 'Rys', + 'Sano', + 'Willenborg', + 'Seppala', + 'Shibley', + 'Cajigas', + 'Gal', + 'Farag', + 'Pickles', + 'Rump', + 'Grills', + 'Mikes', + 'Adderley', + 'Altland', + 'Araki', + 'Beitz', + 'Brotzman', + 'Buonocore', + 'Fayard', + 'Gelber', + 'Jurewicz', + 'Lezcano', + 'Marsteller', + 'Minarik', + 'Opsahl', + 'Pranger', + 'Tiburcio', + 'Zollo', + 'Engh', + 'Henault', + 'Barrineau', + 'Pilkinton', + 'Pratte', + 'Niland', + 'Warda', + 'Southwood', + 'Clinch', + 'Halsell', + 'Mccaa', + 'Isreal', + 'Pinkett', + 'Asch', + 'Beauchesne', + 'Bruemmer', + 'Doebler', + 'Ehlinger', + 'Goelz', + 'Hashemi', + 'Karel', + 'Magiera', + 'Martorano', + 'Mooneyhan', + 'Cibrian', + 'Cavey', + 'Kosko', + 'Christo', + 'Cockrill', + 'Mansker', + 'Balls', + 'Degree', + 'Tiggs', + 'Alberico', + 'Clugston', + 'Elman', + 'Frueh', + 'Kampf', + 'Kochanski', + 'Leider', + 'Marsella', + 'Mckendree', + 'Moffa', + 'Quattrocchi', + 'Raval', + 'Snoke', + 'Akopyan', + 'Barrilleaux', + 'Cambria', + 'Kawaguchi', + 'Bonde', + 'Dawdy', + 'Willig', + 'Kazee', + 'Debow', + 'Beachum', + 'Vicks', + 'Aurelio', + 'Barocio', + 'Bonesteel', + 'Ezzo', + 'Gesell', + 'Krzeminski', + 'Madan', + 'Magda', + 'Manring', + 'Mcfaul', + 'Morera', + 'Purinton', + 'Retzer', + 'Schonfeld', + 'Staszak', + 'Stubbe', + 'Talerico', + 'Wikoff', + 'Zia', + 'Seyfried', + 'Diangelo', + 'Keach', + 'Shipton', + 'Shewmake', + 'Behrmann', + 'Hopps', + 'Paster', + 'Augenstein', + 'Castaldi', + 'Ferrufino', + 'Gregersen', + 'Hosseini', + 'Keniston', + 'Nadolski', + 'Ouimette', + 'Pellett', + 'Riebel', + 'Schwark', + 'Spelman', + 'Tesar', + 'Yahn', + 'Grossnickle', + 'Rosillo', + 'Dostie', + 'Noa', + 'Khalaf', + 'Cardosa', + 'Afzal', + 'Mercure', + 'Wheless', + 'Tailor', + 'Mcgarrah', + 'Miler', + 'Norfolk', + 'Crapps', + 'Dansereau', + 'Jenney', + 'Keast', + 'Lieser', + 'Mihm', + 'Porco', + 'Zelinsky', + 'Sleeth', + 'Mcelreath', + 'Hemann', + 'Capaldi', + 'Huggett', + 'Reagle', + 'Mayotte', + 'Liller', + 'Leen', + 'Demmer', + 'Tunison', + 'Woodbridge', + 'Haymes', + 'Cunning', + 'Blaze', + 'Eatman', + 'Ulysse', + 'Bagshaw', + 'Buczkowski', + 'Cardello', + 'Decola', + 'Diloreto', + 'Evola', + 'Glassburn', + 'Hazelbaker', + 'Holycross', + 'Minasian', + 'Regula', + 'Ruge', + 'Uhlman', + 'Lamprecht', + 'Shifflet', + 'Weikle', + 'Coupe', + 'Isherwood', + 'Dimon', + 'Pop', + 'Willhoite', + 'Bari', + 'Boise', + 'Doom', + 'Mccolley', + 'Bircher', + 'Wannamaker', + 'Eppes', + 'Pea', + 'Okeke', + 'Alpizar', + 'Arista', + 'Barbagallo', + 'Baumert', + 'Bhattacharya', + 'Gheen', + 'Hutchcraft', + 'Karlen', + 'Klier', + 'Ladnier', + 'Marrujo', + 'Reister', + 'Rorrer', + 'Tarpey', + 'Wisecarver', + 'Beydoun', + 'Fillinger', + 'Kemnitz', + 'Takata', + 'Leight', + 'Kross', + 'Junco', + 'Holmer', + 'Sando', + 'Biddix', + 'Dawood', + 'Frisco', + 'Flagler', + 'Arntz', + 'Bache', + 'Bundrick', + 'Glasson', + 'Los', + 'Scheiber', + 'Shellenbarger', + 'Steinmeyer', + 'Sura', + 'Tanski', + 'Teodoro', + 'Vanaken', + 'Jodoin', + 'Klinker', + 'Szydlowski', + 'Yamashiro', + 'Kutch', + 'Hoth', + 'Edwardson', + 'Gess', + 'Mohamad', + 'Goodine', + 'Carolina', + 'Blauser', + 'Emerich', + 'Flook', + 'Graul', + 'Gribben', + 'Herbold', + 'Kreutz', + 'Lavey', + 'Lukacs', + 'Maiorana', + 'Openshaw', + 'Plattner', + 'Sauro', + 'Schardt', + 'Tortorici', + 'Wendlandt', + 'Danowski', + 'Mcnellis', + 'Pinkowski', + 'Linz', + 'Virga', + 'Jardin', + 'Maclaughlin', + 'Rama', + 'Deline', + 'Kimbel', + 'Hagin', + 'Pottinger', + 'Detmer', + 'Ferrone', + 'Matthiesen', + 'Melchert', + 'Ruehl', + 'Takach', + 'Briese', + 'Elmendorf', + 'Valentini', + 'Hersom', + 'Bordeau', + 'Linsley', + 'Keatts', + 'Dina', + 'Boye', + 'Riviere', + 'Stodghill', + 'Madry', + 'Angelos', + 'Bou', + 'Ketterling', + 'Niemczyk', + 'Pardini', + 'Rippel', + 'Schieffer', + 'Schnee', + 'Shogren', + 'Sholl', + 'Ullmann', + 'Ure', + 'Curless', + 'Gonnella', + 'Tholen', + 'Valladolid', + 'Silbernagel', + 'Cohrs', + 'Shahin', + 'Beth', + 'Holmen', + 'Tippie', + 'Opie', + 'Sprowl', + 'Byam', + 'Bethany', + 'Saintil', + 'Auriemma', + 'Blust', + 'Dibello', + 'Digangi', + 'Farnam', + 'Farnan', + 'Linford', + 'Mcgroarty', + 'Meisenheimer', + 'Pagels', + 'Sauber', + 'Schwalbe', + 'Seemann', + 'Slivka', + 'Twardowski', + 'Wickey', + 'Zettler', + 'Zuchowski', + 'Feldhaus', + 'Baldock', + 'Cowman', + 'Carp', + 'Camera', + 'Balon', + 'Neveu', + 'Caminiti', + 'Carreira', + 'Gura', + 'Hershkowitz', + 'Killoran', + 'Narducci', + 'Reigel', + 'Saccone', + 'Tomasi', + 'Wieneke', + 'Sibrian', + 'Hashem', + 'Kellems', + 'Stouder', + 'Villamar', + 'Piette', + 'Wand', + 'Battey', + 'Staunton', + 'Bedore', + 'Hanel', + 'Jutras', + 'Kanner', + 'Mathiesen', + 'Northway', + 'Privitera', + 'Reichelt', + 'Zucco', + 'Roys', + 'Aderholt', + 'Lampson', + 'Olen', + 'Mcgarr', + 'Schools', + 'Leaphart', + 'Lykes', + 'Brightbill', + 'Koos', + 'Lahue', + 'Laplaca', + 'Naqvi', + 'Novo', + 'Puerta', + 'Siers', + 'Strutz', + 'Trimboli', + 'Waldie', + 'Goold', + 'Falke', + 'Corter', + 'Cartmell', + 'Brazel', + 'Farabee', + 'Majeed', + 'Hilden', + 'Kealoha', + 'Neider', + 'Parodi', + 'Rizza', + 'Rong', + 'Silberstein', + 'Snellgrove', + 'Trojanowski', + 'Warneke', + 'Wissler', + 'Yiu', + 'Grein', + 'Sak', + 'Daines', + 'Monzo', + 'Emmerson', + 'Lorraine', + 'Samaroo', + 'Edmund', + 'Cacace', + 'Dornan', + 'Eyman', + 'Hovanec', + 'Jeschke', + 'Limberg', + 'Maturo', + 'Pandey', + 'Somoza', + 'Streiff', + 'Wiemer', + 'Zablocki', + 'Crace', + 'Leinen', + 'Rucci', + 'Blyth', + 'Clemans', + 'Magid', + 'Ferrick', + 'Garriga', + 'Martir', + 'Tanton', + 'Hoon', + 'Echard', + 'Borrell', + 'Howden', + 'Gravett', + 'Lando', + 'Amacher', + 'Dalman', + 'Hollenbaugh', + 'Sigrist', + 'Tamashiro', + 'Therriault', + 'Villafranca', + 'Matthys', + 'Salois', + 'Sforza', + 'Swager', + 'Borah', + 'Sentell', + 'Besson', + 'Ghani', + 'Bilinski', + 'Holzinger', + 'Kus', + 'Lobianco', + 'Morawski', + 'Perz', + 'Sada', + 'Wollenberg', + 'Yusko', + 'Caughron', + 'Diffenderfer', + 'Slowinski', + 'Skiver', + 'Galland', + 'Hodes', + 'Boyne', + 'Towry', + 'Alers', + 'Hellums', + 'Certain', + 'Megginson', + 'Creer', + 'Coutee', + 'Strothers', + 'Stfleur', + 'Barga', + 'Bina', + 'Cellini', + 'Digiulio', + 'Douma', + 'Klement', + 'Mccambridge', + 'Parmeter', + 'Presto', + 'Salmi', + 'Seabaugh', + 'Barreda', + 'Nepomuceno', + 'Zent', + 'Yonce', + 'Loreto', + 'Honer', + 'Conquest', + 'Gathings', + 'Wims', + 'Upshur', + 'Aeschliman', + 'Casaus', + 'Dumke', + 'Earlywine', + 'Ferreyra', + 'Heyne', + 'Hudon', + 'Kuder', + 'Malia', + 'Brueckner', + 'Luchsinger', + 'Ornellas', + 'Ramseyer', + 'Weidemann', + 'Walbert', + 'Zola', + 'Linquist', + 'Storts', + 'Dente', + 'Lebleu', + 'Stockham', + 'Rollinson', + 'Auzenne', + 'Abebe', + 'Bartol', + 'Cozzolino', + 'Der', + 'Fata', + 'Gorr', + 'Janousek', + 'Moschella', + 'Riedy', + 'Dust', + 'Malmgren', + 'Puterbaugh', + 'Sacchetti', + 'Lascano', + 'Begnaud', + 'Duling', + 'Porteous', + 'Debnam', + 'Abron', + 'Delehanty', + 'Fazenbaker', + 'Flener', + 'Gora', + 'Herter', + 'Johann', + 'Keiter', + 'Lucca', + 'Passman', + 'Saindon', + 'Schoppe', + 'Skibinski', + 'Stueber', + 'Tegeler', + 'Jochim', + 'Buttner', + 'Crilly', + 'Swanton', + 'Muncey', + 'Negrin', + 'Thorburn', + 'Delpino', + 'Kinn', + 'Gaiter', + 'Obi', + 'Hohensee', + 'Rollman', + 'Scheff', + 'Shor', + 'Tumbleson', + 'Mccrum', + 'Knack', + 'Llano', + 'Saber', + 'Rosman', + 'Bankson', + 'Atkisson', + 'Kennel', + 'Cammon', + 'Bangura', + 'Cichy', + 'Gillikin', + 'Hiltner', + 'Lubben', + 'Mcqueeney', + 'Nasca', + 'Nordgren', + 'Ostermann', + 'Quito', + 'Sakowski', + 'Schut', + 'Stobaugh', + 'Alessio', + 'Gorelik', + 'Heinzman', + 'Westrich', + 'Nardella', + 'Cruzado', + 'Lansberry', + 'Dubreuil', + 'Nylander', + 'Rabel', + 'Moret', + 'Crout', + 'Ardrey', + 'Rolley', + 'Finks', + 'Cliett', + 'Caito', + 'Clingenpeel', + 'Delprete', + 'Dolen', + 'Heidrich', + 'Hinrichsen', + 'Jindra', + 'Madej', + 'Panzarella', + 'Sandin', + 'Seekins', + 'Shilts', + 'Sokoloff', + 'Maggart', + 'Pigman', + 'Travieso', + 'Denbow', + 'Dollison', + 'Gaye', + 'Binette', + 'Dutta', + 'Grandinetti', + 'Kitch', + 'Tangeman', + 'Finstad', + 'Rodkey', + 'Servis', + 'Tiwari', + 'Rodd', + 'Parfait', + 'Seck', + 'Delaurentis', + 'Dragan', + 'Fleig', + 'Giacobbe', + 'Hilligoss', + 'Kroh', + 'Lippe', + 'Maleski', + 'Perini', + 'Rutten', + 'Stauss', + 'Yoshikawa', + 'Dibattista', + 'Gilsdorf', + 'Riemenschneider', + 'Streck', + 'Gessler', + 'Springstead', + 'Zaki', + 'Lambie', + 'Barczak', + 'Ellerbrock', + 'Foresman', + 'Holstine', + 'Lemm', + 'Santillana', + 'Trautwein', + 'Unsworth', + 'Valderas', + 'Vaquero', + 'Vetsch', + 'Wadleigh', + 'Yonts', + 'Mcguiness', + 'Auvil', + 'Leeder', + 'Sprowls', + 'Cala', + 'Portalatin', + 'Casso', + 'Chirinos', + 'Less', + 'Baltzell', + 'Bo', + 'Whetsell', + 'Ledlow', + 'Fullbright', + 'Arnell', + 'Stainback', + 'Mcleish', + 'Lyn', + 'Bermeo', + 'Billet', + 'Craun', + 'Gladwell', + 'Goral', + 'Herbig', + 'Kluver', + 'Mermelstein', + 'Odette', + 'Poggi', + 'Schacher', + 'Thielman', + 'Cianciolo', + 'Ferrie', + 'Kapusta', + 'Kreager', + 'Messineo', + 'Rovira', + 'Stricklen', + 'Wansley', + 'Amell', + 'Baena', + 'Depaula', + 'Fickett', + 'Housewright', + 'Kreiger', + 'Legate', + 'Lutterman', + 'Men', + 'Pautz', + 'Swecker', + 'Tantillo', + 'Dudeck', + 'Bellas', + 'Marian', + 'Bienvenu', + 'Riden', + 'Hosein', + 'Couser', + 'Batterton', + 'Desantos', + 'Dieterle', + 'Drabek', + 'Grennan', + 'Greulich', + 'Ludlam', + 'Maltos', + 'Marcin', + 'Ostertag', + 'Rednour', + 'Tippetts', + 'Updyke', + 'Ormsbee', + 'Reutter', + 'Uyehara', + 'Musumeci', + 'Antonini', + 'Thistle', + 'Marcia', + 'Renne', + 'Jines', + 'Dorothy', + 'Menter', + 'Crosser', + 'Ditommaso', + 'Glueck', + 'Malta', + 'Mcgranahan', + 'Mensing', + 'Ostroff', + 'Rota', + 'Rothfuss', + 'Borcherding', + 'Haveman', + 'Swallows', + 'Heltzel', + 'Aloi', + 'Stipp', + 'Broda', + 'Darter', + 'Gressett', + 'Brasier', + 'Lana', + 'Crooke', + 'Seegers', + 'Sirmons', + 'Berberian', + 'Goers', + 'Losch', + 'Memon', + 'Paternoster', + 'Rierson', + 'Miyake', + 'Barndt', + 'Kirstein', + 'Azua', + 'Zeck', + 'Britain', + 'Lanman', + 'Gorges', + 'Clock', + 'Alman', + 'Callicutt', + 'Walford', + 'Searight', + 'Eakle', + 'Federici', + 'Hosack', + 'Jarecki', + 'Kauffmann', + 'Maras', + 'Nisley', + 'Sandahl', + 'Shidler', + 'Wnek', + 'Moneymaker', + 'Santander', + 'Schneeberger', + 'Luviano', + 'Gorin', + 'Negus', + 'Coulston', + 'Polin', + 'Winslett', + 'Anstett', + 'Cowsert', + 'Dipiazza', + 'Fitting', + 'Forslund', + 'Poquette', + 'Tibbets', + 'Tomasini', + 'Toor', + 'Starry', + 'Venema', + 'Cedano', + 'Carro', + 'Samons', + 'Matty', + 'Ellenwood', + 'Kilcrease', + 'Noblin', + 'Decatur', + 'Heckard', + 'Nard', + 'Beighley', + 'Delamater', + 'Eblen', + 'Heninger', + 'Kehn', + 'Rotunno', + 'Uppal', + 'Hynek', + 'Zenk', + 'Brasil', + 'Mu', + 'Julio', + 'Cassar', + 'Crisco', + 'Oriley', + 'Turton', + 'Goens', + 'Cargo', + 'Toure', + 'Breitbach', + 'Cahalan', + 'Chadha', + 'Kittinger', + 'Marnell', + 'Masias', + 'Matousek', + 'Mittal', + 'Nieblas', + 'Onan', + 'Purdum', + 'Tursi', + 'Esplin', + 'Etsitty', + 'Fratto', + 'Przybyla', + 'Cassin', + 'Nitti', + 'Arshad', + 'Sandoz', + 'Walzer', + 'Everton', + 'Russum', + 'Morland', + 'Fennel', + 'Viel', + 'Jarrells', + 'Vassell', + 'Frigo', + 'Kodama', + 'Naron', + 'Oelke', + 'Remaley', + 'Shean', + 'Cloonan', + 'Clayman', + 'Lasch', + 'Lepard', + 'Rewis', + 'Vankeuren', + 'Lightbody', + 'Houseworth', + 'Caison', + 'Denmon', + 'Rauls', + 'Sallie', + 'Humphery', + 'Showell', + 'Raysor', + 'Angotti', + 'Barbero', + 'Buxbaum', + 'Capella', + 'Horsch', + 'Kunselman', + 'Nishikawa', + 'Perotti', + 'Sprung', + 'Szucs', + 'Emch', + 'Kotula', + 'Mendizabal', + 'Yeaman', + 'Beste', + 'Kader', + 'Forker', + 'Wiggers', + 'Cotham', + 'Primo', + 'Fetterhoff', + 'Giarrusso', + 'Glosser', + 'Lumbreras', + 'Rosano', + 'Strohecker', + 'Wanek', + 'Waycaster', + 'Worthley', + 'Salasar', + 'Boulos', + 'Pulsipher', + 'Scheider', + 'Lorimer', + 'Alamilla', + 'Zapp', + 'Deis', + 'Tariq', + 'Kasey', + 'Famiglietti', + 'Flansburg', + 'Georgiou', + 'Groft', + 'Heistand', + 'Merker', + 'Stoeckel', + 'Tackitt', + 'Verbeck', + 'Weyers', + 'Wiltrout', + 'Brabec', + 'Caligiuri', + 'Dudzinski', + 'Grieger', + 'Benfer', + 'Pesta', + 'Wool', + 'Sunshine', + 'Oka', + 'Stamour', + 'Barrio', + 'Mathe', + 'Vanduyne', + 'Brager', + 'Mcphatter', + 'Ahluwalia', + 'Borys', + 'Dreibelbis', + 'Kalmbach', + 'Karwoski', + 'Moomaw', + 'Youngren', + 'Offerman', + 'Nine', + 'Symington', + 'Branan', + 'Turberville', + 'Heber', + 'Loughridge', + 'Vanderberg', + 'Mccannon', + 'Linda', + 'Dupee', + 'Cottom', + 'Mcphearson', + 'Razor', + 'Buchwald', + 'Fraze', + 'Grannis', + 'Krolikowski', + 'Lapidus', + 'Madruga', + 'Mcmartin', + 'Quinlivan', + 'Riaz', + 'Spittler', + 'Zahm', + 'Zender', + 'Eisman', + 'Hourihan', + 'Shirazi', + 'Herendeen', + 'Perdew', + 'Pendell', + 'Chernoff', + 'Lyell', + 'Clarey', + 'Macken', + 'Guthridge', + 'Redditt', + 'Bedi', + 'Debenedictis', + 'Distel', + 'Gapinski', + 'Iwanski', + 'Medici', + 'Schmutz', + 'Tuel', + 'Verburg', + 'Galgano', + 'Skogen', + 'Aymond', + 'Raymo', + 'Croney', + 'Carry', + 'Rhynes', + 'Lamour', + 'Shedrick', + 'Tookes', + 'Baltierra', + 'Leitzel', + 'Letchworth', + 'Montesino', + 'Preis', + 'Sanzone', + 'Shantz', + 'Teo', + 'Twohig', + 'Wajda', + 'Windisch', + 'Zinck', + 'Fiero', + 'Hornby', + 'Paget', + 'Serano', + 'Rodrick', + 'Lewison', + 'Dyas', + 'Delcarmen', + 'Garske', + 'Hontz', + 'Mcquown', + 'Melling', + 'Rolando', + 'Rosencrans', + 'Steichen', + 'Teeples', + 'Forseth', + 'Quijas', + 'Schraeder', + 'Vaidya', + 'Ventre', + 'Mountjoy', + 'Morr', + 'Leviner', + 'Paulette', + 'Dobie', + 'Brue', + 'Prier', + 'Biffle', + 'Neyland', + 'Valcourt', + 'Mckeithen', + 'Lemelle', + 'Alviar', + 'Auth', + 'Bahm', + 'Bierbaum', + 'Cazier', + 'Eschbach', + 'Etzler', + 'Nowlan', + 'Sahota', + 'Vanaman', + 'Zaugg', + 'Hogeland', + 'Choat', + 'Walmer', + 'Cepero', + 'Michal', + 'Foxwell', + 'Decoursey', + 'Molyneaux', + 'Peat', + 'Jeanfrancois', + 'Arevalos', + 'Bachert', + 'Beachler', + 'Berrones', + 'Clavijo', + 'Elsen', + 'Fuhs', + 'Hooven', + 'Johannessen', + 'Klausner', + 'Masso', + 'Puzio', + 'Sekula', + 'Smyser', + 'Stepanian', + 'Barg', + 'Trueman', + 'Constante', + 'Cubas', + 'Dowers', + 'Pratts', + 'Cockburn', + 'Counce', + 'Nappier', + 'Lindon', + 'Burrowes', + 'Cokley', + 'Tillmon', + 'Bao', + 'Inks', + 'Liberato', + 'Moehring', + 'Ryker', + 'Sar', + 'Swartzendruber', + 'Torgersen', + 'Treto', + 'Tungate', + 'Ricotta', + 'Weesner', + 'Willyard', + 'Callicoat', + 'Hoque', + 'Atkison', + 'Mcwherter', + 'Dubuisson', + 'Wanzer', + 'Stradford', + 'Abruzzo', + 'Amerman', + 'Bame', + 'Bantz', + 'Bleakley', + 'Galt', + 'Hoobler', + 'Jaquith', + 'Lessman', + 'Polinski', + 'Rasche', + 'Roeber', + 'Rubright', + 'Sarnowski', + 'Signore', + 'Solum', + 'Vankampen', + 'Vath', + 'Malmquist', + 'Mittelstadt', + 'Belyea', + 'Haverty', + 'Wickett', + 'Sansing', + 'Yeatman', + 'Brocker', + 'Wonders', + 'Both', + 'Rabun', + 'Rocke', + 'Meachum', + 'Blane', + 'Lapsley', + 'Biswas', + 'Derocher', + 'Haran', + 'Hehn', + 'Keshishian', + 'Kniffin', + 'Lacina', + 'Skolnik', + 'Spiewak', + 'Wileman', + 'Eble', + 'Kraynak', + 'Wiesen', + 'Micheli', + 'Scroggin', + 'Roch', + 'Denise', + 'Altenburg', + 'Hornstein', + 'Netto', + 'Opel', + 'Passey', + 'Roeske', + 'Schrantz', + 'Abrahamsen', + 'Powless', + 'Callais', + 'Desjardin', + 'Pirro', + 'Yonkers', + 'Macallister', + 'Dady', + 'Ruskin', + 'Escott', + 'Abbot', + 'Sankar', + 'Bolar', + 'Angelucci', + 'Biegel', + 'Cirone', + 'Damewood', + 'Flett', + 'Kronenberg', + 'Ky', + 'Nagler', + 'Perlstein', + 'Saperstein', + 'Tenbrink', + 'Vana', + 'Wnuk', + 'Bonnema', + 'Schoenecker', + 'Pichler', + 'Armendarez', + 'Oiler', + 'Rouch', + 'Boas', + 'Laracuente', + 'Milbourn', + 'Summy', + 'Counter', + 'Gracie', + 'Belfield', + 'Bynoe', + 'Jalloh', + 'Blazier', + 'Bochenek', + 'Broughman', + 'Chuong', + 'Cregger', + 'Estacio', + 'Kaleta', + 'Lanctot', + 'Mish', + 'Novosel', + 'Passero', + 'Ripplinger', + 'Vitt', + 'Walborn', + 'Friscia', + 'Memmott', + 'Tripi', + 'Weinhold', + 'Honn', + 'Gianni', + 'Poch', + 'Sagar', + 'Markum', + 'Primmer', + 'Belmore', + 'Rain', + 'Bevard', + 'Skyles', + 'Farland', + 'Mccleese', + 'Teachey', + 'Moulden', + 'Antolin', + 'Augello', + 'Borrayo', + 'Effler', + 'Hornak', + 'Hosman', + 'Leingang', + 'Limbach', + 'Oregel', + 'Ritzman', + 'Rochefort', + 'Schimke', + 'Stefanelli', + 'Vien', + 'Zurn', + 'Badolato', + 'Bieri', + 'Clarkin', + 'Folino', + 'Kelchner', + 'Pote', + 'Brahm', + 'Hoop', + 'Macbride', + 'Hunting', + 'Brule', + 'Wainright', + 'Rolison', + 'Bennie', + 'Banghart', + 'Bertke', + 'Bozzo', + 'Gadomski', + 'Granberg', + 'Kostecki', + 'Lemelin', + 'Levengood', + 'Puskas', + 'Swanstrom', + 'Willcutt', + 'Deitrich', + 'Grieves', + 'Ferran', + 'Boileau', + 'Kendra', + 'Trippe', + 'Mcconnel', + 'Cara', + 'Stephans', + 'Bachus', + 'Applin', + 'Utsey', + 'Auston', + 'Arras', + 'Bencosme', + 'Berntsen', + 'Decarolis', + 'Dettloff', + 'Duerksen', + 'Pavlovic', + 'Schwantes', + 'Sjostrom', + 'Sugiyama', + 'Sulak', + 'Virani', + 'Winberg', + 'Yoshimoto', + 'Comito', + 'Pandolfo', + 'Cathers', + 'Hardisty', + 'Collom', + 'Wain', + 'Worthing', + 'Leep', + 'Simo', + 'Boom', + 'Bald', + 'Applegarth', + 'Gilbreth', + 'Griest', + 'Jobin', + 'Matsuura', + 'Misko', + 'Scerbo', + 'Scheidler', + 'Sterba', + 'Tomaino', + 'Wixson', + 'Yadao', + 'Hietpas', + 'Gruss', + 'Fors', + 'Gosse', + 'Katt', + 'Virk', + 'Quebedeaux', + 'Barkey', + 'Salam', + 'Willford', + 'Tarry', + 'Chancy', + 'Beynon', + 'Eckes', + 'Eischen', + 'Felger', + 'Kimm', + 'Labate', + 'Mehan', + 'Netzer', + 'Strosnider', + 'Trezza', + 'Vial', + 'Waugaman', + 'Zieman', + 'Ankeny', + 'Digman', + 'Farino', + 'Faro', + 'Vasconcelos', + 'Nevill', + 'Rave', + 'Sabine', + 'Hagg', + 'Weightman', + 'Berton', + 'Fipps', + 'Knapper', + 'Camel', + 'Gilkes', + 'Aldous', + 'Delucca', + 'Dicke', + 'Evitts', + 'Hachey', + 'Rinck', + 'Treese', + 'Uher', + 'Victorio', + 'Vignola', + 'Willert', + 'Baun', + 'Wever', + 'Varn', + 'Yokum', + 'Dunk', + 'Maben', + 'Arzu', + 'Guider', + 'Bonhomme', + 'Majette', + 'Crislip', + 'Gresko', + 'Luppino', + 'Posch', + 'Potenza', + 'Rial', + 'Ruderman', + 'Shaff', + 'Balboni', + 'Solheim', + 'Mey', + 'Sittig', + 'Perman', + 'Sumners', + 'Deaner', + 'Keizer', + 'Reves', + 'Glanville', + 'Menzie', + 'Mccowen', + 'Steib', + 'Portee', + 'Azad', + 'Dallaire', + 'Denno', + 'Deptula', + 'Fischman', + 'Guilbault', + 'Imperato', + 'Koehne', + 'Menning', + 'Mirelez', + 'Stanislawski', + 'Streb', + 'Sumida', + 'Wolke', + 'Kerfoot', + 'Pirie', + 'Saracino', + 'Maslanka', + 'Slominski', + 'Nienaber', + 'Serena', + 'Kamper', + 'Matheis', + 'Westin', + 'Ishman', + 'Biagi', + 'Chiou', + 'Dieckmann', + 'Frieden', + 'Huestis', + 'Presutti', + 'Ribas', + 'Siedlecki', + 'Steege', + 'Uehara', + 'Petrosyan', + 'Siebold', + 'Turi', + 'Rady', + 'Vanorman', + 'Arif', + 'Hiland', + 'Naidu', + 'Clagett', + 'Ludy', + 'Bodley', + 'Avelino', + 'Citro', + 'Cuda', + 'Derbyshire', + 'Kruszewski', + 'Kupper', + 'Mahl', + 'Muratore', + 'Noecker', + 'Osmer', + 'Pasquariello', + 'Schlick', + 'Snover', + 'Strzelecki', + 'Studt', + 'Sunga', + 'Belmares', + 'Seifried', + 'Urioste', + 'Housh', + 'Babu', + 'Bures', + 'Augusto', + 'Faddis', + 'Pun', + 'Chopp', + 'Tullock', + 'Sea', + 'Boisseau', + 'Herbin', + 'Balcer', + 'Copus', + 'Eichenberger', + 'Enterline', + 'Gamarra', + 'Gursky', + 'Hovsepian', + 'Laffin', + 'Melena', + 'Rappe', + 'Soma', + 'Spira', + 'Spraker', + 'Teuscher', + 'Hochhalter', + 'Brenden', + 'Snee', + 'Polan', + 'Hataway', + 'Tirey', + 'Cobler', + 'Marren', + 'Ress', + 'Bennis', + 'Busha', + 'Galler', + 'Orea', + 'Nailor', + 'Magby', + 'Bridgett', + 'Island', + 'Camino', + 'Coderre', + 'Gangloff', + 'Gillilan', + 'Goergen', + 'Henthorne', + 'Heverly', + 'Loughry', + 'Records', + 'Schweikert', + 'Seeds', + 'Vanderwerf', + 'Westall', + 'Cristiano', + 'Biser', + 'Cartmill', + 'Greenly', + 'Kountz', + 'Craney', + 'Sheffey', + 'Gelin', + 'Gourdine', + 'Canham', + 'Edgmon', + 'Enz', + 'Feldpausch', + 'Hestand', + 'Kaus', + 'Kostelnik', + 'Ocanas', + 'Riggi', + 'Rohl', + 'Scheurer', + 'Sleeman', + 'Tosi', + 'Phegley', + 'Abelson', + 'Mclees', + 'Sinor', + 'Babson', + 'Whalley', + 'Manton', + 'Patteson', + 'Doyen', + 'Asad', + 'Thurmon', + 'Cassese', + 'Ditmore', + 'Duva', + 'Pilato', + 'Polaski', + 'Rzepka', + 'Sevin', + 'Sivak', + 'Speckman', + 'Stepien', + 'Switalski', + 'Valletta', + 'Knoth', + 'Niver', + 'Ciancio', + 'Giza', + 'Liebowitz', + 'Orengo', + 'Rothgeb', + 'Witz', + 'Airhart', + 'Gayman', + 'Belland', + 'Eury', + 'Randal', + 'Mcghie', + 'Briganti', + 'Hoopingarner', + 'Lugar', + 'Manfre', + 'Mongelli', + 'Squibb', + 'Vasil', + 'Cap', + 'Veillon', + 'Ege', + 'Spice', + 'Nevel', + 'Vanleer', + 'Petway', + 'Petitfrere', + 'Barcena', + 'Belville', + 'Brezina', + 'Ketcherside', + 'Knodel', + 'Krinsky', + 'Lundahl', + 'Mescher', + 'Pilat', + 'Sneller', + 'Staller', + 'Steinhaus', + 'Stensrud', + 'Szalay', + 'Tani', + 'Saviano', + 'Genna', + 'Emry', + 'Allin', + 'Harvel', + 'Harth', + 'Pay', + 'Harries', + 'Brannum', + 'Elijah', + 'Hoyte', + 'Bazinet', + 'Bhandari', + 'Brozek', + 'Cava', + 'Dalbey', + 'Delgiudice', + 'Klages', + 'Riffey', + 'Straube', + 'Zagar', + 'Zientek', + 'Dilger', + 'Hof', + 'Karwowski', + 'Rybarczyk', + 'Spiering', + 'Stamos', + 'Gangemi', + 'Olavarria', + 'Sardinas', + 'Magin', + 'Payano', + 'Deady', + 'Henricksen', + 'Kary', + 'Garnier', + 'Babic', + 'Behymer', + 'Billig', + 'Huegel', + 'Ishihara', + 'Mcglinchey', + 'Misuraca', + 'Petrosino', + 'Zizzo', + 'Reierson', + 'Wadman', + 'Brander', + 'Risko', + 'Basye', + 'Mcmakin', + 'Straughan', + 'Chesnutt', + 'Sima', + 'Ree', + 'Mankins', + 'Soberanis', + 'Greenup', + 'Commodore', + 'Carucci', + 'Defibaugh', + 'Finfrock', + 'Funston', + 'Grantz', + 'Guiney', + 'Ohrt', + 'Tinsman', + 'Godek', + 'Mcgrory', + 'Mikeska', + 'Kamer', + 'Lovas', + 'Kirshner', + 'Bevacqua', + 'Franqui', + 'Walts', + 'Doke', + 'Orsborn', + 'Tavernier', + 'Kibble', + 'Scipio', + 'Diop', + 'Antczak', + 'Bastida', + 'Callister', + 'Dusseau', + 'Ficarra', + 'Garcilazo', + 'Hughett', + 'Liebel', + 'Rodenbaugh', + 'Rosselli', + 'Teresi', + 'Bohnsack', + 'Steidl', + 'Vanderheiden', + 'Demma', + 'Dutson', + 'Mcmeekin', + 'Glassford', + 'Serrao', + 'Marriner', + 'Mcchristian', + 'Lias', + 'Blahnik', + 'Brunke', + 'Daleo', + 'Fullam', + 'Goetzinger', + 'Leva', + 'Rehder', + 'Ripperger', + 'Shindler', + 'Tussing', + 'Mayr', + 'Rozzi', + 'Bonsignore', + 'Te', + 'Graft', + 'Ok', + 'Clink', + 'Mccamey', + 'Goldring', + 'Tartt', + 'Fullilove', + 'Amodio', + 'Arkin', + 'Dettmann', + 'Ellingwood', + 'Figura', + 'Fritzinger', + 'Heilmann', + 'Hillstrom', + 'Marasigan', + 'Pavlov', + 'Totman', + 'Dokken', + 'Serpico', + 'Shumard', + 'Rathman', + 'Siegmund', + 'Woodhull', + 'Oregon', + 'Roselle', + 'Taul', + 'Maddix', + 'Nwosu', + 'Bavaro', + 'Carella', + 'Cowdrey', + 'Goodnough', + 'Koffler', + 'Mahajan', + 'Montalvan', + 'Morga', + 'Parrella', + 'Quiggle', + 'Rehrig', + 'Rotondi', + 'Tavenner', + 'Wigger', + 'Yax', + 'Bartko', + 'Netzel', + 'Zechman', + 'Socia', + 'Vea', + 'Wemple', + 'Matti', + 'Striplin', + 'Hollin', + 'Geddie', + 'Nolden', + 'Freeney', + 'Jeanjacques', + 'Bermudes', + 'Castrellon', + 'Catino', + 'Feeser', + 'Kreitz', + 'Maisano', + 'Melkonian', + 'Toste', + 'Vancura', + 'Bylsma', + 'Wiant', + 'Mcpheron', + 'Gere', + 'Geoffroy', + 'Fuston', + 'Petteway', + 'Barsky', + 'Bovard', + 'Buttars', + 'Christophersen', + 'Dudzik', + 'Ganger', + 'Hilgers', + 'Holzhauer', + 'Minervini', + 'Pong', + 'Rozycki', + 'Sulzer', + 'Tauscher', + 'Upright', + 'Verastegui', + 'Lobello', + 'Sandt', + 'Timbrook', + 'Yniguez', + 'Nuzzi', + 'Sakata', + 'Koran', + 'Veloso', + 'Cullers', + 'Culton', + 'Reynold', + 'Feagins', + 'Amaker', + 'Cafferty', + 'Coontz', + 'Iden', + 'Mazzotta', + 'Montanye', + 'Wandell', + 'Weiman', + 'Vik', + 'Staib', + 'Lasso', + 'Waynick', + 'Boniface', + 'Massingale', + 'Gainous', + 'Sharper', + 'Columbia', + 'Felkins', + 'Gatzke', + 'Heindel', + 'Ludeman', + 'Mcmunn', + 'Mogavero', + 'Ratti', + 'Rickabaugh', + 'Ripper', + 'Tessman', + 'Triano', + 'Vanderpol', + 'Langille', + 'Holten', + 'Steeley', + 'Solan', + 'Devaul', + 'Lindler', + 'Armor', + 'Fambrough', + 'Golliday', + 'Bognar', + 'Gamba', + 'Gettinger', + 'Hanzel', + 'Krumwiede', + 'Marcinkowski', + 'Nicolay', + 'Peppard', + 'Sisti', + 'Sundeen', + 'Senatore', + 'Diebel', + 'Demarais', + 'Letellier', + 'Goon', + 'Texidor', + 'Baughan', + 'Gunder', + 'Lalor', + 'Wigglesworth', + 'Aird', + 'Basey', + 'Afshar', + 'Anhalt', + 'Bondoc', + 'Bunten', + 'Daniello', + 'Kazmierski', + 'Marcott', + 'Petruska', + 'Trejos', + 'Droege', + 'Fukumoto', + 'Harju', + 'Hauf', + 'Yagi', + 'Mccallie', + 'Moulds', + 'Singleterry', + 'Ramkissoon', + 'Sanks', + 'Siggers', + 'Myrie', + 'Conteh', + 'Biss', + 'Brees', + 'Collopy', + 'Dashner', + 'Dehaas', + 'Delzer', + 'Fees', + 'Finocchiaro', + 'Forsgren', + 'Giampietro', + 'Levandowski', + 'Mallick', + 'Maudlin', + 'Micheletti', + 'Newhard', + 'Parmentier', + 'Pintado', + 'Pliego', + 'Radigan', + 'Selke', + 'Uptain', + 'Wigton', + 'Zabinski', + 'Becenti', + 'Guthmiller', + 'Malecha', + 'Eardley', + 'Muscat', + 'Ruhe', + 'Battersby', + 'Lamie', + 'Stan', + 'Dutch', + 'Duplechain', + 'Dildy', + 'Auch', + 'Baltzer', + 'Degaetano', + 'Mileski', + 'Parrillo', + 'Schoof', + 'Stires', + 'Villescas', + 'Knittle', + 'Degrave', + 'Deihl', + 'Moseman', + 'Prillaman', + 'Wakeley', + 'Jake', + 'Murden', + 'Shareef', + 'Yarbough', + 'Bothe', + 'Boutilier', + 'Breck', + 'Buschman', + 'Coccia', + 'Eberlein', + 'Harriger', + 'Neas', + 'Sullenger', + 'Walp', + 'Yaple', + 'Zinger', + 'Zufelt', + 'Marinaccio', + 'Viele', + 'Markee', + 'Melody', + 'Rooke', + 'Ales', + 'Mumphrey', + 'Bessinger', + 'Bialas', + 'Brugh', + 'Chum', + 'Diehm', + 'Frieze', + 'Hieber', + 'Malouf', + 'Maltz', + 'Mcmanaway', + 'Musante', + 'Pester', + 'Roda', + 'Snarr', + 'Tovey', + 'Buchmann', + 'Fluck', + 'Sadowsky', + 'Viteri', + 'Loewe', + 'Mullaly', + 'Lamboy', + 'Bouman', + 'Provencal', + 'Siddons', + 'Chelette', + 'Rachels', + 'Dynes', + 'Nobel', + 'Desselle', + 'Tillison', + 'Bajaj', + 'Bresee', + 'Hisel', + 'Mallo', + 'Meints', + 'Potocki', + 'Spore', + 'Steier', + 'Toothaker', + 'Wildt', + 'Darcangelo', + 'Karbowski', + 'Scaccia', + 'Lascola', + 'Duman', + 'Mccaul', + 'Rowton', + 'Setters', + 'Hendryx', + 'Belson', + 'Manny', + 'Winckler', + 'Longe', + 'Mclucas', + 'Lenon', + 'Linen', + 'Anstine', + 'Belkin', + 'Drozdowski', + 'Ender', + 'Ferra', + 'Lessig', + 'Marucci', + 'Nardo', + 'Nipp', + 'Passarella', + 'Roecker', + 'Siddique', + 'Stanczak', + 'Stavros', + 'Tomasetti', + 'Lagreca', + 'Seegmiller', + 'Keena', + 'Suddarth', + 'Wayt', + 'Matas', + 'Ryer', + 'Mortimore', + 'Durnell', + 'Pieters', + 'Slocumb', + 'Andaya', + 'Brymer', + 'Dufek', + 'Ekman', + 'Espericueta', + 'Feltes', + 'Hammann', + 'Heydt', + 'Inthavong', + 'Jagielski', + 'Nast', + 'Petrucelli', + 'Phippen', + 'Vanderzanden', + 'Whinery', + 'Zatarain', + 'Zelenak', + 'Aquilina', + 'Hougland', + 'Isais', + 'Canney', + 'Flath', + 'Ragon', + 'Len', + 'Violet', + 'Carra', + 'Everetts', + 'Lockey', + 'Dahmer', + 'Fuquay', + 'Alpers', + 'Borromeo', + 'Bringas', + 'Brumit', + 'Campanile', + 'Folts', + 'Hirai', + 'Kiessling', + 'Krogstad', + 'Ovitt', + 'Bhardwaj', + 'Hlavaty', + 'Monceaux', + 'Spatola', + 'Trunzo', + 'Girvin', + 'Shady', + 'Grimley', + 'Tagg', + 'Weddell', + 'Mcfadyen', + 'Reagin', + 'Philo', + 'Emily', + 'Codd', + 'Cherrington', + 'Skates', + 'Deary', + 'Ballester', + 'Barilla', + 'Cicchetti', + 'Dyche', + 'Goossen', + 'Graveline', + 'Hajduk', + 'Halliwell', + 'Kohnen', + 'Kupiec', + 'Machacek', + 'Manship', + 'Slinker', + 'Mallozzi', + 'Dotter', + 'Brazeau', + 'Manon', + 'Crofford', + 'Gauthreaux', + 'Petillo', + 'Bailor', + 'Ganesh', + 'Reaser', + 'Barren', + 'Adachi', + 'Aguiniga', + 'Cartrette', + 'Crady', + 'Hegland', + 'Isner', + 'Karasek', + 'Labrum', + 'Maroon', + 'Rullo', + 'Schull', + 'Stawicki', + 'Withee', + 'Penfold', + 'Foronda', + 'Claridge', + 'Coiner', + 'Guimaraes', + 'Mawyer', + 'Rivkin', + 'Kiggins', + 'Hackel', + 'Wey', + 'Fairhurst', + 'Albertini', + 'Gaal', + 'Flurry', + 'Patricia', + 'Savery', + 'Colen', + 'Cuthrell', + 'Maffett', + 'Dungey', + 'Luter', + 'Hurston', + 'Ahles', + 'Czapla', + 'Gallas', + 'Kotecki', + 'Lazzari', + 'Marcellino', + 'Valvo', + 'Vukovich', + 'Wisor', + 'Agler', + 'Wease', + 'Gallentine', + 'Christoph', + 'Poyer', + 'Norment', + 'Rhett', + 'Amabile', + 'Barish', + 'Heifner', + 'Kolarik', + 'Mcquarrie', + 'Morua', + 'Nahas', + 'Razzano', + 'Riegle', + 'Torralba', + 'Perfetti', + 'Stalzer', + 'Killman', + 'Lenning', + 'Wyler', + 'Soward', + 'Releford', + 'Battisti', + 'Bergum', + 'Catapano', + 'Doerner', + 'Ehlen', + 'Finken', + 'Genereux', + 'Hillegas', + 'Hopple', + 'Kaatz', + 'Lacson', + 'Macario', + 'Marzolf', + 'Muha', + 'Picha', + 'Springston', + 'Stooksbury', + 'Weide', + 'Glodowski', + 'Lueth', + 'Assaf', + 'Robuck', + 'Lamaster', + 'Foulkes', + 'Swopes', + 'Winkfield', + 'Aristizabal', + 'Aylesworth', + 'Bellotti', + 'Bittick', + 'Capistran', + 'Cizek', + 'Dinneen', + 'Ellender', + 'Friske', + 'Hoffa', + 'Klinge', + 'Kuklinski', + 'Luzier', + 'Martensen', + 'Rolin', + 'Shankles', + 'Siska', + 'Wiegman', + 'Winterbottom', + 'Crookston', + 'Gorospe', + 'Curci', + 'Lamberty', + 'Antonetti', + 'Sheer', + 'Durning', + 'Hootman', + 'Doub', + 'Klaiber', + 'Mayeaux', + 'Domingos', + 'Wheeless', + 'Vantrease', + 'Summerhill', + 'Agresta', + 'Annas', + 'Aquilar', + 'Crea', + 'Froese', + 'Medlen', + 'Peeters', + 'Rhudy', + 'Risse', + 'Schor', + 'Zimmerer', + 'Bombardier', + 'Halfhill', + 'Koppenhaver', + 'Kruckenberg', + 'Boccia', + 'Rella', + 'Carelli', + 'Overson', + 'Tamburro', + 'Rosamond', + 'Lie', + 'Mesquita', + 'Jennett', + 'Jewel', + 'Waye', + 'Bogucki', + 'Colpitts', + 'Galpin', + 'Hrdlicka', + 'Kading', + 'Kushnir', + 'Leano', + 'Liebig', + 'Mceuen', + 'Nestler', + 'Payer', + 'Santarelli', + 'Schrupp', + 'Schwarze', + 'Semrau', + 'Solanki', + 'Terzian', + 'Treloar', + 'Ureno', + 'Vohra', + 'Voshell', + 'Nakanishi', + 'Senese', + 'Dierker', + 'Quinley', + 'Monier', + 'Rounsaville', + 'Mcfaddin', + 'Defrance', + 'Joynes', + 'Levert', + 'Adragna', + 'Buczynski', + 'Cranor', + 'Englebert', + 'Furney', + 'Gorny', + 'Mockler', + 'Pavlicek', + 'Petrini', + 'Schadt', + 'Slagel', + 'Cumpston', + 'Priore', + 'Paonessa', + 'Carling', + 'Espaillat', + 'Hem', + 'Griffo', + 'Tomer', + 'Venn', + 'Giraud', + 'Becks', + 'Mungin', + 'Attard', + 'Brucato', + 'Dreyfus', + 'Droz', + 'Falck', + 'Firebaugh', + 'Fiser', + 'Hemmelgarn', + 'Hofacker', + 'Kreeger', + 'Rippee', + 'Ruehle', + 'Saputo', + 'Scovill', + 'Silbaugh', + 'Smolenski', + 'Spickler', + 'Swango', + 'Kaehler', + 'Mootz', + 'Noblett', + 'Zarcone', + 'Katzenberger', + 'Kita', + 'Brezinski', + 'Castles', + 'Padin', + 'Hinde', + 'Barretta', + 'Amiri', + 'Shelburne', + 'Mccoin', + 'Heaston', + 'Aldredge', + 'Milhouse', + 'Wilbon', + 'Cephus', + 'Barsness', + 'Belch', + 'Blatter', + 'Boyum', + 'Corvino', + 'Dagenais', + 'Doscher', + 'Elizarraraz', + 'Gierke', + 'Habegger', + 'Ketcher', + 'Kristiansen', + 'Oldroyd', + 'Sandage', + 'Tesoriero', + 'Unzueta', + 'Wollam', + 'Cefalu', + 'Achey', + 'Wegmann', + 'Lessner', + 'Bunk', + 'Mallin', + 'Polis', + 'Aronoff', + 'Portal', + 'Crock', + 'Escher', + 'Medler', + 'Pretty', + 'Younge', + 'Agbayani', + 'Brinkmeyer', + 'Castrillon', + 'Feick', + 'Gutmann', + 'Hagenbuch', + 'Hesseltine', + 'Houska', + 'Kimzey', + 'Kolasa', + 'Lentine', + 'Lobaugh', + 'Maimone', + 'Meshell', + 'Nardini', + 'Rosetti', + 'Siefker', + 'Sileo', + 'Silveria', + 'Argumedo', + 'Lesmeister', + 'Donnan', + 'Hermans', + 'Raggio', + 'Dupras', + 'Empson', + 'Bevier', + 'Tumey', + 'Donn', + 'Darville', + 'Douse', + 'Cheyne', + 'Dewing', + 'Jansma', + 'Mayeda', + 'Nield', + 'Obermiller', + 'Opfer', + 'Surma', + 'Tiffin', + 'Tirpak', + 'Wassel', + 'Blickenstaff', + 'Dorland', + 'Kulhanek', + 'Andras', + 'Estupinan', + 'Gonce', + 'Weast', + 'Souto', + 'Guirguis', + 'Glazebrook', + 'Dain', + 'Loyer', + 'Bensley', + 'Verge', + 'Tubman', + 'Onley', + 'Dais', + 'Barash', + 'Bullman', + 'Crispino', + 'Davino', + 'Isenhart', + 'Kneller', + 'Loschiavo', + 'Opper', + 'Pfleger', + 'Wahler', + 'Zelasko', + 'Havrilla', + 'Mintzer', + 'Devoll', + 'Giannelli', + 'Sees', + 'Barritt', + 'Mesta', + 'Sostre', + 'Rohman', + 'Padget', + 'Edds', + 'Slinger', + 'Borowicz', + 'Bregman', + 'Bubar', + 'Debartolo', + 'Desposito', + 'Grieshaber', + 'Ludtke', + 'Pagani', + 'Quiambao', + 'Schapiro', + 'Winward', + 'Bouska', + 'Olstad', + 'Rough', + 'Genz', + 'Husby', + 'Nealis', + 'Hyams', + 'Andrades', + 'Mcgibbon', + 'Edwin', + 'Buckhalter', + 'Baylon', + 'Fiene', + 'Fillingim', + 'Fiorenza', + 'Greenstreet', + 'Krager', + 'Laxson', + 'Noreen', + 'Roberds', + 'Rundquist', + 'Smelcer', + 'Tabone', + 'Train', + 'Zeoli', + 'Defries', + 'Kolp', + 'Maahs', + 'Mcnall', + 'Ehman', + 'Keeth', + 'Shackleton', + 'Hogarth', + 'Westbury', + 'Gulliver', + 'Oquin', + 'Holiman', + 'Saintlouis', + 'Vaughns', + 'Aichele', + 'Arbelaez', + 'Bathurst', + 'Bresler', + 'Cecena', + 'Drollinger', + 'Fellner', + 'Griesemer', + 'Harnois', + 'Hire', + 'Kraker', + 'Roylance', + 'Zaccaria', + 'Dinunzio', + 'Foisy', + 'Nordlund', + 'Peppler', + 'Kishbaugh', + 'Marcil', + 'Mcfarren', + 'Puello', + 'Supplee', + 'Boyea', + 'Depp', + 'Tift', + 'Wince', + 'Pam', + 'Ifill', + 'Brodt', + 'Caamano', + 'Gibler', + 'Litherland', + 'Miesner', + 'Pixler', + 'Schwimmer', + 'Suriano', + 'Abendroth', + 'Gillaspy', + 'Kumpf', + 'Schroepfer', + 'Boals', + 'Seneca', + 'Sasson', + 'Hindes', + 'Posten', + 'Lann', + 'Anctil', + 'Arebalo', + 'Beacom', + 'Boberg', + 'Coufal', + 'Didion', + 'Fromme', + 'Greenan', + 'Guerrette', + 'Hudec', + 'Kazmi', + 'Lucchese', + 'Mouw', + 'Savastano', + 'Schomer', + 'Shorb', + 'Storz', + 'Finazzo', + 'Knigge', + 'Pawlikowski', + 'Cercone', + 'Sutfin', + 'Valdespino', + 'Mccartin', + 'Yurko', + 'Treaster', + 'Peaden', + 'Russin', + 'Dibartolo', + 'Dona', + 'Skillern', + 'Brackens', + 'Amyx', + 'Bornemann', + 'Comtois', + 'Kaestner', + 'Kallenbach', + 'Krupka', + 'Lineback', + 'Lopata', + 'Mcclenahan', + 'Monteverde', + 'Otani', + 'Panchal', + 'Pawlicki', + 'Suman', + 'Vallance', + 'Zammit', + 'Liszewski', + 'Trunk', + 'Sharifi', + 'Lents', + 'Watkinson', + 'Willow', + 'Flaming', + 'Sol', + 'Dory', + 'Purchase', + 'Haris', + 'Bigsby', + 'Boonstra', + 'Emge', + 'Goodpasture', + 'Iwata', + 'Kau', + 'Syring', + 'Vlach', + 'Klaassen', + 'Vicuna', + 'Wasden', + 'Cattell', + 'Ridlon', + 'Fassler', + 'Scullion', + 'Hibbitts', + 'Mcgillis', + 'Pla', + 'Mustin', + 'Darty', + 'Minniefield', + 'Bloyd', + 'Calnan', + 'Casal', + 'Fickel', + 'Gamero', + 'Higuchi', + 'Huante', + 'Knies', + 'Letner', + 'Quang', + 'Teufel', + 'Topolski', + 'Tumminello', + 'Vanorder', + 'Slawinski', + 'Nyce', + 'Asmar', + 'Loudin', + 'Karen', + 'Budden', + 'Mothershed', + 'Fenelon', + 'Mccrorey', + 'Ashenfelter', + 'Auge', + 'Christison', + 'Cilley', + 'Corsetti', + 'Coxwell', + 'Critchley', + 'Griep', + 'Hausner', + 'Hiemstra', + 'Koprowski', + 'Kozicki', + 'Marling', + 'Marmo', + 'Noller', + 'Pich', + 'Recendez', + 'Renegar', + 'Rinne', + 'Zeis', + 'Buzzelli', + 'Lipham', + 'Schaner', + 'Kartchner', + 'Kealy', + 'Sinopoli', + 'Krishna', + 'Brinn', + 'Zachry', + 'Barbre', + 'Sharber', + 'Fritze', + 'Hanshew', + 'Lemere', + 'Maruyama', + 'Masker', + 'Melendy', + 'Pelto', + 'Rigo', + 'Rohling', + 'Scobee', + 'Sundell', + 'Tranter', + 'Vancuren', + 'Augustyniak', + 'Mehringer', + 'Sulkowski', + 'Gittins', + 'Twiford', + 'Dumm', + 'Jacklin', + 'Mcquaig', + 'Richison', + 'Jex', + 'Meritt', + 'Hegler', + 'Duboise', + 'Houze', + 'Akana', + 'Corsaro', + 'Delosangeles', + 'Guidice', + 'Maccallum', + 'Moes', + 'Steinhardt', + 'Stirewalt', + 'Wooters', + 'Schissler', + 'Sobeck', + 'Boyte', + 'Jilek', + 'Suder', + 'Kellis', + 'Blankenbaker', + 'Lank', + 'Mandigo', + 'Fremont', + 'Rideau', + 'Beidler', + 'Boda', + 'Gulotta', + 'Havelka', + 'Herberger', + 'Isenhower', + 'Lattanzi', + 'Pandolfi', + 'Shearman', + 'Wilmarth', + 'Dutkiewicz', + 'Mazzuca', + 'Tabarez', + 'Vermilyea', + 'Kray', + 'Vitti', + 'Packwood', + 'Paulos', + 'Howson', + 'Collman', + 'Ameen', + 'Berisha', + 'Capece', + 'Fantasia', + 'Galas', + 'Laszlo', + 'Luthi', + 'Maietta', + 'Mcconaghy', + 'Naab', + 'Nerio', + 'Pineau', + 'Rossbach', + 'Senne', + 'Unangst', + 'Kautzman', + 'Muhs', + 'Ripka', + 'Wehling', + 'Hoot', + 'Jee', + 'Megna', + 'Tirone', + 'Walle', + 'Brandi', + 'Lutter', + 'Mona', + 'Roley', + 'Mcfann', + 'Swader', + 'Cavett', + 'Delmore', + 'Walthour', + 'Goldson', + 'Biddinger', + 'Bjornstad', + 'Buesing', + 'Cerino', + 'Diede', + 'Hagle', + 'Hodgman', + 'Killmer', + 'Loa', + 'Matsunaga', + 'Micciche', + 'Newquist', + 'Poppen', + 'Shellhammer', + 'Tienda', + 'Tino', + 'Mihelich', + 'Garsia', + 'Orzel', + 'Ericsson', + 'Dose', + 'Kotter', + 'Amante', + 'Hanif', + 'Huckleberry', + 'Blandin', + 'Carvin', + 'Axton', + 'Delosrios', + 'Diekmann', + 'Failing', + 'Filipek', + 'Otting', + 'Rozman', + 'Sadeghi', + 'Slutsky', + 'Speake', + 'Szostak', + 'Tacy', + 'Kmiecik', + 'Macgillivray', + 'Yeakel', + 'Dykman', + 'Gorey', + 'Dowding', + 'Revel', + 'Geathers', + 'Cappa', + 'Davidoff', + 'Lukehart', + 'Mccutchan', + 'Neeb', + 'Nikolic', + 'Piorkowski', + 'Sandvig', + 'Schmidgall', + 'Stockbridge', + 'Thornock', + 'Valk', + 'Wiechmann', + 'Chait', + 'Gacek', + 'Schupbach', + 'Gemma', + 'Rus', + 'Barch', + 'Wyles', + 'Scrivener', + 'Salls', + 'Akram', + 'Mcclatchey', + 'Bromfield', + 'Burl', + 'Redwood', + 'Starkes', + 'Beaston', + 'Boggio', + 'Cantillo', + 'Cina', + 'Cryan', + 'Dubs', + 'Edmisten', + 'Fitzer', + 'Fugere', + 'Fundora', + 'Galvis', + 'Jafri', + 'Nalepa', + 'Peri', + 'Pippenger', + 'Rheault', + 'Rohrbacher', + 'Romberg', + 'Samek', + 'Stehlik', + 'Stepan', + 'Torrisi', + 'Wessner', + 'Zappala', + 'Bangerter', + 'Czerniak', + 'Mcshea', + 'Raczkowski', + 'Rohwer', + 'Spehar', + 'Lague', + 'Messman', + 'Angst', + 'Temme', + 'Tolles', + 'Lawn', + 'Ayars', + 'Austen', + 'Stansel', + 'Fairclough', + 'Tribbett', + 'Peevy', + 'Fraiser', + 'Caradine', + 'Fiegel', + 'Gignac', + 'Halpert', + 'Karels', + 'Knappenberger', + 'Prezioso', + 'Rohlfs', + 'Szot', + 'Varano', + 'Weinreich', + 'Butterbaugh', + 'Heying', + 'Vandewalle', + 'Yandle', + 'Thede', + 'Astor', + 'Blanchfield', + 'Hegeman', + 'Fels', + 'Miniard', + 'Lorio', + 'Muhammed', + 'Lazard', + 'Ehmke', + 'Hulst', + 'Imlay', + 'Kinzler', + 'Knaak', + 'Poehler', + 'Prusak', + 'Rakow', + 'Raupp', + 'Sucher', + 'Tanenbaum', + 'Burich', + 'Macmaster', + 'Shapley', + 'Thurgood', + 'Mires', + 'Gotay', + 'Attia', + 'Martis', + 'Greenley', + 'Fothergill', + 'Bonvillain', + 'Buffalo', + 'Dues', + 'Crute', + 'Cantone', + 'Dewit', + 'Dovel', + 'Klopfer', + 'Philhower', + 'Piatek', + 'Pion', + 'Rapaport', + 'Vanwert', + 'Wikstrom', + 'Graffeo', + 'Kissling', + 'Niday', + 'Soong', + 'Adami', + 'Hammersmith', + 'Keir', + 'Yo', + 'Grizzell', + 'Stclaire', + 'Swales', + 'Nole', + 'Pole', + 'Hartgrove', + 'Carrothers', + 'Carlone', + 'Ciano', + 'Finucane', + 'Fitterer', + 'Gellman', + 'Hakimi', + 'Janos', + 'Krings', + 'Malmstrom', + 'Markwardt', + 'Rodin', + 'Schau', + 'Scheible', + 'Orick', + 'Dine', + 'Tremmel', + 'Shon', + 'Wilms', + 'Bren', + 'Bertin', + 'Poster', + 'Jeng', + 'Stcharles', + 'Jenning', + 'Eutsey', + 'Fayne', + 'Gustave', + 'Mccargo', + 'Boruff', + 'Boschert', + 'Burmester', + 'Colello', + 'Conchas', + 'Devi', + 'Dishaw', + 'Funaro', + 'Gallen', + 'Hsueh', + 'Lanser', + 'Macaraeg', + 'Munster', + 'Petsch', + 'Routon', + 'Werkmeister', + 'Woznicki', + 'Boroff', + 'Cochenour', + 'Dibartolomeo', + 'Elzinga', + 'Heyen', + 'Lapaglia', + 'Schiel', + 'Rauda', + 'Woltman', + 'Carll', + 'Kanda', + 'Runnells', + 'Hazelett', + 'Arnwine', + 'Sherfield', + 'Borthwick', + 'Coyner', + 'Ensey', + 'Feinman', + 'Leyendecker', + 'Lickteig', + 'Lubeck', + 'Maccarone', + 'Minahan', + 'Plew', + 'Saur', + 'Schleich', + 'Sixtos', + 'Soller', + 'Valek', + 'Umland', + 'Swogger', + 'Iannacone', + 'Tomey', + 'Venuto', + 'Peru', + 'Adolf', + 'Lemme', + 'Bureau', + 'River', + 'Buffaloe', + 'Leacock', + 'Threat', + 'Boza', + 'Constancio', + 'Dandurand', + 'Hiscock', + 'Kaley', + 'Michaelsen', + 'Roberti', + 'Sicilia', + 'Sliker', + 'Sooter', + 'Steyer', + 'Tabora', + 'Vanderbeek', + 'Vanscyoc', + 'Piercey', + 'Sabater', + 'Bride', + 'Tippens', + 'Acquaviva', + 'Baublitz', + 'Mccanna', + 'Mckaig', + 'Merenda', + 'Obermeier', + 'Pechacek', + 'Pugmire', + 'Shaneyfelt', + 'Steuer', + 'Zeidler', + 'Bodenheimer', + 'Gaglio', + 'Maceachern', + 'Munsterman', + 'Rayle', + 'Wisnewski', + 'Baar', + 'Thi', + 'Foulds', + 'Rufino', + 'Chrisco', + 'Barrientez', + 'Lare', + 'Munnerlyn', + 'Pitter', + 'Koroma', + 'Caisse', + 'Espe', + 'Kerin', + 'Melchiorre', + 'Mentz', + 'Paasch', + 'Parrales', + 'Rhew', + 'Sigley', + 'Skiff', + 'Stockert', + 'Viglione', + 'Kraska', + 'Botto', + 'Ponzio', + 'Wolfley', + 'Wack', + 'Kilborn', + 'Dunnavant', + 'Pitney', + 'Dolman', + 'Biscoe', + 'Michelle', + 'Azcona', + 'Brasington', + 'Fazzino', + 'Hoefs', + 'Kohlmeyer', + 'Laser', + 'Morea', + 'Morrin', + 'Neuwirth', + 'Nicklaus', + 'Pennypacker', + 'Rueckert', + 'Schriefer', + 'Scovel', + 'Swyers', + 'Thebeau', + 'Mijangos', + 'Douville', + 'Tidball', + 'Smullen', + 'Lecount', + 'Pruiett', + 'Branche', + 'Arment', + 'Babiarz', + 'Char', + 'Granlund', + 'Hillock', + 'Kahrs', + 'Khong', + 'Lalley', + 'Laspina', + 'Pietila', + 'Ponciano', + 'Rosengren', + 'Slee', + 'Snowberger', + 'Weglarz', + 'Camarata', + 'Villalovos', + 'Buza', + 'Kenning', + 'Rohrig', + 'Sedor', + 'Perretta', + 'Hamberg', + 'Mongan', + 'Formby', + 'Portier', + 'Silcott', + 'Levell', + 'Barrantes', + 'Bellefeuille', + 'Beneke', + 'Bilbao', + 'Danahy', + 'Delahanty', + 'Deppen', + 'Dicostanzo', + 'Dudding', + 'Elmquist', + 'Handa', + 'Hatem', + 'Loverde', + 'Mesick', + 'Onofrio', + 'Ramesh', + 'Tiberio', + 'Trachtenberg', + 'Vanwagenen', + 'Cassada', + 'Pepitone', + 'Stillson', + 'Pfarr', + 'Radle', + 'Scallan', + 'Carlen', + 'Bermingham', + 'Sagers', + 'Llorens', + 'Turay', + 'Beamish', + 'Carlini', + 'Galipeau', + 'Heavey', + 'Kempker', + 'Masser', + 'Montellano', + 'Peine', + 'Pietro', + 'Plitt', + 'Pollman', + 'Rike', + 'Spees', + 'Vandervelde', + 'Vanwey', + 'Grundman', + 'Marinucci', + 'Molenda', + 'Shideler', + 'Turrubiartes', + 'Schaer', + 'Firkins', + 'Haid', + 'Parnes', + 'Pulse', + 'Masone', + 'Burpo', + 'Tharrington', + 'Winborn', + 'Petite', + 'Buttry', + 'Clason', + 'Eutsler', + 'Haberer', + 'Haft', + 'Kotler', + 'Meloche', + 'Raether', + 'Rengifo', + 'Roback', + 'Stangle', + 'Wilderman', + 'Chickering', + 'Gervacio', + 'Penaranda', + 'Schnieders', + 'Coyer', + 'Laramee', + 'Curts', + 'Bailiff', + 'Truby', + 'Molder', + 'Hedley', + 'Carbon', + 'Gudger', + 'Fontenette', + 'Askren', + 'Deshane', + 'Enriques', + 'Fake', + 'Jungers', + 'Krech', + 'Niemela', + 'Perfetto', + 'Ritt', + 'Soldano', + 'Stanish', + 'Strege', + 'Wichert', + 'Wolz', + 'Zimbelman', + 'Abplanalp', + 'Nikkel', + 'Oravec', + 'Coile', + 'Mizuno', + 'Fenlon', + 'Vanloo', + 'Callery', + 'Hortman', + 'Hashim', + 'Sorey', + 'Ajayi', + 'Alesi', + 'Alessandro', + 'Avants', + 'Bachtel', + 'Bonine', + 'Butkovich', + 'Cerros', + 'Colina', + 'Dayhoff', + 'Favata', + 'Haning', + 'Kamath', + 'Kosik', + 'Loughrey', + 'Mollo', + 'Nagi', + 'Nesler', + 'Nosek', + 'Ordoyne', + 'Politis', + 'Zwolinski', + 'Yaffe', + 'Sigal', + 'Burow', + 'Scarbro', + 'Buckel', + 'Broxson', + 'Goyer', + 'Goding', + 'Delee', + 'Jefferys', + 'Blissett', + 'Balian', + 'Brader', + 'Curreri', + 'Dickmann', + 'Eckerle', + 'Erives', + 'Fedewa', + 'Frisina', + 'Gropp', + 'Hinck', + 'Lamorte', + 'Litzenberger', + 'Proehl', + 'Struss', + 'Tamburello', + 'Digioia', + 'Galarneau', + 'Jurkiewicz', + 'Macnaughton', + 'Talsma', + 'Vlasak', + 'Weyrauch', + 'Yontz', + 'Kho', + 'Stgermaine', + 'Grauer', + 'Benware', + 'Rearden', + 'Molin', + 'Pendergrast', + 'Sivils', + 'Ellery', + 'Ikner', + 'Metayer', + 'Toran', + 'Seaberry', + 'Banderas', + 'Bannan', + 'Critzer', + 'Doescher', + 'Haakenson', + 'Hignite', + 'Hoeksema', + 'Inserra', + 'Korbel', + 'Kruzel', + 'Langen', + 'Mittelstaedt', + 'Popkin', + 'Schwarting', + 'Toral', + 'Ilagan', + 'Lamica', + 'Lierman', + 'Zimmerly', + 'Fosse', + 'Pagnotta', + 'Trenholm', + 'Clayson', + 'Cerutti', + 'Wollard', + 'Mcburnett', + 'Stallcup', + 'Magan', + 'Wonder', + 'Gillock', + 'Ellisor', + 'Clayburn', + 'Mabery', + 'Cariaga', + 'Crail', + 'Dieckman', + 'Joynt', + 'Kleinert', + 'Kutner', + 'Milla', + 'Nauta', + 'Rende', + 'Robare', + 'Santella', + 'Scianna', + 'Sevcik', + 'Smolik', + 'Staudinger', + 'Cedillos', + 'Shroff', + 'Ueda', + 'Yearout', + 'Zuno', + 'Pottle', + 'Klabunde', + 'Tusa', + 'Schomburg', + 'Alto', + 'Packett', + 'Muns', + 'Dante', + 'Jarnigan', + 'Londo', + 'Bigbee', + 'Isles', + 'Nembhard', + 'Appiah', + 'Hypolite', + 'Acebedo', + 'Arlt', + 'Champney', + 'Kawahara', + 'Lehan', + 'Pavlak', + 'Ritacco', + 'Seckinger', + 'Turvey', + 'Vanevery', + 'Wronski', + 'Bahnsen', + 'Clites', + 'Ellwanger', + 'Husak', + 'Lydic', + 'Zubiate', + 'Muehlbauer', + 'Neumeister', + 'Wellnitz', + 'Langstaff', + 'Gort', + 'Eve', + 'Stones', + 'Stanard', + 'Whichard', + 'Cheers', + 'Baldus', + 'Bertoni', + 'Chesebro', + 'Dino', + 'Dubray', + 'Icenhour', + 'Marquard', + 'Mette', + 'Potash', + 'Winterhalter', + 'Crupi', + 'Lascala', + 'Tauer', + 'Vandenburgh', + 'Mende', + 'Swarey', + 'Sarles', + 'Platter', + 'Dekeyser', + 'Jaye', + 'Pelle', + 'Caroll', + 'Rosette', + 'Shepperson', + 'Fooks', + 'Kennerson', + 'Bolser', + 'Chim', + 'Diefenderfer', + 'Frosch', + 'Holzwarth', + 'Kjos', + 'Langland', + 'Meland', + 'Stufflebeam', + 'Worland', + 'Barrales', + 'Chhay', + 'Corkern', + 'Creegan', + 'Golan', + 'Marceaux', + 'Matsuo', + 'Micallef', + 'Otsuka', + 'Rinella', + 'Creveling', + 'Krane', + 'Mcnay', + 'Detter', + 'Drexel', + 'Kibodeaux', + 'Shippey', + 'Medearis', + 'Samms', + 'Drzewiecki', + 'Fariss', + 'Glandon', + 'Heinecke', + 'Hendler', + 'Jungwirth', + 'Panepinto', + 'Rohleder', + 'Saragosa', + 'Stuller', + 'Wissel', + 'Atwal', + 'Tisch', + 'Esterly', + 'Mourad', + 'Brickell', + 'Bough', + 'Rubens', + 'Angevine', + 'Tolin', + 'Sago', + 'Apfel', + 'Ashdown', + 'Derusha', + 'Fiorino', + 'Koyama', + 'Matteucci', + 'Newbrough', + 'Seufert', + 'Stahley', + 'Tyburski', + 'Zaino', + 'Cdebaca', + 'Hormann', + 'Wangen', + 'Winterton', + 'Beagley', + 'Sowden', + 'Daul', + 'Errington', + 'Steber', + 'Emfinger', + 'Olan', + 'Fiveash', + 'Carriger', + 'Breakfield', + 'Ezekiel', + 'Wallington', + 'Hollimon', + 'Izzard', + 'Lyde', + 'Bellmore', + 'Benkert', + 'Bhargava', + 'Dacanay', + 'Dano', + 'Diprima', + 'Garlitz', + 'Hannemann', + 'Janiak', + 'Klann', + 'Kunce', + 'Malicki', + 'Mcgivney', + 'Nordeen', + 'Procell', + 'Rands', + 'Smeltz', + 'Sutch', + 'Wach', + 'Wentling', + 'Karapetyan', + 'Mcvicar', + 'Pennisi', + 'Perley', + 'Graner', + 'Hartney', + 'Shadley', + 'Pennebaker', + 'Cayce', + 'Marris', + 'Burges', + 'Odem', + 'Charvat', + 'Delgreco', + 'Diven', + 'Latu', + 'Mccallion', + 'Mcfeely', + 'Mon', + 'Nagai', + 'Obrecht', + 'Opdyke', + 'Pearlstein', + 'Pomroy', + 'Prothero', + 'Rado', + 'Roehr', + 'Seiffert', + 'Spake', + 'Stech', + 'Thakur', + 'Trzcinski', + 'Uvalle', + 'Vazques', + 'Anschutz', + 'Boecker', + 'Descoteaux', + 'Idol', + 'Stanzione', + 'Welp', + 'Schumer', + 'Ridner', + 'Kasner', + 'Auton', + 'Barca', + 'Ocheltree', + 'Biernat', + 'Mercuri', + 'Truslow', + 'Witters', + 'Mcelhannon', + 'Mccrackin', + 'Brabson', + 'Baumberger', + 'Double', + 'Garis', + 'Kasparian', + 'Kooistra', + 'Loser', + 'Mangone', + 'Massman', + 'Raimondo', + 'Sparacio', + 'Valli', + 'Viets', + 'Wessell', + 'Kieu', + 'Vonderheide', + 'Wojnar', + 'Furbee', + 'Heyden', + 'Lackie', + 'Ehrich', + 'Roupe', + 'Holy', + 'Care', + 'Isa', + 'Samad', + 'Rougeau', + 'Chavous', + 'Rattler', + 'Wedderburn', + 'President', + 'Blackham', + 'Bobak', + 'Crimi', + 'Durland', + 'Gargus', + 'Gitlin', + 'Levandoski', + 'Niu', + 'Piccirilli', + 'Sauvageau', + 'Schweers', + 'Talty', + 'Uthe', + 'Verga', + 'Warzecha', + 'Erisman', + 'Gallacher', + 'Shanholtz', + 'Fulgencio', + 'Migues', + 'Garin', + 'Heisel', + 'Stong', + 'Christiana', + 'Bonenfant', + 'Clancey', + 'Kindley', + 'Nill', + 'Mood', + 'Atterbury', + 'Tobe', + 'Eisenhardt', + 'Franceschini', + 'Heiland', + 'Kreuzer', + 'Lockaby', + 'Scarola', + 'Tessitore', + 'Warehime', + 'Kukowski', + 'Ruhlman', + 'Frymire', + 'Bartone', + 'Wrightson', + 'Langlinais', + 'Planas', + 'Darsey', + 'Darin', + 'Gammel', + 'Giroir', + 'Aspinall', + 'Hollywood', + 'Childres', + 'Copelin', + 'Teamer', + 'Okoro', + 'Abshier', + 'Arizaga', + 'Berenson', + 'Biegler', + 'Dugdale', + 'Erlich', + 'Gavino', + 'Haaland', + 'Lautenschlager', + 'Lilja', + 'Livingood', + 'Lockner', + 'Pyeatt', + 'Reist', + 'Rummell', + 'Schadler', + 'Snare', + 'Zawada', + 'Dumler', + 'Moncivais', + 'Sammarco', + 'Laraway', + 'Voorhis', + 'Detty', + 'Manko', + 'Zale', + 'Autin', + 'Quaid', + 'Denver', + 'Demario', + 'Nearing', + 'Amerine', + 'Bea', + 'Carraher', + 'Dierkes', + 'Dutko', + 'Hosek', + 'Kassner', + 'Meo', + 'Mesler', + 'Norquist', + 'Pacetti', + 'Pellerito', + 'Ryser', + 'Turnmire', + 'Caniglia', + 'Zollman', + 'Gerwig', + 'Denslow', + 'Stapler', + 'Majid', + 'Prestage', + 'Eargle', + 'Spight', + 'Argabright', + 'Borgeson', + 'Cipollone', + 'Dippold', + 'Korf', + 'Milhoan', + 'Pinelli', + 'Roblero', + 'Scolaro', + 'Sperl', + 'Svensson', + 'Bauguess', + 'Freimuth', + 'Luquin', + 'Barman', + 'Solivan', + 'Buel', + 'Birkeland', + 'Cafiero', + 'Degollado', + 'Demeyer', + 'Hoberg', + 'Homola', + 'Kadel', + 'Koslowski', + 'Lefrancois', + 'Macconnell', + 'Madill', + 'Nudelman', + 'Raucci', + 'Reidenbach', + 'Schermer', + 'Sergio', + 'Bucko', + 'Haegele', + 'Nibert', + 'Sidell', + 'Slape', + 'Hellard', + 'Russi', + 'Wilcock', + 'Verdejo', + 'Lessley', + 'Camille', + 'Topps', + 'Acampora', + 'Blacketer', + 'Clapham', + 'Efaw', + 'Louks', + 'Mersch', + 'Odden', + 'Schettler', + 'Schnarr', + 'Sieracki', + 'Skog', + 'Zobrist', + 'Corless', + 'Zunker', + 'Bega', + 'Victoriano', + 'Singler', + 'Keltz', + 'Valcarcel', + 'Curet', + 'Harvison', + 'Mccullah', + 'Cranfield', + 'Gardin', + 'Mewborn', + 'Bisel', + 'Carfagno', + 'Carli', + 'Chirino', + 'Fairless', + 'Gaboury', + 'Goetze', + 'Guitron', + 'Haut', + 'Krupski', + 'Lata', + 'Misiak', + 'Sawaya', + 'Schomaker', + 'Schulke', + 'Tin', + 'Dewhurst', + 'Krummel', + 'Hannahs', + 'Carlow', + 'Hemp', + 'Bowdoin', + 'Breda', + 'Chriss', + 'Kebede', + 'Binney', + 'Brasseaux', + 'Cunliffe', + 'Gantner', + 'Gillick', + 'Hottle', + 'Hren', + 'Irani', + 'Klitzke', + 'Luhrs', + 'Micale', + 'Oien', + 'Oppelt', + 'Rallo', + 'Ringwald', + 'Stonerock', + 'Strebel', + 'Tiberi', + 'Volner', + 'Whetstine', + 'Wrubel', + 'Brakebill', + 'Fechner', + 'Geurts', + 'Hoefling', + 'Misener', + 'Andros', + 'Dimock', + 'Rosendo', + 'Megill', + 'Gloyd', + 'Garney', + 'Andries', + 'Esco', + 'Rhames', + 'Draine', + 'Plair', + 'Jiggetts', + 'Atcheson', + 'Brienza', + 'Cerveny', + 'Depaoli', + 'Deroo', + 'Dorf', + 'Guidotti', + 'Heimlich', + 'Insalaco', + 'Kaczorowski', + 'Kinnunen', + 'Loureiro', + 'Lyster', + 'Pia', + 'Piccoli', + 'Quale', + 'Sadek', + 'Stenstrom', + 'Strause', + 'Tortorella', + 'Traweek', + 'Vanderwerff', + 'Varian', + 'Vink', + 'Waxler', + 'Wynia', + 'Annese', + 'Economou', + 'Whitsel', + 'Dougher', + 'Schnieder', + 'Cosman', + 'Farra', + 'Osmon', + 'Bardon', + 'Rampersaud', + 'Jane', + 'Kirts', + 'Chennault', + 'Thomison', + 'Graig', + 'Narine', + 'Gunner', + 'Aamodt', + 'Adinolfi', + 'Adolphson', + 'Aki', + 'Alderton', + 'Aloisio', + 'Bellavia', + 'Clutts', + 'Coughran', + 'Frasco', + 'Guinta', + 'Hatala', + 'Ibach', + 'Mecum', + 'Medero', + 'Neria', + 'Nery', + 'Pignataro', + 'Podesta', + 'Statzer', + 'Stombaugh', + 'Szczesny', + 'Kovaleski', + 'Ades', + 'Bauers', + 'Bern', + 'Horsfall', + 'Masood', + 'Cinque', + 'Stay', + 'Beare', + 'Donavan', + 'Ikerd', + 'Seney', + 'Layson', + 'Coler', + 'Tuft', + 'Tamplin', + 'Billinger', + 'Scrivens', + 'Bartolomei', + 'Baza', + 'Dimattia', + 'Dotterer', + 'Dushane', + 'Fulop', + 'Iacovelli', + 'Macnamara', + 'Mahlum', + 'Noteboom', + 'Rebstock', + 'Drechsler', + 'Itzkowitz', + 'Rigler', + 'Schrom', + 'Pirozzi', + 'Ferre', + 'Shiley', + 'Villanova', + 'Barona', + 'Farrel', + 'Shelman', + 'Nute', + 'Rowlette', + 'Tarrance', + 'Cadorette', + 'Christenberry', + 'Deocampo', + 'Farace', + 'Fesmire', + 'Kallman', + 'Koogler', + 'Pitsch', + 'Salce', + 'Schnepf', + 'Totaro', + 'Towey', + 'Urdiales', + 'Gotschall', + 'Brunett', + 'Dier', + 'Hainsworth', + 'Seabury', + 'Cornelious', + 'Altobelli', + 'Andreozzi', + 'Bohlmann', + 'Carranco', + 'Daubenspeck', + 'Delagrange', + 'Delo', + 'Faler', + 'Ficke', + 'Hellinger', + 'Hudman', + 'Ihde', + 'Landolfi', + 'Leiner', + 'Mosman', + 'Rang', + 'Tarbet', + 'Wineman', + 'Fehrman', + 'Guinto', + 'Icenogle', + 'Tomasik', + 'Looman', + 'Iriarte', + 'Denaro', + 'Montross', + 'Piersall', + 'Lauren', + 'Lablanc', + 'Kindrick', + 'Deriso', + 'Manker', + 'Maycock', + 'Cullens', + 'Frieson', + 'Clippinger', + 'Colavito', + 'Fassbender', + 'Fennessy', + 'Granada', + 'Gugliotta', + 'Guiliano', + 'Hirschberg', + 'Kerbs', + 'Kusch', + 'Limmer', + 'Malpica', + 'Mcaloon', + 'Morken', + 'Pytel', + 'Resnik', + 'Spangle', + 'Worstell', + 'Kerkhoff', + 'Kupka', + 'Stanczyk', + 'Storlie', + 'Thurow', + 'Caetano', + 'Ernandez', + 'Males', + 'Coopersmith', + 'Everest', + 'Leander', + 'Demeritt', + 'Thomes', + 'Codner', + 'Livsey', + 'Alcoser', + 'Arico', + 'Balestrieri', + 'Cavalli', + 'Florendo', + 'Gottshall', + 'Hinesley', + 'Lafuente', + 'Landess', + 'Ornstein', + 'Pettingill', + 'Romesburg', + 'Tokunaga', + 'Wiersema', + 'Janeway', + 'Pecha', + 'Steimel', + 'Sproule', + 'Sommerfield', + 'Mirsky', + 'Staines', + 'Pu', + 'Corbit', + 'Mcelmurry', + 'Wickes', + 'Yell', + 'Mordecai', + 'Aye', + 'Boldin', + 'China', + 'Fason', + 'Thibeaux', + 'Nesby', + 'Bergevin', + 'Besecker', + 'Dohrmann', + 'Fujioka', + 'Fyock', + 'Goralski', + 'Kirschenbaum', + 'Knipper', + 'Menor', + 'Mischler', + 'Nolder', + 'Odoherty', + 'Pickerill', + 'Poremba', + 'Swantek', + 'Difabio', + 'Kulka', + 'Servais', + 'Wickizer', + 'Melecio', + 'Zeek', + 'Fruit', + 'Agnes', + 'Bar', + 'Mccarrell', + 'Hopgood', + 'Califano', + 'Cratty', + 'Dishner', + 'Gabrielli', + 'Hamacher', + 'Hinote', + 'Jakob', + 'Klinkhammer', + 'Krasinski', + 'Krysiak', + 'Pardi', + 'Petrilli', + 'Razon', + 'Reifsnyder', + 'Reisig', + 'Reller', + 'Sassano', + 'Steinhart', + 'Wrede', + 'Zevallos', + 'Coombe', + 'Quesnel', + 'Rebuck', + 'Wantz', + 'Bendele', + 'Lacomb', + 'Hagge', + 'Donelan', + 'Kempe', + 'Po', + 'Varnadoe', + 'Constantin', + 'Deon', + 'Motte', + 'Beckum', + 'Parchment', + 'Meriweather', + 'Borucki', + 'Fatima', + 'Gerkin', + 'Guglielmi', + 'Hettich', + 'Hoerr', + 'Karlsson', + 'Kenealy', + 'Paolillo', + 'Pfenning', + 'Rueger', + 'Schildt', + 'Sem', + 'Vilches', + 'Dornbusch', + 'Erdahl', + 'Kleinhenz', + 'Moneypenny', + 'Tomasko', + 'Vandevender', + 'Cromley', + 'Tun', + 'Velasques', + 'Roble', + 'Burgo', + 'Waples', + 'Mabon', + 'Benincasa', + 'Buttermore', + 'Dalbec', + 'Eikenberry', + 'Fuehrer', + 'Hossler', + 'Lepp', + 'Opheim', + 'Sarsfield', + 'Strobl', + 'Strouth', + 'Tousley', + 'Wilczek', + 'Kleppe', + 'Muraoka', + 'Wiencek', + 'Pinckard', + 'Ahsan', + 'Welder', + 'Forton', + 'Lorden', + 'Stlawrence', + 'Marina', + 'Mcquire', + 'Randleman', + 'Pates', + 'Fluitt', + 'Scotland', + 'Clerk', + 'Townsell', + 'Arrasmith', + 'Baisch', + 'Berling', + 'Busler', + 'Curtice', + 'Ebinger', + 'Fleeger', + 'Geng', + 'Goettsch', + 'Henneberry', + 'Johannesen', + 'Mcilrath', + 'Perigo', + 'Phibbs', + 'Riske', + 'Scarcella', + 'Vandyken', + 'Barstad', + 'Dicamillo', + 'Ernsberger', + 'Guebara', + 'Peetz', + 'Newcome', + 'Alterman', + 'Weik', + 'Trier', + 'Yeats', + 'Hugg', + 'Crayne', + 'Ige', + 'Coach', + 'Archuletta', + 'Bodi', + 'Cadavid', + 'Ceccarelli', + 'Derksen', + 'Deutscher', + 'Genter', + 'Gogel', + 'Gorczyca', + 'Grohs', + 'Koplin', + 'Kozloski', + 'Lillo', + 'Oplinger', + 'Pulis', + 'Renk', + 'Repka', + 'Scavo', + 'Vitagliano', + 'Weinkauf', + 'Yellin', + 'Boehlke', + 'Montecalvo', + 'Castrillo', + 'Grenon', + 'Wellen', + 'Keelan', + 'Coville', + 'Rison', + 'Jourdain', + 'Chestnutt', + 'Sharpley', + 'Acharya', + 'Bartles', + 'Burruel', + 'Capelle', + 'Contos', + 'Friedrichsen', + 'Heaberlin', + 'Hermiz', + 'Iracheta', + 'Klutts', + 'Koziel', + 'Salto', + 'Scaturro', + 'Stasik', + 'Stitzel', + 'Wiseley', + 'Paccione', + 'Squyres', + 'Leverich', + 'Holderness', + 'Elvin', + 'Morand', + 'Lizana', + 'Woolen', + 'Amarante', + 'Arn', + 'Biedermann', + 'Daddio', + 'Davilla', + 'Forti', + 'Gripp', + 'Hanzlik', + 'Iannotti', + 'Larin', + 'Nakajima', + 'Novacek', + 'Pesch', + 'Regino', + 'Rosengarten', + 'Schleif', + 'Searing', + 'Sikkema', + 'Walstrom', + 'Guastella', + 'Hemstreet', + 'Rorabaugh', + 'Weisenburger', + 'Cannan', + 'Band', + 'Fowkes', + 'Bennetts', + 'Purviance', + 'Tippin', + 'Brossard', + 'Seigle', + 'Babyak', + 'Billiter', + 'Cartner', + 'Deetz', + 'Dorow', + 'Laur', + 'Leblond', + 'Lecomte', + 'Morando', + 'Reitman', + 'Sarria', + 'Scheu', + 'Timmermann', + 'Vaneck', + 'Vangorp', + 'Windhorst', + 'Kaeser', + 'Kosloski', + 'Cappuccio', + 'Knitter', + 'Evon', + 'Garbett', + 'Wickens', + 'Ruston', + 'Fregia', + 'Ashurst', + 'Ede', + 'Strider', + 'Reaux', + 'Castellani', + 'Debus', + 'Degracia', + 'Hineman', + 'Laning', + 'Litts', + 'Losito', + 'Massi', + 'Mazzara', + 'Schriber', + 'Seyfert', + 'Strength', + 'Treptow', + 'Yuhasz', + 'Kamrath', + 'Krigbaum', + 'Marrocco', + 'Wanta', + 'Yakubov', + 'Hy', + 'Sabedra', + 'Belling', + 'Deats', + 'Mahaffy', + 'Brodrick', + 'Mcneece', + 'Madding', + 'Mottley', + 'Asp', + 'Borgerding', + 'Conrady', + 'Dagenhart', + 'Defusco', + 'Duensing', + 'Ensz', + 'Fockler', + 'Gajda', + 'Masino', + 'Minster', + 'Naso', + 'Nifong', + 'Ohlsen', + 'Prairie', + 'Rosendale', + 'Rotman', + 'Salzano', + 'Samet', + 'Takagi', + 'Vandagriff', + 'Vespa', + 'Zaragosa', + 'Howdyshell', + 'Kilburg', + 'Mellado', + 'Mollet', + 'Varone', + 'Benne', + 'Dillehay', + 'Ruther', + 'Gullick', + 'Lasure', + 'Wilkenson', + 'Lawrance', + 'Amacker', + 'Wisher', + 'Pryer', + 'Torian', + 'Aragona', + 'Dains', + 'Darrigo', + 'Escajeda', + 'Fertitta', + 'Futral', + 'Kielty', + 'Kightlinger', + 'Lanuza', + 'Marich', + 'Mcenaney', + 'Mohrman', + 'Pressnell', + 'Prestia', + 'Scullin', + 'Seidner', + 'Steigerwalt', + 'Wassell', + 'Bonavita', + 'Bourgault', + 'Sentz', + 'Viswanathan', + 'Hanchey', + 'Volpi', + 'Wilensky', + 'Mathey', + 'Mages', + 'Raimo', + 'Cozine', + 'Sprow', + 'Petties', + 'Bracht', + 'Cayabyab', + 'Comp', + 'Flamenco', + 'Friederich', + 'Hori', + 'Husmann', + 'Isidoro', + 'Ketchem', + 'Krishnamurthy', + 'Kucinski', + 'Lalani', + 'Lamacchia', + 'Lecher', + 'Morante', + 'Schrieber', + 'Sciarra', + 'Vandamme', + 'Welz', + 'Bozich', + 'Cancilla', + 'Panduro', + 'Mcglade', + 'Wasmund', + 'Riso', + 'Moronta', + 'Kemple', + 'Rocks', + 'Sainsbury', + 'Solo', + 'Harnage', + 'Sturkie', + 'Hollingworth', + 'Denley', + 'Bumpass', + 'Lovick', + 'Bribiesca', + 'Dewilde', + 'Drohan', + 'Geringer', + 'Kokoszka', + 'Kronberg', + 'Lewinski', + 'Lunney', + 'Morehart', + 'Ty', + 'Vasseur', + 'Vona', + 'Wriston', + 'Casarrubias', + 'Copsey', + 'Rochette', + 'Macwilliams', + 'Natali', + 'Milanes', + 'Rux', + 'Woodcox', + 'Bernett', + 'Bronaugh', + 'Fulwood', + 'Bhalla', + 'Depalo', + 'Hench', + 'Huckeba', + 'Kasch', + 'Kisor', + 'Marinos', + 'Nakahara', + 'Parrent', + 'Rantz', + 'Schoenbeck', + 'Schwieterman', + 'Selk', + 'Swonger', + 'Walding', + 'Nunamaker', + 'Schuchardt', + 'Leverton', + 'Fiallo', + 'Viruet', + 'Fadel', + 'Robel', + 'Calley', + 'Renton', + 'Rack', + 'Brin', + 'Cocks', + 'Mcivor', + 'Bois', + 'Demary', + 'Bason', + 'Dowlen', + 'Prophete', + 'Collymore', + 'Beisner', + 'Briand', + 'Cumberledge', + 'Curro', + 'Cutcher', + 'Daponte', + 'Eckroth', + 'Edgemon', + 'Farinella', + 'Kobe', + 'Muilenburg', + 'Osiecki', + 'Cutsinger', + 'Biggar', + 'Maciver', + 'Quesinberry', + 'Rippetoe', + 'Baswell', + 'Caven', + 'Mimbs', + 'Hurlock', + 'Cham', + 'Cypress', + 'Emile', + 'Beitel', + 'Bellavance', + 'Casada', + 'Fandel', + 'Gillentine', + 'Gorelick', + 'Kassis', + 'Klim', + 'Kohnke', + 'Lutgen', + 'Nalbandian', + 'Schepis', + 'Troester', + 'Hartje', + 'Hippensteel', + 'Kiehn', + 'Kuenzi', + 'Greenburg', + 'Boroughs', + 'Catton', + 'Adney', + 'Olivencia', + 'Mcdermitt', + 'Ashwell', + 'Leazer', + 'Poag', + 'Prevo', + 'Porcher', + 'Hugley', + 'Salone', + 'Jupiter', + 'Bratz', + 'Ehresman', + 'Fauber', + 'Filippelli', + 'Kesling', + 'Kronk', + 'Mcelhiney', + 'Mcgreal', + 'Miyasato', + 'Moomey', + 'Nicolini', + 'Osberg', + 'Ostroski', + 'Sanzo', + 'Sybert', + 'Dimichele', + 'Gerrits', + 'Shatley', + 'Weider', + 'Faraj', + 'Paules', + 'Yarberry', + 'Lege', + 'Pembroke', + 'Clipper', + 'Filmore', + 'Crichlow', + 'Blaustein', + 'Boak', + 'Canzoneri', + 'Crescenzo', + 'Ebaugh', + 'Feig', + 'Jens', + 'Knoebel', + 'Mohammadi', + 'Montour', + 'Norgren', + 'Pasquini', + 'Prost', + 'Reh', + 'Rosal', + 'Thesing', + 'Titcomb', + 'Wolinski', + 'Zeitlin', + 'Depoy', + 'Guccione', + 'Ritsema', + 'Valent', + 'Drey', + 'Govoni', + 'Lonsdale', + 'Hultz', + 'Harvie', + 'Levison', + 'Colomb', + 'Dace', + 'Cleckley', + 'Godette', + 'Brentlinger', + 'Fetrow', + 'Giuffrida', + 'Kopka', + 'Kurtzman', + 'Panameno', + 'Pannone', + 'Parzych', + 'Seipp', + 'Stobbe', + 'Thulin', + 'Torosian', + 'Trani', + 'Zietlow', + 'Montufar', + 'Stohr', + 'Woloszyn', + 'Cimini', + 'Angles', + 'Nicasio', + 'Vi', + 'Em', + 'Couchman', + 'Hobbie', + 'Bluestein', + 'Phillipson', + 'Shiels', + 'Altice', + 'Williston', + 'Kone', + 'Tadesse', + 'Abbruzzese', + 'Badders', + 'Duxbury', + 'Egeland', + 'Freyre', + 'Haen', + 'Hineline', + 'Kniss', + 'Kothe', + 'Kyker', + 'Popelka', + 'Sanjose', + 'Slaugh', + 'Wecker', + 'Wiechman', + 'Bilello', + 'Keezer', + 'Knode', + 'Longhurst', + 'Wisser', + 'Cease', + 'Contrera', + 'Berroa', + 'Aguon', + 'Pott', + 'Blitch', + 'Suares', + 'Bein', + 'Acre', + 'Ailes', + 'Tutwiler', + 'Porte', + 'Ashwood', + 'Blackson', + 'Viverette', + 'Balthazar', + 'Kidane', + 'Allegretti', + 'Corbeil', + 'Crossno', + 'Cudworth', + 'Federspiel', + 'Hamstra', + 'Kibbey', + 'Lefevers', + 'Loomer', + 'Losada', + 'Medema', + 'Palmerin', + 'Peregoy', + 'Previte', + 'Riedinger', + 'Schlossberg', + 'Wilemon', + 'Lepkowski', + 'Mcdanel', + 'Commisso', + 'Baiza', + 'Fones', + 'Divis', + 'Diedrick', + 'Grave', + 'Bonkowski', + 'Cerami', + 'Drinkwine', + 'Hauke', + 'Heun', + 'Keilman', + 'Klemmer', + 'Mella', + 'Olarte', + 'Ryall', + 'Veltman', + 'Wlodarczyk', + 'Bashor', + 'Kubinski', + 'Vanacker', + 'Prouse', + 'Perrott', + 'Berrio', + 'Mccarney', + 'Seiders', + 'Jafari', + 'Louque', + 'Melder', + 'Grazier', + 'Gabay', + 'Hardway', + 'Sadiq', + 'Sully', + 'Durrell', + 'Barno', + 'Maybin', + 'Brazile', + 'Asante', + 'Awalt', + 'Badal', + 'Cucinotta', + 'Grenfell', + 'Hartis', + 'Herbster', + 'Hesch', + 'Klosowski', + 'Overfelt', + 'Pangelinan', + 'Pflum', + 'Rozema', + 'Spivack', + 'Vallez', + 'Vetere', + 'Villamor', + 'Wedekind', + 'Bobrowski', + 'Nguyenthi', + 'Nowaczyk', + 'Vis', + 'Pownall', + 'Susan', + 'Yanni', + 'Gest', + 'Balthrop', + 'Treasure', + 'Harston', + 'Frett', + 'Buttery', + 'Chiarelli', + 'Colledge', + 'Czaplicki', + 'Fahringer', + 'Fedder', + 'Gerstenberger', + 'Gretz', + 'Hallquist', + 'Hemme', + 'Kolling', + 'Krauth', + 'Liquori', + 'Podolsky', + 'Scheirer', + 'Sehgal', + 'Selinger', + 'Wintermute', + 'Chokshi', + 'Dimarzio', + 'Santoni', + 'Wetherby', + 'Flis', + 'Comley', + 'Boyt', + 'Farrah', + 'Mario', + 'Mcquilkin', + 'Tim', + 'Cusic', + 'Enge', + 'Millage', + 'Waheed', + 'Kenan', + 'Silmon', + 'Mcconico', + 'Bougher', + 'Braly', + 'Coriell', + 'Daignault', + 'Henschen', + 'Holsomback', + 'Johal', + 'Kellams', + 'Schaumburg', + 'Stockinger', + 'Urquidi', + 'Cabanillas', + 'Lindbloom', + 'Willinger', + 'Redpath', + 'Baller', + 'Juarbe', + 'Badia', + 'Elderkin', + 'Dessert', + 'Retter', + 'Mccollam', + 'Rivette', + 'Devins', + 'Hewell', + 'Penniman', + 'Arbuthnot', + 'Cotman', + 'Tezeno', + 'Albo', + 'Beezley', + 'Can', + 'Chesler', + 'Dehne', + 'Demchak', + 'Edberg', + 'Gotham', + 'Ingels', + 'Kaercher', + 'Kwiecinski', + 'Landolt', + 'Macdonnell', + 'Malicoat', + 'Meinen', + 'Niswander', + 'Pandit', + 'Pettet', + 'Pliska', + 'Ploch', + 'Ratigan', + 'Sampsel', + 'Sick', + 'Ciampi', + 'Mctighe', + 'Riester', + 'Salvucci', + 'Tornow', + 'Vencill', + 'Racey', + 'Haroldson', + 'Finder', + 'Dennen', + 'Stano', + 'Boys', + 'Camillo', + 'Woodfield', + 'Turrell', + 'Sami', + 'Annan', + 'Yeldell', + 'Madlock', + 'Manigo', + 'Arcila', + 'Bauza', + 'Bisceglia', + 'Crouthamel', + 'Debenedetto', + 'Delude', + 'Dorta', + 'Fairburn', + 'Garciagarcia', + 'Geeslin', + 'Kazanjian', + 'Loescher', + 'Mccarl', + 'Mulqueen', + 'Pultz', + 'Shutter', + 'Spacek', + 'Yamanaka', + 'Borkholder', + 'Halko', + 'Pieroni', + 'Proano', + 'Sarkisyan', + 'Riopelle', + 'Routson', + 'Fogelman', + 'Sou', + 'Tress', + 'Altemus', + 'Bosh', + 'Laroque', + 'Hueston', + 'Latin', + 'Taitt', + 'Lymon', + 'Chadd', + 'Challis', + 'Comella', + 'Drabik', + 'Entz', + 'Hagner', + 'Knobbe', + 'Luckenbill', + 'Macphail', + 'Mogg', + 'Paustian', + 'Rimel', + 'Schilke', + 'Folkman', + 'Lemery', + 'Quinby', + 'Cliburn', + 'Rowand', + 'Wambach', + 'Gammell', + 'Nobrega', + 'Hoggan', + 'Nightengale', + 'Alison', + 'Batte', + 'Borner', + 'Hudnell', + 'Casseus', + 'Boteler', + 'Cantos', + 'Contois', + 'Coventry', + 'Dezarn', + 'Eisenbarth', + 'Hegel', + 'Jahr', + 'Joss', + 'Lober', + 'Marcks', + 'Portilla', + 'Reinders', + 'Scouten', + 'Siri', + 'Sobocinski', + 'Tesh', + 'Veno', + 'Wheeldon', + 'Yankee', + 'Wanke', + 'Wollin', + 'Longobardi', + 'Mccarson', + 'Sampsell', + 'Harrer', + 'Bakewell', + 'Mcgalliard', + 'Truluck', + 'Bremmer', + 'Lois', + 'Goody', + 'Kassim', + 'Conniff', + 'Elenes', + 'Esker', + 'Groshong', + 'Hallisey', + 'Loree', + 'Marken', + 'Molle', + 'Muntean', + 'Ozaki', + 'Roen', + 'Rumer', + 'Shorr', + 'Tanzer', + 'Varady', + 'Hillmer', + 'Macari', + 'Schuld', + 'Swartzlander', + 'Tsuji', + 'Holahan', + 'Abee', + 'Rowse', + 'Pawley', + 'Samp', + 'Shad', + 'Wintz', + 'Rainford', + 'Cellucci', + 'Cumpton', + 'Dando', + 'Dress', + 'Funari', + 'Gouker', + 'Hemberger', + 'Latz', + 'Meckes', + 'Parrinello', + 'Picardi', + 'Pilz', + 'Pretzer', + 'Schriever', + 'Sodano', + 'Stetter', + 'Storti', + 'Tiu', + 'Zimmerle', + 'Dragone', + 'Engert', + 'Fullenkamp', + 'Rockafellow', + 'Siwek', + 'Zillmer', + 'Devol', + 'Milke', + 'Taira', + 'Richner', + 'Aros', + 'Mancil', + 'Yetman', + 'Hanney', + 'Kinion', + 'Ferrand', + 'Conyer', + 'Chahal', + 'Fulfer', + 'Gurski', + 'Horseman', + 'Liebe', + 'Nyhus', + 'Pernice', + 'Pesqueira', + 'Rieker', + 'Trautmann', + 'Yellowhair', + 'Schwanz', + 'Salinger', + 'Carvell', + 'Heymann', + 'Grad', + 'Pharo', + 'Pipher', + 'Magalhaes', + 'Kissee', + 'Winthrop', + 'Leid', + 'Sledd', + 'Bladen', + 'Rahaman', + 'Holdman', + 'Goldwire', + 'Lawal', + 'Sinkfield', + 'Bryk', + 'Butkiewicz', + 'Gagen', + 'Gettle', + 'Goede', + 'Hardenbrook', + 'Heinsohn', + 'Kovalcik', + 'Needleman', + 'Obeso', + 'Parziale', + 'Schaus', + 'Wadlow', + 'Haluska', + 'Stiteler', + 'Zaruba', + 'Tschirhart', + 'Biscardi', + 'Gopal', + 'Avella', + 'Ponto', + 'Levit', + 'Trevor', + 'Pimienta', + 'Plass', + 'Guthery', + 'Cordy', + 'Tuff', + 'Zellars', + 'Altier', + 'Berges', + 'Connick', + 'Deruyter', + 'Divita', + 'Frankovich', + 'Ingenito', + 'Kosman', + 'Lantis', + 'Lovering', + 'Sortino', + 'Waage', + 'Wildrick', + 'Barberio', + 'Domin', + 'Meisels', + 'Sender', + 'Giovanni', + 'Sanguinetti', + 'Beary', + 'Helmstetter', + 'Joens', + 'Beaven', + 'Kines', + 'Surrency', + 'Sheilds', + 'Chamber', + 'Albarez', + 'Ambrocio', + 'Arrellano', + 'Berrigan', + 'Bookwalter', + 'Caravella', + 'Higbie', + 'Lotter', + 'Lougee', + 'Manganiello', + 'Nobriga', + 'Roorda', + 'Serr', + 'Squillace', + 'Tejera', + 'Tipping', + 'Wohler', + 'Carreto', + 'Deignan', + 'Luebbers', + 'Engelhard', + 'Hollenback', + 'Baldo', + 'Gearin', + 'Bia', + 'Figueras', + 'Lule', + 'Libert', + 'Florida', + 'Wyne', + 'Mccright', + 'Jacko', + 'Cawthorne', + 'Rhue', + 'Betton', + 'Cisse', + 'Arth', + 'Bendickson', + 'Cangialosi', + 'Coltharp', + 'Cubias', + 'Czarnik', + 'Erpelding', + 'Erway', + 'Heister', + 'Mergen', + 'Murrietta', + 'Nachman', + 'Nusser', + 'Ostrem', + 'Pei', + 'Pescatore', + 'Reim', + 'Shaull', + 'Spranger', + 'Uphold', + 'Yslas', + 'Heinold', + 'Lindemuth', + 'Redeker', + 'Rochin', + 'Wisehart', + 'Carsey', + 'Nocella', + 'Combe', + 'Thacher', + 'Hammad', + 'Bene', + 'Yelvington', + 'Mccrone', + 'Driessen', + 'Saxby', + 'Maull', + 'Jeune', + 'Amorim', + 'Degrazia', + 'Doege', + 'Flinchbaugh', + 'Goodreau', + 'Hanisch', + 'Hoaglund', + 'Imamura', + 'Lafler', + 'Linne', + 'Profeta', + 'Reifschneider', + 'Santaana', + 'Scaffidi', + 'Shreeve', + 'Stadelman', + 'Dippolito', + 'Pizzuti', + 'Rodenberg', + 'Schartz', + 'Reiger', + 'Solie', + 'Willen', + 'Atallah', + 'Wyers', + 'Harpel', + 'Cleckler', + 'Fobes', + 'Sniffen', + 'Pedroso', + 'Samara', + 'Malcomb', + 'Penry', + 'Stearn', + 'Seller', + 'Abeita', + 'Bilotti', + 'Brosky', + 'Clewell', + 'Fraijo', + 'Gaskey', + 'Goodfriend', + 'Mesaros', + 'Musch', + 'Nulph', + 'Obarr', + 'Roat', + 'Sabato', + 'Sauerwein', + 'Schum', + 'Silsby', + 'Weyenberg', + 'Corrente', + 'Egloff', + 'Kohrs', + 'Sammartino', + 'Thoennes', + 'Carmer', + 'Madura', + 'Shang', + 'Faxon', + 'Monell', + 'Laden', + 'Yousuf', + 'Mcgauley', + 'Salmond', + 'Berhane', + 'Abood', + 'Bondar', + 'Buehrer', + 'Capelli', + 'Gersten', + 'Hambly', + 'Haymaker', + 'Kosar', + 'Lahaie', + 'Lecrone', + 'Lippy', + 'Pohle', + 'Shimmel', + 'Viall', + 'Yother', + 'Deviney', + 'Kosiba', + 'Wiederholt', + 'Sivley', + 'Wheelis', + 'Kanipe', + 'Braz', + 'Peacher', + 'Quadri', + 'Hancox', + 'Paye', + 'Curlin', + 'Broden', + 'Mckeller', + 'Baltodano', + 'Baquero', + 'Bolek', + 'Brede', + 'Bulson', + 'Christmann', + 'Cisler', + 'Delio', + 'Duffee', + 'Duzan', + 'Kuschel', + 'Mohon', + 'Nedrow', + 'Sengupta', + 'Timpe', + 'Veeder', + 'Zollner', + 'Zummo', + 'Hribar', + 'Laredo', + 'Mcdivitt', + 'Nazari', + 'Davern', + 'Heizer', + 'Orejel', + 'Haggett', + 'Flore', + 'Soley', + 'Bardell', + 'Comegys', + 'Bessent', + 'Shaheed', + 'Brugman', + 'Choudhary', + 'Fehl', + 'Fogt', + 'Heckmann', + 'Iacobucci', + 'Klaver', + 'Lumbert', + 'Mussman', + 'Pierotti', + 'Pihl', + 'Sandrock', + 'Scritchfield', + 'Siefken', + 'Stavropoulos', + 'Thomley', + 'Zenker', + 'Enke', + 'Knoke', + 'Rung', + 'Mikita', + 'Kunkler', + 'Deskin', + 'Egnor', + 'Vader', + 'Allers', + 'Pi', + 'Sproull', + 'Peller', + 'Kendell', + 'Jinkins', + 'Iglehart', + 'Brookens', + 'Darrough', + 'Winzer', + 'Amenta', + 'Aughenbaugh', + 'Barnick', + 'Conaty', + 'Eichmann', + 'Gilday', + 'Guhl', + 'Koskela', + 'Makuch', + 'Osoria', + 'Pujols', + 'Reinsch', + 'Reiswig', + 'Rosebrock', + 'Sahli', + 'Seitzinger', + 'Shermer', + 'Vasbinder', + 'Zanghi', + 'Flahive', + 'Mieczkowski', + 'Osmundson', + 'Willmann', + 'Agramonte', + 'Aven', + 'Vanderzee', + 'Fraher', + 'Kannan', + 'Shira', + 'Zetina', + 'Gilden', + 'Hingle', + 'Boutros', + 'Scutt', + 'Foree', + 'Gillins', + 'Screen', + 'Birden', + 'Guinyard', + 'Berreth', + 'Bertini', + 'Bousman', + 'Butchko', + 'Caras', + 'Donoso', + 'Gavilanes', + 'Karow', + 'Kouri', + 'Rediger', + 'Rininger', + 'Rosecrans', + 'Toops', + 'Vigliotti', + 'Cancio', + 'Karger', + 'Milholland', + 'Thielke', + 'Amster', + 'Rosch', + 'Elks', + 'Vasco', + 'Doshier', + 'Belasco', + 'Lean', + 'Dickason', + 'Suitt', + 'Tipler', + 'Obey', + 'Crear', + 'Redic', + 'Agredano', + 'Amarillas', + 'Arnesen', + 'Celedon', + 'Clapsaddle', + 'Coveney', + 'Demorest', + 'Gleich', + 'Guenthner', + 'Haverland', + 'Jaffee', + 'Kusek', + 'Manni', + 'Mysliwiec', + 'Nakama', + 'Ngan', + 'Ohmer', + 'Romanoff', + 'Salaiz', + 'Zeiders', + 'Bartholow', + 'Budke', + 'Centanni', + 'Koppelman', + 'Liberti', + 'Gatta', + 'Lovegrove', + 'Maggs', + 'Malay', + 'Blind', + 'Kerman', + 'Frans', + 'Rendleman', + 'Tyrone', + 'Ambers', + 'Rambert', + 'Killings', + 'Balicki', + 'Bohac', + 'Brisbois', + 'Cervone', + 'Curtner', + 'Ertle', + 'Fantozzi', + 'Feger', + 'Fineman', + 'Garate', + 'Goldy', + 'Gudmundson', + 'Harcrow', + 'Herdt', + 'Klapp', + 'Mirra', + 'Radu', + 'Saiki', + 'Unser', + 'Valko', + 'Verhoff', + 'Candelas', + 'Ireton', + 'Vanhuss', + 'Wierman', + 'Zawistowski', + 'Geiman', + 'Mess', + 'Full', + 'Fuertes', + 'Derickson', + 'Mccole', + 'Godden', + 'Mizzell', + 'Sane', + 'Shirer', + 'Fickling', + 'Marcelle', + 'Tramble', + 'Cappelletti', + 'Catterton', + 'Champeau', + 'Czyzewski', + 'Dirusso', + 'Herget', + 'Heupel', + 'Hinchliffe', + 'Levitsky', + 'Maheu', + 'Nakao', + 'Petsche', + 'Pilkenton', + 'Raska', + 'Rief', + 'Scheidegger', + 'Schmeltzer', + 'Sherlin', + 'Skarda', + 'Strassburg', + 'Sundaram', + 'Wuertz', + 'Bonanni', + 'Montante', + 'Ottesen', + 'Nading', + 'Bram', + 'Debell', + 'Sia', + 'Latch', + 'Largen', + 'Nack', + 'Smillie', + 'Debold', + 'Bruer', + 'Steedley', + 'Mckinny', + 'Radney', + 'Amadio', + 'Bearman', + 'Canny', + 'Cansino', + 'Cupo', + 'Ekstrand', + 'Forrer', + 'Imm', + 'Kawano', + 'Klingaman', + 'Kovacevich', + 'Lukasiewicz', + 'Mcdermid', + 'Michon', + 'Mincks', + 'Piano', + 'Ronayne', + 'Schaum', + 'Sciandra', + 'Villafan', + 'Wolin', + 'Schrager', + 'Strawderman', + 'Hable', + 'Skees', + 'Persky', + 'Defore', + 'Edmonston', + 'Base', + 'Barrell', + 'Cressey', + 'Husser', + 'Matin', + 'Mckennon', + 'Barak', + 'Buffone', + 'Clemence', + 'Delaguila', + 'Eberwein', + 'Eichholz', + 'Faraone', + 'Herington', + 'Kempa', + 'Kenefick', + 'Lahaye', + 'Larusso', + 'Osterloh', + 'Pfluger', + 'Pomponio', + 'Shiu', + 'Stokke', + 'Trembly', + 'Weck', + 'Alire', + 'Babayan', + 'Hustad', + 'Stumph', + 'Zwiebel', + 'Wicke', + 'Brauch', + 'Milos', + 'Haggart', + 'Mento', + 'Kennamer', + 'Thibeau', + 'Winge', + 'Lords', + 'Debaun', + 'Haw', + 'Mould', + 'Elison', + 'Etling', + 'Froemming', + 'Ghazarian', + 'Justesen', + 'Kawai', + 'Lensing', + 'Lindhorst', + 'Poveda', + 'Rabadan', + 'Vigeant', + 'Warnken', + 'Bermel', + 'Manry', + 'Suppes', + 'Stauder', + 'Dayley', + 'Lose', + 'Tappe', + 'Harle', + 'Mcquain', + 'Bettes', + 'Carline', + 'Cordner', + 'Habeeb', + 'Sisney', + 'Kyer', + 'Bruins', + 'Prosise', + 'Molton', + 'Blye', + 'Mccuin', + 'Babler', + 'Caiazzo', + 'Cereceres', + 'Ciaramitaro', + 'Corkran', + 'Crawshaw', + 'Degan', + 'Dunlavy', + 'Gronewold', + 'Hartner', + 'Kornacki', + 'Lapolla', + 'Mountz', + 'Mumpower', + 'Orefice', + 'Prats', + 'Repasky', + 'Schlee', + 'Sekhon', + 'Stanich', + 'Yilmaz', + 'Desisto', + 'Hanko', + 'Nichter', + 'Risenhoover', + 'Tomasso', + 'Blome', + 'Carda', + 'Ebrahimi', + 'Devor', + 'Pappa', + 'Caravello', + 'Lunday', + 'Slim', + 'Praytor', + 'Pickerel', + 'Wahab', + 'Breeland', + 'Flowe', + 'Brodnax', + 'Monds', + 'Sylla', + 'Bekele', + 'Mozee', + 'Beechy', + 'Birky', + 'Dellavalle', + 'Delmastro', + 'Dematteis', + 'Eckberg', + 'Eisenbraun', + 'Englehardt', + 'Fazzio', + 'Gedney', + 'Hana', + 'Keeran', + 'Lallier', + 'Martenson', + 'Mcelheny', + 'Paar', + 'Suski', + 'Vossen', + 'Westergaard', + 'Westermann', + 'Wiemann', + 'Golz', + 'Lofquist', + 'Pracht', + 'Tifft', + 'Ruhnke', + 'Schnider', + 'How', + 'Knaggs', + 'Bleck', + 'Whitelock', + 'Berringer', + 'Clepper', + 'Birkhead', + 'Pilson', + 'Inabinet', + 'Gentles', + 'Respress', + 'Crumble', + 'Bandera', + 'Bartunek', + 'Buerkle', + 'Dulong', + 'Eisinger', + 'Favero', + 'Giusto', + 'Guisinger', + 'Kiddy', + 'Krisher', + 'Lounsberry', + 'Morikawa', + 'Mowdy', + 'Penaflor', + 'Picariello', + 'Quirion', + 'Scali', + 'Scheibel', + 'Schlitt', + 'Sermeno', + 'Thalman', + 'Barraclough', + 'Boshart', + 'Glatfelter', + 'Hjelm', + 'Horlacher', + 'Muratalla', + 'Schepp', + 'Fogerty', + 'Mulero', + 'Manner', + 'Creecy', + 'Leftridge', + 'Ancira', + 'Anselmi', + 'Blew', + 'Coykendall', + 'Dembinski', + 'Emmerling', + 'Fawver', + 'Giard', + 'Heinzen', + 'Kasson', + 'Linam', + 'Lofaro', + 'Magnotta', + 'Pitzen', + 'Ripa', + 'Skowronek', + 'Sliter', + 'Stauch', + 'Szczepaniak', + 'Yerian', + 'Baccam', + 'Berres', + 'Helstrom', + 'Kocurek', + 'Kostelecky', + 'Corkins', + 'Fesperman', + 'Gibble', + 'Liranzo', + 'Karan', + 'Lavely', + 'Yorks', + 'Lisenbee', + 'Jerger', + 'Cockroft', + 'Brodhead', + 'Weathersbee', + 'Salih', + 'Pore', + 'Melbourne', + 'Code', + 'Scotton', + 'Addie', + 'Snipe', + 'Cuffie', + 'Haynesworth', + 'Borawski', + 'Borchard', + 'Cacciola', + 'Dedic', + 'Grzyb', + 'Hecox', + 'Horacek', + 'Nierman', + 'Nofziger', + 'Raup', + 'Rissler', + 'Segler', + 'Serviss', + 'Soon', + 'Tesmer', + 'Campanaro', + 'Curnutte', + 'Rabold', + 'Schreyer', + 'Siebenaler', + 'Zenteno', + 'Deveney', + 'Kuchera', + 'Ruden', + 'Skaff', + 'Sciulli', + 'Howeth', + 'Hanly', + 'Gola', + 'Forkner', + 'Rosene', + 'Beeker', + 'Mazo', + 'Lambson', + 'Younis', + 'Batch', + 'Ayo', + 'Ackles', + 'Hansbrough', + 'Terrance', + 'Bacani', + 'Cracraft', + 'Ebben', + 'Falzarano', + 'Ferreras', + 'Hovatter', + 'Jaskiewicz', + 'Killpack', + 'Kwasniewski', + 'Mahnken', + 'Natera', + 'Noboa', + 'Rapson', + 'Raybuck', + 'Shima', + 'Vahle', + 'Sheeks', + 'Laker', + 'Krok', + 'Debo', + 'Oberly', + 'Chelf', + 'Catala', + 'Airey', + 'Osten', + 'Golay', + 'Eliot', + 'Lebert', + 'Swaggerty', + 'Hue', + 'Seavers', + 'Bomer', + 'Bouyer', + 'Andazola', + 'Blancarte', + 'Brierly', + 'Centofanti', + 'Dalesandro', + 'Dickstein', + 'Kalas', + 'Langman', + 'Mouradian', + 'Okubo', + 'Overbaugh', + 'Popek', + 'Runnion', + 'Sannes', + 'Schamber', + 'Silfies', + 'Sinko', + 'Sit', + 'Cerrillo', + 'Gayler', + 'Kauth', + 'Culkin', + 'Peers', + 'Spidle', + 'Ballon', + 'Rasmus', + 'Queenan', + 'Reynaud', + 'Ambroise', + 'Mcclenton', + 'Adelmann', + 'Avellino', + 'Fickle', + 'Humm', + 'Hussong', + 'Iturralde', + 'Kritzer', + 'Lautzenheiser', + 'Linsky', + 'Malarkey', + 'Mallia', + 'Marban', + 'Mccance', + 'Nawaz', + 'Pallone', + 'Rindfleisch', + 'Schmall', + 'Sowle', + 'Stanco', + 'Whelpley', + 'Winning', + 'Kopczynski', + 'Pickup', + 'Tsou', + 'Phebus', + 'Munter', + 'Sisko', + 'Fico', + 'Mosco', + 'Rani', + 'Kon', + 'Baggott', + 'Brom', + 'Valerius', + 'Fines', + 'Megee', + 'Salsberry', + 'Sheff', + 'Mourning', + 'Archambeault', + 'Bhatnagar', + 'Budreau', + 'Dieffenbach', + 'Gildner', + 'Hevener', + 'Hippler', + 'Jonker', + 'Keef', + 'Kirlin', + 'Litvak', + 'Liz', + 'Mulhearn', + 'Popal', + 'Samaha', + 'Schwartzberg', + 'Sotello', + 'Weiskopf', + 'Neitzke', + 'Strelow', + 'Nitsch', + 'Lynne', + 'Olver', + 'Bange', + 'Boot', + 'Carmine', + 'Bellville', + 'Lafitte', + 'Condry', + 'Mccotter', + 'Spruiell', + 'Moman', + 'Legree', + 'Bongard', + 'Deiss', + 'Devoy', + 'Gusler', + 'Ianni', + 'Kolker', + 'Lagomarsino', + 'Leier', + 'Marbut', + 'Minsky', + 'Okumura', + 'Roza', + 'Siemon', + 'Vescio', + 'Wirkus', + 'Huizinga', + 'Lazalde', + 'Morici', + 'Ungaro', + 'Detamore', + 'Meer', + 'Erman', + 'Sherrow', + 'Laforte', + 'Pellman', + 'Bostock', + 'Lender', + 'Peagler', + 'Rhem', + 'Brisbon', + 'Angers', + 'Azbill', + 'Busto', + 'Coggeshall', + 'Cucci', + 'Defino', + 'Duey', + 'Fecht', + 'Grudzinski', + 'Guarneri', + 'Huesca', + 'Kolbeck', + 'Mennella', + 'Nishi', + 'Ohaver', + 'Porth', + 'Romanello', + 'Serrata', + 'Thoele', + 'Thornsbury', + 'Ulsh', + 'Vanderlinde', + 'Weninger', + 'Bonaventura', + 'Cura', + 'Filley', + 'Grabinski', + 'Kloc', + 'Kulinski', + 'Maruca', + 'Dantoni', + 'Grohman', + 'Starbird', + 'Rach', + 'Asman', + 'Mosso', + 'Slaney', + 'Kall', + 'Nevil', + 'Blann', + 'Frear', + 'Mosey', + 'Wrench', + 'Balkcom', + 'Liburd', + 'Yeboah', + 'Abbatiello', + 'Creviston', + 'Dunivan', + 'Durnin', + 'Eckerman', + 'Fennimore', + 'Gohlke', + 'Holtan', + 'Kochevar', + 'Kraushaar', + 'Landino', + 'Maack', + 'Montefusco', + 'Noguchi', + 'Norgard', + 'Olafson', + 'Paulick', + 'Petropoulos', + 'Principato', + 'Qazi', + 'Sammis', + 'Sida', + 'Sorum', + 'Vandal', + 'Vertrees', + 'Votta', + 'Wiesman', + 'Fleagle', + 'Panaro', + 'Stolarski', + 'Ogborn', + 'Petta', + 'Annett', + 'Campas', + 'Xing', + 'Lorey', + 'Restaino', + 'Forgue', + 'Rourk', + 'Modisette', + 'Aris', + 'Vandunk', + 'Dia', + 'Alverio', + 'Ancell', + 'Bieler', + 'Bouwman', + 'Campillo', + 'Cebreros', + 'Chant', + 'Cira', + 'Cragun', + 'Geppert', + 'Hemmert', + 'Kister', + 'Luger', + 'Ojala', + 'Pfeifle', + 'Piechocki', + 'Saldarriaga', + 'Skoda', + 'Vangorden', + 'Winberry', + 'Zeeb', + 'Gehm', + 'Oshima', + 'Tofte', + 'Tsoi', + 'Delman', + 'Harsha', + 'Finton', + 'Triola', + 'Bingle', + 'Delise', + 'Westergard', + 'Aul', + 'Celia', + 'Headings', + 'Mates', + 'Coste', + 'Venus', + 'Shearn', + 'Adell', + 'Minnifield', + 'Baxa', + 'Cieri', + 'Coppens', + 'Delahoz', + 'Fratus', + 'Gribbins', + 'Homann', + 'Ilg', + 'Majchrzak', + 'Mcclard', + 'Podolak', + 'Pollan', + 'Savio', + 'Schloemer', + 'Sesma', + 'Tilbury', + 'Torrico', + 'Vanduyn', + 'Eisert', + 'Levalley', + 'Silversmith', + 'Zanoni', + 'Grupe', + 'Marmolejos', + 'Marsch', + 'Martes', + 'Gorley', + 'Furbush', + 'Hughlett', + 'Stcyr', + 'Faustin', + 'Bushaw', + 'Cerbone', + 'Equihua', + 'Fiorella', + 'Ganzer', + 'Gugel', + 'Hladik', + 'Kalra', + 'Leuenberger', + 'Lusardi', + 'Nogales', + 'Schifano', + 'Swalley', + 'Tangney', + 'Zakarian', + 'Arenz', + 'Bottcher', + 'Gervasio', + 'Peschel', + 'Potteiger', + 'Teruya', + 'Tullier', + 'Lenhard', + 'Brusseau', + 'Streett', + 'Loan', + 'Fahmy', + 'Broadfoot', + 'Shugars', + 'Wilshire', + 'Mohabir', + 'Baye', + 'Sean', + 'Caruth', + 'Arroyos', + 'Campise', + 'Capparelli', + 'Desanti', + 'Dunsworth', + 'Fasching', + 'Heldman', + 'Keagle', + 'Kulesa', + 'Lawrenz', + 'Monhollen', + 'Niekamp', + 'Nucci', + 'Ostman', + 'Salzmann', + 'Schemmel', + 'Selin', + 'Stencel', + 'Zilka', + 'Friesner', + 'Onstad', + 'Poovey', + 'Squillante', + 'Tullo', + 'Uriegas', + 'Vigilante', + 'Lasswell', + 'Navedo', + 'Dunnagan', + 'Pevey', + 'Santino', + 'Waldren', + 'Leven', + 'Stinnette', + 'Eleazer', + 'Ragas', + 'Cockfield', + 'Lafontant', + 'Babinski', + 'Balash', + 'Hadler', + 'Kantz', + 'Latini', + 'Lavy', + 'Mally', + 'Maurin', + 'Mifsud', + 'Miguez', + 'Muma', + 'Needle', + 'Orrico', + 'Zalazar', + 'Chinen', + 'Coluccio', + 'Gibboney', + 'Knapke', + 'Moczygemba', + 'Leonguerrero', + 'Punzalan', + 'Lortz', + 'Rosel', + 'Mcclaran', + 'Weatherhead', + 'Mcgurn', + 'Sanville', + 'Goe', + 'Phang', + 'Briskey', + 'Bluitt', + 'Hapner', + 'Lamadrid', + 'Leuthold', + 'Litchford', + 'Scaduto', + 'Smoyer', + 'Stonehouse', + 'Streng', + 'Susman', + 'Swoyer', + 'Tempesta', + 'Tiedt', + 'Politi', + 'Ruotolo', + 'Schwendeman', + 'Siegenthaler', + 'Streff', + 'Strite', + 'Kroft', + 'Lewey', + 'Silbert', + 'Frie', + 'Bentson', + 'Coin', + 'Lupe', + 'Mousa', + 'Syler', + 'Fester', + 'Tenny', + 'Surgeon', + 'Blowe', + 'Metellus', + 'Borboa', + 'Danker', + 'Ferch', + 'Fritzsche', + 'Gudiel', + 'Kilmartin', + 'Nieland', + 'Soffer', + 'Yescas', + 'Chappelear', + 'Hincapie', + 'Landowski', + 'Barfoot', + 'Hesketh', + 'Mittelman', + 'Escorcia', + 'Meetze', + 'Coral', + 'Huddleson', + 'Hoo', + 'Googe', + 'Munir', + 'Reine', + 'Studstill', + 'Swims', + 'Ganaway', + 'Daise', + 'Blando', + 'Bream', + 'Cangemi', + 'Dicola', + 'Difalco', + 'Gleim', + 'Goerke', + 'Jauch', + 'Lashway', + 'Mckinlay', + 'Mura', + 'Polsky', + 'Roehrich', + 'Schwalbach', + 'Tegtmeier', + 'Theel', + 'Wuthrich', + 'Yabut', + 'Zara', + 'Ardizzone', + 'Blasius', + 'Deramo', + 'Heffern', + 'Rickels', + 'Wojtas', + 'Bue', + 'Garant', + 'Kitagawa', + 'Vorhees', + 'Randa', + 'Seider', + 'Bi', + 'Womac', + 'Santerre', + 'Mesmer', + 'Bailly', + 'Argue', + 'Spidell', + 'Manu', + 'General', + 'Exantus', + 'Neloms', + 'Piggee', + 'Agcaoili', + 'Ambrosini', + 'Balleza', + 'Bhavsar', + 'Brandstetter', + 'Cascone', + 'Deyton', + 'Fette', + 'Gershman', + 'Hanni', + 'Hitchner', + 'Manthe', + 'Marengo', + 'Ockerman', + 'Pergola', + 'Ratterree', + 'Shober', + 'Swezey', + 'Vadala', + 'Waszak', + 'Wishard', + 'Zhuang', + 'Bobst', + 'Filippini', + 'Giardino', + 'Johanning', + 'Kloepfer', + 'Dahan', + 'Rahmani', + 'Hett', + 'Sha', + 'Spaugh', + 'Darner', + 'Dagen', + 'Gaier', + 'Musco', + 'Holling', + 'Keahey', + 'Merricks', + 'Nur', + 'Andrick', + 'Demauro', + 'Haury', + 'Hsiung', + 'Kotarski', + 'Kriesel', + 'Leleux', + 'Nazar', + 'Oganesyan', + 'Polivka', + 'Sansoucie', + 'Serafino', + 'Stammer', + 'Tamm', + 'Wachowiak', + 'Zinda', + 'Goedde', + 'Pedregon', + 'Snader', + 'Witczak', + 'Kem', + 'Prabhu', + 'Purtle', + 'Nola', + 'Om', + 'Finster', + 'Bryans', + 'Mateus', + 'Bour', + 'Santy', + 'Mola', + 'Guile', + 'Denne', + 'Bol', + 'Mont', + 'Perro', + 'Haji', + 'Swinger', + 'Mitchelle', + 'Creary', + 'Leeks', + 'Barsotti', + 'Bolender', + 'Dohner', + 'Federman', + 'Lancour', + 'Lueken', + 'Pettinger', + 'Rathmann', + 'Schiess', + 'Schulenberg', + 'Troyan', + 'Dafoe', + 'Delahunt', + 'Domagala', + 'Ganske', + 'Grasmick', + 'Guinther', + 'Hlavac', + 'Klumb', + 'Susko', + 'Vanhandel', + 'Burget', + 'Thaker', + 'Winker', + 'Castellucci', + 'Guerette', + 'Garde', + 'Busher', + 'Usery', + 'Braker', + 'Blan', + 'Goar', + 'Loiseau', + 'Anderberg', + 'Bamber', + 'Biagini', + 'Dack', + 'Groeneveld', + 'Habig', + 'Howk', + 'Kutsch', + 'Mcgloin', + 'Nevares', + 'Piedrahita', + 'Puffenbarger', + 'Racer', + 'Stanaland', + 'Turck', + 'Vanvleck', + 'Velardi', + 'Verhoeven', + 'Wernick', + 'Wherley', + 'Zamzow', + 'Binegar', + 'Kaluza', + 'Kudrna', + 'Marbach', + 'Schwichtenberg', + 'Chay', + 'Lanthier', + 'Balling', + 'Parcher', + 'Venner', + 'Nolette', + 'Quant', + 'Grierson', + 'Quest', + 'Level', + 'Birkner', + 'Evancho', + 'Grinde', + 'Horiuchi', + 'Hoselton', + 'Kuk', + 'Maiello', + 'Matuska', + 'Melito', + 'Northey', + 'Pallante', + 'Porzio', + 'Rad', + 'Rizzolo', + 'Thull', + 'Urenda', + 'Dalfonso', + 'Harbold', + 'Kemerer', + 'Knapton', + 'Meeder', + 'Ruckle', + 'Segui', + 'Behne', + 'Bamburg', + 'Galen', + 'Hallen', + 'Herandez', + 'Chittick', + 'Deshon', + 'Verrier', + 'Sorel', + 'Neylon', + 'Thatch', + 'Bayly', + 'Beever', + 'Galka', + 'Gruhn', + 'Gsell', + 'Happe', + 'Hovan', + 'Marter', + 'Matarese', + 'Mellema', + 'Ollila', + 'Schempp', + 'Serda', + 'Skenandore', + 'Stemper', + 'Toupin', + 'Vandeven', + 'Yauger', + 'Koenigs', + 'Mullendore', + 'Ouellet', + 'Sullenberger', + 'Julson', + 'Pelot', + 'Clamp', + 'Berte', + 'Beese', + 'Matkin', + 'Erie', + 'Rosenburg', + 'Reap', + 'Stelle', + 'Rayon', + 'Hoit', + 'Hollyfield', + 'Kindall', + 'Agent', + 'Glascoe', + 'Holts', + 'Wynder', + 'Balderston', + 'Bernardy', + 'Blehm', + 'Casebeer', + 'Emler', + 'Farrugia', + 'Guzzardo', + 'Johnsrud', + 'Maffeo', + 'Mccartan', + 'Redburn', + 'Reesman', + 'Savas', + 'Shamoon', + 'Shown', + 'Spinale', + 'Tabaka', + 'Wedell', + 'Armato', + 'Bassford', + 'Bungard', + 'Faerber', + 'Freet', + 'Oesterle', + 'Vandeberg', + 'Bacha', + 'Stemm', + 'Edgett', + 'Karrick', + 'Girten', + 'Orgill', + 'Meridith', + 'Cullom', + 'Hennington', + 'Minns', + 'Appleberry', + 'Abare', + 'Annen', + 'Beierle', + 'Berish', + 'Cracchiolo', + 'Dilullo', + 'Kehm', + 'Kuhne', + 'Modglin', + 'Norland', + 'Petruzzelli', + 'Schabel', + 'Stauffacher', + 'Villena', + 'Wageman', + 'Willden', + 'Faiella', + 'Mangiaracina', + 'Petralia', + 'Witwer', + 'Tropp', + 'Bores', + 'Burkel', + 'Stanifer', + 'Teele', + 'Cornick', + 'Credit', + 'Dorvil', + 'Bonillas', + 'Callinan', + 'Colleran', + 'Finer', + 'Krach', + 'Lubas', + 'Lutman', + 'Marien', + 'Mccort', + 'Merica', + 'Mies', + 'Nicotra', + 'Novosad', + 'Priem', + 'Ramakrishnan', + 'Zolman', + 'Deitsch', + 'Georgi', + 'Haberstroh', + 'Kofoed', + 'Kreischer', + 'Nazareno', + 'Norkus', + 'Steimle', + 'Fellin', + 'Ghanem', + 'Kosch', + 'Pages', + 'Balthazor', + 'Corte', + 'Hoh', + 'Shrewsberry', + 'Beharry', + 'Waight', + 'Leconte', + 'Clowney', + 'Tesfaye', + 'Andis', + 'Brosch', + 'Bruckman', + 'Carducci', + 'Erbes', + 'Ferreiro', + 'Gatten', + 'Heggen', + 'Kackley', + 'Klamm', + 'Korff', + 'Lehane', + 'Mech', + 'Montanari', + 'Pousson', + 'Soderholm', + 'Strey', + 'Upp', + 'Wahlen', + 'Cedrone', + 'Steuber', + 'Vonfeldt', + 'Deridder', + 'Shams', + 'Barnas', + 'Bake', + 'Brownrigg', + 'Donohoo', + 'Mccorry', + 'Spruce', + 'Masden', + 'Porchia', + 'Fofana', + 'Bless', + 'Caler', + 'Calva', + 'Carnero', + 'Chakraborty', + 'Clenney', + 'Dockendorf', + 'Dziak', + 'Errickson', + 'Ewoldt', + 'Klippel', + 'Krass', + 'Luebbe', + 'Parlett', + 'Paternostro', + 'Peterka', + 'Petitti', + 'Puthoff', + 'Wessman', + 'Brossman', + 'Glotfelty', + 'Grabau', + 'Kortz', + 'Sienko', + 'Yonan', + 'Fakhoury', + 'Bunney', + 'Sillas', + 'Guerry', + 'Sedwick', + 'Okey', + 'Virgo', + 'Babers', + 'Casali', + 'Chiquito', + 'Correnti', + 'Doverspike', + 'Fryberger', + 'Golas', + 'Golob', + 'Hufstetler', + 'Inoa', + 'Lasser', + 'Nesheim', + 'Peveto', + 'Reckner', + 'Rydzewski', + 'Shartzer', + 'Smouse', + 'Tipple', + 'Wantland', + 'Wolfert', + 'Yordy', + 'Zuleta', + 'Heimerl', + 'Mccarren', + 'Cabeza', + 'Neice', + 'Kassem', + 'Hodgen', + 'Charrier', + 'Duggar', + 'Blacksmith', + 'Cush', + 'Trunnell', + 'Laventure', + 'Salahuddin', + 'Batalla', + 'Brahmbhatt', + 'Breslow', + 'Cua', + 'Deatley', + 'Digrazia', + 'Divirgilio', + 'Falin', + 'Freiberger', + 'Gladish', + 'Holyoak', + 'Lazos', + 'Loader', + 'Mcclafferty', + 'Meloni', + 'Muhr', + 'Salzwedel', + 'Schaab', + 'Shehadeh', + 'Suresh', + 'Verdusco', + 'Younglove', + 'Damman', + 'Fulco', + 'Neikirk', + 'Laver', + 'Biro', + 'Shill', + 'Labarr', + 'Kari', + 'Mcclory', + 'Torelli', + 'Knock', + 'Dormer', + 'Papin', + 'Stoneham', + 'Weathington', + 'Albus', + 'Andel', + 'Banville', + 'Cassens', + 'Chalifoux', + 'Dellaquila', + 'Depauw', + 'Deschene', + 'Genung', + 'Greider', + 'Luhman', + 'Mastropietro', + 'Mignogna', + 'Pisarski', + 'Terrien', + 'Thomure', + 'Tornabene', + 'Beheler', + 'Chimento', + 'Engelbert', + 'Gambone', + 'Goettl', + 'Jasperson', + 'Kovalenko', + 'Infinger', + 'Timbs', + 'Dasgupta', + 'Purdon', + 'Velie', + 'Eland', + 'Ankrum', + 'Narain', + 'Mcfarling', + 'Creagh', + 'Bunyan', + 'Rattigan', + 'Reddix', + 'Aumann', + 'Beilfuss', + 'Bogosian', + 'Bramel', + 'Burlingham', + 'Cruzan', + 'Demel', + 'Dorff', + 'Figley', + 'Friesz', + 'Huffstutler', + 'Mcdaris', + 'Meinecke', + 'Moench', + 'Newville', + 'Normile', + 'Pfund', + 'Pilar', + 'Seckman', + 'Szoke', + 'Zyla', + 'Freilich', + 'Hammerle', + 'Kopel', + 'Liskey', + 'Mesina', + 'Schlicher', + 'Dalen', + 'Bettin', + 'Malanga', + 'Dern', + 'Tuckey', + 'Warder', + 'Harren', + 'Siner', + 'Mahdi', + 'Ahmann', + 'Allor', + 'Claywell', + 'Corkill', + 'Follansbee', + 'Iseman', + 'Lawter', + 'Myslinski', + 'Sauser', + 'Tornatore', + 'Bhasin', + 'Governale', + 'Karstens', + 'Klocek', + 'Stempien', + 'Petrino', + 'Kohlmeier', + 'Igou', + 'Sari', + 'Mareno', + 'Bouche', + 'Romas', + 'Urey', + 'Sprott', + 'Ponzo', + 'Nevills', + 'Affolter', + 'Alleva', + 'Allgaier', + 'Azbell', + 'Branagan', + 'Fiebig', + 'Geremia', + 'Grabert', + 'Grahl', + 'Gruwell', + 'Koebel', + 'Krauter', + 'Kuhnert', + 'Kuperman', + 'Laverdiere', + 'Leuck', + 'Masella', + 'Mierzejewski', + 'Platek', + 'Samaan', + 'Selsor', + 'Vickroy', + 'Whitenack', + 'Zanella', + 'Cavagnaro', + 'Galioto', + 'Schoeneman', + 'Zanotti', + 'Bort', + 'Alpaugh', + 'Culverhouse', + 'Perona', + 'Wheelwright', + 'Amber', + 'Bradner', + 'Sedberry', + 'Goethe', + 'Swygert', + 'Nisbett', + 'Harts', + 'Pendelton', + 'Keita', + 'Addair', + 'Anania', + 'Armagost', + 'Brumett', + 'Butala', + 'Celmer', + 'Forquer', + 'Hagadorn', + 'Jalomo', + 'Koranda', + 'Lemmond', + 'Liske', + 'Mcglamery', + 'Ramiro', + 'Tickner', + 'Toso', + 'Tosti', + 'Beerbower', + 'Bichler', + 'Buege', + 'Cadotte', + 'Chiong', + 'Romberger', + 'Mandarino', + 'Deter', + 'Wallack', + 'Bligh', + 'Harer', + 'Terral', + 'Hobert', + 'Doren', + 'Affleck', + 'Marquess', + 'Lewton', + 'Covel', + 'Reff', + 'Gowins', + 'Claybrooks', + 'Artiles', + 'Brunelli', + 'Campusano', + 'Deshaies', + 'Elpers', + 'Fait', + 'Heathcote', + 'Katayama', + 'Landreneau', + 'Nardelli', + 'Padovano', + 'Pendry', + 'Santillano', + 'Ubaldo', + 'Wurz', + 'Bathke', + 'Fillers', + 'Reitano', + 'Patrone', + 'Mountford', + 'Farran', + 'Burdo', + 'Danish', + 'Windell', + 'Amrine', + 'Pilgreen', + 'Pross', + 'Bowery', + 'Girdner', + 'Stockley', + 'Chisom', + 'Bigos', + 'Cavallero', + 'Choma', + 'Chorba', + 'Doubek', + 'Eynon', + 'Fitzmorris', + 'Gergely', + 'Hilsabeck', + 'Hime', + 'Kafer', + 'Kilday', + 'Lairson', + 'Mccanless', + 'Meenan', + 'Mossburg', + 'Muscato', + 'Raap', + 'Ramp', + 'Reali', + 'Reinard', + 'Rivadeneira', + 'Schwenn', + 'Serbin', + 'Soeder', + 'Wagle', + 'Jablonowski', + 'Vanni', + 'Grapes', + 'Hilleary', + 'Mondor', + 'Natalie', + 'Seat', + 'Heming', + 'Waide', + 'Haverly', + 'Eva', + 'Marshman', + 'Mais', + 'Portlock', + 'Scoby', + 'Sharps', + 'Buday', + 'Bumbalough', + 'Burback', + 'Carano', + 'Eustis', + 'Flaim', + 'Fraticelli', + 'Grimme', + 'Heape', + 'Hoaglin', + 'Kreuser', + 'Odgers', + 'Pastorius', + 'Pavek', + 'Rogoff', + 'Skorupski', + 'Stene', + 'Tomasino', + 'Varble', + 'Vasek', + 'Woolums', + 'Arcaro', + 'Graley', + 'Larkey', + 'Ortlieb', + 'Piccone', + 'Verhey', + 'Inch', + 'Laroe', + 'Brockmeier', + 'Familia', + 'Soll', + 'Duplechin', + 'Blevens', + 'Gell', + 'Hipkins', + 'Kleinpeter', + 'Swindall', + 'Sabir', + 'Kinloch', + 'Muldrew', + 'Clausell', + 'Bouch', + 'Casciano', + 'Dewhirst', + 'Draney', + 'Fourman', + 'Fuente', + 'Ganci', + 'Gentzler', + 'Gerhold', + 'Ingoglia', + 'Jerabek', + 'Keisling', + 'Larivee', + 'Negro', + 'Pelchat', + 'Quilty', + 'Reinig', + 'Rubeck', + 'Rudick', + 'Rulli', + 'Spagnoli', + 'Wiltsie', + 'Vitolo', + 'Neuhauser', + 'Khurana', + 'Vint', + 'Kant', + 'Nead', + 'Deroy', + 'Ransford', + 'Stromer', + 'Buley', + 'Bloxom', + 'Rieves', + 'Bastos', + 'Deckman', + 'Duenes', + 'Hessling', + 'Kresse', + 'Langdale', + 'Penberthy', + 'Polyak', + 'Sagun', + 'Salehi', + 'Sas', + 'Soja', + 'Spieth', + 'Verhulst', + 'Walen', + 'Woodling', + 'Acierno', + 'Bergsma', + 'Biskup', + 'Buonomo', + 'Gores', + 'Koffman', + 'Redder', + 'Ishak', + 'Billow', + 'Ratledge', + 'Widder', + 'Margerum', + 'Bussing', + 'Caccamo', + 'Carozza', + 'Cwik', + 'Forner', + 'Goeden', + 'Greninger', + 'Hartenstein', + 'Hermida', + 'Krutz', + 'Kubes', + 'Kulow', + 'Lynott', + 'Mank', + 'Meinders', + 'Mikrut', + 'Moots', + 'Patek', + 'Pogorzelski', + 'Reinstein', + 'Ruiter', + 'Rupard', + 'Salvia', + 'Sissom', + 'Sligar', + 'Spendlove', + 'Vian', + 'Wissing', + 'Witucki', + 'Brossart', + 'Warhurst', + 'Staron', + 'Gilly', + 'Borck', + 'Mccarn', + 'Stanbery', + 'Aydelotte', + 'Etters', + 'Rho', + 'Menzer', + 'Knoble', + 'Luallen', + 'Meda', + 'Myre', + 'Nevils', + 'Seide', + 'Rouser', + 'Bernas', + 'Bressette', + 'Dohn', + 'Domina', + 'Filion', + 'Fossen', + 'Grunder', + 'Hofland', + 'Larranaga', + 'Launius', + 'Lento', + 'Mohrmann', + 'Papenfuss', + 'Polcyn', + 'Pollina', + 'Reinheimer', + 'Rueb', + 'Sacher', + 'Sauseda', + 'Whitwell', + 'Caspers', + 'Dejager', + 'Kastelic', + 'Kildow', + 'Sappenfield', + 'Schultes', + 'Tucciarone', + 'Gogan', + 'Sarti', + 'Percle', + 'Cagney', + 'Wasley', + 'Getts', + 'Sahm', + 'Brandle', + 'Osbon', + 'Febres', + 'Billett', + 'Pall', + 'Spearing', + 'Thursby', + 'Junious', + 'Allenbaugh', + 'Calamia', + 'Cregan', + 'Hostettler', + 'Leete', + 'Pirrone', + 'Ploeger', + 'Revak', + 'Sarlo', + 'Sayavong', + 'Schlichter', + 'Shonkwiler', + 'Soots', + 'Spak', + 'Thien', + 'Torgeson', + 'Urbanczyk', + 'Vredenburg', + 'Wormuth', + 'Yankovich', + 'Badertscher', + 'Holewinski', + 'Kalinoski', + 'Kwasny', + 'Neidert', + 'Remmel', + 'Uram', + 'Zettlemoyer', + 'Sanna', + 'Walthers', + 'Kinkaid', + 'Rummage', + 'Vane', + 'Morgen', + 'Stum', + 'Ainsley', + 'Mckelvie', + 'Barbin', + 'Shariff', + 'Blanchett', + 'Mayon', + 'Broadie', + 'Millien', + 'Azzarello', + 'Bocock', + 'Bohlander', + 'Brennecke', + 'Daman', + 'Dixit', + 'Goth', + 'Kocur', + 'Koslow', + 'Loncar', + 'Narez', + 'Oleksy', + 'Ouderkirk', + 'Rathe', + 'Sandmann', + 'Scarpino', + 'Siegman', + 'Soloway', + 'Tomeo', + 'Vantuyl', + 'Benesch', + 'Doornbos', + 'Gisler', + 'Nistler', + 'Pelzel', + 'Piecuch', + 'Schweiss', + 'Zieba', + 'Domangue', + 'Curti', + 'Iams', + 'Viger', + 'Sandefer', + 'Maybury', + 'Haneline', + 'Shappell', + 'Charlier', + 'Belardo', + 'Lynk', + 'Ocain', + 'Ismael', + 'Blacksher', + 'Lesesne', + 'Blash', + 'Fantroy', + 'Bucciarelli', + 'Deruiter', + 'Fetner', + 'Filla', + 'Frontera', + 'Furlan', + 'Goepfert', + 'Gorsline', + 'Gugino', + 'Kleis', + 'Kriger', + 'Lebarron', + 'Lesnick', + 'Losano', + 'Macquarrie', + 'Marczak', + 'Mazariego', + 'Moraes', + 'Murano', + 'Myint', + 'Philley', + 'Ruffalo', + 'Salyards', + 'Swab', + 'Trester', + 'Vlcek', + 'Abramo', + 'Kaczmarski', + 'Mastronardi', + 'Lafont', + 'Tomerlin', + 'Mchan', + 'Blanda', + 'Deandrade', + 'Klien', + 'Meno', + 'Maia', + 'Durall', + 'Lansdowne', + 'Cones', + 'Adley', + 'Taffe', + 'Ikard', + 'Sylve', + 'Bartok', + 'Farler', + 'Farnworth', + 'Gookin', + 'Guijarro', + 'Hazan', + 'Hosterman', + 'Klees', + 'Knust', + 'Leadingham', + 'Lefeber', + 'Maisch', + 'Muchmore', + 'Pini', + 'Polinsky', + 'Quakenbush', + 'Rought', + 'Ruta', + 'Tingen', + 'Urness', + 'Valade', + 'Wadle', + 'Hietala', + 'Hockenbury', + 'Ivanoff', + 'Mcevers', + 'Miyazaki', + 'Druckenmiller', + 'Neisler', + 'Vroom', + 'Berland', + 'Rizor', + 'Caris', + 'Jenison', + 'Folmer', + 'Si', + 'Pulling', + 'Houge', + 'Snuggs', + 'Enis', + 'Peeks', + 'Stacker', + 'Destin', + 'Ojo', + 'Barraco', + 'Childree', + 'Ciszewski', + 'Dicenzo', + 'Gowing', + 'Granquist', + 'Kapinos', + 'Khalili', + 'Kienitz', + 'Konrath', + 'Kosa', + 'Schilz', + 'Sealock', + 'Soucek', + 'Stefanko', + 'Trow', + 'Udy', + 'Fricano', + 'Hunnewell', + 'Sieler', + 'Stranahan', + 'Thammavongsa', + 'Zettel', + 'Cutrell', + 'Balter', + 'Clavel', + 'Thibert', + 'Ondo', + 'Senna', + 'Kun', + 'Maximo', + 'Wares', + 'Caldeira', + 'Furgerson', + 'Franklyn', + 'Christophe', + 'Bady', + 'Blanken', + 'Boike', + 'Cuen', + 'Davidian', + 'Glauser', + 'Gleave', + 'Guzy', + 'Halleck', + 'Kempfer', + 'Kenkel', + 'Kloth', + 'Knable', + 'Mcenery', + 'Pizzolato', + 'Schryver', + 'Seminara', + 'Shenoy', + 'Somera', + 'Stroop', + 'Weirick', + 'Yatsko', + 'Evanko', + 'Koegel', + 'Lastinger', + 'Schrenk', + 'Vitullo', + 'Holste', + 'Susa', + 'Pedley', + 'Cove', + 'Levett', + 'Gillyard', + 'Boeding', + 'Delpozo', + 'Denoyer', + 'Farese', + 'Floro', + 'Gavina', + 'Hargus', + 'Kisiel', + 'Konig', + 'Krotz', + 'Lundblad', + 'Masoner', + 'Mumper', + 'Nolf', + 'Sandgren', + 'Schussler', + 'Shallcross', + 'Singhal', + 'Standen', + 'Teta', + 'Vacanti', + 'Yokota', + 'Borski', + 'Filice', + 'Frankum', + 'Kleinsmith', + 'Plauche', + 'Spohr', + 'Goya', + 'Rosensteel', + 'Srey', + 'Touhey', + 'Launer', + 'Dome', + 'Mossey', + 'Mclay', + 'Sturgess', + 'Demond', + 'Buren', + 'Millin', + 'Riddles', + 'Arps', + 'Dugar', + 'Carradine', + 'Brasseur', + 'Burchill', + 'Champoux', + 'Chojnowski', + 'Cyphert', + 'Devincentis', + 'Donze', + 'Gaspari', + 'Harshberger', + 'Merchan', + 'Mulgrew', + 'Parma', + 'Pasqua', + 'Pierpoint', + 'Rozeboom', + 'Rumery', + 'Stahle', + 'Stierwalt', + 'Swander', + 'Tiegs', + 'Trabucco', + 'Withington', + 'Frericks', + 'Kilman', + 'Locastro', + 'Samonte', + 'Sanko', + 'Wisman', + 'Flecha', + 'Coplan', + 'Zafra', + 'Art', + 'Maxam', + 'Cavaness', + 'Willi', + 'Vanliew', + 'Fresh', + 'Bauserman', + 'Bergemann', + 'Buchler', + 'Curbow', + 'Dimascio', + 'Einstein', + 'Favila', + 'Galeno', + 'Granat', + 'Halteman', + 'Janczak', + 'Janicek', + 'Jundt', + 'Karren', + 'Modesitt', + 'Provance', + 'Reasons', + 'Riveron', + 'Salts', + 'Salvino', + 'Sawhney', + 'Shallenberger', + 'Sirk', + 'Tylka', + 'Baumler', + 'Mcmenamy', + 'Territo', + 'Thackeray', + 'Much', + 'Papageorge', + 'Rynders', + 'Bacigalupo', + 'Fulwider', + 'Hendricksen', + 'Lepre', + 'Mangel', + 'Dering', + 'Soda', + 'Bazar', + 'Dinning', + 'Portera', + 'Schatzman', + 'Kernodle', + 'Bashford', + 'Ferrebee', + 'Cortner', + 'Sanker', + 'Livings', + 'Jemmott', + 'Arzaga', + 'Cihak', + 'Cobarrubias', + 'Coey', + 'Coutinho', + 'Deneau', + 'Droll', + 'Hickel', + 'Hillmann', + 'Kitto', + 'Lefebre', + 'Lev', + 'Mato', + 'Mcomber', + 'Norlin', + 'Renstrom', + 'Rhyner', + 'Sacca', + 'Sangha', + 'Sutor', + 'Dwire', + 'Huyser', + 'Kressin', + 'Moilanen', + 'Picado', + 'Schmidlin', + 'Albor', + 'Zaldana', + 'Failor', + 'Dubberly', + 'Youse', + 'Mohiuddin', + 'Shiel', + 'Loran', + 'Hamme', + 'Castine', + 'Lanum', + 'Mcelderry', + 'Riggsbee', + 'Madkins', + 'Abts', + 'Bekker', + 'Boccio', + 'Florin', + 'Lienemann', + 'Madara', + 'Manganello', + 'Mcfetridge', + 'Medsker', + 'Minish', + 'Roberg', + 'Sajdak', + 'Schwall', + 'Sedivy', + 'Suto', + 'Wieberg', + 'Catherman', + 'Ficker', + 'Leckrone', + 'Lieder', + 'Rodak', + 'Tomek', + 'Everard', + 'Spong', + 'Glacken', + 'Polka', + 'Aley', + 'Farro', + 'Stanwood', + 'Petter', + 'Desrosier', + 'Kerl', + 'Goslee', + 'Burston', + 'Pennywell', + 'Birchard', + 'Federer', + 'Flicker', + 'Frangos', + 'Korhonen', + 'Kozikowski', + 'Kyger', + 'Mccoskey', + 'Standing', + 'Terada', + 'Trierweiler', + 'Trupiano', + 'Urbanowicz', + 'Viegas', + 'Ybarbo', + 'Brinlee', + 'Daddona', + 'Deisher', + 'Schwieger', + 'Farless', + 'Slaght', + 'Jarvie', + 'Hunkins', + 'Barrack', + 'Bisset', + 'Bruley', + 'Molen', + 'Milas', + 'Matts', + 'Wickware', + 'Timbers', + 'Minus', + 'Kennebrew', + 'Boorman', + 'Faughn', + 'Feight', + 'Githens', + 'Hazelrigg', + 'Hutzell', + 'Klang', + 'Kogler', + 'Lechtenberg', + 'Malachowski', + 'Orsburn', + 'Retz', + 'Saban', + 'Tak', + 'Underdahl', + 'Veldman', + 'Virnig', + 'Wanat', + 'Achord', + 'Drenth', + 'Heibel', + 'Hendee', + 'Raiche', + 'Saunier', + 'Wertheim', + 'Forand', + 'Stathis', + 'Raider', + 'Cassaro', + 'Cly', + 'Hagey', + 'Moad', + 'Rhody', + 'Fogler', + 'Hellen', + 'Sweezy', + 'Farid', + 'Suddreth', + 'Kenneth', + 'Pindell', + 'Corney', + 'Monsanto', + 'Laye', + 'Lingard', + 'Armwood', + 'Asif', + 'Axley', + 'Barkan', + 'Bittel', + 'Boesen', + 'Camilli', + 'Champa', + 'Dauenhauer', + 'Ehrmann', + 'Gangl', + 'Gatrell', + 'Gehret', + 'Hankel', + 'Kalbach', + 'Kessell', + 'Khatoon', + 'Lanese', + 'Manco', + 'Masteller', + 'Pruner', + 'Remmert', + 'Valasek', + 'Vater', + 'Yurick', + 'Zavalza', + 'Biesecker', + 'Frankenberg', + 'Hovorka', + 'Poissant', + 'Neises', + 'Moog', + 'Hadad', + 'Wittenburg', + 'Devere', + 'Hynds', + 'Sparkes', + 'Brailey', + 'Addo', + 'Accetta', + 'Altschuler', + 'Amescua', + 'Corredor', + 'Didonna', + 'Jencks', + 'Keady', + 'Keidel', + 'Mancebo', + 'Matusiak', + 'Rakoczy', + 'Reamy', + 'Sardella', + 'Slotnick', + 'Fotheringham', + 'Gettman', + 'Kunzler', + 'Manganaro', + 'Manygoats', + 'Huelskamp', + 'Newsham', + 'Kohen', + 'Cong', + 'Goulden', + 'Timmers', + 'Aderhold', + 'Shinall', + 'Cowser', + 'Uzzle', + 'Harps', + 'Balster', + 'Baringer', + 'Bechler', + 'Billick', + 'Chenard', + 'Ditta', + 'Fiallos', + 'Kampe', + 'Kretzschmar', + 'Manukyan', + 'Mcbreen', + 'Mcmanigal', + 'Miko', + 'Mol', + 'Orrego', + 'Penalosa', + 'Ronco', + 'Thum', + 'Tupa', + 'Vittitow', + 'Wierzba', + 'Gavitt', + 'Hampe', + 'Kowalkowski', + 'Neuroth', + 'Sterkel', + 'Herling', + 'Boldman', + 'Camus', + 'Drier', + 'Arcia', + 'Feagans', + 'Thompsen', + 'Maka', + 'Villalona', + 'Bonito', + 'Buist', + 'Dato', + 'Yankey', + 'Daluz', + 'Hollands', + 'Durio', + 'Bradly', + 'Daffin', + 'Chhabra', + 'Dettling', + 'Dolinger', + 'Flenniken', + 'Henrichsen', + 'Laduca', + 'Lashomb', + 'Leick', + 'Luchini', + 'Mcmanaman', + 'Minkoff', + 'Nobbe', + 'Oyster', + 'Quintela', + 'Robar', + 'Sakurai', + 'Solak', + 'Stolt', + 'Taddei', + 'Puopolo', + 'Schwarzkopf', + 'Stango', + 'Mcparland', + 'Schembri', + 'Standefer', + 'Dayan', + 'Sculley', + 'Bhuiyan', + 'Delauder', + 'Harrity', + 'Bree', + 'Haste', + 'Mcbay', + 'Garmany', + 'Maison', + 'Common', + 'Hanton', + 'Aigner', + 'Aliaga', + 'Boeve', + 'Cromie', + 'Demick', + 'Filipowicz', + 'Frickey', + 'Garrigus', + 'Heindl', + 'Hilmer', + 'Homeyer', + 'Lanterman', + 'Larch', + 'Masci', + 'Minami', + 'Palmiter', + 'Rufener', + 'Saal', + 'Sarmento', + 'Seewald', + 'Sestito', + 'Somarriba', + 'Sparano', + 'Vorce', + 'Wombles', + 'Zarr', + 'Antonson', + 'Bruzzese', + 'Chillemi', + 'Dannunzio', + 'Hogrefe', + 'Mastandrea', + 'Moynahan', + 'Wangerin', + 'Wedeking', + 'Ziobro', + 'Flegel', + 'Axsom', + 'Buzby', + 'Slowey', + 'Cuebas', + 'App', + 'Dar', + 'Robers', + 'Elting', + 'Demus', + 'Finkley', + 'Taborn', + 'Balogun', + 'Binstock', + 'Gebel', + 'Hinnenkamp', + 'Kosta', + 'Lamphear', + 'Linhares', + 'Luzader', + 'Mcconathy', + 'Motl', + 'Mroczkowski', + 'Reznicek', + 'Rieken', + 'Sadlowski', + 'Schlink', + 'Snuffer', + 'Tep', + 'Vaske', + 'Whisner', + 'Amesquita', + 'Demler', + 'Macdonell', + 'Rajala', + 'Sandate', + 'Kolk', + 'Bickerton', + 'Dugal', + 'Kirtland', + 'Neilan', + 'Doughman', + 'Crye', + 'Depena', + 'Quire', + 'Hafeez', + 'Rosse', + 'Devon', + 'Deboe', + 'Kitchings', + 'Blackett', + 'Acey', + 'Mcculler', + 'Obie', + 'Pleas', + 'Lurry', + 'Abid', + 'Bierlein', + 'Boisclair', + 'Cabanilla', + 'Celano', + 'Conrow', + 'Deeley', + 'Frankhouser', + 'Janowiak', + 'Jarchow', + 'Mcnicol', + 'Peixoto', + 'Pompeo', + 'Reitmeyer', + 'Scalera', + 'Schnorr', + 'Sideris', + 'Solesbee', + 'Stejskal', + 'Strole', + 'Tosto', + 'Wenke', + 'Dombek', + 'Gottschall', + 'Halbur', + 'Kuchler', + 'Kuyper', + 'Wruck', + 'Lorenc', + 'Search', + 'Chohan', + 'Monda', + 'Clowes', + 'Farson', + 'Rhoad', + 'Clavin', + 'Ramus', + 'Hayley', + 'Dolley', + 'Menton', + 'Dejarnett', + 'Challenger', + 'Branner', + 'Shed', + 'Dada', + 'Flewellen', + 'Volcy', + 'Allphin', + 'Barberena', + 'Bencivenga', + 'Bienkowski', + 'Bossi', + 'Corsini', + 'Dardis', + 'Falconi', + 'Fitzhenry', + 'Gaglione', + 'Handlin', + 'Kainz', + 'Lorge', + 'Nase', + 'Pavich', + 'Perelman', + 'Shanafelt', + 'Towsley', + 'Weill', + 'Zollars', + 'Appelt', + 'Cannizzo', + 'Carrubba', + 'Detar', + 'Dobrzynski', + 'Hashman', + 'Maassen', + 'Mccullagh', + 'Rettinger', + 'Roediger', + 'Rybolt', + 'Savoca', + 'Wortmann', + 'Boria', + 'Mairs', + 'Stream', + 'Larmore', + 'Sama', + 'Graden', + 'Hollinshead', + 'Mandy', + 'Gidney', + 'Demming', + 'Alexandra', + 'Caleb', + 'Abdullahi', + 'Cabal', + 'Dikeman', + 'Ellenbecker', + 'Fosnaugh', + 'Funck', + 'Heidorn', + 'Housden', + 'Ilic', + 'Illescas', + 'Kohlmann', + 'Lagman', + 'Larez', + 'Penafiel', + 'Pense', + 'Ragonese', + 'Reitan', + 'Shetterly', + 'Trapasso', + 'Zank', + 'Zecca', + 'Grisanti', + 'Hemker', + 'Mascolo', + 'Muhlenkamp', + 'Riemann', + 'Schindel', + 'Uncapher', + 'Zelman', + 'Koper', + 'Byrn', + 'Calzadilla', + 'Dilly', + 'Beiser', + 'Maller', + 'Bagg', + 'Winnick', + 'Sillman', + 'Bilton', + 'Esmond', + 'Sconyers', + 'Lemma', + 'Geralds', + 'Lazare', + 'Threats', + 'Accurso', + 'Boitnott', + 'Calcagni', + 'Chavera', + 'Corda', + 'Delisio', + 'Demont', + 'Eichel', + 'Faulds', + 'Ficek', + 'Gappa', + 'Graci', + 'Hammaker', + 'Heino', + 'Katcher', + 'Keslar', + 'Larsh', + 'Lashua', + 'Leising', + 'Magri', + 'Manbeck', + 'Mcwatters', + 'Mixer', + 'Moder', + 'Noori', + 'Pallo', + 'Pfingsten', + 'Plett', + 'Prehn', + 'Reyburn', + 'Savini', + 'Sebek', + 'Thang', + 'Lemberg', + 'Xiang', + 'Stiegler', + 'Groman', + 'Bowlen', + 'Grignon', + 'Morren', + 'Dini', + 'Mcaulay', + 'Ngu', + 'Bethell', + 'Warring', + 'Belyeu', + 'Ramcharan', + 'Mcjunkins', + 'Alms', + 'Ayub', + 'Brem', + 'Dresen', + 'Flori', + 'Geesaman', + 'Haugan', + 'Heppler', + 'Hermance', + 'Korinek', + 'Lograsso', + 'Madriaga', + 'Milbrath', + 'Radwan', + 'Riemersma', + 'Rivett', + 'Roggenbuck', + 'Stefanick', + 'Storment', + 'Ziegenfuss', + 'Blackhurst', + 'Daquila', + 'Maruska', + 'Rybka', + 'Schweer', + 'Tandon', + 'Hersman', + 'Galster', + 'Lemp', + 'Hantz', + 'Enderson', + 'Marchal', + 'Conly', + 'Bali', + 'Canaan', + 'Anstead', + 'Savary', + 'Andy', + 'Tisdell', + 'Livas', + 'Grinage', + 'Afanador', + 'Alviso', + 'Aracena', + 'Denio', + 'Dentremont', + 'Eldreth', + 'Gravois', + 'Huebsch', + 'Kalbfleisch', + 'Labree', + 'Mones', + 'Reitsma', + 'Schnapp', + 'Seek', + 'Shuping', + 'Tortorice', + 'Viscarra', + 'Wahlers', + 'Wittner', + 'Yake', + 'Zamani', + 'Carriveau', + 'Delage', + 'Gargan', + 'Goldade', + 'Golec', + 'Lapage', + 'Meinhart', + 'Mierzwa', + 'Riggenbach', + 'Schloesser', + 'Sedam', + 'Winkels', + 'Woldt', + 'Beckers', + 'Teach', + 'Feagan', + 'Booe', + 'Slates', + 'Bears', + 'Market', + 'Moone', + 'Verdun', + 'Ibe', + 'Jeudy', + 'Agudo', + 'Brisendine', + 'Casillo', + 'Chalupa', + 'Daversa', + 'Fissel', + 'Fleites', + 'Giarratano', + 'Glackin', + 'Granzow', + 'Hawver', + 'Hayashida', + 'Hovermale', + 'Huaman', + 'Jezek', + 'Lansdell', + 'Loughery', + 'Niedzielski', + 'Orellano', + 'Pebley', + 'Rojek', + 'Tomic', + 'Yellen', + 'Zerkle', + 'Boettner', + 'Decook', + 'Digilio', + 'Dinsdale', + 'Germer', + 'Kleve', + 'Marcinek', + 'Mendicino', + 'Pehl', + 'Revoir', + 'Osmun', + 'Bahner', + 'Shone', + 'Howald', + 'Kanode', + 'Amari', + 'Enix', + 'Levene', + 'Joffrion', + 'Lenis', + 'Carmicheal', + 'Njoku', + 'Coffel', + 'Ditter', + 'Grupp', + 'Kabel', + 'Kanzler', + 'Konop', + 'Lupi', + 'Mautz', + 'Mccahill', + 'Perella', + 'Perich', + 'Rion', + 'Ruvolo', + 'Torio', + 'Vipperman', + 'Bentivegna', + 'Formanek', + 'Smet', + 'Tarquinio', + 'Wesche', + 'Dearinger', + 'Makara', + 'Duba', + 'Iser', + 'Nicklow', + 'Wignall', + 'Wanger', + 'Goda', + 'Huckstep', + 'Basse', + 'Debruhl', + 'Hainey', + 'Damour', + 'Ebbs', + 'Armond', + 'Ewings', + 'Rease', + 'Okoye', + 'Arentz', + 'Baack', + 'Bellantoni', + 'Buckholz', + 'Cirigliano', + 'Colletta', + 'Dutka', + 'Everingham', + 'Gilardi', + 'Hudelson', + 'Klimczak', + 'Kneip', + 'Papaleo', + 'Peregrino', + 'Piechowski', + 'Prucha', + 'Ryburn', + 'Scholle', + 'Scholtes', + 'Socarras', + 'Wrightsman', + 'Yum', + 'Campau', + 'Dwinell', + 'Haupert', + 'Lotspeich', + 'Madar', + 'Casa', + 'Michals', + 'Rainier', + 'Tenpenny', + 'Lakeman', + 'Spadoni', + 'Cantrelle', + 'Mangal', + 'Chachere', + 'Swoope', + 'Carwell', + 'Voltaire', + 'Durrah', + 'Roulhac', + 'Aboytes', + 'Apuzzo', + 'Bielinski', + 'Bollenbacher', + 'Borjon', + 'Croak', + 'Dansie', + 'Espin', + 'Euceda', + 'Garone', + 'Garthwaite', + 'Hata', + 'Heu', + 'Hogenson', + 'Jahner', + 'Keesey', + 'Kotas', + 'Labrake', + 'Laitinen', + 'Laumann', + 'Miske', + 'Nickless', + 'Onishi', + 'Setty', + 'Shinnick', + 'Takayama', + 'Tassinari', + 'Tribe', + 'Bowdish', + 'Friesenhahn', + 'Hoffarth', + 'Wachowski', + 'Gudgel', + 'Gautney', + 'Matar', + 'Ellenberg', + 'Inghram', + 'Bevil', + 'Rasul', + 'Niblack', + 'Perkin', + 'Goring', + 'Potier', + 'Bachrach', + 'Bozza', + 'Budz', + 'Devens', + 'Ditzel', + 'Drahos', + 'Ducat', + 'Fahrner', + 'Friedline', + 'Geurin', + 'Goodenow', + 'Greenfeld', + 'Grunow', + 'Ingber', + 'Kashani', + 'Kochman', + 'Kozub', + 'Kukuk', + 'Leppo', + 'Liew', + 'Metheney', + 'Molony', + 'Montemurro', + 'Neiss', + 'Postlethwait', + 'Quaglia', + 'Ruszkowski', + 'Shertzer', + 'Titone', + 'Waldmann', + 'Wenninger', + 'Wheeland', + 'Zorich', + 'Mervine', + 'Weatherholtz', + 'Brotman', + 'Malenfant', + 'Nong', + 'Rogness', + 'Dibert', + 'Gallahan', + 'Gange', + 'Chilcott', + 'Axt', + 'Wiler', + 'Jacot', + 'Ory', + 'Abdon', + 'Fenter', + 'Bryars', + 'Ramroop', + 'Jacox', + 'Mainer', + 'Figures', + 'Alig', + 'Bires', + 'Cassata', + 'Cholewa', + 'Dispenza', + 'Eckmann', + 'Gauer', + 'Gloor', + 'Hattori', + 'Huster', + 'Kopplin', + 'Krugman', + 'Lancon', + 'Ledin', + 'Limb', + 'Marentes', + 'Minges', + 'Monacelli', + 'Monteon', + 'Naslund', + 'Nitsche', + 'Rapozo', + 'Rimkus', + 'Schwerdtfeger', + 'Vandenbos', + 'Balandran', + 'Biehn', + 'Briody', + 'Hackmann', + 'Kalka', + 'Keranen', + 'Lortie', + 'Mannella', + 'Shiffler', + 'Stempel', + 'Takaki', + 'Tomassi', + 'Reidel', + 'Ciprian', + 'Penza', + 'Vite', + 'Cormany', + 'Derousse', + 'Beus', + 'Shurley', + 'Courtwright', + 'Donna', + 'Karney', + 'Keats', + 'Harron', + 'Stjacques', + 'Regester', + 'Stoke', + 'Garron', + 'Sulaiman', + 'Fusilier', + 'Hence', + 'Altidor', + 'Rollerson', + 'Anfinson', + 'Badua', + 'Balmaceda', + 'Bringman', + 'Bystrom', + 'Goffinet', + 'Guindon', + 'Hilling', + 'Makepeace', + 'Mooradian', + 'Muzquiz', + 'Newcom', + 'Perrella', + 'Postlewait', + 'Raetz', + 'Riveros', + 'Saephanh', + 'Scharer', + 'Sheeder', + 'Sitar', + 'Umlauf', + 'Voegeli', + 'Yurkovich', + 'Chaudhari', + 'Chianese', + 'Clonch', + 'Gasparini', + 'Giambalvo', + 'Gindlesperger', + 'Rauen', + 'Riegert', + 'Collingsworth', + 'Stief', + 'Zeisler', + 'Kirsten', + 'Vessey', + 'Scherman', + 'Ledwith', + 'Goudie', + 'Ayre', + 'Salome', + 'Knoles', + 'Munyan', + 'Corbet', + 'Hagewood', + 'Humphry', + 'Bernardez', + 'Drummonds', + 'Lide', + 'Veals', + 'Andolina', + 'Anzaldo', + 'Aufiero', + 'Bakshi', + 'Berdan', + 'Birrell', + 'Colcord', + 'Dutro', + 'Eisenhour', + 'Falgoust', + 'Foertsch', + 'Forlenza', + 'Harroun', + 'Kurtenbach', + 'Livesey', + 'Luka', + 'Manseau', + 'Mcdaid', + 'Miska', + 'Overley', + 'Panzica', + 'Reish', + 'Riolo', + 'Roseland', + 'Shenberger', + 'Splinter', + 'Strupp', + 'Sturgell', + 'Swatzell', + 'Totherow', + 'Villarroel', + 'Wenig', + 'Zimny', + 'Brunetto', + 'Hiester', + 'Kasinger', + 'Laverde', + 'Mihalek', + 'Aquila', + 'Moreton', + 'Collums', + 'Ergle', + 'Keziah', + 'Bourbon', + 'Scaff', + 'Leamy', + 'Sublette', + 'Winkley', + 'Arlington', + 'Cuffe', + 'Guity', + 'Mcmickle', + 'Summerour', + 'Baggerly', + 'Biltz', + 'Calma', + 'Dephillips', + 'Graffam', + 'Holsopple', + 'Izumi', + 'Joerger', + 'Kesselman', + 'Kingdon', + 'Kinkel', + 'Knezevich', + 'Liebler', + 'Maceda', + 'Qualey', + 'Robeck', + 'Sciarrino', + 'Sooy', + 'Stahly', + 'Stieglitz', + 'Strike', + 'Unwin', + 'Urizar', + 'Volmer', + 'Winterstein', + 'Aronov', + 'Czyz', + 'Marrazzo', + 'Seagren', + 'Wiegmann', + 'Yearsley', + 'Brommer', + 'Sterne', + 'Armel', + 'Kryger', + 'Barten', + 'Bodwell', + 'Hollett', + 'Sharron', + 'Scobey', + 'Croson', + 'Gainor', + 'Axel', + 'Basore', + 'Bengel', + 'Chiem', + 'Desanctis', + 'Gillooly', + 'Groulx', + 'Hulings', + 'Koenigsberg', + 'Kuchinski', + 'Pagaduan', + 'Pataky', + 'Rietz', + 'Robello', + 'Schuchman', + 'Shek', + 'Brattain', + 'Gottwald', + 'Klapperich', + 'Kosky', + 'Ruegg', + 'Smid', + 'Taillon', + 'Klonowski', + 'Attar', + 'Mansoor', + 'Daus', + 'Falla', + 'Guyot', + 'Hasten', + 'Mcdowall', + 'Tugwell', + 'Remo', + 'Dishmon', + 'Leggette', + 'Sudler', + 'Prescod', + 'Benvenuti', + 'Bittenbender', + 'Burkland', + 'Crehan', + 'Donjuan', + 'Ewbank', + 'Fluegel', + 'Freiman', + 'Fuelling', + 'Grabner', + 'Gras', + 'Horr', + 'Jurich', + 'Kentner', + 'Laski', + 'Minero', + 'Olivos', + 'Porro', + 'Purves', + 'Smethers', + 'Spallone', + 'Stangler', + 'Gebbia', + 'Fowers', + 'Gaster', + 'Fero', + 'Gamon', + 'Wiss', + 'Strassner', + 'Cott', + 'Houp', + 'Fidel', + 'Parisien', + 'Daisy', + 'Calais', + 'Boers', + 'Bolle', + 'Caccavale', + 'Colantonio', + 'Columbo', + 'Goswami', + 'Hakanson', + 'Jelley', + 'Kahlon', + 'Lopezgarcia', + 'Marier', + 'Mesko', + 'Monter', + 'Mowell', + 'Piech', + 'Shortell', + 'Slechta', + 'Starman', + 'Tiemeyer', + 'Troutner', + 'Vandeveer', + 'Voorheis', + 'Woodhams', + 'Helget', + 'Kalk', + 'Kiester', + 'Lagace', + 'Obst', + 'Parrack', + 'Rennert', + 'Rodeheaver', + 'Schuermann', + 'Warmuth', + 'Wisnieski', + 'Yahnke', + 'Yurek', + 'Faver', + 'Belleau', + 'Moan', + 'Remsen', + 'Bonano', + 'Genson', + 'Genis', + 'Risen', + 'Franze', + 'Lauderback', + 'Ferns', + 'Cooler', + 'Mcwilliam', + 'Micheals', + 'Gotch', + 'Teat', + 'Bacus', + 'Banik', + 'Bernhart', + 'Doell', + 'Francese', + 'Gasbarro', + 'Gietzen', + 'Gossen', + 'Haberle', + 'Havlicek', + 'Henion', + 'Kevorkian', + 'Liem', + 'Loor', + 'Moede', + 'Mostafa', + 'Mottern', + 'Naito', + 'Nofsinger', + 'Omelia', + 'Pirog', + 'Pirone', + 'Plucinski', + 'Raghavan', + 'Robaina', + 'Seliga', + 'Stade', + 'Steinhilber', + 'Wedin', + 'Wieman', + 'Zemaitis', + 'Creps', + 'Gumina', + 'Inglett', + 'Jhaveri', + 'Kolasinski', + 'Salvesen', + 'Vallely', + 'Weseman', + 'Zysk', + 'Gourlay', + 'Zanger', + 'Delorey', + 'Sneider', + 'Tacey', + 'Valls', + 'Ossman', + 'Watton', + 'Breau', + 'Burell', + 'Villard', + 'Janice', + 'Honor', + 'Arterberry', + 'Sow', + 'Cucchiara', + 'Diemert', + 'Fluty', + 'Guadiana', + 'Ionescu', + 'Kearley', + 'Krzyzanowski', + 'Lavecchia', + 'Lundmark', + 'Melichar', + 'Mulkern', + 'Odonohue', + 'Payment', + 'Pinnow', + 'Popoff', + 'Prus', + 'Reinoehl', + 'Scarlata', + 'Schamp', + 'Schowalter', + 'Scinto', + 'Semmler', + 'Sheline', + 'Sigg', + 'Trauger', + 'Bleiler', + 'Carrino', + 'Hauth', + 'Kunsman', + 'Reicks', + 'Rotenberg', + 'Soltesz', + 'Wascher', + 'Mattina', + 'Tamblyn', + 'Bellanca', + 'Heward', + 'Seif', + 'Agha', + 'Gosling', + 'Defreese', + 'Lyvers', + 'Robley', + 'Hadi', + 'Ledyard', + 'Mitchner', + 'Berrien', + 'Clinkscale', + 'Affeldt', + 'Aung', + 'Azpeitia', + 'Boehnlein', + 'Cavan', + 'Ekdahl', + 'Ellyson', + 'Fahl', + 'Herrig', + 'Hulick', + 'Ihrke', + 'Kaeding', + 'Keagy', + 'Mehlman', + 'Minniear', + 'Paniccia', + 'Pleva', + 'Prestidge', + 'Pulford', + 'Quattrone', + 'Riquelme', + 'Rombach', + 'Sarwar', + 'Sivertsen', + 'Sprang', + 'Wardrop', + 'Anglemyer', + 'Bobek', + 'Scronce', + 'Snethen', + 'Stancliff', + 'Booton', + 'Pinal', + 'Weihe', + 'Bria', + 'Lopresto', + 'Awbrey', + 'Fogal', + 'Ning', + 'Hydrick', + 'Lumb', + 'Pommier', + 'Hendy', + 'Armon', + 'Spenser', + 'Beachem', + 'Decrescenzo', + 'Heaphy', + 'Kalata', + 'Kastl', + 'Kosel', + 'Kunert', + 'Laatsch', + 'Lanpher', + 'Malinski', + 'Mazzie', + 'Neuendorf', + 'Salloum', + 'Tays', + 'Yackel', + 'Calvario', + 'Feese', + 'Feldner', + 'Kness', + 'Kozuch', + 'Magat', + 'Pantalone', + 'Rilling', + 'Teska', + 'Fantauzzi', + 'Wartman', + 'Stansbery', + 'Sox', + 'Napp', + 'Schauf', + 'Cumings', + 'Coxon', + 'Labor', + 'Brash', + 'Egleston', + 'Quintin', + 'Oki', + 'Date', + 'Tuckett', + 'Devaux', + 'Hewins', + 'Abdelrahman', + 'Schumpert', + 'Dort', + 'Limbrick', + 'Linwood', + 'Delaine', + 'Liverpool', + 'Azimi', + 'Biever', + 'Craigo', + 'Eschete', + 'Fortini', + 'Francom', + 'Giacomini', + 'Girdler', + 'Halasz', + 'Hillin', + 'Inglese', + 'Isaza', + 'Lewman', + 'Ploetz', + 'Rampley', + 'Reifsteck', + 'Rossano', + 'Sanagustin', + 'Sotak', + 'Spainhower', + 'Stecklein', + 'Stolberg', + 'Teschner', + 'Thew', + 'Blaszczyk', + 'Caradonna', + 'Cillo', + 'Diluzio', + 'Hagemeyer', + 'Holstrom', + 'Jewkes', + 'Mcquaide', + 'Osterhaus', + 'Twaddle', + 'Wenck', + 'Yakel', + 'Zeiner', + 'Zulauf', + 'Mirabelli', + 'Gerold', + 'Sherr', + 'Ogando', + 'Achilles', + 'Woodlee', + 'Underdown', + 'Peirson', + 'Abdelaziz', + 'Bently', + 'Junes', + 'Furtick', + 'Muckle', + 'Freemon', + 'Jamar', + 'Scriber', + 'Michaux', + 'Cheatum', + 'Hollings', + 'Telfair', + 'Amadeo', + 'Bargar', + 'Berchtold', + 'Boomhower', + 'Camba', + 'Compo', + 'Dellavecchia', + 'Doring', + 'Doyel', + 'Geck', + 'Giovannini', + 'Herda', + 'Kopko', + 'Kuns', + 'Maciag', + 'Neenan', + 'Neglia', + 'Nienhuis', + 'Niznik', + 'Pieczynski', + 'Quintos', + 'Quirin', + 'Ravi', + 'Teaster', + 'Tipsword', + 'Troiani', + 'Consuegra', + 'Damaso', + 'Garavaglia', + 'Pischke', + 'Prowse', + 'Rumore', + 'Simcoe', + 'Slentz', + 'Sposito', + 'Sulser', + 'Weichel', + 'Sandobal', + 'Siver', + 'Vickerman', + 'Sham', + 'Gutridge', + 'Gracy', + 'Weatherington', + 'Benett', + 'Nottage', + 'Myricks', + 'Tukes', + 'Alcaide', + 'Curatolo', + 'Dalziel', + 'Fandrich', + 'Fisette', + 'Gianino', + 'Grether', + 'Hari', + 'Ichikawa', + 'Lantzy', + 'Monteforte', + 'Moskovitz', + 'Porritt', + 'Raz', + 'Rodenbeck', + 'Ryczek', + 'Strehle', + 'Vanduzer', + 'Voge', + 'Wiker', + 'Yanik', + 'Zangari', + 'Cahue', + 'Dellapenna', + 'Gohr', + 'Gurka', + 'Imburgia', + 'Langenberg', + 'Kivi', + 'Pikul', + 'Sexson', + 'Sharrer', + 'Aramburo', + 'Kadar', + 'Casasola', + 'Nina', + 'Borras', + 'Toledano', + 'Wykle', + 'Naeem', + 'Bailer', + 'Lalla', + 'Booty', + 'Turenne', + 'Merrit', + 'Duffus', + 'Hemmingway', + 'Asare', + 'Ahlborn', + 'Arroyave', + 'Brandenberger', + 'Carolus', + 'Coonan', + 'Dacunha', + 'Dost', + 'Filter', + 'Freudenberg', + 'Grabski', + 'Hengel', + 'Holohan', + 'Kohne', + 'Kollmann', + 'Levick', + 'Lupinacci', + 'Meservey', + 'Reisdorf', + 'Rodabaugh', + 'Shimon', + 'Soth', + 'Spall', + 'Tener', + 'Thier', + 'Welshans', + 'Chermak', + 'Ciolino', + 'Frantzen', + 'Grassman', + 'Okuda', + 'Passantino', + 'Schellinger', + 'Sneath', + 'Bolla', + 'Bobe', + 'Maves', + 'Matey', + 'Shafi', + 'Rothchild', + 'Ker', + 'Verrette', + 'Thorington', + 'Lathers', + 'Merriwether', + 'Bendall', + 'Bercier', + 'Botz', + 'Claybaugh', + 'Creson', + 'Dilone', + 'Gabehart', + 'Gencarelli', + 'Ghormley', + 'Hacking', + 'Haefele', + 'Haros', + 'Holderby', + 'Krotzer', + 'Nanda', + 'Oltmanns', + 'Orndoff', + 'Poniatowski', + 'Rosol', + 'Sheneman', + 'Shifrin', + 'Smay', + 'Swickard', + 'Thayne', + 'Tripathi', + 'Vonbehren', + 'Pummill', + 'Schnitker', + 'Steines', + 'Beechler', + 'Faron', + 'Villari', + 'Spickard', + 'Levings', + 'Polack', + 'Standre', + 'Castel', + 'Louise', + 'Janey', + 'Lindor', + 'Bulthuis', + 'Cantrall', + 'Cisnero', + 'Dangel', + 'Deborde', + 'Decena', + 'Grandon', + 'Gritz', + 'Heberlein', + 'Kestenbaum', + 'Kubitz', + 'Luers', + 'Naiman', + 'Ramaswamy', + 'Sek', + 'Slauson', + 'Walsworth', + 'Biehler', + 'Capano', + 'Casstevens', + 'Forgette', + 'Furnas', + 'Gilkison', + 'Janoski', + 'Jerde', + 'Mcclimans', + 'Rohlf', + 'Vliet', + 'Heeney', + 'Zapanta', + 'Lighthall', + 'Shallow', + 'Neils', + 'Raikes', + 'Clarkston', + 'Claud', + 'Guilbeaux', + 'Pennie', + 'Arizola', + 'Aud', + 'Checketts', + 'Corvera', + 'Easterbrook', + 'Gamm', + 'Grassel', + 'Guarin', + 'Hanf', + 'Hitsman', + 'Lackman', + 'Lubitz', + 'Lupian', + 'Olexa', + 'Omori', + 'Oscarson', + 'Picasso', + 'Plewa', + 'Schmahl', + 'Stolze', + 'Todisco', + 'Zarzycki', + 'Baluyot', + 'Cerrito', + 'Elenbaas', + 'Gavidia', + 'Heisner', + 'Karpowicz', + 'Neidhardt', + 'Silkwood', + 'Taras', + 'Dobias', + 'Widen', + 'Blandino', + 'Fanguy', + 'Probus', + 'Guilbert', + 'Shadowens', + 'Keepers', + 'Bruin', + 'Hitson', + 'Crymes', + 'Roston', + 'Beaubrun', + 'Arrambide', + 'Betti', + 'Brockhaus', + 'Bumanglag', + 'Cabreja', + 'Dicenso', + 'Hartlaub', + 'Hertlein', + 'Lapenna', + 'Rathje', + 'Rotert', + 'Salzberg', + 'Siniard', + 'Tomsic', + 'Wondra', + 'Zenger', + 'Norrod', + 'Opalka', + 'Osment', + 'Zhan', + 'Lemcke', + 'Meranda', + 'Joles', + 'Labay', + 'Monserrate', + 'Grime', + 'Martha', + 'Coltrain', + 'Vardaman', + 'Wragg', + 'Frater', + 'Offer', + 'Elcock', + 'Auble', + 'Bistline', + 'Chorney', + 'Colgate', + 'Dadamo', + 'Deol', + 'Discher', + 'Ertz', + 'Fletchall', + 'Galletti', + 'Geffre', + 'Grall', + 'Hoos', + 'Iezzi', + 'Kawecki', + 'Madamba', + 'Margolies', + 'Mccreedy', + 'Okimoto', + 'Oum', + 'Pangan', + 'Pasternack', + 'Plazola', + 'Prochazka', + 'Tewes', + 'Tramontana', + 'Yauch', + 'Zarling', + 'Zemanek', + 'Altshuler', + 'Bartkowski', + 'Cuoco', + 'Garcialopez', + 'Kauzlarich', + 'Shishido', + 'Zaun', + 'Hallin', + 'Starliper', + 'Belflower', + 'Kneece', + 'Genet', + 'Palmero', + 'Willmott', + 'Riek', + 'Belger', + 'Abbitt', + 'Statum', + 'Jacque', + 'Chisley', + 'Habersham', + 'Berardinelli', + 'Bodle', + 'Deshaw', + 'Ingalsbe', + 'Kirchgessner', + 'Kuna', + 'Pellow', + 'Pickler', + 'Pistole', + 'Rosenstock', + 'Salceda', + 'Sawatzky', + 'Schappell', + 'Scholer', + 'Shellabarger', + 'Spader', + 'Swadley', + 'Travelstead', + 'Varin', + 'Villwock', + 'Wiemers', + 'Bedoy', + 'Borowiak', + 'Celio', + 'Dornfeld', + 'Juhnke', + 'Livernois', + 'Sakaguchi', + 'Sandall', + 'Sivertson', + 'Whitcraft', + 'Anda', + 'Aprile', + 'Kritz', + 'Speier', + 'Karman', + 'Kise', + 'Madia', + 'Bodo', + 'Madole', + 'Harl', + 'Gach', + 'Stalls', + 'Holme', + 'Lomba', + 'Tutton', + 'Windon', + 'Bines', + 'Benoist', + 'Cirrincione', + 'Coday', + 'Delrosso', + 'Dlouhy', + 'Domenick', + 'Edelmann', + 'Goos', + 'Hamling', + 'Huda', + 'Hutzel', + 'Lanasa', + 'Loudenslager', + 'Lueras', + 'Magnussen', + 'Mcferran', + 'Nowinski', + 'Pikula', + 'Precht', + 'Quilici', + 'Robling', + 'Rusche', + 'Schettino', + 'Scibelli', + 'Soderman', + 'Spirito', + 'Teaford', + 'Updegrove', + 'Weygandt', + 'Zervos', + 'Brunker', + 'Demuro', + 'Eckenrod', + 'Emley', + 'Franek', + 'Frankenberger', + 'Longbrake', + 'Magallanez', + 'Stofko', + 'Zenz', + 'Galik', + 'Crevier', + 'Fina', + 'Harari', + 'Dudney', + 'Inga', + 'Sowles', + 'Folker', + 'Cressy', + 'Eckerson', + 'Gerringer', + 'Capito', + 'Huxtable', + 'Arcement', + 'Lansdown', + 'Amara', + 'Brazill', + 'Flye', + 'Currington', + 'Buffin', + 'Desta', + 'Cheuvront', + 'Fuoco', + 'Gerbino', + 'Hilyer', + 'Hogsed', + 'Kubis', + 'Lautner', + 'Loeber', + 'Meyn', + 'Mortell', + 'Nunziato', + 'Opdahl', + 'Panebianco', + 'Reffner', + 'Repsher', + 'Riccobono', + 'Wik', + 'Circle', + 'Hovde', + 'Keaveney', + 'Landsberg', + 'Pesavento', + 'Bedel', + 'Glas', + 'Thurn', + 'Jaffer', + 'Dantin', + 'Risi', + 'Many', + 'Egler', + 'Craghead', + 'Ann', + 'Turnbo', + 'Crumby', + 'Faciane', + 'Brummell', + 'Bujak', + 'Chaddock', + 'Cullop', + 'Eberling', + 'Ennen', + 'Frum', + 'Gassert', + 'Grothaus', + 'Hucke', + 'Lanphere', + 'Lozon', + 'Macadam', + 'Mezo', + 'Peretti', + 'Perlin', + 'Prestwich', + 'Redmann', + 'Ringley', + 'Rivenburg', + 'Sandow', + 'Spreitzer', + 'Stachnik', + 'Szczesniak', + 'Tworek', + 'Wogan', + 'Zygmunt', + 'Austad', + 'Chiappone', + 'Gelineau', + 'Lannom', + 'Livezey', + 'Monrroy', + 'Norem', + 'Oetting', + 'Ostberg', + 'Takeshita', + 'Gorsky', + 'Allcorn', + 'Pemble', + 'Josselyn', + 'Lanzo', + 'Hoare', + 'Ticer', + 'Netterville', + 'Lawes', + 'Lenton', + 'Garraway', + 'Cyprian', + 'Alferez', + 'Allocco', + 'Aslanian', + 'Brenna', + 'Carachure', + 'Devoss', + 'Dubas', + 'Garrabrant', + 'Gerloff', + 'Gerritsen', + 'Hobaugh', + 'Jasek', + 'Kulis', + 'Lenehan', + 'Lodes', + 'Mandich', + 'Manter', + 'Mcfatridge', + 'Mikolajczak', + 'Netz', + 'Perrelli', + 'Ribar', + 'Sekerak', + 'Shingledecker', + 'Talamante', + 'Taverna', + 'Thoresen', + 'Throneberry', + 'Vanacore', + 'Vieau', + 'Wermuth', + 'Zeidan', + 'Counihan', + 'Dircks', + 'Markovitz', + 'Panas', + 'Steffel', + 'Bergstedt', + 'Mohar', + 'Sonne', + 'Mitsch', + 'Aceituno', + 'Loker', + 'Treen', + 'Prunier', + 'Amberson', + 'Allington', + 'Artley', + 'Caffery', + 'Rhoney', + 'Nimmer', + 'Ledwell', + 'Barkus', + 'Fralin', + 'Locks', + 'Azzara', + 'Bartosik', + 'Bertelson', + 'Birman', + 'Brogna', + 'Cachola', + 'Dennington', + 'Enea', + 'Gallogly', + 'Grafe', + 'Jankowiak', + 'Kaas', + 'Karis', + 'Kostick', + 'Lentsch', + 'Locken', + 'Mathys', + 'Maxcy', + 'Monegro', + 'Olano', + 'Paske', + 'Raible', + 'Rowbotham', + 'Vanderbeck', + 'Vanosdol', + 'Wenzler', + 'Yglesias', + 'Eisenberger', + 'Grzelak', + 'Hamidi', + 'Hottel', + 'Markoff', + 'Santagata', + 'Seefeld', + 'Stachowicz', + 'Stiehl', + 'Staver', + 'Raad', + 'Sarber', + 'Rudge', + 'Connelley', + 'Danser', + 'Chumney', + 'Hind', + 'Desper', + 'Fergusson', + 'Ringwood', + 'Byles', + 'Alyea', + 'Benzinger', + 'Betzer', + 'Brix', + 'Chiarella', + 'Chiriboga', + 'Cicala', + 'Cohick', + 'Creeden', + 'Delligatti', + 'Garbacz', + 'Grossberg', + 'Habecker', + 'Inscoe', + 'Irias', + 'Karlsen', + 'Kilts', + 'Koetter', + 'Laflin', + 'Laperle', + 'Mizner', + 'Navia', + 'Nolet', + 'Procaccini', + 'Pula', + 'Scarfo', + 'Schmelz', + 'Taaffe', + 'Troth', + 'Vanlaningham', + 'Vosberg', + 'Whitchurch', + 'Benak', + 'Hanawalt', + 'Lindman', + 'Moschetti', + 'Rozas', + 'Sporleder', + 'Stopka', + 'Turowski', + 'Wahlgren', + 'Youngstrom', + 'Jabbour', + 'Myerson', + 'Perlow', + 'Cannone', + 'Kil', + 'Stiverson', + 'Cedar', + 'Sweeden', + 'Pourciau', + 'Salina', + 'Delmoral', + 'Balle', + 'Cohea', + 'Bute', + 'Rayne', + 'Cawthorn', + 'Conely', + 'Cartlidge', + 'Powel', + 'Nwankwo', + 'Centrella', + 'Delaura', + 'Deprey', + 'Dulude', + 'Garrod', + 'Gassen', + 'Greenberger', + 'Huneke', + 'Kunzman', + 'Laakso', + 'Oppermann', + 'Radich', + 'Rozen', + 'Schoffstall', + 'Swetnam', + 'Vitrano', + 'Wolber', + 'Amirault', + 'Przybysz', + 'Trzeciak', + 'Fontan', + 'Mathie', + 'Roswell', + 'Mcquitty', + 'Kye', + 'Lucious', + 'Chilcutt', + 'Difazio', + 'Diperna', + 'Gashi', + 'Goodstein', + 'Gruetzmacher', + 'Imus', + 'Krumholz', + 'Lanzetta', + 'Leaming', + 'Lehigh', + 'Lobosco', + 'Pardoe', + 'Pellicano', + 'Purtee', + 'Ramanathan', + 'Roszkowski', + 'Satre', + 'Steinborn', + 'Stinebaugh', + 'Thiesen', + 'Tierno', + 'Wrisley', + 'Yazdani', + 'Zwilling', + 'Berntson', + 'Gisclair', + 'Golliher', + 'Neumeier', + 'Stohl', + 'Swartley', + 'Wannemacher', + 'Wickard', + 'Duford', + 'Rosello', + 'Merfeld', + 'Arko', + 'Cotney', + 'Hai', + 'Milley', + 'Figueira', + 'Willes', + 'Helmes', + 'Abair', + 'Life', + 'Izard', + 'Duskin', + 'Moland', + 'Primer', + 'Hagos', + 'Anyanwu', + 'Balasubramanian', + 'Bluth', + 'Calk', + 'Chrzan', + 'Constanza', + 'Durney', + 'Ekholm', + 'Erny', + 'Ferrando', + 'Froberg', + 'Gonyer', + 'Guagliardo', + 'Hreha', + 'Kobza', + 'Kuruvilla', + 'Preziosi', + 'Ricciuti', + 'Rosiles', + 'Schiesser', + 'Schmoyer', + 'Slota', + 'Szeliga', + 'Verba', + 'Widjaja', + 'Wrzesinski', + 'Zufall', + 'Bumstead', + 'Dohrman', + 'Dommer', + 'Eisenmenger', + 'Glogowski', + 'Kaufhold', + 'Kuiken', + 'Ricklefs', + 'Sinyard', + 'Steenbergen', + 'Schweppe', + 'Chatwin', + 'Dingee', + 'Mittleman', + 'Menear', + 'Milot', + 'Riccardo', + 'Clemenson', + 'Mellow', + 'Gabe', + 'Rolla', + 'Vander', + 'Casselberry', + 'Hubbart', + 'Colvert', + 'Billingsly', + 'Burgman', + 'Cattaneo', + 'Duthie', + 'Hedtke', + 'Heidler', + 'Hertenstein', + 'Hudler', + 'Hustead', + 'Ibsen', + 'Krutsinger', + 'Mauceri', + 'Mersereau', + 'Morad', + 'Rentfro', + 'Rumrill', + 'Shedlock', + 'Sindt', + 'Soulier', + 'Squitieri', + 'Trageser', + 'Vatter', + 'Vollman', + 'Wagster', + 'Caselli', + 'Dibacco', + 'Gick', + 'Kachel', + 'Lukaszewski', + 'Minniti', + 'Neeld', + 'Zarrella', + 'Hedglin', + 'Jahan', + 'Nathe', + 'Starn', + 'Kana', + 'Bernet', + 'Rossa', + 'Barro', + 'Smylie', + 'Bowlds', + 'Mccalley', + 'Oniel', + 'Thaggard', + 'Cayson', + 'Sinegal', + 'Bergfeld', + 'Bickmore', + 'Boch', + 'Bushway', + 'Carneiro', + 'Cerio', + 'Colbath', + 'Eade', + 'Eavenson', + 'Epping', + 'Fredricksen', + 'Gramer', + 'Hassman', + 'Hinderer', + 'Kantrowitz', + 'Kaplowitz', + 'Kelner', + 'Lecates', + 'Lothrop', + 'Lupica', + 'Masterman', + 'Meeler', + 'Neumiller', + 'Newbauer', + 'Noyce', + 'Nulty', + 'Shanker', + 'Taheri', + 'Timblin', + 'Vitucci', + 'Zappone', + 'Femia', + 'Hejl', + 'Helmbrecht', + 'Kiesow', + 'Maschino', + 'Brougher', + 'Koff', + 'Reffett', + 'Langhoff', + 'Milman', + 'Sidener', + 'Levie', + 'Chaudry', + 'Rattan', + 'Finkler', + 'Bollen', + 'Booz', + 'Shipps', + 'Theall', + 'Scallion', + 'Furlough', + 'Landfair', + 'Albuquerque', + 'Beckstrand', + 'Colglazier', + 'Darcey', + 'Fahr', + 'Gabert', + 'Gertner', + 'Gettler', + 'Giovannetti', + 'Hulvey', + 'Juenger', + 'Kantola', + 'Kemmerling', + 'Leclere', + 'Liberati', + 'Lopezlopez', + 'Minerva', + 'Redlich', + 'Shoun', + 'Sickinger', + 'Vivier', + 'Yerdon', + 'Ziomek', + 'Dechert', + 'Delbene', + 'Galassi', + 'Rawdon', + 'Wesenberg', + 'Laurino', + 'Grosjean', + 'Levay', + 'Zike', + 'Stukey', + 'Loft', + 'Kool', + 'Hatchel', + 'Mainville', + 'Salis', + 'Greenslade', + 'Mantey', + 'Spratlin', + 'Fayette', + 'Marner', + 'Rolan', + 'Pain', + 'Colquhoun', + 'Brave', + 'Locust', + 'Sconiers', + 'Bahler', + 'Barrero', + 'Bartha', + 'Basnett', + 'Berghoff', + 'Bomgardner', + 'Brindisi', + 'Campoli', + 'Carawan', + 'Chhim', + 'Corro', + 'Crissey', + 'Deterding', + 'Dileonardo', + 'Dowis', + 'Hagemeier', + 'Heichel', + 'Kipfer', + 'Lemberger', + 'Maestri', + 'Mauri', + 'Nakatani', + 'Notestine', + 'Polakowski', + 'Schlobohm', + 'Segel', + 'Socci', + 'Stieg', + 'Thorstad', + 'Trausch', + 'Whitledge', + 'Wilkowski', + 'Barkdull', + 'Dubeau', + 'Ellingsen', + 'Hayduk', + 'Lauter', + 'Lizak', + 'Machamer', + 'Makarewicz', + 'Shuffield', + 'Heiserman', + 'Sandeen', + 'Plough', + 'Stemler', + 'Bossler', + 'Catalina', + 'Betley', + 'Bonello', + 'Pryde', + 'Nickey', + 'Schanck', + 'Single', + 'Mulberry', + 'Point', + 'Danson', + 'Flemmings', + 'Behnken', + 'Catone', + 'Cummiskey', + 'Currens', + 'Gersch', + 'Kitamura', + 'Meddaugh', + 'Montagne', + 'Nouri', + 'Olejnik', + 'Pintar', + 'Placke', + 'Quinter', + 'Rakers', + 'Stuteville', + 'Sullo', + 'Voelz', + 'Barabas', + 'Estock', + 'Hultberg', + 'Savitz', + 'Treml', + 'Vigneault', + 'Jezierski', + 'Zayed', + 'Dewell', + 'Yanko', + 'Moulin', + 'Whalin', + 'Elsworth', + 'Summit', + 'Esty', + 'Mahadeo', + 'Shular', + 'Amedee', + 'Bellerose', + 'Bendixen', + 'Briski', + 'Buysse', + 'Desa', + 'Dobry', + 'Dufner', + 'Fetterly', + 'Finau', + 'Gaudioso', + 'Giangrande', + 'Heuring', + 'Kitchel', + 'Latulippe', + 'Pombo', + 'Vancott', + 'Woofter', + 'Bojarski', + 'Cretella', + 'Heumann', + 'Limpert', + 'Mcneff', + 'Pluff', + 'Tumlinson', + 'Widick', + 'Yeargan', + 'Hanft', + 'Novinger', + 'Ruddle', + 'Wrye', + 'Felde', + 'Basic', + 'Babington', + 'Karson', + 'Forgy', + 'Rendall', + 'Icard', + 'Jann', + 'Ady', + 'Therrell', + 'Sroufe', + 'Maden', + 'Ganus', + 'Preddy', + 'Marberry', + 'Fonder', + 'Latty', + 'Leatherbury', + 'Mentor', + 'Brissett', + 'Mcglory', + 'Readus', + 'Akau', + 'Bellone', + 'Berendt', + 'Bok', + 'Broten', + 'Colosi', + 'Corio', + 'Gilani', + 'Huffmaster', + 'Kieler', + 'Leonor', + 'Lips', + 'Madron', + 'Missey', + 'Nabozny', + 'Panning', + 'Reinwald', + 'Ridener', + 'Silvio', + 'Soder', + 'Spieler', + 'Vaeth', + 'Vincenti', + 'Walczyk', + 'Washko', + 'Wiater', + 'Wilen', + 'Windish', + 'Consalvo', + 'Fioravanti', + 'Hinners', + 'Paduano', + 'Ranum', + 'Parlato', + 'Dweck', + 'Matern', + 'Cryder', + 'Rubert', + 'Furgason', + 'Virella', + 'Boylen', + 'Devenport', + 'Perrodin', + 'Hollingshed', + 'Pennix', + 'Bogdanski', + 'Carretero', + 'Cubillos', + 'Deponte', + 'Forrey', + 'Gatchalian', + 'Geisen', + 'Gombos', + 'Hartlage', + 'Huddy', + 'Kou', + 'Matsko', + 'Muffley', + 'Niazi', + 'Nodarse', + 'Pawelek', + 'Pyper', + 'Stahnke', + 'Udall', + 'Baldyga', + 'Chrostowski', + 'Frable', + 'Handshoe', + 'Helderman', + 'Lambing', + 'Marolf', + 'Maynez', + 'Bunde', + 'Coia', + 'Piersol', + 'Agne', + 'Manwarren', + 'Bolter', + 'Kirsh', + 'Limerick', + 'Degray', + 'Bossie', + 'Frizell', + 'Saulters', + 'Staple', + 'Raspberry', + 'Arvie', + 'Abler', + 'Caya', + 'Ceci', + 'Dado', + 'Dewoody', + 'Hartzel', + 'Haverstick', + 'Kagel', + 'Kinnan', + 'Krock', + 'Kubica', + 'Laun', + 'Leimbach', + 'Mecklenburg', + 'Messmore', + 'Milich', + 'Mor', + 'Nachreiner', + 'Novelo', + 'Poer', + 'Vaupel', + 'Wery', + 'Breisch', + 'Cashdollar', + 'Corbridge', + 'Craker', + 'Heiberger', + 'Landress', + 'Leichty', + 'Wiedmann', + 'Yankowski', + 'Rigel', + 'Eary', + 'Riggen', + 'Nazir', + 'Shambo', + 'Gingery', + 'Guyon', + 'Bogie', + 'Kar', + 'Manifold', + 'Lafavor', + 'Montas', + 'Yeadon', + 'Cutchin', + 'Burkins', + 'Achille', + 'Bulls', + 'Torry', + 'Bartkus', + 'Beshara', + 'Busalacchi', + 'Calkin', + 'Corkum', + 'Crilley', + 'Cuny', + 'Delgaudio', + 'Devenney', + 'Emanuelson', + 'Fiel', + 'Galanti', + 'Gravina', + 'Herzing', + 'Huckaba', + 'Jaquish', + 'Kellermann', + 'Ketola', + 'Klunder', + 'Kolinski', + 'Kosak', + 'Loscalzo', + 'Moehle', + 'Ressel', + 'Skora', + 'Steakley', + 'Traugott', + 'Volden', + 'Berrong', + 'Kehres', + 'Loeffelholz', + 'Mensinger', + 'Nudo', + 'Pargas', + 'Endy', + 'Corniel', + 'Azzam', + 'Soard', + 'Flud', + 'Shuffler', + 'Hiley', + 'Logwood', + 'Ducre', + 'Aikey', + 'Ardolino', + 'Bergstresser', + 'Cen', + 'Delpriore', + 'Divelbiss', + 'Fishkin', + 'Gaucin', + 'Hemmingsen', + 'Inscore', + 'Kathman', + 'Kempen', + 'Koble', + 'Maestre', + 'Mcmonigle', + 'Merendino', + 'Meske', + 'Pietrzyk', + 'Renfrew', + 'Shevchenko', + 'Wied', + 'Digeronimo', + 'Heberer', + 'Himmelberger', + 'Nordmeyer', + 'Pocius', + 'Sigurdson', + 'Simic', + 'Steury', + 'Kealey', + 'Sabat', + 'Verstraete', + 'Patchell', + 'Finigan', + 'Critz', + 'Janelle', + 'Cima', + 'Zachariah', + 'Lebon', + 'Kellough', + 'Whitehall', + 'Jaudon', + 'Civil', + 'Dokes', + 'Slappy', + 'Bernacki', + 'Castronovo', + 'Douty', + 'Formoso', + 'Handelman', + 'Hauswirth', + 'Janowicz', + 'Klostermann', + 'Lochridge', + 'Mcdiarmid', + 'Schmale', + 'Shaddox', + 'Sitzes', + 'Spaw', + 'Urbanik', + 'Voller', + 'Fujikawa', + 'Kimmet', + 'Klingel', + 'Stoffregen', + 'Thammavong', + 'Varelas', + 'Whobrey', + 'Mandella', + 'Montuori', + 'Safrit', + 'Turan', + 'Khokhar', + 'Sircy', + 'Sabio', + 'Fill', + 'Brandao', + 'Avans', + 'Mencer', + 'Sherley', + 'Mccadden', + 'Sydney', + 'Smack', + 'Lastrapes', + 'Rowser', + 'Moultry', + 'Faulcon', + 'Arnall', + 'Babiak', + 'Balsam', + 'Bezanson', + 'Bocook', + 'Bohall', + 'Celi', + 'Costillo', + 'Crom', + 'Crusan', + 'Dibari', + 'Donaho', + 'Followell', + 'Gaudino', + 'Gericke', + 'Gori', + 'Hurrell', + 'Jakubiak', + 'Kazemi', + 'Koslosky', + 'Massoud', + 'Niebla', + 'Noffke', + 'Panjwani', + 'Papandrea', + 'Patella', + 'Plambeck', + 'Plichta', + 'Prinkey', + 'Raptis', + 'Ruffini', + 'Shoen', + 'Temkin', + 'Thul', + 'Vandall', + 'Wyeth', + 'Zalenski', + 'Consoli', + 'Gumbert', + 'Milanowski', + 'Musolf', + 'Naeger', + 'Okonski', + 'Orrison', + 'Solache', + 'Verdone', + 'Woehler', + 'Yonke', + 'Risdon', + 'Orzech', + 'Bergland', + 'Collen', + 'Bloodsworth', + 'Furgeson', + 'Moch', + 'Callegari', + 'Alphonso', + 'Ozier', + 'Paulding', + 'Ringold', + 'Yarde', + 'Abbett', + 'Axford', + 'Capwell', + 'Datz', + 'Delillo', + 'Delisa', + 'Dicaprio', + 'Dimare', + 'Faughnan', + 'Fehrenbacher', + 'Gellert', + 'Ging', + 'Gladhill', + 'Goates', + 'Hammerstrom', + 'Hilbun', + 'Iodice', + 'Kadish', + 'Kilker', + 'Lurvey', + 'Maue', + 'Michna', + 'Parslow', + 'Pawelski', + 'Quenzer', + 'Raboin', + 'Sader', + 'Sawka', + 'Velis', + 'Wilczewski', + 'Willemsen', + 'Zebley', + 'Benscoter', + 'Denhartog', + 'Dolinsky', + 'Malacara', + 'Mccosh', + 'Modugno', + 'Tsay', + 'Vanvoorst', + 'Mincher', + 'Nickol', + 'Elster', + 'Kerce', + 'Brittan', + 'Quilter', + 'Spike', + 'Mcintee', + 'Boldon', + 'Balderama', + 'Cauffman', + 'Chovanec', + 'Difonzo', + 'Fagerstrom', + 'Galanis', + 'Jeziorski', + 'Krasowski', + 'Lansdale', + 'Laven', + 'Magallan', + 'Mahal', + 'Mehrer', + 'Naus', + 'Peltzer', + 'Petraitis', + 'Pritz', + 'Salway', + 'Savich', + 'Schmehl', + 'Teniente', + 'Tuccillo', + 'Wahlquist', + 'Wetz', + 'Brozovich', + 'Catalfamo', + 'Dioguardi', + 'Guzzetta', + 'Hanak', + 'Lipschutz', + 'Sholtis', + 'Bleecker', + 'Sattar', + 'Thivierge', + 'Camfield', + 'Heslep', + 'Tree', + 'Calvey', + 'Mcgowin', + 'Strickling', + 'Manderson', + 'Dieudonne', + 'Bonini', + 'Bruinsma', + 'Burgueno', + 'Cotugno', + 'Fukunaga', + 'Krog', + 'Lacerda', + 'Larrivee', + 'Lepera', + 'Pinilla', + 'Reichenberger', + 'Rovner', + 'Rubiano', + 'Saraiva', + 'Smolka', + 'Soboleski', + 'Tallmadge', + 'Wigand', + 'Wikle', + 'Bentsen', + 'Bierer', + 'Cohenour', + 'Dobberstein', + 'Holderbaum', + 'Polhamus', + 'Skousen', + 'Theiler', + 'Fornes', + 'Sisley', + 'Zingale', + 'Nimtz', + 'Prieur', + 'Mccaughan', + 'Fawaz', + 'Hobbins', + 'Killingbeck', + 'Roads', + 'Nicolson', + 'Mcculloh', + 'Verges', + 'Badley', + 'Shorten', + 'Litaker', + 'Laseter', + 'Stthomas', + 'Mcguffie', + 'Depass', + 'Flemons', + 'Ahola', + 'Armacost', + 'Bearse', + 'Downum', + 'Drechsel', + 'Farooqi', + 'Filosa', + 'Francesconi', + 'Kielbasa', + 'Latella', + 'Monarch', + 'Ozawa', + 'Papadakis', + 'Politano', + 'Poucher', + 'Roussin', + 'Safley', + 'Schwer', + 'Tesoro', + 'Tsan', + 'Wintersteen', + 'Zanni', + 'Barlage', + 'Brancheau', + 'Buening', + 'Dahlem', + 'Forni', + 'Gerety', + 'Gutekunst', + 'Leamer', + 'Liwanag', + 'Meech', + 'Wigal', + 'Bonta', + 'Cheetham', + 'Crespi', + 'Fahs', + 'Prow', + 'Postle', + 'Delacy', + 'Dufort', + 'Gallery', + 'Romey', + 'Aime', + 'Molock', + 'Dixion', + 'Carstarphen', + 'Appleyard', + 'Aylsworth', + 'Barberi', + 'Contini', + 'Cugini', + 'Eiben', + 'Faso', + 'Hartog', + 'Jelen', + 'Loayza', + 'Maugeri', + 'Mcgannon', + 'Osorno', + 'Paratore', + 'Sahagian', + 'Sarracino', + 'Scallon', + 'Sypniewski', + 'Teters', + 'Throgmorton', + 'Vogelpohl', + 'Walkowski', + 'Winchel', + 'Niedermeyer', + 'Jayroe', + 'Montello', + 'Neyer', + 'Milder', + 'Obar', + 'Stanis', + 'Pro', + 'Pin', + 'Fatheree', + 'Cotterell', + 'Reeds', + 'Comrie', + 'Zamor', + 'Gradney', + 'Poullard', + 'Betker', + 'Bondarenko', + 'Buchko', + 'Eischens', + 'Glavan', + 'Hannold', + 'Heafner', + 'Karaffa', + 'Krabbe', + 'Meinzer', + 'Olgin', + 'Raeder', + 'Sarff', + 'Senechal', + 'Sette', + 'Shovlin', + 'Slife', + 'Tallarico', + 'Trivino', + 'Wyszynski', + 'Audia', + 'Facemire', + 'Januszewski', + 'Klebba', + 'Kovacik', + 'Moroni', + 'Nieder', + 'Schorn', + 'Sundby', + 'Tehan', + 'Trias', + 'Kissler', + 'Margo', + 'Jefcoat', + 'Bulow', + 'Maire', + 'Vizcarrondo', + 'Ki', + 'Ayuso', + 'Mayhan', + 'Usman', + 'Blincoe', + 'Whidby', + 'Tinson', + 'Calarco', + 'Cena', + 'Ciccarello', + 'Cloe', + 'Consolo', + 'Davydov', + 'Decristofaro', + 'Delmundo', + 'Dubrow', + 'Ellinwood', + 'Gehling', + 'Halberstadt', + 'Hascall', + 'Hoeffner', + 'Huettl', + 'Iafrate', + 'Imig', + 'Khoo', + 'Krausz', + 'Kuether', + 'Kulla', + 'Marchesani', + 'Ormonde', + 'Platzer', + 'Preusser', + 'Rebel', + 'Reidhead', + 'Riehm', + 'Robertshaw', + 'Runco', + 'Sandino', + 'Spare', + 'Trefethen', + 'Tribby', + 'Yamazaki', + 'Ziesmer', + 'Calamari', + 'Deyoe', + 'Marullo', + 'Neidigh', + 'Salveson', + 'Senesac', + 'Ausburn', + 'Herner', + 'Seagrave', + 'Lormand', + 'Niblock', + 'Somes', + 'Naim', + 'Murren', + 'Callander', + 'Glassco', + 'Henri', + 'Jabbar', + 'Bordes', + 'Altemose', + 'Bagnell', + 'Belloso', + 'Beougher', + 'Birchall', + 'Cantara', + 'Demetriou', + 'Galford', + 'Hast', + 'Heiny', + 'Hieronymus', + 'Jehle', + 'Khachatryan', + 'Kristof', + 'Kubas', + 'Mano', + 'Munar', + 'Ogas', + 'Riccitelli', + 'Sidman', + 'Suchocki', + 'Tortorello', + 'Trombino', + 'Vullo', + 'Badura', + 'Clerkin', + 'Criollo', + 'Dashnaw', + 'Mednick', + 'Pickrel', + 'Mawson', + 'Hockey', + 'Alo', + 'Frankland', + 'Gaby', + 'Hoda', + 'Marchena', + 'Fawbush', + 'Cowing', + 'Aydelott', + 'Dieu', + 'Rise', + 'Morten', + 'Gunby', + 'Modeste', + 'Balcerzak', + 'Cutbirth', + 'Dejoseph', + 'Desaulniers', + 'Dimperio', + 'Dubord', + 'Gruszka', + 'Haske', + 'Hehr', + 'Kolander', + 'Kusiak', + 'Lampron', + 'Mapel', + 'Montie', + 'Mumme', + 'Naramore', + 'Raffel', + 'Ruter', + 'Sawa', + 'Sencion', + 'Somogyi', + 'Ventola', + 'Zabawa', + 'Alagna', + 'Burmaster', + 'Chirco', + 'Gjerde', + 'Hilgenberg', + 'Huntress', + 'Kochel', + 'Nist', + 'Schena', + 'Toolan', + 'Wurzer', + 'Masih', + 'Ritts', + 'Rousse', + 'Buckey', + 'Sausedo', + 'Dolle', + 'Bena', + 'Franca', + 'Commins', + 'Gago', + 'Pattie', + 'Brener', + 'Verley', + 'Griffy', + 'Heiskell', + 'Osley', + 'Babula', + 'Barbone', + 'Berzins', + 'Demirjian', + 'Dietze', + 'Haseltine', + 'Heinbaugh', + 'Henneke', + 'Korba', + 'Levitz', + 'Lorenzini', + 'Mansilla', + 'Peffley', + 'Poletti', + 'Portelli', + 'Rottinghaus', + 'Scifres', + 'Stadel', + 'Stettner', + 'Swauger', + 'Vanwart', + 'Vorhies', + 'Worst', + 'Yadav', + 'Yebra', + 'Kreiter', + 'Mroczek', + 'Pennella', + 'Stangelo', + 'Suchan', + 'Weiand', + 'Widhalm', + 'Wojcicki', + 'Gutzman', + 'Griffee', + 'Konicki', + 'Moorehouse', + 'Neighbor', + 'Butte', + 'Cooter', + 'Humpherys', + 'Morrish', + 'Stockhausen', + 'Slatter', + 'Cheely', + 'Yassin', + 'Bazil', + 'Mcsween', + 'Anastos', + 'Annunziato', + 'Bora', + 'Burkitt', + 'Cino', + 'Codding', + 'Criado', + 'Firestine', + 'Goecke', + 'Golda', + 'Holloran', + 'Homen', + 'Laubscher', + 'Memmer', + 'Navejar', + 'Peraino', + 'Petrizzo', + 'Pflieger', + 'Pint', + 'Porcello', + 'Raffety', + 'Riedesel', + 'Salado', + 'Scaletta', + 'Schuring', + 'Slaydon', + 'Solecki', + 'Spomer', + 'Waldridge', + 'Zawislak', + 'Bottone', + 'Helgesen', + 'Knippel', + 'Loutzenhiser', + 'Mallinson', + 'Malnar', + 'Pethtel', + 'Sissel', + 'Thorstenson', + 'Winokur', + 'Dittmann', + 'Fencl', + 'Kernen', + 'Gath', + 'Hiney', + 'Godman', + 'Hopton', + 'Tinley', + 'Wamble', + 'Greg', + 'Garrette', + 'Acoff', + 'Ausman', + 'Burggraf', + 'Colliver', + 'Dejulio', + 'Fedorchak', + 'Finocchio', + 'Grasse', + 'Harpold', + 'Hopman', + 'Kilzer', + 'Losasso', + 'Lovallo', + 'Neumayer', + 'Purohit', + 'Reddinger', + 'Scheper', + 'Valbuena', + 'Wenzl', + 'Eilerman', + 'Galbo', + 'Haydu', + 'Vipond', + 'Wesselman', + 'Yeagle', + 'Boutelle', + 'Odonnel', + 'Morocco', + 'Speak', + 'Ruckel', + 'Cornier', + 'Burbidge', + 'Esselman', + 'Daisey', + 'Juran', + 'Henard', + 'Trench', + 'Hurry', + 'Estis', + 'Allport', + 'Beedy', + 'Blower', + 'Bogacz', + 'Caldas', + 'Carriero', + 'Garand', + 'Gonterman', + 'Harbeck', + 'Husar', + 'Lizcano', + 'Lonardo', + 'Meneely', + 'Misiewicz', + 'Pagliuca', + 'Pember', + 'Rybacki', + 'Safar', + 'Seeberger', + 'Siharath', + 'Spoerl', + 'Tattersall', + 'Birchmeier', + 'Denunzio', + 'Dustman', + 'Franchini', + 'Gettel', + 'Goldrick', + 'Goodheart', + 'Keshishyan', + 'Mcgrogan', + 'Newingham', + 'Scheier', + 'Skorupa', + 'Utech', + 'Weidenbach', + 'Chaloupka', + 'Grater', + 'Libman', + 'Recore', + 'Savona', + 'Verbeke', + 'Lunetta', + 'Schlater', + 'Staffieri', + 'Troll', + 'Leyton', + 'Peto', + 'Trella', + 'Follin', + 'Morro', + 'Woodhall', + 'Krauser', + 'Salles', + 'Brunty', + 'Wadford', + 'Shaddock', + 'Minnie', + 'Mountcastle', + 'Butter', + 'Galentine', + 'Longsworth', + 'Edgecombe', + 'Babino', + 'Printup', + 'Humbles', + 'Vessel', + 'Relford', + 'Taite', + 'Aliberti', + 'Brostrom', + 'Budlong', + 'Bykowski', + 'Coursen', + 'Darga', + 'Doutt', + 'Gomberg', + 'Greaser', + 'Hilde', + 'Hirschy', + 'Mayorquin', + 'Mcartor', + 'Mechler', + 'Mein', + 'Montville', + 'Peskin', + 'Popiel', + 'Ricciardelli', + 'Terrana', + 'Urton', + 'Cardiff', + 'Foiles', + 'Humann', + 'Pokorney', + 'Seehafer', + 'Sporer', + 'Timme', + 'Tweten', + 'Widrick', + 'Harnack', + 'Chamlee', + 'Lafountaine', + 'Lowdermilk', + 'Akel', + 'Maulden', + 'Sloman', + 'Odonald', + 'Hitchman', + 'Pendergraph', + 'Klugh', + 'Mctier', + 'Stargell', + 'Hailu', + 'Kanu', + 'Abrahamian', + 'Ackerly', + 'Belongia', + 'Cudmore', + 'Jaskolski', + 'Kedzierski', + 'Licciardi', + 'Lowenberg', + 'Meitzler', + 'Metzer', + 'Mitcheltree', + 'Nishioka', + 'Pascuzzi', + 'Pelphrey', + 'Ramones', + 'Schuchard', + 'Smithee', + 'Bignell', + 'Blaszak', + 'Borello', + 'Fiacco', + 'Garrelts', + 'Guzowski', + 'Rychlik', + 'Siebers', + 'Speziale', + 'Zauner', + 'Corell', + 'Welt', + 'Koby', + 'Auletta', + 'Bursch', + 'Luckman', + 'Vanhoesen', + 'Russian', + 'Statton', + 'Yahya', + 'Boxx', + 'Haltiwanger', + 'Redhead', + 'Mcgregory', + 'Baccari', + 'Berrey', + 'Bogden', + 'Braniff', + 'Cafarelli', + 'Clavette', + 'Corallo', + 'Dealy', + 'Gilger', + 'Gitter', + 'Goldwasser', + 'Hillesheim', + 'Hulsizer', + 'Jankovic', + 'Limburg', + 'Lopera', + 'Mcaleese', + 'Mcclintick', + 'Montealegre', + 'Mosko', + 'Nogle', + 'Ordones', + 'Papesh', + 'Peragine', + 'Picco', + 'Podraza', + 'Ras', + 'Rezek', + 'Rork', + 'Schraufnagel', + 'Scipione', + 'Terlizzi', + 'Vanblarcom', + 'Yoshino', + 'Beaverson', + 'Behunin', + 'Isch', + 'Janiga', + 'Koeppe', + 'Laurich', + 'Vondrak', + 'Walkley', + 'Hottenstein', + 'Garms', + 'Macknight', + 'Seagroves', + 'Shehata', + 'Arons', + 'Liley', + 'Pressly', + 'Cowper', + 'Branon', + 'Abdella', + 'Milord', + 'Appenzeller', + 'Ardila', + 'Belgard', + 'Boop', + 'Burbano', + 'Capitano', + 'Carrig', + 'Conrey', + 'Donica', + 'Fineberg', + 'Gemberling', + 'Harrier', + 'Hufnagle', + 'Kitner', + 'Lessing', + 'Manoukian', + 'Menk', + 'Repetto', + 'Rhinesmith', + 'Stechschulte', + 'Yep', + 'Zuhlke', + 'Abundiz', + 'Buccellato', + 'Closser', + 'Gielow', + 'Nurmi', + 'Pelka', + 'Piscitello', + 'Shoaff', + 'Champlain', + 'Conran', + 'Leidig', + 'Carel', + 'Zahid', + 'Dimitri', + 'Sapia', + 'Labauve', + 'Khalifa', + 'Gonsoulin', + 'Parrot', + 'Propps', + 'Dunnaway', + 'Cayo', + 'Mccleod', + 'Bonifas', + 'Dirkes', + 'Farruggia', + 'Gut', + 'Heacox', + 'Herrejon', + 'Ipina', + 'Keatley', + 'Kowitz', + 'Kratky', + 'Langseth', + 'Nidiffer', + 'Plimpton', + 'Riesenberg', + 'Sulewski', + 'Tabar', + 'Takara', + 'Tomassetti', + 'Tweet', + 'Weltz', + 'Youtsey', + 'Franckowiak', + 'Geffert', + 'Glawe', + 'Hillestad', + 'Ladewig', + 'Luckow', + 'Radebaugh', + 'Ransbottom', + 'Stordahl', + 'Weimar', + 'Wiegers', + 'Jowett', + 'Tomb', + 'Waitt', + 'Beaudreau', + 'Notter', + 'Rijo', + 'Denike', + 'Mam', + 'Vent', + 'Gamage', + 'Carre', + 'Childrey', + 'Heaven', + 'Forge', + 'Beckom', + 'Collick', + 'Bovell', + 'Hardimon', + 'Shells', + 'Bolf', + 'Canete', + 'Cozby', + 'Dunlavey', + 'Febo', + 'Lamke', + 'Lant', + 'Larned', + 'Leiss', + 'Lofthouse', + 'Marohn', + 'Stradling', + 'Subramaniam', + 'Vitug', + 'Ziccardi', + 'Akamine', + 'Bellissimo', + 'Bottini', + 'Braund', + 'Cavasos', + 'Heltsley', + 'Landstrom', + 'Lisiecki', + 'Navejas', + 'Sobczyk', + 'Trela', + 'Yablonski', + 'Yocham', + 'Fier', + 'Laiche', + 'Zenor', + 'Grew', + 'Naval', + 'Garratt', + 'Sako', + 'Zollicoffer', + 'Momon', + 'Bensman', + 'Cirincione', + 'Dimitrov', + 'Domeier', + 'Gaska', + 'Gensel', + 'Gernert', + 'Groot', + 'Guarisco', + 'Llorente', + 'Ludemann', + 'Moisan', + 'Muzio', + 'Neiswender', + 'Ottaway', + 'Paslay', + 'Readinger', + 'Skok', + 'Spittle', + 'Sweany', + 'Tanzi', + 'Upadhyay', + 'Valone', + 'Varas', + 'Benecke', + 'Faulstich', + 'Hebda', + 'Jobst', + 'Schleis', + 'Shuart', + 'Treinen', + 'Fok', + 'Dentler', + 'Ginty', + 'Ronda', + 'Tess', + 'Scantlin', + 'Kham', + 'Murin', + 'Faubert', + 'Ocarroll', + 'Maranda', + 'Gadsby', + 'Mouse', + 'Lunden', + 'Asquith', + 'Batley', + 'Bazzle', + 'Hooke', + 'Macneal', + 'Desnoyers', + 'Verdier', + 'Biglow', + 'Leverson', + 'Becherer', + 'Cecilio', + 'Correale', + 'Ehinger', + 'Erney', + 'Fassnacht', + 'Humpal', + 'Korpela', + 'Kratt', + 'Kunes', + 'Lockyer', + 'Macho', + 'Manfredo', + 'Maturino', + 'Raineri', + 'Seiger', + 'Stant', + 'Tecson', + 'Tempest', + 'Traverse', + 'Vonk', + 'Wormington', + 'Yeske', + 'Erichsen', + 'Fiorelli', + 'Fouty', + 'Hodgkiss', + 'Lindenbaum', + 'Matusik', + 'Mazzocco', + 'Oldani', + 'Ronca', + 'Amero', + 'Ormand', + 'Cagley', + 'Teutsch', + 'Likins', + 'Blurton', + 'Lapier', + 'Rensch', + 'Howitt', + 'Kady', + 'Broce', + 'Gaba', + 'Summerson', + 'Faure', + 'Densley', + 'Matkins', + 'Boleware', + 'Rahming', + 'Degrate', + 'Broaden', + 'Barbian', + 'Brancaccio', + 'Dimiceli', + 'Doukas', + 'Fredell', + 'Fritchman', + 'Gahr', + 'Geerdes', + 'Heidrick', + 'Hernon', + 'Ipsen', + 'Koci', + 'Lato', + 'Lyng', + 'Montella', + 'Petraglia', + 'Redlinger', + 'Riedlinger', + 'Rodier', + 'Shenton', + 'Smigiel', + 'Spanbauer', + 'Swetland', + 'Sypolt', + 'Taubert', + 'Wallander', + 'Willers', + 'Ziller', + 'Bielak', + 'Careaga', + 'Droddy', + 'Girardot', + 'Kanouse', + 'Perusse', + 'Schwier', + 'Velo', + 'Westrum', + 'Bouza', + 'Calverley', + 'Shupert', + 'Simi', + 'Zieger', + 'Nicole', + 'Fergeson', + 'Guerrant', + 'Tongue', + 'Amison', + 'Darius', + 'Banasiak', + 'Cocca', + 'Dannemiller', + 'Frommer', + 'Guardia', + 'Herl', + 'Lippa', + 'Nappo', + 'Olaya', + 'Ozburn', + 'Patry', + 'Pontiff', + 'Rauth', + 'Reier', + 'Rolfs', + 'Sassone', + 'Servidio', + 'Shough', + 'Tencza', + 'Ernster', + 'Helminiak', + 'Mcmanamon', + 'Ottens', + 'Vinh', + 'Bula', + 'Elza', + 'Serres', + 'Holan', + 'Wetherill', + 'Balis', + 'Schexnider', + 'Harral', + 'Dulany', + 'Webley', + 'Addleman', + 'Antonopoulos', + 'Badman', + 'Czerwonka', + 'Deweerd', + 'Donaghey', + 'Duszynski', + 'Firkus', + 'Foell', + 'Goyne', + 'Hattabaugh', + 'Herbel', + 'Liebelt', + 'Lovera', + 'Quenneville', + 'Ramic', + 'Rissmiller', + 'Schlag', + 'Selover', + 'Seyer', + 'Stangeland', + 'Stutesman', + 'Suminski', + 'Sweger', + 'Tetlow', + 'Thornbury', + 'Votava', + 'Weberg', + 'Canniff', + 'Evetts', + 'Gutterman', + 'Kasparek', + 'Krenzer', + 'Luckenbaugh', + 'Mainwaring', + 'Vanderweide', + 'Balladares', + 'Riesterer', + 'Salmen', + 'Mirando', + 'Rockman', + 'Warnes', + 'Crispell', + 'Corban', + 'Chrystal', + 'Barlowe', + 'Perot', + 'Ka', + 'Stockett', + 'Montfort', + 'Reagor', + 'Coote', + 'Christon', + 'Dor', + 'Apt', + 'Bandel', + 'Bibbee', + 'Brunkhorst', + 'Dexheimer', + 'Disharoon', + 'Engelstad', + 'Glaza', + 'Locey', + 'Loughney', + 'Minotti', + 'Posa', + 'Renzulli', + 'Schlauch', + 'Shadix', + 'Sloboda', + 'Topor', + 'Vacha', + 'Cerulli', + 'Ciaravino', + 'Cisek', + 'Congrove', + 'Domzalski', + 'Fleitas', + 'Helfand', + 'Lehnen', + 'Moleski', + 'Walski', + 'Dazey', + 'Mckellips', + 'Kanne', + 'Deguire', + 'Macmurray', + 'Marcelli', + 'Creach', + 'Antrobus', + 'Hykes', + 'Barriere', + 'Avinger', + 'Handford', + 'Beaufort', + 'Abend', + 'Bozzi', + 'Burnsworth', + 'Crosthwaite', + 'Eilert', + 'Frigon', + 'Hanbury', + 'Hoilman', + 'Isaksen', + 'Juday', + 'Legarda', + 'Mcgourty', + 'Mittler', + 'Olkowski', + 'Pau', + 'Pescador', + 'Pinkerman', + 'Renno', + 'Rescigno', + 'Salsgiver', + 'Schlanger', + 'Sobek', + 'Stasi', + 'Talaga', + 'Tish', + 'Tropea', + 'Umphress', + 'Weisheit', + 'Bartolini', + 'Dassow', + 'Ferullo', + 'Fetherolf', + 'Kimery', + 'Kurihara', + 'Schneiter', + 'Sramek', + 'Swier', + 'Weinzierl', + 'Karrer', + 'Hurta', + 'Lodico', + 'Conkright', + 'Sandvik', + 'Pash', + 'Pinell', + 'Dougal', + 'Burnet', + 'Hoe', + 'Rann', + 'Curvin', + 'Route', + 'Outler', + 'Corprew', + 'Berhe', + 'Eleby', + 'Acoba', + 'Ante', + 'Baio', + 'Befort', + 'Brueck', + 'Chevere', + 'Ciani', + 'Farnes', + 'Hamar', + 'Hirschhorn', + 'Imbrogno', + 'Kegg', + 'Leever', + 'Mesker', + 'Nodal', + 'Olveda', + 'Paletta', + 'Pilant', + 'Rissman', + 'Sebold', + 'Siebel', + 'Smejkal', + 'Stai', + 'Vanderkolk', + 'Allday', + 'Canupp', + 'Dieck', + 'Hinders', + 'Karcz', + 'Shomaker', + 'Tuinstra', + 'Urquizo', + 'Wiltgen', + 'Withem', + 'Yanda', + 'Blizard', + 'Christenbury', + 'Helser', + 'Jing', + 'Stave', + 'Waddill', + 'Mairena', + 'Rebert', + 'Gara', + 'Shipes', + 'Hartsoe', + 'Bargeron', + 'Arne', + 'Ebrahim', + 'Basha', + 'Rozar', + 'Venter', + 'Mounger', + 'Marsalis', + 'Gildon', + 'Antkowiak', + 'Brus', + 'Cicalese', + 'Einspahr', + 'Faucheux', + 'Frix', + 'Gateley', + 'Hamberger', + 'Holdorf', + 'Hollibaugh', + 'Junod', + 'Keaveny', + 'Knechtel', + 'Kuffel', + 'Mcwhirt', + 'Navis', + 'Neave', + 'Rackers', + 'Romagnoli', + 'Shawhan', + 'Valvano', + 'Vina', + 'Wielgus', + 'Wojtaszek', + 'Bartnik', + 'Fiebelkorn', + 'Gertsch', + 'Morgenthaler', + 'Nambo', + 'Nemmers', + 'Nihart', + 'Nilges', + 'Pulgarin', + 'Recktenwald', + 'Vandenbrink', + 'Wion', + 'Cundy', + 'Burby', + 'Cu', + 'Vansciver', + 'Herne', + 'Doughtie', + 'Cowdery', + 'Woodle', + 'Lafosse', + 'Hodgens', + 'Mckune', + 'Car', + 'Callens', + 'Corsey', + 'Brimage', + 'Westry', + 'Arismendez', + 'Benenati', + 'Brine', + 'Brookbank', + 'Burfield', + 'Charnock', + 'Copado', + 'Demilio', + 'Elvira', + 'Fantini', + 'Ferko', + 'Flanagin', + 'Gotto', + 'Hartsough', + 'Heckart', + 'Herskowitz', + 'Hoene', + 'Ishibashi', + 'Kysar', + 'Leaverton', + 'Longfield', + 'Mischel', + 'Musleh', + 'Neyra', + 'Obeirne', + 'Ostrum', + 'Pedretti', + 'Pilkerton', + 'Plasse', + 'Reesor', + 'Roznowski', + 'Rusinko', + 'Sickle', + 'Spiteri', + 'Stash', + 'Syracuse', + 'Trachsel', + 'Weinand', + 'Gruenberg', + 'Gutkowski', + 'Morella', + 'Morneault', + 'Slivinski', + 'Blessinger', + 'Taketa', + 'Hussaini', + 'Obeid', + 'Seebeck', + 'Spayd', + 'Keasling', + 'Famularo', + 'Carne', + 'Lacosse', + 'Morino', + 'Gutzmer', + 'Spinola', + 'Deahl', + 'Crumm', + 'Folley', + 'Lennard', + 'Rowson', + 'Pickron', + 'Union', + 'Abraha', + 'Yohannes', + 'Whidbee', + 'Mccaster', + 'Batzel', + 'Borowy', + 'Disanti', + 'Druck', + 'Elsbury', + 'Eschmann', + 'Fehn', + 'Flesner', + 'Grawe', + 'Haapala', + 'Helvie', + 'Hudy', + 'Joswick', + 'Kilcullen', + 'Mabus', + 'Marzo', + 'Obradovich', + 'Oriordan', + 'Phy', + 'Scarff', + 'Schappert', + 'Scire', + 'Vandevander', + 'Weyland', + 'Anstey', + 'Feeback', + 'Komarek', + 'Kyllo', + 'Manivong', + 'Timberman', + 'Tinkey', + 'Zempel', + 'Haselhorst', + 'Herberg', + 'Laris', + 'Morter', + 'Fredman', + 'Reny', + 'Ferrall', + 'Silverthorne', + 'Shuttlesworth', + 'Stigers', + 'Koker', + 'Mollette', + 'Mansel', + 'Chrisp', + 'Glymph', + 'Preyer', + 'Worlds', + 'Arutyunyan', + 'Carrizosa', + 'Dambrosia', + 'Dantuono', + 'Delduca', + 'Florencio', + 'Garafola', + 'Habermehl', + 'Hanaway', + 'Harmes', + 'Heinonen', + 'Hellstrom', + 'Herzer', + 'Klahr', + 'Kobler', + 'Korner', + 'Lancia', + 'Leask', + 'Ledo', + 'Manzanarez', + 'Myung', + 'Prestigiacomo', + 'Serpe', + 'Tonche', + 'Ventrella', + 'Walrod', + 'Warga', + 'Wasmer', + 'Weins', + 'Zaccaro', + 'Bartus', + 'Fiumara', + 'Incorvaia', + 'Khatun', + 'Kisamore', + 'Riesen', + 'Santry', + 'Schmierer', + 'Talamo', + 'Zaccone', + 'Liddick', + 'Mcclune', + 'Hade', + 'Calcutt', + 'Gillet', + 'Husein', + 'Be', + 'Lavell', + 'Veley', + 'Buckholtz', + 'Naves', + 'Debrosse', + 'Palms', + 'Lacewell', + 'Tates', + 'Tekle', + 'Golphin', + 'Asleson', + 'Bartlebaugh', + 'Benter', + 'Bielefeld', + 'Cappetta', + 'Hanback', + 'Heeg', + 'Helf', + 'Hibberd', + 'Holsworth', + 'Kowalchuk', + 'Kruczek', + 'Lieurance', + 'Markwood', + 'Muckey', + 'Rasey', + 'Rautio', + 'Salek', + 'Schwaller', + 'Scibilia', + 'Speltz', + 'Stopper', + 'Struckman', + 'Surowiec', + 'Texter', + 'Venturi', + 'Wolfenden', + 'Zortman', + 'Dehler', + 'Gillogly', + 'Hoelzel', + 'Iida', + 'Paparella', + 'Petrea', + 'Pflaum', + 'Spampinato', + 'Spaur', + 'Umbaugh', + 'Cerney', + 'Athens', + 'Salvas', + 'Gardinier', + 'Ammar', + 'Arns', + 'Calvi', + 'Palazzola', + 'Starlin', + 'Quave', + 'Rhame', + 'Gulliford', + 'Nettle', + 'Picken', + 'Warde', + 'Pelissier', + 'Mcteer', + 'Freeny', + 'Tappin', + 'Bromell', + 'People', + 'Carthen', + 'Battenfield', + 'Bunte', + 'Estrin', + 'Fitzner', + 'Flattery', + 'Hlavacek', + 'Holecek', + 'Jorstad', + 'Jurczak', + 'Kraszewski', + 'Lencioni', + 'Mamula', + 'Mater', + 'Petrakis', + 'Safranek', + 'Santorelli', + 'Speyer', + 'Waterworth', + 'Worner', + 'Antonellis', + 'Codispoti', + 'Docken', + 'Economos', + 'Petrilla', + 'Puccinelli', + 'Rondinelli', + 'Leibel', + 'Santoya', + 'Hader', + 'Yeakley', + 'Dowse', + 'Hattan', + 'Lia', + 'Emel', + 'Corse', + 'Danes', + 'Rambin', + 'Dura', + 'Kyne', + 'Sanderford', + 'Mincer', + 'Rawl', + 'Staves', + 'Mccleave', + 'Faniel', + 'Abeln', + 'Asta', + 'Beymer', + 'Cresap', + 'Cryderman', + 'Gutwein', + 'Kaszuba', + 'Maland', + 'Marella', + 'Mcmannis', + 'Molenaar', + 'Olivarria', + 'Panfil', + 'Pieratt', + 'Ramthun', + 'Resurreccion', + 'Rosander', + 'Rostad', + 'Sallas', + 'Santone', + 'Schey', + 'Shasteen', + 'Spalla', + 'Sui', + 'Tannous', + 'Tarman', + 'Trayer', + 'Wolman', + 'Chausse', + 'Debacker', + 'Dozal', + 'Hach', + 'Klossner', + 'Kruchten', + 'Mahowald', + 'Rosenlund', + 'Steffenhagen', + 'Vanmaanen', + 'Wildasin', + 'Winiecki', + 'Dilauro', + 'Wygal', + 'Cadmus', + 'Smallman', + 'Sear', + 'Berch', + 'Nabor', + 'Bro', + 'Storr', + 'Goynes', + 'Chestang', + 'Alvillar', + 'Arya', + 'Aton', + 'Bors', + 'Brydon', + 'Castagno', + 'Catena', + 'Catterson', + 'Chhun', + 'Delrossi', + 'Garnsey', + 'Harbeson', + 'Holum', + 'Iglesia', + 'Kleen', + 'Lavallie', + 'Lossing', + 'Miyata', + 'Myszka', + 'Peth', + 'Pyka', + 'Radler', + 'Roggenkamp', + 'Sarra', + 'Schmeltz', + 'Schreifels', + 'Schrimpf', + 'Scrogham', + 'Sieminski', + 'Singson', + 'Stichter', + 'Vajda', + 'Vilardo', + 'Ziff', + 'Cegielski', + 'Fanara', + 'Mefferd', + 'Polanski', + 'Reining', + 'Roggow', + 'Sassi', + 'Wagenknecht', + 'Roadcap', + 'Tuman', + 'Demesa', + 'Surita', + 'Armando', + 'Macks', + 'Megan', + 'Angello', + 'Bosher', + 'Neugent', + 'Croslin', + 'Bumpas', + 'Gladman', + 'Demmons', + 'Mcnairy', + 'Sermons', + 'Okonkwo', + 'Alvira', + 'Barfuss', + 'Bersch', + 'Bustin', + 'Ciriello', + 'Cords', + 'Cuddeback', + 'Debono', + 'Delosh', + 'Haeger', + 'Ida', + 'Kneer', + 'Koppen', + 'Kottwitz', + 'Laib', + 'Matsushita', + 'Mckone', + 'Meester', + 'Ohashi', + 'Pickert', + 'Risso', + 'Vannice', + 'Vargason', + 'Vorpahl', + 'Gluth', + 'Goossens', + 'Kloeppel', + 'Krolczyk', + 'Lequire', + 'Nila', + 'Savoia', + 'Wassmer', + 'Bianca', + 'Rousselle', + 'Wittler', + 'Gillean', + 'Cervi', + 'Fremin', + 'Vanzanten', + 'Varvel', + 'Sween', + 'Peron', + 'Trudo', + 'Welford', + 'Scovil', + 'Beazer', + 'Cohill', + 'Estime', + 'Alcalde', + 'Bugay', + 'Bushard', + 'Dethloff', + 'Gahn', + 'Gronau', + 'Hogston', + 'Kleinfelter', + 'Ksiazek', + 'Lyness', + 'Marak', + 'Munafo', + 'Noorani', + 'Plonski', + 'Pontarelli', + 'Presas', + 'Ringenberg', + 'Sabillon', + 'Schaut', + 'Shankland', + 'Sheil', + 'Shugrue', + 'Soter', + 'Stankovich', + 'Arrants', + 'Boeckmann', + 'Boothroyd', + 'Dysinger', + 'Gersh', + 'Monnig', + 'Scheiderer', + 'Slifka', + 'Vilardi', + 'Podell', + 'Tarallo', + 'Goodroe', + 'Sardinha', + 'Blish', + 'Califf', + 'Dorion', + 'Dougall', + 'Hamza', + 'Boggus', + 'Mccan', + 'Branscomb', + 'Baatz', + 'Bendix', + 'Hartstein', + 'Hechler', + 'Komatsu', + 'Kooiman', + 'Loghry', + 'Lorson', + 'Mcgoff', + 'Moga', + 'Monsees', + 'Nigg', + 'Pacitti', + 'Shiffman', + 'Shoupe', + 'Snarski', + 'Vrba', + 'Wilmeth', + 'Yurchak', + 'Budney', + 'Estok', + 'Knipple', + 'Krzywicki', + 'Librizzi', + 'Obringer', + 'Poliquin', + 'Severtson', + 'Vecchiarelli', + 'Zelazny', + 'Eis', + 'Wildeman', + 'Gatt', + 'Gordin', + 'Dusenbury', + 'Prew', + 'Mander', + 'Tine', + 'Debarr', + 'Bann', + 'Mcguirt', + 'Vanloan', + 'Basdeo', + 'Kosh', + 'Bertha', + 'Mcglothen', + 'Youman', + 'Hallums', + 'Mcfield', + 'Asano', + 'Barbo', + 'Braver', + 'Bua', + 'Buetow', + 'Buttke', + 'Estela', + 'Kauk', + 'Kosmicki', + 'Kuecker', + 'Lahm', + 'Lienhard', + 'Lombera', + 'Menken', + 'Niederhauser', + 'Norcia', + 'Petrelli', + 'Phong', + 'Piontkowski', + 'Prihoda', + 'Raffo', + 'Sherpa', + 'Shinsky', + 'Skoczylas', + 'Sosinski', + 'Sua', + 'Sur', + 'Thorndike', + 'Trease', + 'Wessler', + 'Witting', + 'Ackroyd', + 'Bartnick', + 'Dziuba', + 'Lisko', + 'Muradyan', + 'Pistilli', + 'Riechers', + 'Saxman', + 'Rodi', + 'Venables', + 'Holway', + 'Vargus', + 'Oley', + 'Delmont', + 'Fuster', + 'Wyndham', + 'Whittenberg', + 'Chustz', + 'Swilling', + 'Moncure', + 'Housey', + 'Mckiver', + 'Shelvin', + 'Aslin', + 'Begeman', + 'Capek', + 'Christlieb', + 'Colasanti', + 'Daidone', + 'Detlefsen', + 'Elsass', + 'Faus', + 'Francke', + 'Hensarling', + 'Hollmann', + 'Isaacks', + 'Kocis', + 'Kofman', + 'Kwiatek', + 'Osterkamp', + 'Pickar', + 'Prellwitz', + 'Ramo', + 'Steenson', + 'Tomasulo', + 'Weinreb', + 'Wiard', + 'Ambs', + 'Baglio', + 'Frayre', + 'Hisaw', + 'Justman', + 'Morrical', + 'Sherfey', + 'Gera', + 'Ilgenfritz', + 'Silos', + 'Boge', + 'Darocha', + 'Hennon', + 'Hendriks', + 'Purrington', + 'Eunice', + 'Kirks', + 'Barbar', + 'Guichard', + 'Bonny', + 'Lobban', + 'Winrow', + 'Alavi', + 'Binner', + 'Canan', + 'Ciullo', + 'Cyran', + 'Doolen', + 'Enquist', + 'Fatzinger', + 'Forsell', + 'Harnisch', + 'Hirose', + 'Lunz', + 'Mcbrearty', + 'Mcgavin', + 'Minkin', + 'Ralphs', + 'Ruegsegger', + 'Shetter', + 'Slagter', + 'Tyminski', + 'Ubben', + 'Vanderschaaf', + 'Wigfield', + 'Zellman', + 'Bettenhausen', + 'Busker', + 'Jabs', + 'Mishkin', + 'Sturdy', + 'Vanstone', + 'Tierce', + 'Cormican', + 'Mazzucco', + 'Buenger', + 'Gallier', + 'Duma', + 'Rainbow', + 'Herlong', + 'Chriswell', + 'Litsey', + 'Wyke', + 'Kissoon', + 'Sesler', + 'Farve', + 'Lalanne', + 'Myhand', + 'Heggs', + 'Andujo', + 'Arcilla', + 'Bult', + 'Caponigro', + 'Commerford', + 'Ditmars', + 'Dressen', + 'Eggemeyer', + 'Forstner', + 'From', + 'Heldreth', + 'Hevia', + 'Leiphart', + 'Mastrocola', + 'Mcanelly', + 'Mccrillis', + 'Mellick', + 'Mogle', + 'Mummey', + 'Nishiyama', + 'Nordine', + 'Picinich', + 'Rafiq', + 'Savo', + 'Selvig', + 'Sestak', + 'Shafran', + 'Smithhart', + 'Soltani', + 'Stillion', + 'Szuch', + 'Tigert', + 'Trine', + 'Un', + 'Brest', + 'Callari', + 'Jaskowiak', + 'Maneval', + 'Sarchet', + 'Szuba', + 'Taubman', + 'Wandel', + 'Blok', + 'Pasquarello', + 'Sava', + 'Diekman', + 'Blight', + 'Lovgren', + 'Clemson', + 'Lince', + 'Kanady', + 'Whipps', + 'Coren', + 'Coye', + 'Patman', + 'Souffrant', + 'Bloodsaw', + 'Amano', + 'Cassaday', + 'Cutillo', + 'Dayrit', + 'Deringer', + 'Duwe', + 'Favazza', + 'Fennema', + 'Hackleman', + 'Harders', + 'Imperiale', + 'Kano', + 'Kingma', + 'Meuser', + 'Neiger', + 'Neitz', + 'Nied', + 'Prows', + 'Riss', + 'Rotundo', + 'Scheurich', + 'Stopa', + 'Tonks', + 'Veen', + 'Volante', + 'Maerz', + 'Nunnelley', + 'Sommerfeldt', + 'Spoonemore', + 'Wechter', + 'Wehrli', + 'Ackert', + 'Begun', + 'Dreyfuss', + 'Frezza', + 'Mako', + 'Nagao', + 'Lassetter', + 'Linse', + 'Raum', + 'Graca', + 'Enslow', + 'Bruff', + 'Hodgkin', + 'Coone', + 'Trippett', + 'Tippitt', + 'Sumerlin', + 'Carelock', + 'Whitelow', + 'Beightol', + 'Cappadona', + 'Carrizal', + 'Clendaniel', + 'Cresci', + 'Dietzman', + 'Figge', + 'Heyde', + 'Jarema', + 'Kyllonen', + 'Laminack', + 'Luddy', + 'Monical', + 'Mula', + 'Picotte', + 'Sandiego', + 'Seki', + 'Senner', + 'Starkman', + 'Stassi', + 'Stuckert', + 'Wiers', + 'Wieting', + 'Ziska', + 'Ardelean', + 'Hulslander', + 'Loewenstein', + 'Mearns', + 'Roese', + 'Sweaney', + 'Winick', + 'Zaring', + 'Farry', + 'Dulle', + 'Gunnerson', + 'Duden', + 'Arts', + 'Lame', + 'Mcquerry', + 'Smiles', + 'Pennick', + 'Adderly', + 'Becka', + 'Bluemel', + 'Bocek', + 'Bouwens', + 'Deren', + 'Dewitz', + 'Doland', + 'Ewton', + 'Funnell', + 'Gavel', + 'Haidar', + 'Kalkbrenner', + 'Kawashima', + 'Kueker', + 'Lutze', + 'Macareno', + 'Nenninger', + 'Schone', + 'Seever', + 'Sexauer', + 'Sibilia', + 'Sperrazza', + 'Vanderhoef', + 'Vanoss', + 'Werre', + 'Wotton', + 'Behney', + 'Bossart', + 'Ellithorpe', + 'Eyrich', + 'Fosco', + 'Fulginiti', + 'Grumbles', + 'Hoeger', + 'Kizziah', + 'Kloiber', + 'Kudo', + 'Majcher', + 'Stickels', + 'Stoler', + 'Umholtz', + 'Vasallo', + 'Wenker', + 'Wittmeyer', + 'Telesco', + 'Jha', + 'Maulding', + 'Campton', + 'Verble', + 'Mclure', + 'Bernardin', + 'Eison', + 'Coffie', + 'Ceesay', + 'Balakrishnan', + 'Barich', + 'Bigman', + 'Blumenstein', + 'Bonafede', + 'Cebulski', + 'Chesbro', + 'Cuaresma', + 'Demarino', + 'Derienzo', + 'Donmoyer', + 'Fairall', + 'Gelpi', + 'Giambra', + 'Hasselman', + 'Highlander', + 'Hunker', + 'Iyengar', + 'Kulaga', + 'Kuznicki', + 'Labus', + 'Limbert', + 'Molchan', + 'Neuharth', + 'Overgaard', + 'Paszkiewicz', + 'Plescia', + 'Redcay', + 'Ritzer', + 'Smirnov', + 'Valiquette', + 'Vannortwick', + 'Warstler', + 'Yantz', + 'Beardall', + 'Cimmino', + 'Crnkovich', + 'Konishi', + 'Kosowski', + 'Ragen', + 'Sebert', + 'Valla', + 'Venancio', + 'Maltez', + 'Skehan', + 'Abrantes', + 'Colfer', + 'Beman', + 'Wilhelmsen', + 'Wilking', + 'Rorer', + 'Shutes', + 'Albany', + 'Wearing', + 'Assefa', + 'Angeloni', + 'Bisher', + 'Blancett', + 'Briel', + 'Chiara', + 'Clearman', + 'Dengel', + 'Detert', + 'Fadely', + 'Flinders', + 'Garguilo', + 'Goes', + 'Hakimian', + 'Henehan', + 'Homewood', + 'Kalla', + 'Keirn', + 'Kerwood', + 'Laflam', + 'Lynskey', + 'Minhas', + 'Mow', + 'Olk', + 'Ostergaard', + 'Palecek', + 'Poirrier', + 'Raudenbush', + 'Schlottman', + 'Shatz', + 'Sieloff', + 'Stikeleather', + 'Swavely', + 'Tapanes', + 'Teehan', + 'Wendorff', + 'Wollner', + 'Bichsel', + 'Brandenburger', + 'Demattia', + 'Eggebrecht', + 'Koelzer', + 'Landrigan', + 'Morsch', + 'Pittinger', + 'Rewerts', + 'Schopf', + 'Tetro', + 'Westenberger', + 'Kieft', + 'Overy', + 'Cutrona', + 'Misa', + 'Erich', + 'Swapp', + 'Welchel', + 'Messa', + 'Ala', + 'Witbeck', + 'Mothershead', + 'Stofer', + 'Mcneice', + 'Ayling', + 'Zakaria', + 'Bu', + 'Rauf', + 'Richbourg', + 'Fristoe', + 'Dorch', + 'Mcclarin', + 'Privott', + 'Bonsu', + 'Ayson', + 'Bifulco', + 'Brungard', + 'Bub', + 'Budzynski', + 'Chizmar', + 'Coriz', + 'Corser', + 'Daughdrill', + 'Delre', + 'Elfers', + 'Fabrizi', + 'Gunawan', + 'Haecker', + 'Hammac', + 'Handwerk', + 'Larcom', + 'Liera', + 'Littlewood', + 'Luikart', + 'Pasquarella', + 'Radman', + 'Ranft', + 'Rigas', + 'Santin', + 'Sorbello', + 'Tayag', + 'Ureste', + 'Weidinger', + 'Yerena', + 'Aase', + 'Galyen', + 'Halferty', + 'Hindley', + 'Kunath', + 'Laprairie', + 'Oza', + 'Stohler', + 'Tokarczyk', + 'Yusupov', + 'Nogueras', + 'Jersey', + 'Eastes', + 'Agron', + 'Boso', + 'Kender', + 'Couse', + 'Moreta', + 'Larrow', + 'Degrace', + 'Sonier', + 'Tisdel', + 'Creque', + 'Esther', + 'Girtman', + 'Seraphin', + 'Wesby', + 'Kargbo', + 'Adjei', + 'Angeline', + 'Biby', + 'Brucks', + 'Bucaro', + 'Farman', + 'Gerdeman', + 'Hodsdon', + 'Hoying', + 'Kasperek', + 'Keinath', + 'Kidman', + 'Kleier', + 'Kuban', + 'Lacko', + 'Latourette', + 'Leffert', + 'Leonhart', + 'Mathern', + 'Ploss', + 'Poblano', + 'Raigoza', + 'Santor', + 'Schmitzer', + 'Sirico', + 'Skalsky', + 'Spreen', + 'Standlee', + 'Vonbargen', + 'Cederberg', + 'Cornforth', + 'Dercole', + 'Diblasio', + 'Fleer', + 'Fredlund', + 'Gehris', + 'Guck', + 'Lannen', + 'Lurz', + 'Mazzaferro', + 'Neukam', + 'Rookstool', + 'Scharrer', + 'Sevey', + 'Sicairos', + 'Skrocki', + 'Sneeringer', + 'Stefanowicz', + 'Zuleger', + 'Harmel', + 'Sendejo', + 'Bearer', + 'Shur', + 'Weers', + 'Norell', + 'Plotnick', + 'Cecchi', + 'Gandia', + 'Bastone', + 'Tole', + 'Tramell', + 'Willock', + 'Rhome', + 'Curington', + 'Rapley', + 'Hazley', + 'Todman', + 'Lathon', + 'Alperin', + 'Axtman', + 'Boeke', + 'Butson', + 'Cestaro', + 'Cosgriff', + 'Docter', + 'Eblin', + 'Filsinger', + 'Franzone', + 'Gareau', + 'Garfinkle', + 'Gatch', + 'Germosen', + 'Grzywacz', + 'Huesman', + 'Kasel', + 'Kazan', + 'Manalang', + 'Marando', + 'Marchio', + 'Massimino', + 'Mcneer', + 'Menger', + 'Milanese', + 'Monrreal', + 'Moretto', + 'Mulvany', + 'Petkus', + 'Rehling', + 'Rubbo', + 'Rudnik', + 'Settlemire', + 'Treon', + 'Yaklin', + 'Zittel', + 'Betzold', + 'Bohlin', + 'Churilla', + 'Conrath', + 'Ozbun', + 'Sciuto', + 'Stitz', + 'Sweigert', + 'Tamanaha', + 'Wallgren', + 'Eplin', + 'Ion', + 'Liford', + 'Orendorff', + 'Wootan', + 'Carmical', + 'Mince', + 'Stormes', + 'Lantry', + 'Sportsman', + 'Corron', + 'Padia', + 'Cunnington', + 'Pitta', + 'Ori', + 'Obara', + 'Gaultney', + 'Vanlue', + 'Emmitt', + 'Roddey', + 'Payen', + 'Elmi', + 'Culmer', + 'Mealing', + 'Allegra', + 'Bano', + 'Batterman', + 'Bickell', + 'Dager', + 'Drach', + 'Duchesneau', + 'Erdos', + 'Fedorko', + 'Fluhr', + 'Gassmann', + 'Gillig', + 'Goedert', + 'Golomb', + 'Hatler', + 'Jalali', + 'Joosten', + 'Koke', + 'Lausch', + 'Leisner', + 'Mallinger', + 'Marsolek', + 'Mashek', + 'Ognibene', + 'Oishi', + 'Outman', + 'Paganelli', + 'Passino', + 'Petrak', + 'Rosenwald', + 'Schroader', + 'Stehman', + 'Tenuta', + 'Todt', + 'Tritz', + 'Boerman', + 'Doeden', + 'Etcheverry', + 'Grissinger', + 'Gruenewald', + 'Lijewski', + 'Marcom', + 'Niebauer', + 'Rukavina', + 'Sakuma', + 'Woehrle', + 'Amores', + 'Krammes', + 'Shontz', + 'Bunning', + 'Widdowson', + 'Blankenburg', + 'Goans', + 'Longan', + 'Aboud', + 'Michelli', + 'Rivere', + 'Colla', + 'Lory', + 'Lougheed', + 'Wadel', + 'Chalkley', + 'Gaubert', + 'Goodlin', + 'Bommer', + 'Abbs', + 'Rashad', + 'Malachi', + 'Abrigo', + 'Akre', + 'Antolik', + 'Bachner', + 'Blegen', + 'Cona', + 'Diantonio', + 'Emde', + 'Enrico', + 'Follette', + 'Hagarty', + 'Hanser', + 'Hulsman', + 'Jelinski', + 'Kalisz', + 'Kolek', + 'Kough', + 'Ninneman', + 'Offield', + 'Perezgarcia', + 'Plude', + 'Printy', + 'Rosengrant', + 'Salminen', + 'Schamberger', + 'Teall', + 'Zipfel', + 'Bickler', + 'Casanas', + 'Holtzapple', + 'Sachdeva', + 'Scharnhorst', + 'Schnack', + 'Grode', + 'Strough', + 'Teare', + 'Korona', + 'Creelman', + 'Simper', + 'Marett', + 'Nadeem', + 'Pollet', + 'Eduardo', + 'Chipley', + 'Vanrossum', + 'Fabio', + 'Colona', + 'Whirley', + 'Hider', + 'Plaskett', + 'Trabue', + 'Gibert', + 'Cabiness', + 'Loyal', + 'Rayson', + 'Aloia', + 'Aukerman', + 'Broxterman', + 'Cada', + 'Catalanotto', + 'Condos', + 'Corriher', + 'Eliopoulos', + 'Furia', + 'Girolamo', + 'Haese', + 'Israelson', + 'Jaworowski', + 'Jirik', + 'Kalmar', + 'Leipold', + 'Lemmo', + 'Loja', + 'Loughmiller', + 'Matelski', + 'Mcrorie', + 'Moeckel', + 'Naill', + 'Raczka', + 'Rathgeber', + 'Shamoun', + 'Shannahan', + 'Simler', + 'Stamer', + 'Stonehocker', + 'Twersky', + 'Voeltz', + 'Willets', + 'Wolgamott', + 'Yamin', + 'Acri', + 'Dalgleish', + 'Ehrenreich', + 'Huish', + 'Huxley', + 'Pinkstaff', + 'Rincones', + 'Saric', + 'Shreiner', + 'Stitely', + 'Tippets', + 'Vanamburg', + 'Zbikowski', + 'Sharrett', + 'Suther', + 'Renta', + 'Balles', + 'Florentine', + 'Chrisley', + 'Offner', + 'Matheus', + 'Akens', + 'Dugue', + 'Rigaud', + 'Mohamud', + 'Magloire', + 'Stigger', + 'Andrist', + 'Chaudoin', + 'Clos', + 'Cragin', + 'Dinius', + 'Duignan', + 'Elk', + 'Frenz', + 'Frogge', + 'Giammarino', + 'Hackl', + 'Jaeckel', + 'Knieriem', + 'Lajara', + 'Lisak', + 'Luxton', + 'Merriott', + 'Montini', + 'Olender', + 'Orebaugh', + 'Orren', + 'Osika', + 'Sciascia', + 'Selvaggio', + 'Stoneback', + 'Sweis', + 'Torosyan', + 'Trupp', + 'Wardrip', + 'Wigle', + 'Beissel', + 'Brakke', + 'Carosella', + 'Dobek', + 'Eidem', + 'Homolka', + 'Kemery', + 'Kinderman', + 'Palla', + 'Puccini', + 'Szarek', + 'Vandehei', + 'Arca', + 'Jou', + 'Needs', + 'Habermann', + 'Hyle', + 'Jagoda', + 'Smigielski', + 'Guttierrez', + 'Awwad', + 'Maccormack', + 'Bassin', + 'Achee', + 'Demark', + 'Jardon', + 'Kelsoe', + 'Olear', + 'Comacho', + 'Rosetta', + 'Peddie', + 'Delsol', + 'Nwachukwu', + 'Bagdasarian', + 'Boehringer', + 'Bunke', + 'Burkhammer', + 'Delahoya', + 'Dietzen', + 'Ditmer', + 'Duchaine', + 'Felske', + 'Gumpert', + 'Hansson', + 'Hedeen', + 'Jalil', + 'Kalal', + 'Kanan', + 'Kaska', + 'Kaufer', + 'Knoff', + 'Kornblum', + 'Lanzi', + 'Obenchain', + 'Piatkowski', + 'Prugh', + 'Rima', + 'Shadduck', + 'Sodergren', + 'Spitzley', + 'Tauzin', + 'Weigelt', + 'Baldassarre', + 'Biglin', + 'Fuhriman', + 'Gaumond', + 'Ledvina', + 'Meckler', + 'Minteer', + 'Nesser', + 'Riederer', + 'Ruelle', + 'Turchi', + 'Alberg', + 'Vanderlip', + 'Halder', + 'Hop', + 'Larmon', + 'Bonfield', + 'Ketch', + 'Mannis', + 'Mcallen', + 'Alfonzo', + 'Sampey', + 'Guillet', + 'Madaris', + 'Lisby', + 'Crowner', + 'Frager', + 'Coar', + 'Crewe', + 'Levier', + 'Ligons', + 'Abello', + 'Brinsfield', + 'Buccieri', + 'Cantera', + 'Cieslinski', + 'Cragle', + 'Flater', + 'Grunert', + 'Higinbotham', + 'Janish', + 'Kuennen', + 'Lanners', + 'Lesiak', + 'Litvin', + 'Madueno', + 'Maffia', + 'Manetta', + 'Marschke', + 'Mourer', + 'Nordahl', + 'Nordan', + 'Pankowski', + 'Petron', + 'Qualley', + 'Recht', + 'Rosenbach', + 'Ruttenberg', + 'Saam', + 'Savarino', + 'Solana', + 'Stumpff', + 'Tsukamoto', + 'Vanlanen', + 'Wainer', + 'Kasza', + 'Kuehler', + 'Landgren', + 'Omahony', + 'Paullin', + 'Ramales', + 'Schmelzle', + 'Schnakenberg', + 'Touma', + 'Urgiles', + 'Vorndran', + 'Corne', + 'Higman', + 'Dutil', + 'Reef', + 'Racanelli', + 'Gladwin', + 'Jaspers', + 'Crutchley', + 'Homme', + 'Hughbanks', + 'Crismon', + 'Burdin', + 'Dise', + 'Enzor', + 'Hally', + 'Mccone', + 'Mckell', + 'Belo', + 'Moat', + 'Ijames', + 'Bussie', + 'Papillion', + 'Pratcher', + 'Baranek', + 'Bidlack', + 'Boyadjian', + 'Chern', + 'Conahan', + 'Dimuzio', + 'Erker', + 'Fregeau', + 'Gelsinger', + 'Gonzalo', + 'Heo', + 'Hoog', + 'Jovanovich', + 'Kaschak', + 'Kasik', + 'Katich', + 'Laible', + 'Mastel', + 'Muellner', + 'Pingleton', + 'Rexroth', + 'Schmitter', + 'Stick', + 'Strollo', + 'Traficante', + 'Veteto', + 'Wampole', + 'Winings', + 'Amalfitano', + 'Amiot', + 'Camaj', + 'Cuartas', + 'Drotar', + 'Eatherton', + 'Fioretti', + 'Fudala', + 'Gehrman', + 'Gittleman', + 'Heppe', + 'Maffucci', + 'Tammen', + 'Chovan', + 'Ginley', + 'Stipes', + 'Antigua', + 'Ironside', + 'Kuroda', + 'Lebar', + 'Laske', + 'Salay', + 'Gisi', + 'Mccormic', + 'Veron', + 'Robbin', + 'Morain', + 'Mayden', + 'Vanputten', + 'Triplet', + 'Ravenel', + 'Moragne', + 'Bowdry', + 'Agundez', + 'Allinson', + 'Bosko', + 'Buehrle', + 'Devey', + 'Gasiorowski', + 'Goettel', + 'Halleran', + 'Innocenti', + 'Orser', + 'Scarpati', + 'Scherff', + 'Schlott', + 'Skilling', + 'Speedy', + 'Staal', + 'Szafran', + 'Szczech', + 'Szczepanik', + 'Venturella', + 'Vert', + 'Vogelgesang', + 'Vollbrecht', + 'Wiehe', + 'Achterberg', + 'Fadness', + 'Groene', + 'Halbrooks', + 'Leavenworth', + 'Pruski', + 'Redifer', + 'Schmiesing', + 'Stanforth', + 'Stepanski', + 'Ziel', + 'Hefter', + 'Urman', + 'Muela', + 'Simpler', + 'Elick', + 'Shalabi', + 'Cooner', + 'Ferriera', + 'Templer', + 'Prashad', + 'Gorum', + 'Wheller', + 'Spratling', + 'Gutter', + 'Eke', + 'Rias', + 'Belcourt', + 'Bernards', + 'Camburn', + 'Cerqueira', + 'Conkel', + 'Deist', + 'Derobertis', + 'Desio', + 'Eimer', + 'Fayad', + 'Frommelt', + 'Guariglia', + 'Laba', + 'Labine', + 'Lanius', + 'Loconte', + 'Nop', + 'Omary', + 'Penninger', + 'Pentland', + 'Pinkus', + 'Richoux', + 'Sturrock', + 'Theil', + 'Vanvranken', + 'Bartoszek', + 'Bruski', + 'Engelken', + 'Kranich', + 'Mrazek', + 'Muralles', + 'Pienta', + 'Salido', + 'Sridhar', + 'Turkington', + 'Vellucci', + 'Verhage', + 'Derenzo', + 'Lucker', + 'Wands', + 'Parrow', + 'Branyon', + 'Houff', + 'Bossier', + 'Reels', + 'Rockmore', + 'Altmeyer', + 'Anacker', + 'Antoniou', + 'Berlinger', + 'Busser', + 'Caracci', + 'Caseres', + 'Corcino', + 'Demint', + 'Dhanani', + 'Erekson', + 'Farinacci', + 'Ganesan', + 'Gornick', + 'Gresser', + 'Kremers', + 'Kreuter', + 'Lesieur', + 'Linarez', + 'Mccrystal', + 'Morang', + 'Pucillo', + 'Spicuzza', + 'Tranchina', + 'Tullar', + 'Vantilburg', + 'Yeck', + 'Zandstra', + 'Zeleny', + 'Bearss', + 'Burgner', + 'Delich', + 'Fetsch', + 'Grom', + 'Kreisel', + 'Laprise', + 'Legarreta', + 'Musacchio', + 'Rembold', + 'Sjoblom', + 'Skalicky', + 'Sokolov', + 'Tuminello', + 'Vanskiver', + 'Zidek', + 'Severa', + 'Stables', + 'Guffy', + 'Lebeck', + 'Barradas', + 'Chanley', + 'Dayal', + 'Villafranco', + 'Droke', + 'Popwell', + 'Renier', + 'Bolten', + 'Mille', + 'Swagerty', + 'Grismore', + 'Brantly', + 'Divens', + 'Ottey', + 'Hagger', + 'Advincula', + 'Boschee', + 'Buckbee', + 'Carlan', + 'Casciato', + 'Cregar', + 'Fehring', + 'Ianniello', + 'Interrante', + 'Juedes', + 'Kosier', + 'Lizaola', + 'Lorenzetti', + 'Mccauslin', + 'Older', + 'Osuch', + 'Ramstad', + 'Sare', + 'Stavinoha', + 'Taborda', + 'Warmoth', + 'Weissmann', + 'Winograd', + 'Woeste', + 'Zywicki', + 'Blalack', + 'Chavoya', + 'Clickner', + 'Daigrepont', + 'Dissinger', + 'Kovalik', + 'Lemler', + 'Shortall', + 'Tucholski', + 'Vanmetre', + 'Zetino', + 'Niezgoda', + 'Recupero', + 'Booms', + 'Ramsburg', + 'Berka', + 'Mininger', + 'Tamer', + 'Baka', + 'Jago', + 'Bucks', + 'Laude', + 'Andrepont', + 'Gair', + 'Hayer', + 'Kitching', + 'Towson', + 'Slappey', + 'Syms', + 'Derico', + 'Badie', + 'Kenon', + 'Goffney', + 'Amigon', + 'Belsito', + 'Bergamo', + 'Caputi', + 'Delpilar', + 'Entsminger', + 'Gehres', + 'Geimer', + 'Hada', + 'Krolak', + 'Kruer', + 'Malaney', + 'Mancias', + 'Misiaszek', + 'Pring', + 'Salonga', + 'Schaefers', + 'Schmied', + 'Schwertfeger', + 'Scialabba', + 'Stemmer', + 'Stifter', + 'Suon', + 'Szczygiel', + 'Weisse', + 'Yackley', + 'Decasas', + 'Donado', + 'Drenning', + 'Eppich', + 'Kertesz', + 'Mihal', + 'Mochizuki', + 'Schiebel', + 'Schlageter', + 'Scruton', + 'Weckerly', + 'Wemhoff', + 'Wernette', + 'Zietz', + 'Iwanicki', + 'Ara', + 'Barson', + 'Resor', + 'Rampy', + 'Iskander', + 'Oharra', + 'Kope', + 'Soli', + 'Bodkins', + 'Bussa', + 'Maletta', + 'Clemen', + 'Vaneaton', + 'Berkel', + 'Salvage', + 'Gilchrest', + 'Whitter', + 'Bruster', + 'Mccowin', + 'Gullatt', + 'Cherubin', + 'Flamer', + 'Gueye', + 'Angerer', + 'Baray', + 'Barreca', + 'Bresson', + 'Brougham', + 'Buscaglia', + 'Candee', + 'Decelles', + 'Durflinger', + 'Dusenbery', + 'Enomoto', + 'Galliano', + 'Klooster', + 'Lowrimore', + 'Manda', + 'Morace', + 'Raisanen', + 'Ravenscraft', + 'Rutman', + 'Schmieg', + 'Schorsch', + 'Selim', + 'Stanchfield', + 'Stankowski', + 'Tolosa', + 'Uyeno', + 'Vancleef', + 'Kamdar', + 'Kazlauskas', + 'Kwasnik', + 'Pivonka', + 'Shrode', + 'Sellinger', + 'Deliz', + 'Longerbeam', + 'Schobert', + 'Shader', + 'Collister', + 'Curtright', + 'Franc', + 'Wakely', + 'Duree', + 'Laban', + 'Gascoigne', + 'Noy', + 'Hulon', + 'Michele', + 'Crowden', + 'Dolton', + 'Ryner', + 'Gene', + 'Tetterton', + 'Laffitte', + 'Laidler', + 'Hoston', + 'Akter', + 'Biebel', + 'Bohnenkamp', + 'Bottger', + 'Brecheisen', + 'Bumbarger', + 'Burgert', + 'Burtnett', + 'Coffing', + 'Corigliano', + 'Dault', + 'Dettinger', + 'Fenech', + 'Golaszewski', + 'Hernando', + 'Hoppel', + 'Kadrmas', + 'Khim', + 'Labrado', + 'Leh', + 'Michiels', + 'Milkovich', + 'Mosel', + 'Nestle', + 'Nunan', + 'Palomarez', + 'Peretz', + 'Perno', + 'Popowski', + 'Pottebaum', + 'Rallis', + 'Rase', + 'Rotramel', + 'Sokolik', + 'Sparlin', + 'Zipf', + 'Abruzzese', + 'Branin', + 'Cheslock', + 'Chimenti', + 'Czechowski', + 'Diveley', + 'Eisenbeis', + 'Eisenhut', + 'Friedt', + 'Gehlhausen', + 'Kamphaus', + 'Mctiernan', + 'Monnett', + 'Schue', + 'Steffensmeier', + 'Gens', + 'Schlotterbeck', + 'Ask', + 'Leser', + 'Renville', + 'Wisenbaker', + 'Kellow', + 'Mounsey', + 'Dupin', + 'Causer', + 'Yapp', + 'Stmary', + 'Bowditch', + 'Nickolson', + 'Molla', + 'Larke', + 'Kamau', + 'Cardinali', + 'Deely', + 'Deep', + 'Dietel', + 'Ferraris', + 'Fons', + 'Hahm', + 'Huy', + 'Imber', + 'Leichliter', + 'Longanecker', + 'Lordi', + 'Ludewig', + 'Maiolo', + 'Mckern', + 'Meyering', + 'Muhl', + 'Nylen', + 'Ohlendorf', + 'Palmgren', + 'Raffield', + 'Reusser', + 'Revette', + 'Ridolfi', + 'Rosemeyer', + 'Seber', + 'Silberberg', + 'Sitzmann', + 'Tayman', + 'Tygart', + 'Vertz', + 'Volkmer', + 'Bellemare', + 'Benanti', + 'Bialecki', + 'Biber', + 'Dipierro', + 'Dornbush', + 'Eichhorst', + 'Messana', + 'Neisen', + 'Ottoson', + 'Salmonson', + 'Turcott', + 'Vlachos', + 'Wojdyla', + 'Dagg', + 'Hernan', + 'Mannes', + 'Fent', + 'Tappen', + 'Hyers', + 'Gery', + 'Deam', + 'Channing', + 'Gesner', + 'Swaringen', + 'Lakins', + 'Cogbill', + 'Allsbrook', + 'Kennemore', + 'Sumrell', + 'Luma', + 'Rookard', + 'Shakoor', + 'Philbert', + 'Maragh', + 'Wordlaw', + 'Ofori', + 'Arseneault', + 'Arslanian', + 'Aydin', + 'Balthaser', + 'Bensch', + 'Boord', + 'Botting', + 'Brummet', + 'Cassiday', + 'Chubbuck', + 'Crance', + 'Dobis', + 'Dymek', + 'Kakar', + 'Kipnis', + 'Kooi', + 'Kovack', + 'Malzahn', + 'Melendes', + 'Micucci', + 'Miklas', + 'Molander', + 'Nungesser', + 'Razavi', + 'Reppond', + 'Reznick', + 'Rosten', + 'Schwegler', + 'Sielaff', + 'Sincavage', + 'Soave', + 'Socorro', + 'Tausch', + 'Tracz', + 'Vey', + 'Weltman', + 'Wittich', + 'Emswiler', + 'Etzkorn', + 'Kuchenbecker', + 'Lampi', + 'Pfahler', + 'Thronson', + 'Trefz', + 'Pont', + 'Hendrie', + 'Russon', + 'Coleson', + 'Gregori', + 'Herzfeld', + 'Tamas', + 'Oslin', + 'Warrell', + 'Basher', + 'Elizabeth', + 'Nickolas', + 'Prigmore', + 'Okray', + 'Cannedy', + 'Mercy', + 'Daigre', + 'Leggins', + 'Savannah', + 'Russaw', + 'Opoku', + 'Angier', + 'Behrle', + 'Budny', + 'Cislo', + 'Covalt', + 'Dershem', + 'Devincent', + 'Dhar', + 'Dombrosky', + 'Dragovich', + 'Drobny', + 'Fess', + 'Genthner', + 'Gierhart', + 'Hadzic', + 'Hehir', + 'Henle', + 'Heyd', + 'Hudlow', + 'Janko', + 'Kapral', + 'Kietzman', + 'Malburg', + 'Maret', + 'Mcever', + 'Sann', + 'Scheidel', + 'Schultheiss', + 'Sedita', + 'Sigl', + 'Starace', + 'Stoklosa', + 'Tainter', + 'Tamburrino', + 'Vankleeck', + 'Vannucci', + 'Wernecke', + 'Widmayer', + 'Agresti', + 'Boshell', + 'Dartt', + 'Dobkin', + 'Effertz', + 'Gaydosh', + 'Hocevar', + 'Kluger', + 'Mcguffee', + 'Pekala', + 'Tuchman', + 'Keylon', + 'Pletz', + 'Germond', + 'Keedy', + 'Meir', + 'Tromp', + 'Solly', + 'Baerga', + 'Jawad', + 'Chanda', + 'Scobie', + 'Snowball', + 'Pricer', + 'Graper', + 'Bally', + 'Mcfarlan', + 'Duncombe', + 'Mccory', + 'Costen', + 'Poplar', + 'Denkins', + 'Padmore', + 'Waithe', + 'Adduci', + 'Aldaba', + 'Berhow', + 'Cocuzza', + 'Dubroc', + 'Earnheart', + 'Eickholt', + 'Gutzwiller', + 'Heavin', + 'Himebaugh', + 'Jakubik', + 'Kiang', + 'Klusman', + 'Knueppel', + 'Neddo', + 'Oakey', + 'Rachlin', + 'Spegal', + 'Spizzirri', + 'Stavola', + 'Zika', + 'Beverlin', + 'Boehle', + 'Caltagirone', + 'Chernick', + 'Ciaccia', + 'Courchaine', + 'Covault', + 'Crihfield', + 'Fojtik', + 'Gronski', + 'Huwe', + 'Ostrovsky', + 'Quraishi', + 'Rauber', + 'Scalici', + 'Schuetze', + 'Advani', + 'Galer', + 'Rog', + 'Husson', + 'Karpen', + 'Ess', + 'Henman', + 'Slatten', + 'Bango', + 'Barkin', + 'Vessell', + 'Mayson', + 'Kittles', + 'Quince', + 'Beardmore', + 'Breceda', + 'Carmony', + 'Ciliberto', + 'Cotroneo', + 'Dimitroff', + 'Granahan', + 'Haacke', + 'Huska', + 'Jankiewicz', + 'Klipp', + 'Kostic', + 'Langarica', + 'Lanphier', + 'Maran', + 'Marmion', + 'Mclinden', + 'Mcpeake', + 'Minkel', + 'Nicolo', + 'Quihuis', + 'Siemsen', + 'Somero', + 'Spuhler', + 'Spychalski', + 'Stary', + 'Stitzer', + 'Stucke', + 'Tango', + 'Ticas', + 'Vivero', + 'Campen', + 'Fei', + 'Ganas', + 'Klipfel', + 'Vodicka', + 'Zajdel', + 'Ulin', + 'Bodey', + 'Moral', + 'Fellenz', + 'Charo', + 'Cliver', + 'Clasby', + 'Neeson', + 'Durell', + 'Hew', + 'Mcgray', + 'Breaker', + 'Haslem', + 'Verser', + 'Broner', + 'Mannings', + 'Darensbourg', + 'Petithomme', + 'Akbari', + 'Amdahl', + 'Boeger', + 'Bougie', + 'Buffo', + 'Cisar', + 'Deleonardis', + 'Diffee', + 'Dillen', + 'Dingley', + 'Dugo', + 'Fedora', + 'Habibi', + 'Hartland', + 'Hennelly', + 'Kachmar', + 'Louth', + 'Mughal', + 'Muska', + 'Narang', + 'Pontillo', + 'Roel', + 'Shehorn', + 'Smick', + 'Soliven', + 'Starzyk', + 'Swaminathan', + 'Teagarden', + 'Thune', + 'Vokes', + 'Volkov', + 'Weckesser', + 'Wigen', + 'Donaghue', + 'Ederer', + 'Glaus', + 'Gwozdz', + 'Kimler', + 'Kocak', + 'Lagerquist', + 'Pellecchia', + 'Ruminski', + 'Scholler', + 'Steurer', + 'Tlatelpa', + 'Zegarra', + 'Janssens', + 'Jass', + 'Ciriaco', + 'Kessner', + 'Georg', + 'Harre', + 'Brannam', + 'Beel', + 'Kaine', + 'Roher', + 'Evora', + 'Rittman', + 'Sion', + 'Millon', + 'Morre', + 'Bouler', + 'Seegars', + 'Jenifer', + 'Bernd', + 'Chahine', + 'Crisanto', + 'Desautel', + 'Dirosa', + 'Fehringer', + 'Fukui', + 'Hetz', + 'Hueber', + 'Ivanova', + 'Klecker', + 'Kulzer', + 'Machi', + 'Menn', + 'Mudry', + 'Niro', + 'Nyenhuis', + 'Pressel', + 'Prusinski', + 'Roske', + 'Shaefer', + 'Stear', + 'Stumpo', + 'Teas', + 'Tolsma', + 'Troha', + 'Vanveen', + 'Waltermire', + 'Zaretsky', + 'Zingg', + 'Arntson', + 'Dizdarevic', + 'Kassebaum', + 'Natzke', + 'Passanisi', + 'Rodebaugh', + 'Skonieczny', + 'Vanhoozer', + 'Wiechert', + 'Golonka', + 'Roycroft', + 'Robl', + 'Lisboa', + 'Brandis', + 'Symmes', + 'Nou', + 'Pawson', + 'Comins', + 'Ranker', + 'Silman', + 'Lonas', + 'Goldthwaite', + 'Aries', + 'Leckey', + 'Conolly', + 'Ezelle', + 'Degrasse', + 'Tarte', + 'Bonaventure', + 'Rambeau', + 'Alsobrooks', + 'Blumenberg', + 'Snape', + 'Delane', + 'Sarr', + 'Rankine', + 'Mcclarty', + 'Skipwith', + 'Mapps', + 'Poke', + 'Ahlman', + 'Brunkow', + 'Crissinger', + 'Critcher', + 'Cronce', + 'Earney', + 'Fischler', + 'Franta', + 'Haist', + 'Hirschfield', + 'Jacobe', + 'Karraker', + 'Kronenberger', + 'Layland', + 'Liscano', + 'Lohrman', + 'Luy', + 'Macik', + 'Makinen', + 'Mis', + 'Musarra', + 'Orbe', + 'Ortloff', + 'Potempa', + 'Presta', + 'Rebollo', + 'Rudden', + 'Schab', + 'Settlemyre', + 'Shaban', + 'Shiraishi', + 'Shrake', + 'Suba', + 'Tornquist', + 'Treglia', + 'Vanschaick', + 'Velten', + 'Waln', + 'Addeo', + 'Dacquisto', + 'Fenno', + 'Gilberg', + 'Halberstam', + 'Holck', + 'Landgrebe', + 'Lipa', + 'Luehrs', + 'Mkrtchyan', + 'Proscia', + 'Schucker', + 'Selner', + 'Sinisi', + 'Wandersee', + 'Weigold', + 'Winterrowd', + 'Stoutenburg', + 'Medinger', + 'Bittman', + 'Gerges', + 'Langelier', + 'Berdine', + 'Hartshorne', + 'Matters', + 'Lavere', + 'Delauter', + 'Caillouet', + 'Elford', + 'Derrington', + 'Mollison', + 'Erskin', + 'Doswell', + 'Loadholt', + 'Stepter', + 'Contee', + 'Adwell', + 'Banez', + 'Birchler', + 'Bodman', + 'Bransfield', + 'Butzer', + 'Cenci', + 'Fabro', + 'Fila', + 'Follman', + 'Geoffrion', + 'Hardegree', + 'Klindt', + 'Kuzniar', + 'Lapenta', + 'Lasorsa', + 'Lykens', + 'Madariaga', + 'Mcginnity', + 'Mezger', + 'Milleson', + 'Nisly', + 'Palau', + 'Salz', + 'Sholly', + 'Spartz', + 'Spevak', + 'Svehla', + 'Trafford', + 'Treu', + 'Winski', + 'Zervas', + 'Bautch', + 'Dybas', + 'Hillenburg', + 'Krahl', + 'Loretto', + 'Mcanany', + 'Meschke', + 'Panuco', + 'Pezzullo', + 'Pokorski', + 'Reinertson', + 'Spoden', + 'Steinbrenner', + 'Wedig', + 'Mom', + 'Furner', + 'Harpin', + 'Carlston', + 'Oo', + 'Betten', + 'Duro', + 'Veronica', + 'Klutz', + 'Coven', + 'Siles', + 'Carby', + 'Duvernay', + 'Gory', + 'Adamczak', + 'Adee', + 'Agius', + 'Bachicha', + 'Belka', + 'Bridenstine', + 'Cappella', + 'Chiao', + 'Georgiadis', + 'Hansmann', + 'Kettlewell', + 'Klemann', + 'Kracke', + 'Legacy', + 'Mateja', + 'Mcgarrigle', + 'Peitz', + 'Pergande', + 'Proia', + 'Reicher', + 'Rentfrow', + 'Rudkin', + 'Sahni', + 'Santopietro', + 'Sarin', + 'Schear', + 'Seckel', + 'Sopp', + 'Sorci', + 'Terbush', + 'Uplinger', + 'Vantol', + 'Zaro', + 'Cuppett', + 'Depetro', + 'Hofferber', + 'Kreifels', + 'Kuznetsov', + 'Matassa', + 'Mazanec', + 'Naegle', + 'Sphar', + 'Villaneda', + 'Wachholz', + 'Pastrano', + 'Pilotte', + 'Shedden', + 'Molt', + 'Dalia', + 'Bishara', + 'Dumoulin', + 'Dehnert', + 'Dilmore', + 'Termine', + 'Bracher', + 'Laplace', + 'Sherin', + 'Morine', + 'Garrott', + 'Banford', + 'Drumwright', + 'Linnen', + 'Belay', + 'Juste', + 'Moment', + 'Adamec', + 'Alessandrini', + 'Bolda', + 'Buonanno', + 'Corrow', + 'Couvillon', + 'Dahnke', + 'Durrani', + 'Errett', + 'Fingerhut', + 'Ittner', + 'Kandler', + 'Khosla', + 'Mascio', + 'Mesch', + 'Napolitan', + 'Packman', + 'Parady', + 'Saline', + 'Spatafore', + 'Squiers', + 'Stailey', + 'Stolar', + 'Strommen', + 'Vahey', + 'Vanbebber', + 'Wimpee', + 'Wolinsky', + 'Yambao', + 'Ciocca', + 'Fornwalt', + 'Giannattasio', + 'Herbers', + 'Korol', + 'Lindenberger', + 'Lysne', + 'Piacentini', + 'Vogeler', + 'Cassetta', + 'Hildebran', + 'Masoud', + 'Shiller', + 'Fisler', + 'Loll', + 'Wattles', + 'Carris', + 'Hippe', + 'Torregrossa', + 'Thain', + 'Enman', + 'Kanno', + 'Jeane', + 'Clendenning', + 'Halt', + 'Dorin', + 'Carnathan', + 'Bisch', + 'Simm', + 'Goatley', + 'July', + 'Oke', + 'Basley', + 'Dillahunt', + 'Times', + 'Mcglown', + 'Cohens', + 'Jeanphilippe', + 'Benshoof', + 'Bensing', + 'Bir', + 'Birnie', + 'Burklow', + 'Capili', + 'Cordts', + 'Falanga', + 'Farooqui', + 'Furber', + 'Godino', + 'Gollnick', + 'Harmening', + 'Hilpert', + 'Hrivnak', + 'Iribe', + 'Krienke', + 'Kuntzman', + 'Laslo', + 'Loso', + 'Omohundro', + 'Rabadi', + 'Reisenauer', + 'Rohrich', + 'Salak', + 'Schuckman', + 'Semmel', + 'Sendelbach', + 'Sidler', + 'Stegmann', + 'Sudbeck', + 'Tara', + 'Walcher', + 'Walkenhorst', + 'Wellbrock', + 'Capaldo', + 'Cotnoir', + 'Durrence', + 'Fralix', + 'Leibfried', + 'Schlarb', + 'Whitenight', + 'Grannan', + 'Mugford', + 'Filo', + 'Soh', + 'Deprez', + 'Semidey', + 'Vandivier', + 'Shawl', + 'Happy', + 'Gartley', + 'Jonathan', + 'Bouquet', + 'Warsaw', + 'Verne', + 'Furse', + 'Holms', + 'Bassette', + 'Fishburne', + 'Ambrosius', + 'Amrein', + 'Astorino', + 'Bedonie', + 'Bibee', + 'Brearley', + 'Chesher', + 'Colasurdo', + 'Deike', + 'Dimarino', + 'Felling', + 'Freid', + 'Gad', + 'Gambale', + 'Gieser', + 'Greff', + 'Halseth', + 'Hamor', + 'Hargens', + 'Hohenberger', + 'Hohler', + 'Illes', + 'Koscielniak', + 'Kotara', + 'Krygier', + 'Lopinto', + 'Mangas', + 'Mantione', + 'Mcendree', + 'Musich', + 'Nordling', + 'Panagopoulos', + 'Pollio', + 'Score', + 'Semaan', + 'Tortorelli', + 'Trabert', + 'Troung', + 'Vittorio', + 'Barkdoll', + 'Dombeck', + 'Ferriter', + 'Gancarz', + 'Gubbels', + 'Kertz', + 'Langenderfer', + 'Roppolo', + 'Siglin', + 'Trnka', + 'Vanderkooi', + 'Yaun', + 'Witkin', + 'Caryl', + 'Boies', + 'Carattini', + 'Hannes', + 'Harmison', + 'Mctavish', + 'Bille', + 'Sullivant', + 'Yeakey', + 'Respess', + 'Gooley', + 'Maura', + 'Jukes', + 'Oguin', + 'Demory', + 'Morson', + 'Hathorne', + 'Anklam', + 'Antaya', + 'Bentler', + 'Bettcher', + 'Bresette', + 'Broadrick', + 'Degante', + 'Demaray', + 'Dipinto', + 'Doberstein', + 'Dorminey', + 'Dorwart', + 'Gugliuzza', + 'Jesser', + 'Kjar', + 'Kujala', + 'Lemarr', + 'Lynds', + 'Novitsky', + 'Oropesa', + 'Scarpulla', + 'Schave', + 'Siravo', + 'Torma', + 'Uva', + 'Winkowski', + 'Boscia', + 'Buikema', + 'Byland', + 'Enneking', + 'Enstrom', + 'Gotsch', + 'Kulakowski', + 'Mattheis', + 'Niemuth', + 'Oberdorf', + 'Rabuck', + 'Shinners', + 'Struebing', + 'Dickes', + 'Hettrick', + 'Pille', + 'Vilar', + 'Blewitt', + 'Gutt', + 'Haseley', + 'Pennel', + 'Figuereo', + 'Lassalle', + 'Tannahill', + 'Teats', + 'Mumby', + 'Cheves', + 'Spark', + 'Ale', + 'Wally', + 'Lowndes', + 'Ballo', + 'Couper', + 'Alberta', + 'Puller', + 'Rochell', + 'Bachar', + 'Ballengee', + 'Bellizzi', + 'Boback', + 'Cammarano', + 'Dirr', + 'Findling', + 'Fruin', + 'Ghattas', + 'Kaliszewski', + 'Kammeyer', + 'Kwiecien', + 'Lamora', + 'Lehrke', + 'Macewen', + 'Nasta', + 'Neibert', + 'Ogaz', + 'Olesky', + 'Otano', + 'Prescher', + 'Romick', + 'Scibetta', + 'Slicker', + 'Ungerer', + 'Vanheel', + 'Wadas', + 'Weissert', + 'Armiger', + 'Brusca', + 'Christeson', + 'Crookshanks', + 'Demarinis', + 'Fahrney', + 'Heiple', + 'Howat', + 'Knoedler', + 'Kuske', + 'Leifheit', + 'Lukach', + 'Nauert', + 'Obremski', + 'Seidenberg', + 'Smigelski', + 'Visscher', + 'Wauneka', + 'Whitmoyer', + 'Wyand', + 'Ilardi', + 'Jackel', + 'Rackham', + 'Macgowan', + 'Braid', + 'Bringle', + 'Dirk', + 'Paci', + 'Wears', + 'Vanbergen', + 'Sidle', + 'Mellish', + 'Paino', + 'State', + 'Cargle', + 'Harcum', + 'Beyene', + 'Mwangi', + 'Anderle', + 'Cancienne', + 'Compeau', + 'Egle', + 'Farone', + 'Harke', + 'Hollopeter', + 'Jambor', + 'Jermyn', + 'Kadakia', + 'Kerker', + 'Langowski', + 'Lechman', + 'Nagengast', + 'Narvaiz', + 'Paola', + 'Partch', + 'Plucker', + 'Rawe', + 'Rohland', + 'Rosebrook', + 'Stanphill', + 'Stoltman', + 'Volkers', + 'Balingit', + 'Bausman', + 'Besler', + 'Dalto', + 'Edgren', + 'Hairfield', + 'Janek', + 'Kenoyer', + 'Koska', + 'Mihok', + 'Monjaraz', + 'Reisz', + 'Snedegar', + 'Vandezande', + 'Viscomi', + 'Kiene', + 'Dib', + 'Kuc', + 'Magley', + 'Swearingin', + 'Culliton', + 'Roome', + 'Fendrick', + 'Trindade', + 'Whaling', + 'Tarbutton', + 'Sider', + 'Swingler', + 'Lover', + 'Clarida', + 'Jocelyn', + 'Mervin', + 'Blaize', + 'Semper', + 'Bagsby', + 'Pree', + 'Dieujuste', + 'Anacleto', + 'Annable', + 'Bacci', + 'Bottari', + 'Cinco', + 'Delzell', + 'Dowless', + 'Drilling', + 'Egert', + 'Fanton', + 'Geerts', + 'Ghaffari', + 'Guggenheim', + 'Hankes', + 'Hediger', + 'Hornig', + 'Kauer', + 'Kossman', + 'Krasnow', + 'Lauman', + 'Lebsack', + 'Liendo', + 'Marhefka', + 'Noguez', + 'Oxman', + 'Pa', + 'Pella', + 'Pongratz', + 'Prisk', + 'Rajagopalan', + 'Rozo', + 'Vanvorst', + 'Wachob', + 'Avolio', + 'Banet', + 'Boissonneault', + 'Coglianese', + 'Crudele', + 'Dobratz', + 'Gerdts', + 'Koors', + 'Mazzanti', + 'Ozimek', + 'Vanhove', + 'Zern', + 'Kalama', + 'Mikelson', + 'Renehan', + 'Blecher', + 'Meath', + 'Bonus', + 'Wesch', + 'Kirkey', + 'Goldbeck', + 'Hun', + 'Morgans', + 'Strohman', + 'Lanagan', + 'Wyly', + 'Syers', + 'Berne', + 'Tondreau', + 'Witts', + 'Budhu', + 'Flott', + 'Alsbrooks', + 'Mabin', + 'Kingsberry', + 'Berend', + 'Brandeberry', + 'Carandang', + 'Ciavarella', + 'Foil', + 'Galano', + 'Garzia', + 'Golembeski', + 'Kossow', + 'Kren', + 'Lefave', + 'Macmahon', + 'Nilan', + 'Peregrina', + 'Pralle', + 'Sahakian', + 'Sarate', + 'Scalzi', + 'Soulliere', + 'Srock', + 'Stammen', + 'Sterry', + 'Tadych', + 'Trembath', + 'Watwood', + 'Wolske', + 'Woolson', + 'Aversano', + 'Chavana', + 'Digiuseppe', + 'Escano', + 'Harkrider', + 'Liebmann', + 'Soldan', + 'Swiatkowski', + 'Tomala', + 'Keay', + 'Lindstedt', + 'Maille', + 'Thurner', + 'Favia', + 'Guedes', + 'Simao', + 'Rambow', + 'Chriscoe', + 'Hiss', + 'Mcraney', + 'Barke', + 'Hobday', + 'Buri', + 'Sigle', + 'Bawa', + 'Lalande', + 'Bordon', + 'Friley', + 'Feild', + 'Arington', + 'Jons', + 'Funderburke', + 'Mccommons', + 'Troublefield', + 'Mable', + 'Hullum', + 'Wrice', + 'Cager', + 'Barse', + 'Braunschweig', + 'Dasch', + 'Fraioli', + 'Giefer', + 'Giovanniello', + 'Glahn', + 'Hatheway', + 'Holtrop', + 'Katsaros', + 'Koetting', + 'Malinoski', + 'Markov', + 'Mcclosky', + 'Mccormac', + 'Mertins', + 'Milito', + 'Mroczka', + 'Overdorf', + 'Palombi', + 'Peninger', + 'Provenza', + 'Quinnell', + 'Roady', + 'Ruthven', + 'Savitsky', + 'Shenefield', + 'Stapel', + 'Venkataraman', + 'Zachow', + 'Aaberg', + 'Bajorek', + 'Bankowski', + 'Barquero', + 'Delcamp', + 'Deshler', + 'Halili', + 'Hebenstreit', + 'Hirota', + 'Hladky', + 'Kliethermes', + 'Koestner', + 'Kroes', + 'Luepke', + 'Mckeough', + 'Mielcarek', + 'Nobis', + 'Olenik', + 'Plessinger', + 'Shillingburg', + 'Spadaccini', + 'Springborn', + 'Werden', + 'Willenbring', + 'Zyskowski', + 'Paucar', + 'Werst', + 'Wohlwend', + 'Nauss', + 'Alma', + 'Tebeau', + 'Paskett', + 'Spindle', + 'Twiddy', + 'Alomar', + 'Mi', + 'Billard', + 'Bails', + 'Channer', + 'Fripp', + 'Abreo', + 'Adamowicz', + 'Bocian', + 'Breden', + 'Breitkreutz', + 'Celona', + 'Chizek', + 'Chrestman', + 'Ciaramella', + 'Compher', + 'Crannell', + 'Dermer', + 'Duryee', + 'Feuerborn', + 'Garrels', + 'Gausman', + 'Grippi', + 'Guadamuz', + 'Hatlestad', + 'Heon', + 'Hokenson', + 'Kaden', + 'Kluever', + 'Lagares', + 'Mamone', + 'Mascola', + 'Matich', + 'Messimer', + 'Mezera', + 'Mongiello', + 'Moradi', + 'Nessler', + 'Nijjar', + 'Nin', + 'Pasquarelli', + 'Pawlowicz', + 'Petitto', + 'Petruccelli', + 'Pullano', + 'Rebar', + 'Romack', + 'Rosener', + 'Soland', + 'Solow', + 'Vandervelden', + 'Vazguez', + 'Vonruden', + 'Balmes', + 'Berninger', + 'Broecker', + 'Clogston', + 'Fontanella', + 'Gubbins', + 'Kampen', + 'Levenhagen', + 'Lyter', + 'Nagamine', + 'Regas', + 'Riecke', + 'Veltre', + 'Wojahn', + 'Angelino', + 'Mccomber', + 'Grisso', + 'Saran', + 'Pecore', + 'Sorter', + 'Encalada', + 'Robart', + 'Deerman', + 'Lori', + 'Mcnee', + 'Dagher', + 'Villars', + 'Chaplain', + 'Houtman', + 'Dingwall', + 'Akerson', + 'Donaway', + 'Dimmer', + 'Mittman', + 'Camm', + 'Kenedy', + 'Bilbro', + 'Brocks', + 'Mansaray', + 'Acebo', + 'Ahr', + 'Alayon', + 'Benyo', + 'Blatnik', + 'Degidio', + 'Dumire', + 'Elefante', + 'Gase', + 'Gilboy', + 'Gradillas', + 'Haverstock', + 'Heberle', + 'Hilmes', + 'Hjort', + 'Johnsey', + 'Lambiase', + 'Marland', + 'Mcevilly', + 'Mergenthaler', + 'Mini', + 'Noska', + 'Patrie', + 'Rohrback', + 'Seelbach', + 'Stopher', + 'Trzaska', + 'Vanessen', + 'Veillette', + 'Walizer', + 'Zapalac', + 'Andalon', + 'Beukema', + 'Cieslik', + 'Dukart', + 'Gerads', + 'Gilhooly', + 'Hinebaugh', + 'Jumonville', + 'Macchi', + 'Oldenkamp', + 'Plotz', + 'Robideau', + 'Streed', + 'Trochez', + 'Grames', + 'Beltram', + 'Fishbaugh', + 'Lais', + 'Ossa', + 'Wilden', + 'Erick', + 'Dosier', + 'Trust', + 'Swaine', + 'Darity', + 'Mccroy', + 'Yuille', + 'Cantave', + 'Barsanti', + 'Carbonara', + 'Cavanah', + 'Chrismer', + 'Cuestas', + 'Czaplewski', + 'Denes', + 'Dorio', + 'Geraldo', + 'Giebler', + 'Goewey', + 'Gorniak', + 'Grabe', + 'Guidera', + 'Hannig', + 'Herin', + 'Kadow', + 'Klauer', + 'Kleppinger', + 'Lerro', + 'Manoogian', + 'Mentzel', + 'Muramoto', + 'Ollinger', + 'Pacey', + 'Pufahl', + 'Quero', + 'Revuelta', + 'Rickles', + 'Rudie', + 'Ruggerio', + 'Salberg', + 'Schwoerer', + 'Stephani', + 'Stevick', + 'Strada', + 'Thorley', + 'Thrun', + 'Virts', + 'Wingett', + 'Balfe', + 'Branaman', + 'Brookshier', + 'Carlsson', + 'Chismar', + 'Habben', + 'Migdal', + 'Ozga', + 'Rivest', + 'Russman', + 'Schellhorn', + 'Staup', + 'Pietri', + 'Welby', + 'Cisney', + 'Hijazi', + 'Brines', + 'Calderin', + 'Mudrick', + 'Domine', + 'Parlow', + 'Ervine', + 'Banis', + 'Mathenia', + 'Carbin', + 'Rashed', + 'Mcgilvery', + 'Prichett', + 'Feimster', + 'Smoots', + 'Persley', + 'Desire', + 'Abadi', + 'Bercaw', + 'Bertz', + 'Bibian', + 'Brosious', + 'Brunken', + 'Calvano', + 'Chenette', + 'Chiusano', + 'Dendinger', + 'Diffley', + 'Eichenberg', + 'Gawne', + 'Gelardi', + 'Gottman', + 'Gulyas', + 'Hak', + 'Haydock', + 'Hettler', + 'Hinsch', + 'Kozlik', + 'Krebbs', + 'Krichbaum', + 'Loges', + 'Lyssy', + 'Mitnick', + 'Podolski', + 'Priego', + 'Radhakrishnan', + 'Reineck', + 'Ruggirello', + 'Samborski', + 'Schwalb', + 'Sitek', + 'Sprinkel', + 'Tkachuk', + 'Viscuso', + 'Working', + 'Zinner', + 'Anspaugh', + 'Anthes', + 'Bratsch', + 'Breining', + 'Cejka', + 'Delbuono', + 'Hugill', + 'Huyett', + 'Irlbeck', + 'Kilgus', + 'Langwell', + 'Margulis', + 'Meara', + 'Napierala', + 'Stanaway', + 'Worton', + 'Gaucher', + 'Bakeman', + 'Pasos', + 'Feazel', + 'Evitt', + 'Marrin', + 'Baskette', + 'Orne', + 'Ivens', + 'Burnstein', + 'Rodell', + 'Bowell', + 'Maraj', + 'Lango', + 'Boudoin', + 'Wider', + 'Walkins', + 'Raheem', + 'Talford', + 'Jeanmarie', + 'Drumgoole', + 'Arnot', + 'Bennick', + 'Buchinger', + 'Cleven', + 'Corsello', + 'Delucchi', + 'Dicocco', + 'Eachus', + 'Eilts', + 'Fandino', + 'Fyke', + 'Giammarco', + 'Gwartney', + 'Hawken', + 'Henkelman', + 'Jaggi', + 'Jurczyk', + 'Kamman', + 'Kattner', + 'Keator', + 'Klus', + 'Leidner', + 'Ligas', + 'Martus', + 'Maslow', + 'Piccinini', + 'Pysher', + 'Riga', + 'Siek', + 'Sizelove', + 'Vanostrand', + 'Vastine', + 'Viviani', + 'Youngerman', + 'Zahniser', + 'Brigante', + 'Burklund', + 'Cajina', + 'Coppolino', + 'Goytia', + 'Icenhower', + 'Ihnen', + 'Jablonsky', + 'Koepsell', + 'Mennenga', + 'Redenius', + 'Tengan', + 'Weishaupt', + 'Dorst', + 'Kief', + 'Busk', + 'Luba', + 'Quine', + 'Deshotels', + 'Roulston', + 'Diniz', + 'Chandley', + 'Saleeby', + 'Maro', + 'Faidley', + 'Burrous', + 'Ilyas', + 'Roster', + 'Clovis', + 'Bacot', + 'Pembleton', + 'Bellot', + 'Entzminger', + 'Ryce', + 'Posley', + 'Alvi', + 'Audino', + 'Bitters', + 'Boomershine', + 'Boyack', + 'Branda', + 'Bresnan', + 'Brusco', + 'Bunda', + 'Catanzarite', + 'Dohmen', + 'Elbaum', + 'Farago', + 'Ferrentino', + 'Gimpel', + 'Grzeskowiak', + 'Gutting', + 'Henandez', + 'Herbeck', + 'Hoben', + 'Hunnell', + 'Ibbotson', + 'Kida', + 'Kirchman', + 'Kubin', + 'Laplume', + 'Laskin', + 'Lefferts', + 'Leimer', + 'Locatelli', + 'Pitsenbarger', + 'Reum', + 'Rittgers', + 'Scadden', + 'Shammas', + 'Tatge', + 'Tiongson', + 'Wengler', + 'Wenrick', + 'Wortley', + 'Bretado', + 'Detloff', + 'Dlugosz', + 'Eisemann', + 'Embler', + 'Graffius', + 'Kienast', + 'Kucher', + 'Larew', + 'Lemmerman', + 'Maners', + 'Peckinpaugh', + 'Rupnow', + 'Schubring', + 'Staheli', + 'Stege', + 'Talwar', + 'Truszkowski', + 'Coda', + 'Comunale', + 'Holtry', + 'Newfield', + 'Blankley', + 'Devino', + 'Wahba', + 'Cathell', + 'Timson', + 'Setzler', + 'Shacklett', + 'Nicols', + 'Rocque', + 'Nest', + 'Freelove', + 'Neat', + 'Kina', + 'Caslin', + 'Creal', + 'Wyre', + 'Compere', + 'Brisker', + 'Givhan', + 'Menifee', + 'Hymon', + 'Boakye', + 'Aguillar', + 'Alpern', + 'Antico', + 'Attridge', + 'Bjorge', + 'Bordwell', + 'Brumbach', + 'Castronova', + 'Cowher', + 'Fakhouri', + 'Hanigan', + 'Heidecker', + 'Hosick', + 'Lorang', + 'Magadan', + 'Marovich', + 'Masur', + 'Nienow', + 'Passow', + 'Priola', + 'Prose', + 'Radillo', + 'Saracco', + 'Schlender', + 'Sellards', + 'Stirn', + 'Strathman', + 'Supan', + 'Taguchi', + 'Tufte', + 'Vanderleest', + 'Vanderpoel', + 'Vondra', + 'Wayment', + 'Wisinski', + 'Brodowski', + 'Cichowski', + 'Delarocha', + 'Demyan', + 'Dobies', + 'Hegner', + 'Karapetian', + 'Konieczka', + 'Lazarz', + 'Loughner', + 'Portanova', + 'Rosentreter', + 'Rothlisberger', + 'Schropp', + 'Trenkamp', + 'Flaharty', + 'Murfin', + 'Waner', + 'Baiz', + 'Dunegan', + 'Gillson', + 'Erne', + 'Mahin', + 'Hardgrave', + 'Felps', + 'Bevens', + 'Abdou', + 'Songy', + 'Boule', + 'Wisham', + 'Devonshire', + 'Havis', + 'Relf', + 'Pean', + 'Manago', + 'Brazzle', + 'Mckelvin', + 'Goulbourne', + 'Pinkins', + 'Yelder', + 'Akina', + 'Allerton', + 'Aminov', + 'Barsamian', + 'Biondolillo', + 'Bouchillon', + 'Bustle', + 'Dolney', + 'Dunkerley', + 'Farha', + 'Floor', + 'Gaustad', + 'Gilberti', + 'Helder', + 'Kolber', + 'Kuznia', + 'Longhi', + 'Mamaril', + 'Milhorn', + 'Mozo', + 'Norbury', + 'Okano', + 'Perkovich', + 'Rafanan', + 'Rulo', + 'Ruperto', + 'Scow', + 'Shadoan', + 'Smisek', + 'Steinfeldt', + 'Thobe', + 'Venturino', + 'Widell', + 'Broccoli', + 'Helmig', + 'Koegler', + 'Lewandoski', + 'Pequignot', + 'Radermacher', + 'Resetar', + 'Rostro', + 'Sebald', + 'Walgren', + 'Lottes', + 'Capraro', + 'Grine', + 'Gordner', + 'Crus', + 'Easom', + 'Bayle', + 'Barts', + 'Duguid', + 'Estel', + 'Peggs', + 'Cheaney', + 'Rossin', + 'Mackel', + 'Vassel', + 'Fils', + 'Senat', + 'Alarie', + 'Allar', + 'Brownlie', + 'Bumbaugh', + 'Caissie', + 'Cordone', + 'Critser', + 'Delconte', + 'Falzon', + 'Formosa', + 'Frerking', + 'Gadea', + 'Ganem', + 'Guzek', + 'Hauch', + 'Heese', + 'Hemmen', + 'Holzschuh', + 'Impson', + 'Jablon', + 'Kiedrowski', + 'Krob', + 'Kuhnle', + 'Laake', + 'Larouche', + 'Leaton', + 'Leyland', + 'Lorenson', + 'Macduff', + 'Maready', + 'Newberger', + 'Ohnstad', + 'Pinela', + 'Polino', + 'Postema', + 'Pyon', + 'Radziewicz', + 'Rathod', + 'Salopek', + 'Salvadore', + 'Sawchuk', + 'Trotto', + 'Vereb', + 'Auslander', + 'Beninati', + 'Blunck', + 'Decandia', + 'Deeney', + 'Escatel', + 'Foskett', + 'Hagmann', + 'Hussar', + 'Jakubek', + 'Kluender', + 'Mcelhinny', + 'Salatino', + 'Sangalang', + 'Schoenfeldt', + 'Stogdill', + 'Svitak', + 'Taravella', + 'Tezak', + 'Wieseler', + 'Komperda', + 'Reinitz', + 'Malis', + 'Duce', + 'Salib', + 'Keelin', + 'Labell', + 'Symmonds', + 'Gwynne', + 'Byus', + 'Burgy', + 'Delfosse', + 'Benskin', + 'Hedgepath', + 'Ursin', + 'Kinnebrew', + 'Tinnon', + 'Callum', + 'Allah', + 'Arduini', + 'Azucena', + 'Birkel', + 'Bowermaster', + 'Caires', + 'Chrobak', + 'Cottier', + 'Cropley', + 'Crotteau', + 'Dutan', + 'Ezernack', + 'Fabiani', + 'Fauser', + 'Feeny', + 'Ferdig', + 'Fliss', + 'Gallus', + 'Harlacher', + 'Hasselbach', + 'Honsinger', + 'Landberg', + 'Lohn', + 'Losinski', + 'Maung', + 'Melikian', + 'Nooney', + 'Oyervides', + 'Prum', + 'Riepe', + 'Seebach', + 'Sendejas', + 'Sprick', + 'Torino', + 'Weida', + 'Geschke', + 'Girgenti', + 'Klever', + 'Rathert', + 'Roszell', + 'Sarich', + 'Shimmin', + 'Trimpe', + 'Turrubiates', + 'Zelada', + 'Danzig', + 'Diamant', + 'Hannen', + 'Odland', + 'Puzzo', + 'Slyter', + 'Smaldone', + 'Ebey', + 'Beg', + 'Magel', + 'Tebbs', + 'Gali', + 'Winney', + 'Juba', + 'Stargel', + 'Waren', + 'Stann', + 'Ducasse', + 'Vaugh', + 'Lewers', + 'Stjuste', + 'Heckstall', + 'Bokhari', + 'Bonino', + 'Brummond', + 'Caterino', + 'Deatrick', + 'Decorte', + 'Demara', + 'Dubree', + 'Dulski', + 'Feck', + 'Foglio', + 'Heinzelman', + 'Jory', + 'Knoell', + 'Kronick', + 'Maclay', + 'Mastrogiovanni', + 'Reichling', + 'Rueff', + 'Sellitto', + 'Sensing', + 'Sheu', + 'Soberanes', + 'Stahlecker', + 'Wholey', + 'Yochim', + 'Zeiss', + 'Bojanowski', + 'Bonawitz', + 'Caporaso', + 'Dalesio', + 'Exposito', + 'Giovinazzo', + 'Palardy', + 'Rastogi', + 'Saenger', + 'Sirek', + 'Sonoda', + 'Sovereign', + 'Weimann', + 'Wirtanen', + 'Enerson', + 'Olliff', + 'Kallam', + 'Leggitt', + 'Goude', + 'Rampey', + 'Letsinger', + 'Walles', + 'Kater', + 'Betsill', + 'Creese', + 'Lisbon', + 'Abitz', + 'Bednarik', + 'Bendorf', + 'Berkovich', + 'Brevik', + 'Cassatt', + 'Ciarlo', + 'Cookman', + 'Cosma', + 'Defee', + 'Essner', + 'Fallas', + 'Holda', + 'Kemler', + 'Kovich', + 'Krimmel', + 'Landauer', + 'Meharg', + 'Moncus', + 'Nabi', + 'Redenbaugh', + 'Ruwe', + 'Scalisi', + 'Shughart', + 'Sloma', + 'Sovine', + 'Tomaso', + 'Trueba', + 'Urista', + 'Vanyo', + 'Wolanski', + 'Zettle', + 'Arvanitis', + 'Baeten', + 'Caponi', + 'Carrazco', + 'Galambos', + 'Hartsook', + 'Helseth', + 'Kobylarz', + 'Krugh', + 'Meckel', + 'Ohnemus', + 'Voytek', + 'Winegarden', + 'Zuba', + 'Piloto', + 'Shames', + 'Debella', + 'Keddy', + 'Perra', + 'Winks', + 'Hemrick', + 'Snowdon', + 'Cleere', + 'Leavey', + 'Courington', + 'Herson', + 'Nelon', + 'Bloise', + 'Mcphie', + 'Catledge', + 'Mcneary', + 'Hoffler', + 'Suell', + 'Coard', + 'Woolfork', + 'Biros', + 'Brouhard', + 'Dinovo', + 'Disano', + 'Emami', + 'Flegal', + 'Hardebeck', + 'Hobin', + 'Huttner', + 'Kloosterman', + 'Knutzen', + 'Kopinski', + 'Mailman', + 'Mankey', + 'Mccamish', + 'Mccorquodale', + 'Minichiello', + 'Miyasaki', + 'Osher', + 'Prutzman', + 'Sagen', + 'Shawgo', + 'Sokolow', + 'Southam', + 'Sulik', + 'Wiedel', + 'Wollschlager', + 'Cantalupo', + 'Cruser', + 'Denomme', + 'Dinardi', + 'Donahey', + 'Havlin', + 'Lasecki', + 'Margraf', + 'Mchaffie', + 'Mihaly', + 'Omlor', + 'Roope', + 'Schremp', + 'Vanhecke', + 'Washabaugh', + 'Zaunbrecher', + 'Joost', + 'Pensinger', + 'Kraner', + 'Mikles', + 'Delair', + 'Bukhari', + 'Earll', + 'Sans', + 'Gatliff', + 'Casteneda', + 'Shalom', + 'Fidalgo', + 'Leitao', + 'Degrange', + 'Fruits', + 'Kercheval', + 'Mew', + 'Chopin', + 'Seawood', + 'Agro', + 'Aliano', + 'Badour', + 'Betsch', + 'Buchbinder', + 'Cleavenger', + 'Collazos', + 'Cusmano', + 'Dienes', + 'Dittus', + 'Eggenberger', + 'Fierst', + 'Gingell', + 'Greever', + 'Grisales', + 'Hegstrom', + 'Justen', + 'Kalt', + 'Kirkhart', + 'Krage', + 'Kyzar', + 'Livolsi', + 'Neyhart', + 'Nunziata', + 'Orlich', + 'Parcel', + 'Peshlakai', + 'Schemm', + 'Segner', + 'Urieta', + 'Wolfman', + 'Coonradt', + 'Disilvestro', + 'Dobrowski', + 'Gramza', + 'Kotlyar', + 'Micka', + 'Miksch', + 'Mione', + 'Montone', + 'Palmerton', + 'Parrill', + 'Passafiume', + 'Rosoff', + 'Spaziani', + 'Venditto', + 'Wisch', + 'Fini', + 'Horky', + 'Perel', + 'Arzuaga', + 'Nasworthy', + 'Carland', + 'Elden', + 'Moises', + 'Maione', + 'Glace', + 'Laverdure', + 'Sieh', + 'Toulouse', + 'Hannam', + 'Cumber', + 'Rendell', + 'Hardey', + 'Maddison', + 'Brittle', + 'Helen', + 'Aina', + 'Allwood', + 'Fenty', + 'Herard', + 'Traore', + 'Ator', + 'Bedsaul', + 'Bickert', + 'Brendlinger', + 'Camuso', + 'Dutter', + 'Eastlick', + 'Fernholz', + 'Guza', + 'Heitzenrater', + 'Huo', + 'Isbill', + 'Katzenstein', + 'Keigley', + 'Kelnhofer', + 'Klarich', + 'Mangat', + 'Mathiason', + 'Murzyn', + 'Odenthal', + 'Pascarelli', + 'Passwaters', + 'Rotunda', + 'Schons', + 'Sein', + 'Sobon', + 'Stayner', + 'Tri', + 'Uhlir', + 'Viscusi', + 'Winstanley', + 'Xi', + 'Yodice', + 'Aerts', + 'Antosh', + 'Baldinger', + 'Brislin', + 'Christopoulos', + 'Faurot', + 'Fusselman', + 'Hamsher', + 'Henckel', + 'Macht', + 'Moellering', + 'Oclair', + 'Pavelko', + 'Poehlman', + 'Rajewski', + 'Richcreek', + 'Schmeichel', + 'Venkatesh', + 'Zemba', + 'Zuelke', + 'Dechellis', + 'Reddig', + 'Splain', + 'Claw', + 'Mottram', + 'Crise', + 'Villaflor', + 'Allocca', + 'Buttrum', + 'Cocking', + 'Mundie', + 'Tavis', + 'Saidi', + 'Latter', + 'Tuberville', + 'Spease', + 'Leatherberry', + 'Peatross', + 'Claridy', + 'Duerson', + 'Durley', + 'Mekonnen', + 'Thiam', + 'Aderman', + 'Al', + 'Andreu', + 'Beine', + 'Bowron', + 'Campi', + 'Chura', + 'Ciraulo', + 'Daywalt', + 'Fleek', + 'Friant', + 'Gahm', + 'Gongaware', + 'Grosh', + 'Heaslip', + 'Knape', + 'Kravets', + 'Kritikos', + 'Kumagai', + 'Kustra', + 'Madani', + 'Mich', + 'Norlander', + 'Paulhus', + 'Rabanal', + 'Saker', + 'Stupak', + 'Suchomel', + 'Vandenberghe', + 'Wehrenberg', + 'Zaccardi', + 'Davlin', + 'Dykhouse', + 'Grandfield', + 'Hullender', + 'Kallis', + 'Livshits', + 'Rihn', + 'Criger', + 'Michl', + 'Tutino', + 'Zulueta', + 'Cristo', + 'Meline', + 'Fetch', + 'Dung', + 'Shami', + 'Teale', + 'Cocker', + 'Eshbach', + 'Phagan', + 'Millea', + 'Tayloe', + 'Olivia', + 'Houchen', + 'Peddy', + 'Ferryman', + 'Boodram', + 'Maduro', + 'Fullman', + 'Landingham', + 'Pee', + 'Argenbright', + 'Aronowitz', + 'Baldenegro', + 'Barentine', + 'Bernasconi', + 'Bicking', + 'Bohle', + 'Camerer', + 'Dufford', + 'Ende', + 'Gessel', + 'Grauman', + 'Jaqua', + 'Kagawa', + 'Kalinski', + 'Kanz', + 'Klasen', + 'Koloski', + 'Kriete', + 'Litalien', + 'Maish', + 'Massar', + 'Muraski', + 'Pickelsimer', + 'Sagraves', + 'Servellon', + 'Shellito', + 'Shiveley', + 'Stanislaw', + 'Volland', + 'Biehle', + 'Cruey', + 'Eagar', + 'Ermis', + 'Goracke', + 'Mackert', + 'Malloch', + 'Merillat', + 'Rylee', + 'Schelin', + 'Tibbals', + 'Zandi', + 'Golde', + 'Steuart', + 'Jamie', + 'Lavis', + 'Bromwell', + 'Tregre', + 'Alkhatib', + 'Carvey', + 'Essa', + 'Wale', + 'Mccarey', + 'Brandley', + 'Hermon', + 'Stenhouse', + 'Oguinn', + 'Barclift', + 'Sylvan', + 'Smyre', + 'Ellerby', + 'Alemany', + 'Beyl', + 'Boven', + 'Bultema', + 'Buzan', + 'Cappo', + 'Cottongim', + 'Detore', + 'Dierolf', + 'Dueck', + 'Egelston', + 'Emard', + 'Eveleth', + 'Ferrini', + 'Fodera', + 'Hidy', + 'Kahley', + 'Karasik', + 'Klare', + 'Koudelka', + 'Lafleche', + 'Minturn', + 'Montemarano', + 'Plock', + 'Ratterman', + 'Reingold', + 'Rieber', + 'Schnackenberg', + 'Schrade', + 'Steffek', + 'Stehling', + 'Sticha', + 'Velaquez', + 'Weissberg', + 'Allnutt', + 'Barkhurst', + 'Bettendorf', + 'Canonico', + 'Deshmukh', + 'Dobosz', + 'Glab', + 'Kirkeby', + 'Menapace', + 'Parizek', + 'Pursifull', + 'Ragucci', + 'Raisch', + 'Schronce', + 'Tuason', + 'Duross', + 'Hainer', + 'Kinnick', + 'Rens', + 'Williamsen', + 'Hilke', + 'Hark', + 'Mellett', + 'Decarvalho', + 'Filyaw', + 'Sian', + 'Mccard', + 'Symon', + 'Grade', + 'Giboney', + 'Sadik', + 'Caul', + 'Gater', + 'Sulton', + 'Dungee', + 'Adriance', + 'Almas', + 'Andler', + 'Bellina', + 'Belshe', + 'Blouch', + 'Bradeen', + 'Brandwein', + 'Buechele', + 'Cristina', + 'Davidov', + 'Defiore', + 'Defrain', + 'Derasmo', + 'Dober', + 'Grosshans', + 'Hoek', + 'Hofstad', + 'Ingman', + 'Kille', + 'Langill', + 'Matic', + 'Niederer', + 'Novella', + 'Oelkers', + 'Percifield', + 'Phariss', + 'Pola', + 'Pompei', + 'Potthast', + 'Raden', + 'Radick', + 'Rendina', + 'Sicotte', + 'Sleep', + 'Wadhwa', + 'Buccheri', + 'Calogero', + 'Catrett', + 'Flemmer', + 'Mancinas', + 'Mcmichen', + 'Measel', + 'Pudlo', + 'Ruether', + 'Shusterman', + 'Stabley', + 'Teffeteller', + 'Waisanen', + 'Zappulla', + 'Symanski', + 'Mckenrick', + 'Moger', + 'Obispo', + 'Armenteros', + 'Roses', + 'Makki', + 'Faley', + 'Rumford', + 'Schonberg', + 'Hizer', + 'Blaydes', + 'Coor', + 'Mccalip', + 'Stancill', + 'Cal', + 'Murat', + 'Amie', + 'Placide', + 'Akpan', + 'Bembenek', + 'Bilyk', + 'Bizzarro', + 'Bugge', + 'Cunnane', + 'Degenhart', + 'Doehring', + 'Flammia', + 'Fritcher', + 'Godinho', + 'Gouger', + 'Heyboer', + 'Humenik', + 'Iannaccone', + 'Lacivita', + 'Lagunes', + 'Leitzke', + 'Luty', + 'Maute', + 'Micke', + 'Midura', + 'Nydam', + 'Rasp', + 'Rediker', + 'Requejo', + 'Roskos', + 'Ruckert', + 'Saldierna', + 'Salemme', + 'Tsuchiya', + 'Vallas', + 'Werder', + 'Arenivas', + 'Bartholomay', + 'Brozowski', + 'Dusza', + 'Frevert', + 'Giannopoulos', + 'Kormos', + 'Martos', + 'Mollenhauer', + 'Romanek', + 'Solinger', + 'Tomaro', + 'Zangara', + 'Buttrick', + 'Pardy', + 'Alvelo', + 'Breth', + 'Hemond', + 'Kayes', + 'Manne', + 'Grandchamp', + 'Gilbo', + 'Calame', + 'Clippard', + 'Gieger', + 'Penalver', + 'Ecton', + 'Totton', + 'Poyser', + 'Kettles', + 'Hosang', + 'Waker', + 'Maryland', + 'Girma', + 'Baribeau', + 'Boehnke', + 'Brunick', + 'Buhrow', + 'Cerreta', + 'Dascoli', + 'Eroh', + 'Fallert', + 'Fotopoulos', + 'Granholm', + 'Hebdon', + 'Hoelzer', + 'Hyser', + 'Lisanti', + 'Mastrianni', + 'Mewes', + 'Mulanax', + 'Nikolai', + 'Odekirk', + 'Ofallon', + 'Onnen', + 'Or', + 'Osso', + 'Ridpath', + 'Schara', + 'Schnipke', + 'Slayter', + 'Sodhi', + 'Steffler', + 'Stegemann', + 'Weisensel', + 'Bertling', + 'Dueitt', + 'Keehner', + 'Khaimov', + 'Kramlich', + 'Salkeld', + 'Ulbricht', + 'Vultaggio', + 'Dennin', + 'Mondo', + 'Kett', + 'Dom', + 'Kalan', + 'Yaney', + 'Nicley', + 'Carabello', + 'Ellegood', + 'Mcglocklin', + 'Figuero', + 'Pillard', + 'Wolfrey', + 'Leys', + 'Cobert', + 'Wahid', + 'Fede', + 'Ausbrooks', + 'Gums', + 'Gillion', + 'Mcgeachy', + 'Parran', + 'Likely', + 'Marbley', + 'Argote', + 'Bhullar', + 'Botros', + 'Brethauer', + 'Chell', + 'Conradi', + 'Covill', + 'Crays', + 'Crysler', + 'Handke', + 'Hanneken', + 'Hidrogo', + 'Hirayama', + 'Huebert', + 'Hurford', + 'Iskra', + 'Malczewski', + 'Menees', + 'Monforte', + 'Murdick', + 'Naclerio', + 'Nohr', + 'Pangallo', + 'Payeur', + 'Pozniak', + 'Rammel', + 'Schield', + 'Schrick', + 'Seifer', + 'Sperduto', + 'Stagliano', + 'Staubs', + 'Stromme', + 'Tourigny', + 'Traister', + 'Vandecar', + 'Wilhelms', + 'Wilinski', + 'Wittke', + 'Clougherty', + 'Crotwell', + 'Hannula', + 'Heavrin', + 'Heidinger', + 'Keehan', + 'Ortwein', + 'Palinkas', + 'Seivert', + 'Sloniker', + 'Yielding', + 'Lac', + 'Shove', + 'Venard', + 'Violett', + 'Foresta', + 'Gapp', + 'Dejongh', + 'Ambrosia', + 'Simkin', + 'Sastre', + 'Mcarthy', + 'Bering', + 'Sarah', + 'Hickling', + 'Sookdeo', + 'Val', + 'Colden', + 'Feltus', + 'Hailes', + 'Canizalez', + 'Cloke', + 'Connole', + 'Dancel', + 'Demmon', + 'Ehrler', + 'Fruchey', + 'Helinski', + 'Hepfer', + 'Katzen', + 'Kressler', + 'Lagrow', + 'Nethercutt', + 'Novitski', + 'Papale', + 'Pesola', + 'Petrosian', + 'Pies', + 'Prazak', + 'Preza', + 'Reiche', + 'Salle', + 'Savic', + 'Servello', + 'Sherbondy', + 'Solazzo', + 'Stabenow', + 'Walstad', + 'Yaden', + 'Zagal', + 'Zani', + 'Dimambro', + 'Engquist', + 'Fochtman', + 'Frasch', + 'Fuerstenberg', + 'Galus', + 'Gronowski', + 'Grossenbacher', + 'Hahs', + 'Iavarone', + 'Kerper', + 'Kravchenko', + 'Kwolek', + 'Lusignan', + 'Lybbert', + 'Maertens', + 'Mahany', + 'Medico', + 'Orrantia', + 'Reitmeier', + 'Sieve', + 'Sterbenz', + 'Tenpas', + 'Wischmeyer', + 'Zajkowski', + 'Cregg', + 'Shetley', + 'Tisher', + 'Coup', + 'Murdy', + 'Lysaght', + 'Sesco', + 'Koy', + 'Wakley', + 'Bertholf', + 'Swaner', + 'Stakes', + 'Gren', + 'Elahi', + 'Torney', + 'Gopaul', + 'Egland', + 'Gingles', + 'Aurich', + 'Biela', + 'Binz', + 'Blumenstock', + 'Boardwine', + 'Boehner', + 'Boening', + 'Crankshaw', + 'Decarli', + 'Fauble', + 'Georgopoulos', + 'Gieske', + 'Hasselbring', + 'Heeb', + 'Janosik', + 'Kalafut', + 'Karpf', + 'Kramm', + 'Lanyon', + 'Lewelling', + 'Lilla', + 'Marik', + 'Moyano', + 'Oppel', + 'Panagos', + 'Renovato', + 'Rohlman', + 'Rostron', + 'Todhunter', + 'Torello', + 'Umfleet', + 'Wien', + 'Youker', + 'Ytuarte', + 'Zavada', + 'Altvater', + 'Arnzen', + 'Blixt', + 'Elek', + 'Geiselman', + 'Hiltunen', + 'Jachim', + 'Kolenovic', + 'Kooyman', + 'Muecke', + 'Pierron', + 'Preisler', + 'Rogus', + 'Schoeller', + 'Solimine', + 'Speagle', + 'Courser', + 'Mascarenhas', + 'Dorer', + 'Scotten', + 'Goy', + 'Avers', + 'Blanca', + 'Choung', + 'Goleman', + 'Nanna', + 'Lave', + 'Seley', + 'Meggison', + 'Ripoll', + 'Mannan', + 'Bihm', + 'Tribbey', + 'Ports', + 'Asby', + 'Philibert', + 'Furby', + 'Keal', + 'Louallen', + 'Idris', + 'Artist', + 'Branford', + 'Sabree', + 'Ainley', + 'Amezola', + 'Andreason', + 'Athans', + 'Batiz', + 'Bostelman', + 'Bozic', + 'Butman', + 'Coiro', + 'Defina', + 'Garbo', + 'Gewirtz', + 'Hathcoat', + 'Heebner', + 'Helbing', + 'Kasler', + 'Kastler', + 'Kearby', + 'Krus', + 'Lezotte', + 'Lithgow', + 'Mealor', + 'Moltz', + 'Morcom', + 'Norbeck', + 'Novicki', + 'Osmani', + 'Posluszny', + 'Quiroa', + 'Rahal', + 'Roddenberry', + 'Rodino', + 'Sallade', + 'Saraceni', + 'Schmaus', + 'Stathopoulos', + 'Swatek', + 'Tupy', + 'Vonseggern', + 'Zens', + 'Ahonen', + 'Arrazola', + 'Avedisian', + 'Bachtell', + 'Bastarache', + 'Chavero', + 'Darienzo', + 'Giampa', + 'Gillott', + 'Hierholzer', + 'Kruckeberg', + 'Lafrenz', + 'Milkowski', + 'Missildine', + 'Passaretti', + 'Rogstad', + 'Saadeh', + 'Sielski', + 'Slavick', + 'Tieken', + 'Wittenmyer', + 'Yepiz', + 'Zimdars', + 'Rail', + 'Kook', + 'Jian', + 'Piet', + 'Sanjurjo', + 'Shampine', + 'Christel', + 'Hechavarria', + 'Blucher', + 'Crimm', + 'Lebreton', + 'Charbonnet', + 'Bolls', + 'Stroder', + 'Baise', + 'Mcnease', + 'Alen', + 'Priestly', + 'Mannie', + 'Doleman', + 'Areas', + 'Atayde', + 'Berent', + 'Bodmer', + 'Brodin', + 'Buntrock', + 'Eckrich', + 'Emberson', + 'Hilgert', + 'Hirn', + 'Holihan', + 'Hoshino', + 'Jeung', + 'Leece', + 'Leonardis', + 'Macera', + 'Mcferron', + 'Muster', + 'Naef', + 'Pecka', + 'Peloso', + 'Pensyl', + 'Reaney', + 'Reidinger', + 'Rockholt', + 'Tabrizi', + 'Trauth', + 'Trulock', + 'Tupou', + 'Asbridge', + 'Franzel', + 'Gesualdi', + 'Grimwood', + 'Hardinger', + 'Kondrat', + 'Koskinen', + 'Ludolph', + 'Marchesi', + 'Mehrtens', + 'Racioppi', + 'Sabey', + 'Stroebel', + 'Swendsen', + 'Vandewalker', + 'Korber', + 'Messler', + 'Mowat', + 'Kor', + 'Pua', + 'Sarazin', + 'Wayson', + 'Oland', + 'Bandi', + 'Fabel', + 'Frankl', + 'Rane', + 'Mozer', + 'Weaber', + 'Moustafa', + 'Robe', + 'Lindy', + 'Medaris', + 'Derden', + 'Benthall', + 'Ayler', + 'Osias', + 'Choyce', + 'Scantlebury', + 'Patmon', + 'Ahlgrim', + 'Boffa', + 'Brideau', + 'Bubeck', + 'Bubel', + 'Casio', + 'Casique', + 'Casten', + 'Colebank', + 'Demoura', + 'Devincenzo', + 'Elsesser', + 'Fauci', + 'Frentz', + 'Hemler', + 'Keitel', + 'Luan', + 'Luhn', + 'Luquette', + 'Mazurowski', + 'Mendibles', + 'Mickiewicz', + 'Minelli', + 'Mistler', + 'Nemer', + 'Nikolaus', + 'Offill', + 'Pezza', + 'Ruzich', + 'Skrzypek', + 'Swimmer', + 'Trucks', + 'Vaccarella', + 'Zeidman', + 'Brattin', + 'Deblock', + 'Dufrane', + 'Gural', + 'Hufstedler', + 'Kapuscinski', + 'Lyerla', + 'Musolino', + 'Neubecker', + 'Polus', + 'Protzman', + 'Retzloff', + 'Sachdev', + 'Sazama', + 'Shrider', + 'Tobolski', + 'Mcbane', + 'Clabo', + 'Fredrich', + 'Lace', + 'Bertran', + 'Kama', + 'Simonet', + 'Lippitt', + 'Thomlinson', + 'Vallot', + 'Dede', + 'Brimley', + 'Parler', + 'Standfield', + 'Goodie', + 'Isidore', + 'Philogene', + 'Abramczyk', + 'Andert', + 'Besancon', + 'Bieda', + 'Birkey', + 'Boquet', + 'Borak', + 'Bottino', + 'Breyfogle', + 'Crill', + 'Daffern', + 'Derrig', + 'Dimalanta', + 'Dresch', + 'Feulner', + 'Friede', + 'Furth', + 'Gamet', + 'Garramone', + 'Gaunce', + 'Gitto', + 'Guandique', + 'Hoxworth', + 'Hubers', + 'Ingwersen', + 'Junio', + 'Kassing', + 'Magrath', + 'Martelle', + 'Mcweeney', + 'Neris', + 'Nesheiwat', + 'Remlinger', + 'Rentmeester', + 'Schlein', + 'Schoneman', + 'Sterr', + 'Streib', + 'Szymanowski', + 'Trompeter', + 'Tullius', + 'Cherico', + 'Cremin', + 'Dominey', + 'Gotthardt', + 'Kowalke', + 'Onderdonk', + 'Pirrello', + 'Rumberger', + 'Schreur', + 'Westerhoff', + 'Maroni', + 'Dire', + 'Menta', + 'Hoeg', + 'Meise', + 'Standerfer', + 'Roam', + 'Tibbett', + 'Beevers', + 'Evrard', + 'Locklair', + 'Brester', + 'Sirmon', + 'Woodbeck', + 'Wires', + 'Durette', + 'Raul', + 'Stephanie', + 'Mcwain', + 'Skeeters', + 'Wilbourne', + 'Debroux', + 'Keyton', + 'Noris', + 'Fanta', + 'Goshen', + 'Kithcart', + 'Shepheard', + 'Sherod', + 'Buntyn', + 'Gissendanner', + 'Goodley', + 'Mckissic', + 'Bissinger', + 'Biswell', + 'Borruso', + 'Danese', + 'Eslava', + 'Gehle', + 'Gibeau', + 'Gionet', + 'Greth', + 'Gul', + 'Hambley', + 'Harshfield', + 'Helin', + 'Henken', + 'Hogland', + 'Hoxha', + 'Hurlbutt', + 'Kaminer', + 'Kien', + 'Kliebert', + 'Koivisto', + 'Kooken', + 'Laconte', + 'Lovo', + 'Manninen', + 'Maxham', + 'Mcleland', + 'Mclerran', + 'Milici', + 'Negrette', + 'Nicotera', + 'Nissan', + 'Philipson', + 'Pimenta', + 'Pinch', + 'Rietveld', + 'Seyller', + 'Shollenberger', + 'Sochacki', + 'Telleria', + 'Toda', + 'Unrue', + 'Vanbenschoten', + 'Versace', + 'Villada', + 'Watry', + 'Wirsing', + 'Zeimet', + 'Zynda', + 'Angelillo', + 'Fleissner', + 'Freehling', + 'Grewell', + 'Heick', + 'Kartes', + 'Kishi', + 'Kopke', + 'Laubenstein', + 'Leske', + 'Lohmeier', + 'Marotz', + 'Moccio', + 'Mullineaux', + 'Muzyka', + 'Ostermiller', + 'Penuelas', + 'Plagge', + 'Stolarz', + 'Wertenberger', + 'Sella', + 'Allinger', + 'Betzler', + 'Rosenkrantz', + 'Trimarchi', + 'Dionicio', + 'Frohman', + 'Landenberger', + 'Shillings', + 'Chill', + 'Leather', + 'Sonn', + 'Connel', + 'Fougere', + 'Alia', + 'Wisby', + 'Haisley', + 'Minion', + 'Mccathern', + 'Rozzell', + 'Armbrister', + 'Ryant', + 'Almeyda', + 'Bonjour', + 'Bordas', + 'Bozard', + 'Buccola', + 'Cihlar', + 'Dargis', + 'Faivre', + 'Fejes', + 'Grulke', + 'Harken', + 'Heimberger', + 'Hochmuth', + 'Keadle', + 'Kedrowski', + 'Kortman', + 'Krahenbuhl', + 'Krasniqi', + 'Kundrat', + 'Leistner', + 'Loguidice', + 'Mcauliff', + 'Mchatton', + 'Minella', + 'Muccio', + 'Normington', + 'Nuttle', + 'Orsino', + 'Reker', + 'Respicio', + 'Shein', + 'Teichert', + 'Varisco', + 'Accomando', + 'Amelio', + 'Burckhard', + 'Fleischhacker', + 'Hagglund', + 'Kessenich', + 'Langrehr', + 'Lauderbaugh', + 'Misquez', + 'Muneton', + 'Ourada', + 'Rulon', + 'Scholze', + 'Stellmach', + 'Sudano', + 'Thelander', + 'Yeckley', + 'Corsino', + 'Grage', + 'Isla', + 'Narramore', + 'Coolman', + 'Heatherington', + 'Newey', + 'Kunda', + 'Motts', + 'Tawfik', + 'Tindel', + 'Passon', + 'Sypher', + 'Conceicao', + 'Haraway', + 'Deamer', + 'Nored', + 'Mamo', + 'Mcgilberry', + 'Akerley', + 'Andreatta', + 'Aronhalt', + 'Barz', + 'Bebber', + 'Brubacher', + 'Cabriales', + 'Dyckman', + 'Ellers', + 'Finerty', + 'Hargan', + 'Haselton', + 'Hellmuth', + 'Hoffmeier', + 'Homrich', + 'Hrabak', + 'Intrieri', + 'Lebeda', + 'Lutzke', + 'Malka', + 'Mcglinn', + 'Nicklin', + 'Nusz', + 'Pennings', + 'Rebmann', + 'Rodocker', + 'Sacra', + 'Saksa', + 'Shehane', + 'Siever', + 'Snide', + 'Sotero', + 'Sponsel', + 'Therien', + 'Viti', + 'Wubben', + 'Zieske', + 'Billingham', + 'Bruschi', + 'Cullipher', + 'Eppolito', + 'Greuel', + 'Huq', + 'Matott', + 'Mohlman', + 'Monterroza', + 'Risberg', + 'Shvartsman', + 'Sigafoos', + 'Zehring', + 'Manuele', + 'Asghar', + 'Shelp', + 'Grieder', + 'Hippert', + 'Dani', + 'Beserra', + 'Kennan', + 'Scholfield', + 'Joh', + 'Swailes', + 'Pear', + 'Hell', + 'Kittler', + 'Pickeral', + 'Somerset', + 'Streat', + 'Tinner', + 'Landor', + 'Pretlow', + 'Tensley', + 'Abela', + 'Abramovich', + 'Acocella', + 'Avino', + 'Bacchi', + 'Bayliff', + 'Beganovic', + 'Belinsky', + 'Bilicki', + 'Borowiec', + 'Bucknam', + 'Calandro', + 'Ciszek', + 'Cooling', + 'Cundari', + 'Derk', + 'Ekern', + 'Engelson', + 'Fennessey', + 'Ferencz', + 'Filipkowski', + 'Frescas', + 'Frisinger', + 'Gegg', + 'Hanken', + 'Harbach', + 'Jipson', + 'Kasal', + 'Kinstler', + 'Langenbach', + 'Leccese', + 'Maalouf', + 'Mcinerny', + 'Mcpartlin', + 'Meth', + 'Mitzner', + 'Riano', + 'Saggese', + 'Schroff', + 'Skibicki', + 'Textor', + 'Vancampen', + 'Vukelich', + 'Wascom', + 'Workinger', + 'Xin', + 'Bronkema', + 'Gerstel', + 'Geving', + 'Gravlin', + 'Hannay', + 'Haughn', + 'Lippi', + 'Lonsway', + 'Paradowski', + 'Poust', + 'Thinnes', + 'Wassenaar', + 'Hemm', + 'Isip', + 'Pastorino', + 'Barkett', + 'Montalban', + 'Ballestero', + 'Floren', + 'Rossen', + 'Chuba', + 'Burrington', + 'Derman', + 'Wickland', + 'Dunman', + 'Beek', + 'Petitjean', + 'Michelin', + 'Chapell', + 'Pullam', + 'Adamcik', + 'Albarracin', + 'Batrez', + 'Berghuis', + 'Birkland', + 'Boulier', + 'Broderson', + 'Bruun', + 'Cicio', + 'Davidow', + 'Denova', + 'Dooner', + 'Espeland', + 'Fifita', + 'Guidone', + 'Hartnell', + 'Havranek', + 'Janca', + 'Klepac', + 'Langhorst', + 'Lippmann', + 'Merrihew', + 'Mondelli', + 'Monterosso', + 'Moster', + 'Noxon', + 'Poznanski', + 'Reents', + 'Samaras', + 'Silvius', + 'Srour', + 'Stasio', + 'Steffe', + 'Steimer', + 'Stracke', + 'Taney', + 'Theodorou', + 'Trickel', + 'Tunks', + 'Vavrek', + 'Whitfill', + 'Wohlfeil', + 'Zirkelbach', + 'Brissey', + 'Busboom', + 'Collignon', + 'Emling', + 'Fratzke', + 'Genrich', + 'Giglia', + 'Hayakawa', + 'Lupinski', + 'Pulvermacher', + 'Steinbrink', + 'Xayavong', + 'Yerkey', + 'Arlotta', + 'Calia', + 'Pfiffner', + 'Gostomski', + 'Declerck', + 'Demedeiros', + 'Dirickson', + 'Wo', + 'Hosie', + 'Chad', + 'Herbison', + 'Fleece', + 'Connon', + 'Dun', + 'Gaffin', + 'Plush', + 'Gravette', + 'Houseal', + 'Seaward', + 'Esson', + 'Mayhorn', + 'Surrell', + 'Horsford', + 'Mcduffey', + 'Huger', + 'Alexie', + 'Apsey', + 'Belke', + 'Bourcier', + 'Cardena', + 'Daun', + 'Dunworth', + 'Ehrsam', + 'Elizardo', + 'Elkhatib', + 'Emick', + 'Fernau', + 'Finnan', + 'Hitzeman', + 'Housand', + 'Kallstrom', + 'Katen', + 'Kerstein', + 'Kiracofe', + 'Klammer', + 'Largaespada', + 'Limoges', + 'Lodwick', + 'Lozito', + 'Madl', + 'Mauthe', + 'Mogel', + 'Newstrom', + 'Ninh', + 'Obrochta', + 'Opsal', + 'Ordiway', + 'Osentoski', + 'Paxman', + 'Plume', + 'Rickenbach', + 'Rinks', + 'Saltmarsh', + 'Scheuring', + 'Schwegel', + 'Skov', + 'Woodrome', + 'Zdanowicz', + 'Zera', + 'Basgall', + 'Bornhorst', + 'Clotfelter', + 'Coulthard', + 'Dresner', + 'Fischl', + 'Grahek', + 'Grefe', + 'Knightly', + 'Kuenzel', + 'Mccumbers', + 'Millstein', + 'Mulnix', + 'Weiher', + 'Yust', + 'Metter', + 'Polio', + 'Ayad', + 'Banke', + 'Lawlis', + 'Coba', + 'Twyford', + 'Burck', + 'Barthold', + 'Sames', + 'Jacquot', + 'Allsopp', + 'Mcglaun', + 'Hollinsworth', + 'Gillings', + 'Buchannon', + 'Bas', + 'Beaber', + 'Berto', + 'Bobrow', + 'Bochicchio', + 'Bohland', + 'Burghart', + 'Chaloux', + 'Costella', + 'Depace', + 'Dils', + 'Diviney', + 'Ehly', + 'Ermer', + 'Fussner', + 'Gunia', + 'Guterrez', + 'Holik', + 'Holster', + 'Kasperski', + 'Koscinski', + 'Lamoureaux', + 'Marotti', + 'Masullo', + 'Mcconahy', + 'Mehlhaff', + 'Mocarski', + 'Moosman', + 'Pavlich', + 'Pfisterer', + 'Ruacho', + 'Semrad', + 'Slemmer', + 'Stineman', + 'Toelle', + 'Vanderstelt', + 'Wagy', + 'Wuensch', + 'Wykes', + 'Zar', + 'Bouchie', + 'Friis', + 'Gehrt', + 'Hempfling', + 'Henkes', + 'Huggler', + 'Kelbaugh', + 'Petrenko', + 'Pfost', + 'Rubendall', + 'Shimel', + 'Stapf', + 'Sweeton', + 'Tsuda', + 'Vitanza', + 'Voytko', + 'Bibbo', + 'Hagee', + 'Majer', + 'Mangieri', + 'Pala', + 'Volle', + 'Cabassa', + 'Lipsett', + 'Macdougal', + 'Minar', + 'Eline', + 'Eskin', + 'Angeletti', + 'Lattner', + 'Kimple', + 'Marsan', + 'Tornes', + 'Moncur', + 'Sanderfer', + 'Crite', + 'Levels', + 'Valcin', + 'Motton', + 'Foggie', + 'Battistoni', + 'Bedient', + 'Bendt', + 'Bennison', + 'Bonnin', + 'Caridi', + 'Cedotal', + 'Choinski', + 'Cossin', + 'Devargas', + 'Deveny', + 'Dosher', + 'Dredge', + 'Fittro', + 'Gorgone', + 'Gourd', + 'Herra', + 'Holwerda', + 'Iannello', + 'Klintworth', + 'Kubena', + 'Leyvas', + 'Magowan', + 'Mendolia', + 'Nehme', + 'Pelikan', + 'Pfalzgraf', + 'Raith', + 'Reichenberg', + 'Reinertsen', + 'Sens', + 'Simer', + 'Spektor', + 'Sweda', + 'Wordell', + 'Blasing', + 'Dinoto', + 'Goblirsch', + 'Helming', + 'Hibshman', + 'Lamountain', + 'Latka', + 'Licausi', + 'Malerba', + 'Mentink', + 'Meskill', + 'Moening', + 'Montminy', + 'Ryno', + 'Sluka', + 'Solarz', + 'Swainston', + 'Tagliaferri', + 'Twichell', + 'Vertucci', + 'Voland', + 'Wolgast', + 'Bissen', + 'Duray', + 'Flaum', + 'Taves', + 'Caplin', + 'Hayat', + 'Pollett', + 'Baris', + 'Taher', + 'Anes', + 'Beza', + 'Pere', + 'Tipper', + 'Farrey', + 'Slott', + 'Sinquefield', + 'Bobbett', + 'Calico', + 'Eigner', + 'Gambrill', + 'Donigan', + 'Daney', + 'Natt', + 'Gettis', + 'Kincy', + 'Dolberry', + 'Curenton', + 'Elzie', + 'Beretta', + 'Carbine', + 'Carpenito', + 'Clarin', + 'Conrado', + 'Conradt', + 'Courteau', + 'Daft', + 'Debruler', + 'Delahunty', + 'Duerst', + 'Dzik', + 'Ellner', + 'Faeth', + 'Fournet', + 'Galinski', + 'Goldenstein', + 'Hanauer', + 'Higgason', + 'Hoeper', + 'Hollo', + 'Ildefonso', + 'Jocson', + 'Kasprowicz', + 'Kochanowski', + 'Labrosse', + 'Lazaroff', + 'Leino', + 'Levinsky', + 'Lopezhernandez', + 'Mckeague', + 'Otremba', + 'Paluzzi', + 'Pevehouse', + 'Polgar', + 'Raneri', + 'Rumler', + 'Sanantonio', + 'Schissel', + 'Senteno', + 'Sieling', + 'Smee', + 'Swiggum', + 'Tarnow', + 'Tavakoli', + 'Tholl', + 'Valdiviezo', + 'Willadsen', + 'Wilmouth', + 'Dudziak', + 'Eskenazi', + 'Garity', + 'Gravino', + 'Impastato', + 'Kuhner', + 'Mcclaflin', + 'Nein', + 'Precourt', + 'Rotenberry', + 'Sciara', + 'Arenson', + 'Coupland', + 'Sedler', + 'Pizer', + 'Him', + 'Combee', + 'Rhorer', + 'Gelles', + 'Baroody', + 'Basten', + 'Sprinkles', + 'Vanier', + 'Clementson', + 'Robberson', + 'Harten', + 'Kade', + 'Bhola', + 'Bahar', + 'Pellum', + 'Isadore', + 'Dixie', + 'Axline', + 'Backs', + 'Berdahl', + 'Billeter', + 'Bily', + 'Broerman', + 'Declercq', + 'Derleth', + 'Fanucchi', + 'Forkey', + 'Gallinger', + 'Gionfriddo', + 'Gretzinger', + 'Grima', + 'Helgren', + 'Hoelting', + 'Hundertmark', + 'Inscho', + 'Jernberg', + 'Kamiya', + 'Lekas', + 'Marchini', + 'Markuson', + 'Matsushima', + 'Meineke', + 'Mizrachi', + 'Moglia', + 'Nagele', + 'Naro', + 'Padillo', + 'Palleschi', + 'Palomba', + 'Purgason', + 'Qadri', + 'Recalde', + 'Rosiak', + 'Rumney', + 'Savitt', + 'Shibuya', + 'Szalkowski', + 'Wagg', + 'Wolsey', + 'Zumpano', + 'Benbrook', + 'Blasdel', + 'Carusone', + 'Karalis', + 'Koep', + 'Kohles', + 'Rumbo', + 'Siggins', + 'Unverzagt', + 'Eatherly', + 'Kapper', + 'Salser', + 'Wege', + 'Zinsmeister', + 'Alf', + 'Wish', + 'Falero', + 'Bur', + 'Imam', + 'Biven', + 'Merritts', + 'Kaigler', + 'Verdell', + 'Feggins', + 'Acerra', + 'Antenucci', + 'Benegas', + 'Bisesi', + 'Boshers', + 'Chap', + 'Clouatre', + 'Doxtater', + 'Dullea', + 'Eischeid', + 'Gundry', + 'Hinger', + 'Hodak', + 'Iseminger', + 'Juris', + 'Kirchen', + 'Knezevic', + 'Kobrin', + 'Krizek', + 'Leza', + 'Lusty', + 'Luttrull', + 'Mattke', + 'Mossbarger', + 'Narro', + 'Osland', + 'Ostwald', + 'Pepperman', + 'Pritzl', + 'Reasner', + 'Schimming', + 'Schulenburg', + 'Trefry', + 'Vigorito', + 'Bayardo', + 'Bieser', + 'Brinkmeier', + 'Camposano', + 'Cremeens', + 'Delgrande', + 'Demopoulos', + 'Deyarmin', + 'Grismer', + 'Jubb', + 'Kinker', + 'Lauf', + 'Mabile', + 'Muehl', + 'Orlick', + 'Pillado', + 'Pizzano', + 'Poppleton', + 'Quickel', + 'Stoneberg', + 'Szwed', + 'Zadrozny', + 'Ziemke', + 'Zupko', + 'Diesel', + 'Hornbrook', + 'Pillion', + 'Holaway', + 'Massad', + 'Rossmiller', + 'Parriott', + 'Toya', + 'Dross', + 'Burwick', + 'Kaman', + 'Bruna', + 'Milles', + 'Acrey', + 'Toogood', + 'Austell', + 'Chastang', + 'Jasmine', + 'Eckford', + 'Stiggers', + 'Saintvil', + 'Adeyemi', + 'Basto', + 'Bolon', + 'Brilliant', + 'Brockhoff', + 'Colao', + 'Emens', + 'Endler', + 'Fabris', + 'Falletta', + 'Felver', + 'Ferdon', + 'Golinski', + 'Gosdin', + 'Gronlund', + 'Guijosa', + 'Hainley', + 'Halama', + 'Heinicke', + 'Heldenbrand', + 'Helmkamp', + 'Hoctor', + 'Hoeck', + 'Kroboth', + 'Lamagna', + 'Lingg', + 'Locurto', + 'Marchewka', + 'Micco', + 'Mormino', + 'Newmeyer', + 'Ostrosky', + 'Redel', + 'Saccoccio', + 'Stavely', + 'Stidd', + 'Tonne', + 'Tonnesen', + 'Umbach', + 'Vardanyan', + 'Wank', + 'Wolven', + 'Cilento', + 'Delmonaco', + 'Denigris', + 'Gerbig', + 'Gradilla', + 'Grebner', + 'Landini', + 'Marohl', + 'Muenchow', + 'Niedermeier', + 'Nussbaumer', + 'Nycz', + 'Pizzino', + 'Schader', + 'Schuneman', + 'Takano', + 'Ureta', + 'Vanderloop', + 'Windholz', + 'Wombacher', + 'Woulfe', + 'Hamley', + 'Schickel', + 'Yuill', + 'Batta', + 'Galant', + 'Mofield', + 'Kint', + 'Barnell', + 'Ashmead', + 'Crossin', + 'Lasco', + 'Chasen', + 'Swire', + 'Gleghorn', + 'Bearfield', + 'Goodgame', + 'Daris', + 'Plump', + 'Derricott', + 'Burno', + 'Baylock', + 'Vanterpool', + 'Judon', + 'Mells', + 'Proby', + 'Bagan', + 'Batcheller', + 'Bjelland', + 'Boline', + 'Boullion', + 'Broomall', + 'Carcia', + 'Cassinelli', + 'Cerro', + 'Colantuono', + 'Dembeck', + 'Doto', + 'Eckersley', + 'Edell', + 'Ewy', + 'Goodness', + 'Huhta', + 'Kallen', + 'Keimig', + 'Kemppainen', + 'Koopmann', + 'Lacap', + 'Lehtinen', + 'Maciolek', + 'Marchuk', + 'Mcfate', + 'Mentel', + 'Minihan', + 'Mohsin', + 'Oppedisano', + 'Patriarca', + 'Raske', + 'Schueneman', + 'Shostak', + 'Sibal', + 'Spadafore', + 'Suitor', + 'Tavella', + 'Vy', + 'Wies', + 'Beadnell', + 'Bogusz', + 'Cleverly', + 'Dellorusso', + 'Dudenhoeffer', + 'Glendinning', + 'Glomb', + 'Heinkel', + 'Jiwani', + 'Lonigro', + 'Machala', + 'Marsicano', + 'Neuenfeldt', + 'Overlock', + 'Popko', + 'Russomanno', + 'Saxer', + 'Scicchitano', + 'Spiegelberg', + 'Spindel', + 'Timpone', + 'Vincelette', + 'Waidelich', + 'Wissink', + 'Woolstenhulme', + 'Danza', + 'Sleasman', + 'Frometa', + 'Savinon', + 'Higgerson', + 'Helmich', + 'Nahar', + 'Campus', + 'Hassey', + 'Mccorkel', + 'Tola', + 'Ferrington', + 'Nicolls', + 'Markes', + 'Edgley', + 'Dupriest', + 'Wah', + 'Mclester', + 'Scantling', + 'Goffe', + 'Battie', + 'Battershell', + 'Bearup', + 'Bisig', + 'Brouillet', + 'Canby', + 'Chaussee', + 'Colandrea', + 'Colocho', + 'Daube', + 'Dobransky', + 'Dolbow', + 'Dyk', + 'Elfrink', + 'Figel', + 'Hauter', + 'Henkels', + 'Keillor', + 'Kollasch', + 'Krabill', + 'Kubly', + 'Kvasnicka', + 'Leise', + 'Martirosyan', + 'Mihalic', + 'Montecinos', + 'Myren', + 'Okerlund', + 'Ozer', + 'Rajput', + 'Reihl', + 'Rimando', + 'Saffle', + 'Schmelter', + 'Tellado', + 'Wachsmuth', + 'Wussow', + 'Zylka', + 'Caiola', + 'Certo', + 'Disabatino', + 'Ehrke', + 'Lahmann', + 'Lamartina', + 'Manheim', + 'Mckevitt', + 'Nardozzi', + 'Neuzil', + 'Novotney', + 'Oldfather', + 'Sietsema', + 'Stemmler', + 'Stumm', + 'Ueno', + 'Weckwerth', + 'Berrocal', + 'Nolde', + 'Alava', + 'Revier', + 'Sester', + 'Saller', + 'Tonga', + 'Kala', + 'Reveron', + 'Homesley', + 'Pagett', + 'Blackie', + 'Raimer', + 'Fitt', + 'Kimbley', + 'Amory', + 'Cabler', + 'Juett', + 'Crate', + 'Burres', + 'Siddle', + 'Barnfield', + 'Bordenave', + 'Cubit', + 'Elem', + 'Hardmon', + 'Augspurger', + 'Barriger', + 'Bau', + 'Bloomingdale', + 'Busta', + 'Canoy', + 'Carapia', + 'Cavenaugh', + 'Conkin', + 'Coppernoll', + 'Daloia', + 'Debruyne', + 'Egly', + 'Esmail', + 'Estorga', + 'Gladu', + 'Gladue', + 'Harvath', + 'Hirschmann', + 'Juel', + 'Kappus', + 'Kopriva', + 'Krul', + 'Lavorgna', + 'Maginn', + 'Malphrus', + 'Mcilhenny', + 'Perazzo', + 'Peredo', + 'Pineo', + 'Rigoni', + 'Robleto', + 'Schoene', + 'Sevillano', + 'Stears', + 'Stoltzfoos', + 'Sutley', + 'Terracciano', + 'Villacres', + 'Yoak', + 'Brensinger', + 'Brodzinski', + 'Cordial', + 'Cornacchia', + 'Corralejo', + 'Demarchi', + 'Dziuk', + 'Hirzel', + 'Keirns', + 'Kocourek', + 'Kupec', + 'Nazaryan', + 'Oftedahl', + 'Pignatelli', + 'Pundt', + 'Repinski', + 'Ryther', + 'Sampedro', + 'Shemanski', + 'Siess', + 'Trettel', + 'Urquilla', + 'Vantil', + 'Vicens', + 'Dunahoo', + 'Safer', + 'Romaniello', + 'Tallo', + 'Cavell', + 'Cobern', + 'Yarrow', + 'Serge', + 'Adel', + 'Allum', + 'Pruit', + 'Wali', + 'Forson', + 'Bells', + 'Blyden', + 'Andreotti', + 'Bagnato', + 'Beauchaine', + 'Biedrzycki', + 'Brabo', + 'Brodman', + 'Bruyere', + 'Canizares', + 'Chio', + 'Coudriet', + 'Dara', + 'Dhawan', + 'Diclemente', + 'Doro', + 'Elvir', + 'Fivecoat', + 'Frate', + 'Furuya', + 'Greis', + 'Halbleib', + 'Heuerman', + 'Hoener', + 'Holberg', + 'Hoogendoorn', + 'Inclan', + 'Jokinen', + 'Kretchmer', + 'Lafromboise', + 'Mccomsey', + 'Mckiddy', + 'Pelky', + 'Plaia', + 'Ponti', + 'Reichl', + 'Schicker', + 'Sotto', + 'Staehle', + 'Thau', + 'Turchin', + 'Zill', + 'Aicher', + 'Arrigoni', + 'Bertagnolli', + 'Binetti', + 'Dahlheimer', + 'Delashmit', + 'Disque', + 'Hemmerling', + 'Hovater', + 'Kachur', + 'Massmann', + 'Schlup', + 'Turkovich', + 'Underberg', + 'Wambolt', + 'Vassey', + 'Larney', + 'Brisky', + 'Minas', + 'Kata', + 'Magar', + 'Arlen', + 'Corporan', + 'Westland', + 'Detherage', + 'Reen', + 'Morale', + 'Hoes', + 'Baynham', + 'Norrington', + 'Lartigue', + 'Hakeem', + 'Kendrix', + 'Cazeau', + 'Amadi', + 'Mczeal', + 'Alwin', + 'Barcellos', + 'Bastedo', + 'Bintz', + 'Brackenbury', + 'Brockel', + 'Bucek', + 'Cecala', + 'Dapper', + 'Dettore', + 'Dowdall', + 'Dralle', + 'Essenmacher', + 'Evaristo', + 'Fecher', + 'Feldmeier', + 'Fetherston', + 'Futterman', + 'Garlinghouse', + 'Germani', + 'Gotz', + 'Hoen', + 'Janikowski', + 'Kiess', + 'Lagerstrom', + 'Lozinski', + 'Magnone', + 'Markow', + 'Mayall', + 'Mehdi', + 'Mineau', + 'Morgenroth', + 'Nitzsche', + 'Nordell', + 'Pavlock', + 'Peruzzi', + 'Pettine', + 'Pinos', + 'Polidoro', + 'Rahl', + 'Rudis', + 'Ryback', + 'Santellan', + 'Scharfenberg', + 'Schnake', + 'Schwake', + 'Seeling', + 'Senk', + 'Siron', + 'Speich', + 'Summerhays', + 'Torno', + 'Vangieson', + 'Wiacek', + 'Begnoche', + 'Carrejo', + 'Chervenak', + 'Edminster', + 'Halonen', + 'Macumber', + 'Mazeika', + 'Mikami', + 'Minetti', + 'Mosbrucker', + 'Mundis', + 'Onder', + 'Prowant', + 'Pyo', + 'Sedlack', + 'Stanbro', + 'Woehl', + 'Wrage', + 'Carpentieri', + 'Guedry', + 'Hodde', + 'Waggy', + 'Weitman', + 'Handal', + 'Gosman', + 'Mckeone', + 'Oliveria', + 'Soutar', + 'Glance', + 'Surprise', + 'Milius', + 'Crammer', + 'Mclear', + 'Borris', + 'Malon', + 'Mane', + 'Arrick', + 'Brazzel', + 'Matthewson', + 'Philemon', + 'Selvy', + 'Lites', + 'Deadwyler', + 'Marzette', + 'Alipio', + 'Arancibia', + 'Arrona', + 'Basista', + 'Blethen', + 'Brull', + 'Colaianni', + 'Dreese', + 'Giammona', + 'Giovanetti', + 'Grandmaison', + 'Grondahl', + 'Gulli', + 'Hellenbrand', + 'Iturbe', + 'Koesters', + 'Kondracki', + 'Konitzer', + 'Kubic', + 'Lauerman', + 'Mcfadin', + 'Musquiz', + 'Papalia', + 'Porrazzo', + 'Prien', + 'Reichley', + 'Treichler', + 'Ursua', + 'Vanblaricom', + 'Wich', + 'Windler', + 'Wos', + 'Zampino', + 'Alexopoulos', + 'Bambrick', + 'Beabout', + 'Brechtel', + 'Buroker', + 'Dahler', + 'Everding', + 'Furno', + 'Gikas', + 'Gilkeson', + 'Hubka', + 'Konwinski', + 'Krisko', + 'Kuligowski', + 'Maltbie', + 'Molstad', + 'Nonnemacher', + 'Nowotny', + 'Odisho', + 'Remsburg', + 'Rollyson', + 'Siegmann', + 'Slaubaugh', + 'Wasco', + 'Carlyon', + 'Chanin', + 'Cominsky', + 'Karber', + 'Aynes', + 'Swamy', + 'Kolden', + 'Rochel', + 'Julin', + 'Demarcus', + 'Malena', + 'Morice', + 'Burst', + 'Sukhu', + 'Mccravy', + 'Rinehardt', + 'Veazie', + 'Isaiah', + 'Bradby', + 'Poellnitz', + 'Agyemang', + 'Agate', + 'Aschoff', + 'Beenken', + 'Bogenschutz', + 'Casamento', + 'Correira', + 'Ebers', + 'Ellertson', + 'Forcum', + 'Gortney', + 'Jarriel', + 'Jasmer', + 'Kennebeck', + 'Kimpton', + 'Lad', + 'Lasek', + 'Licavoli', + 'Lipper', + 'Luedecke', + 'Maqueda', + 'Matsen', + 'Mest', + 'Neang', + 'Neault', + 'Newlun', + 'Oetken', + 'Rodick', + 'Rollinger', + 'Sabins', + 'Schalow', + 'Sheils', + 'Spilde', + 'Virzi', + 'Watz', + 'Wehrly', + 'Boscarino', + 'Chavolla', + 'Dasaro', + 'Eisenbach', + 'Ignatowski', + 'Kievit', + 'Kuzminski', + 'Lickliter', + 'Moravek', + 'Pawling', + 'Prause', + 'Redler', + 'Wunschel', + 'Suchanek', + 'Eyring', + 'Loge', + 'Tout', + 'Fross', + 'Swiss', + 'Deforrest', + 'Umphlett', + 'Herran', + 'Matton', + 'Passe', + 'Ode', + 'Della', + 'Caillier', + 'Baten', + 'Chesterfield', + 'Odneal', + 'Azeez', + 'Salami', + 'Ramson', + 'Mcvea', + 'Pittmon', + 'Cheatom', + 'Dorsainvil', + 'Cheeseboro', + 'Lavalais', + 'Allegro', + 'Bressi', + 'Brocklehurst', + 'Cassarino', + 'Dario', + 'Gazzola', + 'Glinka', + 'Goffredo', + 'Halabi', + 'Kroeze', + 'Lenig', + 'Marciel', + 'Marcussen', + 'Massoni', + 'Mayernik', + 'Nawrot', + 'Palazzi', + 'Pfefferkorn', + 'Placeres', + 'Polimeni', + 'Recendiz', + 'Sawdey', + 'Seidell', + 'Suchecki', + 'Titzer', + 'Virag', + 'Vitulli', + 'Wiltfong', + 'Wolden', + 'Woolworth', + 'Yandow', + 'Zeiter', + 'Zogg', + 'Brosh', + 'Dunsmoor', + 'Gucciardo', + 'Gumz', + 'Luginbill', + 'Mathwig', + 'Pannullo', + 'Raitt', + 'Reutzel', + 'Sonnen', + 'Bahri', + 'Guiffre', + 'Hons', + 'Platner', + 'Balaguer', + 'Lapre', + 'Rabbani', + 'Talent', + 'Hoster', + 'Thal', + 'Apo', + 'Duggin', + 'Kirley', + 'Burnard', + 'Lourie', + 'Wilham', + 'Craton', + 'Griff', + 'Falwell', + 'Upperman', + 'Laverne', + 'Wi', + 'Foucher', + 'Sudberry', + 'Oriol', + 'Cowens', + 'Marshell', + 'Chargois', + 'Bordley', + 'Artale', + 'Boeker', + 'Cookston', + 'Dattilio', + 'Dewinter', + 'Ditton', + 'Droessler', + 'Dusch', + 'Eltringham', + 'Feige', + 'Giel', + 'Grigas', + 'Hannagan', + 'Haubner', + 'Henzler', + 'Kippes', + 'Kneebone', + 'Lozeau', + 'Mallek', + 'Mandato', + 'Mangiapane', + 'Matusek', + 'Newgard', + 'Notte', + 'Purdin', + 'Ramaker', + 'Reddoch', + 'Rensing', + 'Rohrman', + 'Romm', + 'Rudiger', + 'Torti', + 'Travaglini', + 'Uno', + 'Wojciak', + 'Yannuzzi', + 'Zeien', + 'Arpino', + 'Borgstrom', + 'Burkemper', + 'Cristino', + 'Detjen', + 'Gienger', + 'Glockner', + 'Grillot', + 'Jentz', + 'Kendzierski', + 'Klebe', + 'Knippenberg', + 'Kusler', + 'Olofson', + 'Orlov', + 'Rindt', + 'Stallbaumer', + 'Troost', + 'Turri', + 'Uzelac', + 'Weichert', + 'Sweazy', + 'Alcivar', + 'Canner', + 'Lottman', + 'Salame', + 'Berkes', + 'Pickren', + 'Ganson', + 'Odonell', + 'Geron', + 'Kasa', + 'Banbury', + 'Tinnel', + 'Umble', + 'Flow', + 'Kirt', + 'Rhule', + 'Diles', + 'Seeney', + 'Givans', + 'Mckethan', + 'Crusoe', + 'Darko', + 'Mucker', + 'Kizzee', + 'Daniely', + 'Nutall', + 'Angove', + 'Appelhans', + 'Balder', + 'Blatchley', + 'Botkins', + 'Brisk', + 'Burandt', + 'Clowdus', + 'Debauche', + 'Deily', + 'Group', + 'Hoecker', + 'Holsonback', + 'Humpert', + 'Jacquin', + 'Jurica', + 'Karnik', + 'Krontz', + 'Lapiana', + 'Lenzo', + 'Luscombe', + 'Madey', + 'Mirabito', + 'Neifert', + 'Pennino', + 'Piechota', + 'Pizzimenti', + 'Reeg', + 'Roarty', + 'Routzahn', + 'Salsedo', + 'Schuff', + 'Silveri', + 'Steckman', + 'Supak', + 'Swackhamer', + 'Trusler', + 'Vizzini', + 'Wences', + 'Whelton', + 'Zachar', + 'Albertsen', + 'Bischel', + 'Brigandi', + 'Campoy', + 'Castagnola', + 'Doenges', + 'Flessner', + 'Garbers', + 'Jezewski', + 'Kozlov', + 'Niedbalski', + 'Schillo', + 'Schoepke', + 'Schranz', + 'Trulson', + 'Vanwyhe', + 'Versluis', + 'Zavadil', + 'Brau', + 'Rudell', + 'Golen', + 'Meter', + 'Sherrin', + 'Tolly', + 'Mandala', + 'Calcano', + 'Lewing', + 'Sedeno', + 'Ramalho', + 'Haggar', + 'Borns', + 'Matherson', + 'Cobin', + 'Turnley', + 'Pone', + 'Tuner', + 'Crandle', + 'Sturkey', + 'Heggins', + 'Tisby', + 'Allbaugh', + 'Baars', + 'Bethard', + 'Brenizer', + 'Bussman', + 'Casebier', + 'Castanos', + 'Climaco', + 'Dux', + 'Farrens', + 'Frediani', + 'Gaccione', + 'Garciaperez', + 'Hoppa', + 'Juckett', + 'Klinkner', + 'Kooy', + 'Krinke', + 'Locy', + 'Lovecchio', + 'Lukin', + 'Machia', + 'Mand', + 'Maslin', + 'Mehrotra', + 'Nicolet', + 'Peyser', + 'Reckart', + 'Roanhorse', + 'Rokicki', + 'Sargis', + 'Sciullo', + 'Shevchuk', + 'Sindoni', + 'Slankard', + 'Sobiech', + 'Stoneberger', + 'Stys', + 'Tuzzolino', + 'Waligora', + 'Wiland', + 'Clabough', + 'Drawbaugh', + 'Figurski', + 'Gibeault', + 'Gojcaj', + 'Hartfiel', + 'Inbody', + 'Konarski', + 'Kruszka', + 'Letarte', + 'Lillich', + 'Mccandlish', + 'Mollenkopf', + 'Oltmann', + 'Pfenninger', + 'Ruediger', + 'Schaben', + 'Shauger', + 'Wilczak', + 'Wolanin', + 'Ziehm', + 'Bassinger', + 'Brannick', + 'Schlereth', + 'Capri', + 'Roscher', + 'Pasqual', + 'Lallo', + 'Sweney', + 'Rozario', + 'Hamblet', + 'Muckleroy', + 'Frankson', + 'Moure', + 'Shrieves', + 'Bosket', + 'Strowbridge', + 'Hawkin', + 'Cooperwood', + 'Agena', + 'Barrowman', + 'Belko', + 'Blasdell', + 'Brobeck', + 'Chieffo', + 'Cooperrider', + 'Dickard', + 'Erion', + 'Fradkin', + 'Hattery', + 'Hefferon', + 'Hofstra', + 'Hoiland', + 'Jirak', + 'Klugman', + 'Klundt', + 'Knope', + 'Lawniczak', + 'Luckenbach', + 'Manzione', + 'Mccombie', + 'Minden', + 'Mousel', + 'Ridling', + 'Rightmire', + 'Ritzel', + 'Santori', + 'Semmens', + 'Snyders', + 'Spargur', + 'Staszewski', + 'Swiech', + 'Tasso', + 'Veldhuizen', + 'Vuolo', + 'Wojnarowski', + 'Yoe', + 'Bachler', + 'Cimo', + 'Hippen', + 'Klimaszewski', + 'Kohlhepp', + 'Kovacich', + 'Kretsch', + 'Lacoursiere', + 'Lopezmartinez', + 'Marsiglia', + 'Metzker', + 'Murchie', + 'Paradee', + 'Pfefferle', + 'Rothert', + 'Skellenger', + 'Tourangeau', + 'Beumer', + 'Thunder', + 'Uden', + 'Broe', + 'Moxon', + 'Kassin', + 'Murton', + 'Hockley', + 'Vinet', + 'Suthers', + 'Bayman', + 'Cokeley', + 'Ailey', + 'Crossfield', + 'Desha', + 'Dowson', + 'Acheampong', + 'Boomsma', + 'Buer', + 'Caratachea', + 'Dascenzo', + 'Debes', + 'Degroote', + 'Dillie', + 'Dorsi', + 'Dorward', + 'Eyestone', + 'Geister', + 'Gonia', + 'Heiler', + 'Hin', + 'Hoheisel', + 'Horger', + 'Hulce', + 'Kainer', + 'Kerkman', + 'Kloehn', + 'Krempasky', + 'Kuehnel', + 'Leetch', + 'Lio', + 'Lohrey', + 'Lucchetti', + 'Machnik', + 'Majeske', + 'Martire', + 'Mores', + 'Oyen', + 'Pappert', + 'Platas', + 'Podany', + 'Prata', + 'Radoncic', + 'Sainato', + 'Salada', + 'Serota', + 'Tatsch', + 'Torbeck', + 'Vilhauer', + 'Waltner', + 'Wauters', + 'Welge', + 'Yoss', + 'Bigwood', + 'Brunsman', + 'Civitello', + 'Compston', + 'Cuccaro', + 'Denholm', + 'Emmick', + 'Gadzinski', + 'Goedken', + 'Graumann', + 'Hackert', + 'Hardacre', + 'Hehl', + 'Magliocco', + 'Marotto', + 'Ozanich', + 'Pidcock', + 'Schlangen', + 'Scoma', + 'Sobecki', + 'Spreng', + 'Thalmann', + 'Wolfrum', + 'Groninger', + 'Howatt', + 'Kindy', + 'Swor', + 'Ledden', + 'Voyer', + 'Colli', + 'Andrae', + 'Duchemin', + 'Boker', + 'Malter', + 'Snooks', + 'Morss', + 'Haylett', + 'Mitter', + 'Fairey', + 'Kenerson', + 'Albea', + 'Ellerson', + 'Alcindor', + 'Gadison', + 'Arabia', + 'Bundren', + 'Calica', + 'Cartaya', + 'Cielo', + 'Ebbers', + 'Entler', + 'Friedly', + 'Granja', + 'Landt', + 'Lorensen', + 'Michelini', + 'Oliveto', + 'Piela', + 'Reust', + 'Roussos', + 'Sanluis', + 'Seier', + 'Sobolik', + 'Stader', + 'Stetzer', + 'Tetley', + 'Zirbes', + 'Bridenbaugh', + 'Chinnici', + 'Crabbs', + 'Evilsizer', + 'Favaloro', + 'Haeberle', + 'Hopfensperger', + 'Kijowski', + 'Kingbird', + 'Leikam', + 'Montavon', + 'Petrossian', + 'Quizhpi', + 'Spoelstra', + 'Testani', + 'Plaut', + 'Windt', + 'Dubie', + 'Kozinski', + 'Sorell', + 'Nish', + 'Katon', + 'Soy', + 'Pelcher', + 'Sayres', + 'Waitman', + 'Relph', + 'Hearld', + 'Farewell', + 'Giordani', + 'Canida', + 'Martian', + 'Suliman', + 'Mckesson', + 'Randon', + 'Eastmond', + 'Willaims', + 'Collington', + 'Hardge', + 'Asevedo', + 'Beauchene', + 'Bebeau', + 'Bobick', + 'Bogacki', + 'Bolich', + 'Bonadonna', + 'Butsch', + 'Coltrin', + 'Corbello', + 'Dastrup', + 'Dunshee', + 'Firpo', + 'Foister', + 'Franssen', + 'Fredriksen', + 'Gfeller', + 'Glassner', + 'Johanns', + 'Korson', + 'Langsam', + 'Linstrom', + 'Longstaff', + 'Lukic', + 'Maler', + 'Marteney', + 'Milardo', + 'Rhatigan', + 'Ruetz', + 'Semel', + 'Senske', + 'Shatswell', + 'Simmering', + 'Tasch', + 'Vanskike', + 'Verano', + 'Viscardi', + 'Weidmann', + 'Doubet', + 'Farraj', + 'Fritter', + 'Griesinger', + 'Horkey', + 'Hornik', + 'Izatt', + 'Klayman', + 'Mantei', + 'Notz', + 'Oberholzer', + 'Petko', + 'Rueth', + 'Rygiel', + 'Tumolo', + 'Unterreiner', + 'Urgo', + 'Weisbecker', + 'Weniger', + 'Zarro', + 'Zunino', + 'Goldmann', + 'Verderber', + 'Glennie', + 'Shere', + 'Lamos', + 'Face', + 'Sparger', + 'Donnay', + 'Kage', + 'Leason', + 'Mcgue', + 'Brickle', + 'Mae', + 'Thomaston', + 'Dunnell', + 'Tillie', + 'Miggins', + 'Geffrard', + 'Aubel', + 'Backe', + 'Beaumier', + 'Bloor', + 'Brackbill', + 'Brandvold', + 'Bylund', + 'Carbary', + 'Catrambone', + 'Dapolito', + 'Dillenburg', + 'Elliff', + 'Fehnel', + 'Ferriss', + 'Gellner', + 'Graw', + 'Guilbeault', + 'Hautala', + 'Hollenberg', + 'Imparato', + 'Kaner', + 'Kley', + 'Lanzer', + 'Laterza', + 'Legner', + 'Lombardozzi', + 'Mcerlean', + 'Mcgilton', + 'Mohring', + 'Neeper', + 'Pollinger', + 'Pullara', + 'Sagona', + 'Scripter', + 'Skillen', + 'Streeper', + 'Tritch', + 'Vayda', + 'Verbeek', + 'Wenberg', + 'Youngers', + 'Bayus', + 'Cobaugh', + 'Dolak', + 'Forys', + 'Genther', + 'Jankovich', + 'Kneale', + 'Komp', + 'Kreher', + 'Kuwahara', + 'Mclouth', + 'Melland', + 'Molesky', + 'Neustadt', + 'Oesterling', + 'Quirke', + 'Roeper', + 'Stantz', + 'Vandenboom', + 'Venhuizen', + 'Westermeyer', + 'Embury', + 'Cozort', + 'Crispo', + 'Woollard', + 'Thiery', + 'Lecy', + 'Terris', + 'Stencil', + 'Yero', + 'Bollard', + 'Chander', + 'Shepp', + 'Younkins', + 'Jon', + 'Anselm', + 'Deveraux', + 'Better', + 'Birth', + 'Hoskie', + 'Kirtz', + 'Encalade', + 'Aprea', + 'Bernick', + 'Bialy', + 'Bolenbaugh', + 'Chinea', + 'Cwiklinski', + 'Dunavan', + 'Dunckel', + 'Essen', + 'Ferner', + 'Gallick', + 'Gruba', + 'Hauss', + 'Intriago', + 'Javaid', + 'Kaney', + 'Klemens', + 'Kuriakose', + 'Leyda', + 'Losurdo', + 'Mcelhone', + 'Methot', + 'Morioka', + 'Mundorf', + 'Nocito', + 'Nordmann', + 'Oommen', + 'Pfahl', + 'Piquette', + 'Prinsen', + 'Sacramento', + 'Shenker', + 'Skidgel', + 'Sobalvarro', + 'Soldo', + 'Synan', + 'Tostenson', + 'Trotti', + 'Vienneau', + 'Vigneau', + 'Waitkus', + 'Wiess', + 'Bartmess', + 'Comparan', + 'Dalonzo', + 'Dutrow', + 'Fleegle', + 'Fronek', + 'Handrich', + 'Hazelip', + 'Heinig', + 'Macapagal', + 'Masciarelli', + 'Pitstick', + 'Radakovich', + 'Ripberger', + 'Schwebel', + 'Slomski', + 'Stinchfield', + 'Zegers', + 'Zeiser', + 'Kimmer', + 'Rippon', + 'Satz', + 'Bosques', + 'Mcnickle', + 'Yarwood', + 'Babar', + 'Ghazi', + 'Mcquary', + 'Africa', + 'Sofer', + 'Marsland', + 'Curby', + 'Odor', + 'Gillem', + 'Selmer', + 'Delmas', + 'Lamison', + 'Lanes', + 'Shadd', + 'Goard', + 'Haylock', + 'Sermon', + 'Meachem', + 'Vernet', + 'Akiona', + 'Avitabile', + 'Berkson', + 'Bisono', + 'Busic', + 'Caroselli', + 'Corradi', + 'Delval', + 'Egley', + 'Elkind', + 'Everling', + 'Ferrario', + 'Frumkin', + 'Gelder', + 'Gironda', + 'Glasheen', + 'Goette', + 'Gotts', + 'Haub', + 'Herro', + 'Hudzik', + 'Hula', + 'Inboden', + 'Isensee', + 'Kiesewetter', + 'Koetje', + 'Laughridge', + 'Lovewell', + 'Meeuwsen', + 'Mokry', + 'Navarez', + 'Plake', + 'Quain', + 'Reppucci', + 'Sorn', + 'Tallerico', + 'Uselman', + 'Verrastro', + 'Wineberg', + 'Blazina', + 'Falardeau', + 'Garavito', + 'Gellerman', + 'Havins', + 'Kurdziel', + 'Liedel', + 'Lofstrom', + 'Pakula', + 'Presby', + 'Ringstad', + 'Rokosz', + 'Schuchart', + 'Seckler', + 'Verderame', + 'Veselka', + 'Asfour', + 'Delanoy', + 'Fromer', + 'Koba', + 'Kostrzewa', + 'Melle', + 'Merkey', + 'Scalese', + 'Oritz', + 'Kilgour', + 'Piker', + 'Janet', + 'Huge', + 'Hails', + 'Dobey', + 'Escoe', + 'Rasool', + 'Gilcrest', + 'Codrington', + 'Jeangilles', + 'Outley', + 'Bambach', + 'Beaulac', + 'Begue', + 'Bobeck', + 'Buccino', + 'Carrigg', + 'Cranney', + 'Denninger', + 'Dicioccio', + 'Eapen', + 'Fargnoli', + 'Fatica', + 'Fernicola', + 'Forse', + 'Freck', + 'Gardipee', + 'Gibas', + 'Goeman', + 'Guadian', + 'Hlad', + 'Jakab', + 'Kishimoto', + 'Krenn', + 'Lagesse', + 'Lhommedieu', + 'Lusch', + 'Mausolf', + 'Mazzocchi', + 'Mcdavitt', + 'Noseworthy', + 'Passante', + 'Placzek', + 'Quamme', + 'Ringgenberg', + 'Spiegelman', + 'Vinluan', + 'Wachsman', + 'Bacigalupi', + 'Baechle', + 'Baetz', + 'Barsch', + 'Colbaugh', + 'Devoto', + 'Dimercurio', + 'Dosanjh', + 'Dukeman', + 'Ferger', + 'Garinger', + 'Grelle', + 'Guyett', + 'Harpenau', + 'Hundal', + 'Kamerer', + 'Klomp', + 'Licklider', + 'Martinec', + 'Matzek', + 'Nixdorf', + 'Pankonin', + 'Pogosyan', + 'Schweickert', + 'Smethurst', + 'Stroope', + 'Zwack', + 'Tebbetts', + 'Stains', + 'Tosado', + 'Carles', + 'Rings', + 'Hebard', + 'Choplin', + 'Townshend', + 'Doorn', + 'Aja', + 'Picking', + 'Oneall', + 'Logie', + 'Aro', + 'Dua', + 'Heney', + 'Manard', + 'Atchinson', + 'Breech', + 'Brashers', + 'Addams', + 'Nooner', + 'Barsh', + 'Orum', + 'Dancey', + 'Bamba', + 'Kareem', + 'Theard', + 'Marseille', + 'Molette', + 'Getachew', + 'Saintfleur', + 'Frimpong', + 'Anglada', + 'Attardo', + 'Barreira', + 'Bleicher', + 'Bonecutter', + 'Bricco', + 'Compian', + 'Creppel', + 'Cuadras', + 'Cuccio', + 'Cutsforth', + 'Dinino', + 'Eskelson', + 'Freemyer', + 'Friedhoff', + 'Grandt', + 'Holzmann', + 'Hoverson', + 'Hurteau', + 'Iacona', + 'Jergens', + 'Kingham', + 'Leiterman', + 'Leugers', + 'Leyh', + 'Lotti', + 'Majkowski', + 'Mossberg', + 'Nuffer', + 'Oaxaca', + 'Pagenkopf', + 'Paille', + 'Petzoldt', + 'Rogalla', + 'Siddens', + 'Siddoway', + 'Spatafora', + 'Tufo', + 'Weismann', + 'Werntz', + 'Wilz', + 'Ammirati', + 'Benninghoff', + 'Escarsega', + 'Fessel', + 'Hurless', + 'Jastrzebski', + 'Klingerman', + 'Kurilla', + 'Kuzmin', + 'Meserole', + 'Politz', + 'Pollino', + 'Rettke', + 'Sinay', + 'Strebeck', + 'Strycharz', + 'Suhre', + 'Thumm', + 'Trybus', + 'Uhrin', + 'Weisberger', + 'Zeger', + 'Carringer', + 'Sitts', + 'Lungren', + 'Iiams', + 'Sudbury', + 'Surrette', + 'Chellis', + 'Yore', + 'Joice', + 'Foot', + 'Ausley', + 'Scioneaux', + 'Mcaffee', + 'Pinn', + 'Maina', + 'Dorce', + 'Agrusa', + 'Albornoz', + 'Arave', + 'Bacallao', + 'Bendavid', + 'Bochner', + 'Bortle', + 'Carragher', + 'Chalfin', + 'Courtade', + 'Dagle', + 'Debuhr', + 'Fowble', + 'Galinsky', + 'Hardigree', + 'Haulk', + 'Hendron', + 'Herringshaw', + 'Jayaraman', + 'Koestler', + 'Konicek', + 'Kutscher', + 'Lachowicz', + 'Lafauci', + 'Lansky', + 'Lazarski', + 'Lolli', + 'Ludvigsen', + 'Manternach', + 'Martorelli', + 'Mcquillin', + 'Mikaelian', + 'Northcraft', + 'Nyborg', + 'Palone', + 'Peckman', + 'Schwebach', + 'Simbeck', + 'Sittler', + 'Udovich', + 'Viesca', + 'Yazell', + 'Zimmers', + 'Bielen', + 'Cohron', + 'Dearcos', + 'Feezor', + 'Hilgart', + 'Karriker', + 'Klingberg', + 'Leisenring', + 'Napora', + 'Nedved', + 'Okeson', + 'Seratt', + 'Trautner', + 'Trimarco', + 'Turkel', + 'Bronder', + 'Itani', + 'Verona', + 'Blackbird', + 'Laque', + 'Karpel', + 'Louro', + 'Hamson', + 'Ashland', + 'Gruel', + 'Breer', + 'Wesely', + 'Bebo', + 'Conery', + 'Mccarry', + 'Cradic', + 'Aytes', + 'Dikes', + 'Soltau', + 'Debois', + 'Berko', + 'Callins', + 'Anastacio', + 'Balbi', + 'Bata', + 'Bechel', + 'Borsuk', + 'Chihuahua', + 'Cindric', + 'Denapoli', + 'Dotzler', + 'Dusing', + 'Dziekan', + 'Eifler', + 'Franchino', + 'Garritano', + 'Herrarte', + 'Jaskot', + 'Kettell', + 'Kingsford', + 'Marsters', + 'Oshel', + 'Overacker', + 'Pagliarulo', + 'Pannier', + 'Pyun', + 'Rardon', + 'Reville', + 'Rogozinski', + 'Scatena', + 'Schoeppner', + 'Senkbeil', + 'Silkey', + 'Takhar', + 'Whitebread', + 'Wiech', + 'Adelsberger', + 'Aslinger', + 'Bhattacharyya', + 'Brege', + 'Burright', + 'Cafarella', + 'Chlebowski', + 'Decaprio', + 'Dilello', + 'Dresher', + 'Finkbiner', + 'Gerlich', + 'Ignasiak', + 'Kataoka', + 'Kearl', + 'Pingitore', + 'Sellick', + 'Sinning', + 'Stojanovic', + 'Vanasten', + 'Vanluven', + 'Westerfeld', + 'Mahala', + 'Biancardi', + 'Velardo', + 'Payes', + 'Debello', + 'Kyes', + 'Reever', + 'Joung', + 'Coran', + 'Perrow', + 'Linzer', + 'Birchett', + 'Poles', + 'Cajuste', + 'Albergo', + 'Andal', + 'Belaire', + 'Borell', + 'Bruehl', + 'Celani', + 'Cerruti', + 'Crellin', + 'Delcarlo', + 'Dubach', + 'Elicker', + 'Fialkowski', + 'Ganim', + 'Gladieux', + 'Glendening', + 'Glomski', + 'Kalp', + 'Kavan', + 'Kawabata', + 'Kever', + 'Kisch', + 'Maiorino', + 'Masaki', + 'Mcgeough', + 'Miyoshi', + 'Nand', + 'Nitka', + 'Novakovich', + 'Penagos', + 'Pierini', + 'Rassi', + 'Rorke', + 'Rosenboom', + 'Rossmann', + 'Scarfone', + 'Scarsella', + 'Siedschlag', + 'Sobotta', + 'Studnicka', + 'Teeling', + 'Tegtmeyer', + 'Woznick', + 'Beske', + 'Dersch', + 'Deschepper', + 'Duffner', + 'Geroux', + 'Lindvall', + 'Linnemann', + 'Roethler', + 'Scanlin', + 'Schaecher', + 'Schmude', + 'Schwertner', + 'Shimamoto', + 'Stratmann', + 'Stufflebean', + 'Ulatowski', + 'Witkop', + 'Landrus', + 'Sahin', + 'Araque', + 'Massett', + 'Meanor', + 'Sebo', + 'Delic', + 'Bryand', + 'Frederico', + 'Portuondo', + 'Verry', + 'Browe', + 'Winecoff', + 'Gipp', + 'Khamis', + 'Ingrum', + 'Gilliand', + 'Poinsett', + 'Hagley', + 'Valliant', + 'Henly', + 'Bingley', + 'Romulus', + 'Moyd', + 'Abascal', + 'Adelstein', + 'Arabian', + 'Barcelos', + 'Barot', + 'Cabacungan', + 'Darco', + 'Dickmeyer', + 'Gindi', + 'Grone', + 'Haberland', + 'Hachem', + 'Humbarger', + 'Insco', + 'Kravchuk', + 'Mackowski', + 'Madrazo', + 'Malesky', + 'Markowicz', + 'Mcconnon', + 'Meiring', + 'Micalizzi', + 'Moeser', + 'Mortier', + 'Muegge', + 'Ollar', + 'Pamperin', + 'Pusch', + 'Remache', + 'Roginski', + 'Rothbauer', + 'Sellin', + 'Stachurski', + 'Stelmack', + 'Suprenant', + 'Totzke', + 'Uemura', + 'Vandercook', + 'Yott', + 'Zaher', + 'Autio', + 'Barnhard', + 'Brys', + 'Chisenhall', + 'Deiters', + 'Fetsko', + 'Finzel', + 'Gangwer', + 'Grygiel', + 'Heidelberger', + 'Kommer', + 'Latchford', + 'Liszka', + 'Mcconaha', + 'Miazga', + 'Nettesheim', + 'Oelschlager', + 'Rafuse', + 'Reichow', + 'Santosuosso', + 'Sebastiani', + 'Serratore', + 'Spenner', + 'Steffenson', + 'Strehl', + 'Tropeano', + 'Vanstraten', + 'Vegh', + 'Virrueta', + 'Wilhide', + 'Prey', + 'Ullmer', + 'Ferraz', + 'Mazor', + 'Vinje', + 'Mory', + 'Rody', + 'Dowen', + 'Bord', + 'Rajkumar', + 'Qadir', + 'Turbin', + 'Rorex', + 'Wilmott', + 'Grandpre', + 'Bucker', + 'Reasonover', + 'Holoman', + 'Mustapha', + 'Warsame', + 'Laday', + 'Whack', + 'Blahut', + 'Boxell', + 'Britnell', + 'Buehl', + 'Burri', + 'Cesaro', + 'Degrand', + 'Demetro', + 'Fadeley', + 'Fischel', + 'Florer', + 'Givler', + 'Gockley', + 'Iuliano', + 'Koral', + 'Kotlarz', + 'Kraai', + 'Kvamme', + 'Latchaw', + 'Lopeman', + 'Manocchio', + 'Martinezgarcia', + 'Minehart', + 'Narasimhan', + 'Nier', + 'Niziolek', + 'Oliff', + 'Piascik', + 'Pitera', + 'Pronovost', + 'Roseboom', + 'Rosevear', + 'Runkles', + 'Santmyer', + 'Skillin', + 'Stamas', + 'Storbeck', + 'Teicher', + 'Titterington', + 'Tomkinson', + 'Tzeng', + 'Vukovic', + 'Wescoat', + 'Algeo', + 'Aronow', + 'Balbach', + 'Brockbank', + 'Caloca', + 'Caughlin', + 'Devincenzi', + 'Doetsch', + 'Filby', + 'Godar', + 'Keeven', + 'Marchetta', + 'Quiram', + 'Rudeen', + 'Siemen', + 'Suderman', + 'Tacke', + 'Walby', + 'Fram', + 'Maccarthy', + 'Fana', + 'Kimberley', + 'Richens', + 'Doser', + 'Bigford', + 'Brazie', + 'Haroon', + 'Mcginniss', + 'Knipfer', + 'Seltz', + 'Laton', + 'Balow', + 'Cramp', + 'Edger', + 'Alonge', + 'Beagles', + 'Ken', + 'Peary', + 'Lifsey', + 'Acy', + 'Lightbourne', + 'Antwi', + 'Arntzen', + 'Bracknell', + 'Brewbaker', + 'Carville', + 'Cinquemani', + 'Corales', + 'Corgan', + 'Craze', + 'Dechristopher', + 'Eltzroth', + 'Fjelstad', + 'Forinash', + 'Gudenkauf', + 'Hapeman', + 'Hassing', + 'Hurm', + 'Jaurigue', + 'Kneisel', + 'Kulwicki', + 'Lookingbill', + 'Moist', + 'Naderi', + 'Nicoli', + 'Nicoson', + 'Olvey', + 'Remaly', + 'Stare', + 'Steinruck', + 'Switala', + 'Tada', + 'Toves', + 'Traber', + 'Tuohey', + 'Venti', + 'Vinal', + 'Wahle', + 'Yarosh', + 'Balinski', + 'Bauknecht', + 'Bernauer', + 'Bink', + 'Chudzik', + 'Coppess', + 'Corrick', + 'Gruener', + 'Kutter', + 'Malkiewicz', + 'Marking', + 'Mcgrain', + 'Melberg', + 'Ohmann', + 'Pellicane', + 'Regehr', + 'Schmoldt', + 'Schmuhl', + 'Starmer', + 'Stiens', + 'Whilden', + 'Yearick', + 'Desmith', + 'Habiger', + 'Papay', + 'Study', + 'Toot', + 'Franzoni', + 'Neuhoff', + 'Boreman', + 'Sayas', + 'Hinks', + 'Dax', + 'Sasnett', + 'Hannis', + 'Rotan', + 'Haze', + 'Jennifer', + 'Barganier', + 'Milson', + 'Kinnie', + 'Boyde', + 'Dyce', + 'Cuttino', + 'Neals', + 'Mccovery', + 'Abaya', + 'Balz', + 'Bezold', + 'Breighner', + 'Buttacavoli', + 'Cattani', + 'Detzel', + 'Douthat', + 'Dunay', + 'Eicholtz', + 'Eirich', + 'Felkner', + 'Friedenberg', + 'Haskew', + 'Henes', + 'Jamroz', + 'Kelter', + 'Kutzer', + 'Laughner', + 'Livoti', + 'Magistro', + 'Makinson', + 'Manwell', + 'Mckimmy', + 'Mcwethy', + 'Pacholski', + 'Pankau', + 'Poh', + 'Purewal', + 'Remedios', + 'Ringuette', + 'Rocchi', + 'Rojero', + 'Sabina', + 'Schiffner', + 'Sellen', + 'Setaro', + 'Soledad', + 'Stoermer', + 'Tal', + 'Vanwyk', + 'Waack', + 'Xenos', + 'Yoakam', + 'Zweber', + 'Apachito', + 'Belluomini', + 'Cancelliere', + 'Cervini', + 'Davidovich', + 'Deguia', + 'Doxtator', + 'Errera', + 'Eshbaugh', + 'Mandt', + 'Pautler', + 'Raczynski', + 'Roemmich', + 'Rosamilia', + 'Shelhamer', + 'Vandevoorde', + 'Vanengen', + 'Vindiola', + 'Weyman', + 'Dufur', + 'Reaver', + 'Bugh', + 'Starley', + 'Macmullen', + 'Mataya', + 'Bucknell', + 'Taitano', + 'Coole', + 'Huguet', + 'Top', + 'Rockford', + 'Carrithers', + 'Garrell', + 'Toppins', + 'Mayner', + 'Dantes', + 'Tones', + 'Dauphine', + 'Shillingford', + 'Massiah', + 'Angermeier', + 'Arrizon', + 'Azer', + 'Badami', + 'Beeck', + 'Buddenhagen', + 'Cheyney', + 'Danielski', + 'Delgiorno', + 'Enslin', + 'Erber', + 'Fluegge', + 'Fresco', + 'Frishman', + 'Geigle', + 'Gervase', + 'Giangregorio', + 'Glauber', + 'Hedding', + 'Janota', + 'Labore', + 'Ladley', + 'Levee', + 'Lipuma', + 'Lomanto', + 'Magos', + 'Mangen', + 'Miltner', + 'Mitschke', + 'Pingley', + 'Puertas', + 'Schwed', + 'Seminario', + 'Sinsel', + 'Sliney', + 'Spielmann', + 'Standage', + 'Waas', + 'Cooprider', + 'Delguercio', + 'Dockham', + 'Dohse', + 'Doubrava', + 'Emerine', + 'Frazzini', + 'Godown', + 'Heidbreder', + 'Ladow', + 'Lariccia', + 'Molzahn', + 'Opiela', + 'Ordorica', + 'Otterness', + 'Owczarzak', + 'Rafalski', + 'Smigel', + 'Urbas', + 'Andon', + 'Kota', + 'Ruzzo', + 'Pheasant', + 'Proch', + 'Sullinger', + 'Ezra', + 'Portes', + 'Mynhier', + 'Depree', + 'Slight', + 'Selley', + 'Daughety', + 'Shamel', + 'Glasby', + 'Casher', + 'Brisby', + 'Whittley', + 'Brye', + 'Mackins', + 'Allam', + 'Berwanger', + 'Borgmeyer', + 'Brumlow', + 'Cashmore', + 'Clementz', + 'Coopman', + 'Corti', + 'Danzer', + 'Deater', + 'Delprado', + 'Dibuono', + 'Dwan', + 'Edling', + 'Ekins', + 'Feighner', + 'Galica', + 'Gasparro', + 'Geisert', + 'Gilvin', + 'Glotzbach', + 'Goostree', + 'Hollenkamp', + 'Hronek', + 'Kamins', + 'Khun', + 'Klimowicz', + 'Langella', + 'Letz', + 'Lindh', + 'Lycan', + 'Magouirk', + 'Mcbryar', + 'Milonas', + 'Patalano', + 'Petrides', + 'Plocher', + 'Signer', + 'Sinagra', + 'Taibi', + 'Thissen', + 'Thueson', + 'Tietje', + 'Trebilcock', + 'Zelek', + 'Alavez', + 'Beyersdorf', + 'Ferraiolo', + 'Flodin', + 'Fulwiler', + 'Gieselman', + 'Heisinger', + 'Hutmacher', + 'Laraia', + 'Lempke', + 'Marchiano', + 'Mendia', + 'Milberger', + 'Murri', + 'Willhelm', + 'Yannone', + 'Diss', + 'Golab', + 'Meuth', + 'Strebe', + 'Berenguer', + 'Cunard', + 'Girvan', + 'Pacer', + 'Nate', + 'Weare', + 'Dile', + 'Donate', + 'Pamer', + 'Charlet', + 'Roades', + 'Krah', + 'Merton', + 'Debrito', + 'Montel', + 'Guimont', + 'Caire', + 'Olley', + 'Ausborn', + 'Ramdass', + 'Stores', + 'Hush', + 'Watler', + 'Robotham', + 'Stanislaus', + 'Bellevue', + 'Almeter', + 'Bartold', + 'Bathgate', + 'Bollier', + 'Boundy', + 'Bushart', + 'Buzek', + 'Cauthon', + 'Daudelin', + 'Delguidice', + 'Depaolis', + 'Dysert', + 'Forsee', + 'Goglia', + 'Gruenhagen', + 'Guilfoil', + 'Guldin', + 'Gurnee', + 'Henzel', + 'Jurney', + 'Kable', + 'Korenek', + 'Kussman', + 'Liese', + 'Mauss', + 'Mexicano', + 'Morini', + 'Oathout', + 'Paragas', + 'Phommachanh', + 'Pixton', + 'Pucciarelli', + 'Rabine', + 'Ramlow', + 'Ravert', + 'Redhouse', + 'Renault', + 'Rybinski', + 'Sahlin', + 'Scherger', + 'Schoeffler', + 'Smolinsky', + 'Stadnik', + 'Stallsmith', + 'Timoney', + 'Whiteeagle', + 'Woodsmall', + 'Zinter', + 'Bargmann', + 'Basich', + 'Bossio', + 'Coutant', + 'Curcuru', + 'Duitsman', + 'Hunkele', + 'Kingry', + 'Kotek', + 'Mancusi', + 'Orama', + 'Paszek', + 'Schrodt', + 'Schuknecht', + 'Torsiello', + 'Troise', + 'Wernimont', + 'Wipperfurth', + 'Wissner', + 'Zahradnik', + 'Deasis', + 'Pac', + 'Vowles', + 'Montesi', + 'Carie', + 'Name', + 'Broy', + 'Hillson', + 'Exton', + 'Skerritt', + 'Ude', + 'Allston', + 'Cliatt', + 'Chevis', + 'Poitier', + 'Barrasso', + 'Bartnicki', + 'Broski', + 'Cobleigh', + 'Crickenberger', + 'Cruces', + 'Cumba', + 'Diodato', + 'Dipietrantonio', + 'Eyerly', + 'Fedler', + 'Fetting', + 'Francavilla', + 'Frein', + 'Gasparyan', + 'Gingold', + 'Gunnarson', + 'Houy', + 'Huelsmann', + 'Jeppsen', + 'Labreck', + 'Lefton', + 'Maenza', + 'Mauritz', + 'Mingione', + 'Mullany', + 'Mussell', + 'Muston', + 'Paraiso', + 'Peelman', + 'Penuel', + 'Piccola', + 'Punt', + 'Ramella', + 'Rauser', + 'Reas', + 'Reino', + 'Schlack', + 'Sebastiano', + 'Sgambati', + 'Shackett', + 'Szpak', + 'Thalacker', + 'Theissen', + 'Tutko', + 'Astarita', + 'Blazejewski', + 'Dejaynes', + 'Djordjevic', + 'Eckenroth', + 'Estala', + 'Giacomo', + 'Glaub', + 'Golubski', + 'Guerreiro', + 'Housholder', + 'Kashuba', + 'Klute', + 'Lennartz', + 'Messamore', + 'Rovito', + 'Schreurs', + 'Starcevich', + 'Starkel', + 'Szczerba', + 'Thomassen', + 'Varkey', + 'Yorio', + 'Guba', + 'Unzicker', + 'Howry', + 'Bido', + 'Farella', + 'Frane', + 'Werry', + 'Cornia', + 'Postal', + 'Humphres', + 'Ran', + 'Macnair', + 'Duston', + 'Aveni', + 'Mcconn', + 'Sistare', + 'Wadell', + 'Naraine', + 'Mubarak', + 'Lonzo', + 'Shyne', + 'Tilmon', + 'Symonette', + 'Shinholster', + 'Oree', + 'Ogarro', + 'Quashie', + 'Almario', + 'Antonsen', + 'Armetta', + 'Avetisyan', + 'Bania', + 'Barricklow', + 'Bloemker', + 'Cannavo', + 'Dolliver', + 'Espenshade', + 'Falor', + 'Fukuhara', + 'Gemme', + 'Goldfinger', + 'Gonya', + 'Hamamoto', + 'Hindi', + 'Hiraldo', + 'Holquin', + 'Janco', + 'Janow', + 'Lemming', + 'Macchio', + 'Mago', + 'Mavity', + 'Mcnamer', + 'Mushrush', + 'Niskanen', + 'Ohms', + 'Pawluk', + 'Popple', + 'Poser', + 'Schiavi', + 'Stram', + 'Streight', + 'Stueck', + 'Vansandt', + 'Vivona', + 'Vongphakdy', + 'Zalar', + 'Zipper', + 'Altic', + 'Billmeyer', + 'Boghosian', + 'Bohlke', + 'Cisewski', + 'Gabrielsen', + 'Gianotti', + 'Heffler', + 'Holian', + 'Kannenberg', + 'Lenius', + 'Manuelito', + 'Mugavero', + 'Reinier', + 'Rekowski', + 'Sadlier', + 'Scialdone', + 'Stromquist', + 'Vittetoe', + 'Vorwald', + 'Widrig', + 'Audi', + 'Peral', + 'Devery', + 'Gato', + 'Sower', + 'Vanes', + 'Bonnes', + 'Hense', + 'Counsell', + 'Frankie', + 'Colford', + 'Wanser', + 'Mickels', + 'Briddell', + 'Washinton', + 'Antilla', + 'Baxendale', + 'Beining', + 'Belveal', + 'Boedecker', + 'Bottenfield', + 'Bufano', + 'Castellana', + 'Chaikin', + 'Cherne', + 'Costilow', + 'Dzialo', + 'Goeken', + 'Gombert', + 'Hammerman', + 'Hansman', + 'Hartling', + 'Kalani', + 'Klich', + 'Kolodziejski', + 'Kramar', + 'Lapinsky', + 'Latterell', + 'Lipsitz', + 'Loma', + 'Lukenbill', + 'Marxen', + 'Metallo', + 'Molner', + 'Niquette', + 'Ostrand', + 'Pelster', + 'Previti', + 'Rennaker', + 'Roering', + 'Roode', + 'Saltos', + 'Sangiovanni', + 'Schiraldi', + 'Schlafer', + 'Schwering', + 'Seedorf', + 'Sklenar', + 'Spinello', + 'Steinhorst', + 'Urueta', + 'Vonstein', + 'Bonczek', + 'Casalino', + 'Chiaro', + 'Doffing', + 'Downham', + 'Gillotti', + 'Hearl', + 'Karges', + 'Kunesh', + 'Langeland', + 'Maertz', + 'Mattinson', + 'Mignano', + 'Pasquinelli', + 'Petracca', + 'Pherigo', + 'Pikus', + 'Reichmuth', + 'Schwegman', + 'Schwerdt', + 'Seelman', + 'Winquist', + 'Wyka', + 'Yahr', + 'Bunkers', + 'Delnegro', + 'Norder', + 'Manas', + 'Polites', + 'Grape', + 'Jares', + 'Surges', + 'Asa', + 'Copeman', + 'Askar', + 'Goman', + 'Whitmyer', + 'Cohran', + 'Imbert', + 'Beaner', + 'Hugger', + 'Petion', + 'Lauture', + 'Andringa', + 'Athanas', + 'Butrick', + 'Caronna', + 'Dedominicis', + 'Eligio', + 'Fasick', + 'Hilinski', + 'Hinely', + 'Idler', + 'Janosko', + 'Kempner', + 'Klosinski', + 'Lapeyrouse', + 'Lindroth', + 'Marcon', + 'Meding', + 'Peppin', + 'Quizon', + 'Rectenwald', + 'Roessner', + 'Roets', + 'Schonberger', + 'Szostek', + 'Wassink', + 'Whan', + 'Yeakle', + 'Alguire', + 'Bielenberg', + 'Bisaillon', + 'Bonenberger', + 'Centola', + 'Colaizzi', + 'Deroos', + 'Eberlin', + 'Ehrig', + 'Ferenc', + 'Freiermuth', + 'Fruchter', + 'Garnto', + 'Huxford', + 'Knous', + 'Luttman', + 'Mulry', + 'Schirm', + 'Stankovic', + 'Authier', + 'Derise', + 'Doo', + 'Kessen', + 'Maline', + 'Porada', + 'Vasconez', + 'Haseman', + 'Tonner', + 'Woodroof', + 'Bedrossian', + 'Cranmore', + 'Dodaro', + 'Hommes', + 'Harmony', + 'Peno', + 'Mccommon', + 'Colver', + 'Olinde', + 'Oba', + 'Colone', + 'Warbington', + 'Monie', + 'Whitmill', + 'Moxey', + 'Canion', + 'Mcclenney', + 'Hallmon', + 'Austill', + 'Berni', + 'Boehning', + 'Bueso', + 'Cefalo', + 'Conneely', + 'Demicco', + 'Dieppa', + 'Duris', + 'Durnil', + 'Erxleben', + 'Hashimi', + 'Hedquist', + 'Koc', + 'Lamattina', + 'Lassman', + 'Ligman', + 'Lukins', + 'Mackler', + 'Manolis', + 'Mou', + 'Oblak', + 'Omahoney', + 'Paolo', + 'Pollok', + 'Priess', + 'Reeh', + 'Rempfer', + 'Rickerd', + 'Schoettle', + 'Serritella', + 'Steedman', + 'Suss', + 'Tanimoto', + 'Thaden', + 'Thelin', + 'Vanwingerden', + 'Wacha', + 'Weldin', + 'Youkhana', + 'Bazzano', + 'Behring', + 'Caliri', + 'Cocchi', + 'Croissant', + 'Dibbern', + 'Figiel', + 'Flygare', + 'Grieshop', + 'Iten', + 'Kaupp', + 'Linnane', + 'Plybon', + 'Rappleye', + 'Romanik', + 'Saefong', + 'Schetter', + 'Schryer', + 'Siwik', + 'Snitker', + 'Tomasic', + 'Wavra', + 'Auen', + 'Thone', + 'Marso', + 'Shadid', + 'Cake', + 'Louvier', + 'Macia', + 'Areola', + 'Kardell', + 'Strome', + 'Coogle', + 'Delis', + 'Pistorius', + 'Raybourn', + 'Sula', + 'Math', + 'Sanda', + 'Renaldo', + 'Pat', + 'Florance', + 'Brank', + 'Alice', + 'Rosebrough', + 'Quiett', + 'Henigan', + 'Mcclees', + 'Dase', + 'Bagot', + 'Kings', + 'Lanehart', + 'Barbary', + 'Stitts', + 'Aurora', + 'Baldoni', + 'Barkalow', + 'Bohnet', + 'Bosshart', + 'Decapua', + 'Denbo', + 'Deneault', + 'Dinse', + 'Dul', + 'Estle', + 'Filipski', + 'Fishell', + 'Fluckiger', + 'Glassberg', + 'Janick', + 'Juda', + 'Kibbee', + 'Kreisler', + 'Lawther', + 'Levangie', + 'Lichtenwalner', + 'Lucking', + 'Meiner', + 'Mileham', + 'Milz', + 'Reposa', + 'Rinehimer', + 'Rupley', + 'Sandez', + 'Schinke', + 'Sharpnack', + 'Sineath', + 'Tax', + 'Thumma', + 'Urda', + 'Widdison', + 'Bergdoll', + 'Bruhl', + 'Chesmore', + 'Delfavero', + 'Ferderer', + 'Haueter', + 'Hirshberg', + 'Hollobaugh', + 'Lalama', + 'Mckeag', + 'Mehlhoff', + 'Mirchandani', + 'Orwick', + 'Puskarich', + 'Schlotzhauer', + 'Stoiber', + 'Swetz', + 'Basara', + 'Magaw', + 'Amble', + 'Hawe', + 'Toren', + 'Parilla', + 'Gowell', + 'Selkirk', + 'Edris', + 'Ariel', + 'Kihara', + 'Dunkerson', + 'Halk', + 'Mooty', + 'Tippen', + 'Fullenwider', + 'Herford', + 'Salton', + 'Feider', + 'Buckhannon', + 'Mckneely', + 'Milon', + 'Whiters', + 'Barasch', + 'Baria', + 'Basques', + 'Beavin', + 'Borre', + 'Branz', + 'Broers', + 'Conca', + 'Cortopassi', + 'Courchesne', + 'Crisanti', + 'Cumpian', + 'Dagan', + 'Dekay', + 'Demartin', + 'Dewaard', + 'Dowland', + 'Duffell', + 'Ebersol', + 'Faiola', + 'Frontz', + 'Fryling', + 'Garczynski', + 'Hanway', + 'Huettner', + 'Janovsky', + 'Johndrow', + 'Kahana', + 'Kaniewski', + 'Kulish', + 'Lich', + 'Lincks', + 'Loppnow', + 'Macnab', + 'Mcconaughy', + 'Melroy', + 'Noviello', + 'Orn', + 'Pacas', + 'Peppel', + 'Polidori', + 'Radi', + 'Riesgo', + 'Romanoski', + 'Sagrero', + 'Schirripa', + 'Spack', + 'Sternhagen', + 'Tamburri', + 'Traczyk', + 'Uballe', + 'Vandruff', + 'Voght', + 'Weant', + 'Weinel', + 'Angerman', + 'Boultinghouse', + 'Dolinar', + 'Dripps', + 'Dubow', + 'Ehrhard', + 'Janvrin', + 'Lazear', + 'Liddiard', + 'Madayag', + 'Mirkin', + 'Monticello', + 'Mulka', + 'Oliger', + 'Pierceall', + 'Pittner', + 'Polkowski', + 'Prindiville', + 'Rasnic', + 'Tellefsen', + 'Uffelman', + 'Vandenbergh', + 'Weisenbach', + 'Wiedmeyer', + 'Wintle', + 'Wisz', + 'Yorba', + 'Holtmeyer', + 'Tabet', + 'Laham', + 'Barsoum', + 'Henner', + 'Idle', + 'Shaft', + 'Rennels', + 'Swarm', + 'Forgie', + 'Khaled', + 'Avon', + 'Hewey', + 'Grober', + 'Pipe', + 'Macfadden', + 'Keath', + 'Fergason', + 'Polland', + 'Brownley', + 'Haslip', + 'Crocket', + 'Tines', + 'Juniel', + 'Opara', + 'Bethley', + 'Ambuehl', + 'Bagheri', + 'Baquera', + 'Bertoli', + 'Bisek', + 'Borroto', + 'Botten', + 'Bovenzi', + 'Bruntz', + 'Buehring', + 'Canche', + 'Cicco', + 'Dambach', + 'Delellis', + 'Deniston', + 'Dirico', + 'Feagle', + 'Frayne', + 'Haagenson', + 'Janicke', + 'Kashyap', + 'Kastel', + 'Kruck', + 'Langi', + 'Lapka', + 'Marschner', + 'Megia', + 'Nesta', + 'Nevala', + 'Oblinger', + 'Picchi', + 'Rodeffer', + 'Salkin', + 'Scavuzzo', + 'Sladky', + 'Soyars', + 'Suchil', + 'Thielbar', + 'Timoteo', + 'Vanhise', + 'Varden', + 'Waldoch', + 'Watling', + 'Werk', + 'Becvar', + 'Betteridge', + 'Bolliger', + 'Bonifield', + 'Buchberger', + 'Caprara', + 'Castrogiovanni', + 'Fallaw', + 'Geeting', + 'Hiegel', + 'Hulgan', + 'Kokesh', + 'Lanting', + 'Mcphetridge', + 'Nuxoll', + 'Soun', + 'Strothman', + 'Triska', + 'Vensel', + 'Wesolek', + 'Wixted', + 'Wolgemuth', + 'Yedinak', + 'Anthis', + 'Manfred', + 'Agans', + 'Lafoe', + 'Mcginnes', + 'Folwell', + 'Galvao', + 'Carmo', + 'Valin', + 'Woon', + 'Degregory', + 'Evangelist', + 'Coast', + 'Strater', + 'Decou', + 'Pears', + 'Nellums', + 'Kynard', + 'Boursiquot', + 'Ruffins', + 'Akhavan', + 'Baloga', + 'Barany', + 'Buche', + 'Davoli', + 'Fennewald', + 'Figler', + 'Frede', + 'Gannett', + 'Ghannam', + 'Handlon', + 'Herridge', + 'Jakel', + 'Kamphuis', + 'Kattan', + 'Kemplin', + 'Klecka', + 'Korver', + 'Kozakiewicz', + 'Linenberger', + 'Lofaso', + 'Lorman', + 'Lueder', + 'Mcconahay', + 'Mcternan', + 'Mench', + 'Norenberg', + 'Oro', + 'Ostenson', + 'Pant', + 'Peardon', + 'Pertuit', + 'Ritzert', + 'Salvetti', + 'Sandner', + 'Sheek', + 'Sniegowski', + 'Sorbo', + 'Sperbeck', + 'Sump', + 'Supinski', + 'Sweetin', + 'Toenjes', + 'Velotta', + 'Venier', + 'Veracruz', + 'Wender', + 'Yamagata', + 'Arostegui', + 'Balestra', + 'Blumstein', + 'Carras', + 'Grauberger', + 'Howdeshell', + 'Murayama', + 'Nippert', + 'Notch', + 'Reisert', + 'Sebren', + 'Tetzloff', + 'Venneman', + 'Douds', + 'Lineman', + 'Powles', + 'Huet', + 'Matto', + 'Roes', + 'Dillin', + 'Lagan', + 'Bakes', + 'Yann', + 'Canterberry', + 'Milum', + 'Hinderman', + 'Linzey', + 'Ballen', + 'Ventress', + 'Prysock', + 'Bangle', + 'Blinder', + 'Bugaj', + 'Carlisi', + 'Dimario', + 'Dzikowski', + 'Gaetz', + 'Galves', + 'Ghazal', + 'Golebiewski', + 'Hadsall', + 'Hogberg', + 'Krammer', + 'Kreisher', + 'Lamia', + 'Luhmann', + 'Lupa', + 'Michelotti', + 'Nesci', + 'Paape', + 'Posthumus', + 'Reth', + 'Sassman', + 'Schlechter', + 'Schlie', + 'Schumacker', + 'Seliger', + 'Shanholtzer', + 'Strojny', + 'Taglieri', + 'Tibbles', + 'Tregoning', + 'Valine', + 'Zeiset', + 'Antu', + 'Bierwirth', + 'Birenbaum', + 'Boeder', + 'Dobkins', + 'Fenoglio', + 'Jentsch', + 'Marcinkiewicz', + 'Mruk', + 'Muhlbauer', + 'Namba', + 'Oettinger', + 'Rigor', + 'Rothweiler', + 'Schmader', + 'Schork', + 'Vandevoort', + 'Brenny', + 'Neels', + 'Fodge', + 'Que', + 'Dalpe', + 'Guerard', + 'Lammey', + 'Alfredo', + 'Corrin', + 'Quarry', + 'Reise', + 'Derrow', + 'Worrel', + 'Tennent', + 'Cassis', + 'Winson', + 'Cornet', + 'Garlin', + 'Saucer', + 'Ursery', + 'Saffo', + 'Battee', + 'Ackerley', + 'Ackland', + 'Allmendinger', + 'Altamura', + 'Anastas', + 'Artola', + 'Baldassari', + 'Bayron', + 'Bouwkamp', + 'Buonopane', + 'Chronis', + 'Coffaro', + 'Dech', + 'Delfierro', + 'Depaulo', + 'Digges', + 'Dowda', + 'Drab', + 'Feijoo', + 'Formato', + 'Friedli', + 'Hanahan', + 'Hegna', + 'Igarashi', + 'Kamai', + 'Kory', + 'Kuzel', + 'Lewkowicz', + 'Lumbra', + 'Mccreadie', + 'Meisch', + 'Montoro', + 'Pamintuan', + 'Petrow', + 'Pulcini', + 'Shewell', + 'Spitznagel', + 'Swedlund', + 'Terhorst', + 'Wilberg', + 'Willwerth', + 'Affinito', + 'Baune', + 'Beichner', + 'Boutell', + 'Challender', + 'Ellestad', + 'Gomm', + 'Hochstatter', + 'Jasko', + 'Kielar', + 'Kimmerle', + 'Kirshenbaum', + 'Kotila', + 'Lecker', + 'Manross', + 'Mcnevin', + 'Neuburger', + 'Verderosa', + 'Wiltsey', + 'Caminero', + 'Gianfrancesco', + 'Shiverdecker', + 'Amman', + 'Flavell', + 'Oconor', + 'Shure', + 'Hanagan', + 'Bokor', + 'Mashaw', + 'Ground', + 'Brittenham', + 'Pinera', + 'Smaltz', + 'Hold', + 'Gallamore', + 'Delon', + 'Hearing', + 'Rynes', + 'Cocklin', + 'Cassie', + 'Calligan', + 'Josue', + 'Congo', + 'Tennell', + 'Blyther', + 'Azarian', + 'Bauernfeind', + 'Beeghly', + 'Berget', + 'Brayfield', + 'Cerasoli', + 'Dedecker', + 'Gloeckner', + 'Herriges', + 'Hoganson', + 'Ivancic', + 'Jakeway', + 'Kayne', + 'Kitko', + 'Kohlbeck', + 'Krabbenhoft', + 'Kumari', + 'Lauri', + 'Leiber', + 'Minke', + 'Montecino', + 'Moutray', + 'Munshi', + 'Ohlin', + 'Portocarrero', + 'Rados', + 'Roedl', + 'Rossing', + 'Schake', + 'Simonin', + 'Staffa', + 'Stroschein', + 'Titman', + 'Treder', + 'Vonada', + 'Xenakis', + 'Aulds', + 'Benedick', + 'Boulais', + 'Butikofer', + 'Butorac', + 'Contento', + 'Goetting', + 'Goldammer', + 'Hopke', + 'Koppes', + 'Phetteplace', + 'Roehrs', + 'Schul', + 'Slabach', + 'Steinmiller', + 'Sucharski', + 'Vorwerk', + 'Wahlert', + 'Wheatcraft', + 'Abellera', + 'Jutte', + 'Baumgarner', + 'Tijerino', + 'Awadallah', + 'Horen', + 'Lina', + 'Stanbrough', + 'College', + 'Jarry', + 'Keas', + 'Mordan', + 'Ramnauth', + 'Rena', + 'Wa', + 'Petters', + 'Ramnath', + 'Hellams', + 'Mamon', + 'Cheese', + 'Meggett', + 'Anttila', + 'Beilman', + 'Binsfeld', + 'Brining', + 'Brubeck', + 'Carcione', + 'Chandran', + 'Chaudhuri', + 'Cogliano', + 'Dimaano', + 'Dols', + 'Doughten', + 'Ehrenfeld', + 'Elena', + 'Fausnaugh', + 'Fetz', + 'Fogelson', + 'Fraleigh', + 'Gaza', + 'Giesey', + 'Gockel', + 'Gougeon', + 'Granito', + 'Grassia', + 'Hauserman', + 'Idrovo', + 'Iwan', + 'Janning', + 'Kaffenberger', + 'Kichline', + 'Kimoto', + 'Kolodny', + 'Kortum', + 'Lafevers', + 'Lodi', + 'Longton', + 'Ludke', + 'Manganelli', + 'Mccuan', + 'Merryfield', + 'Mezquita', + 'Morandi', + 'Neibauer', + 'Oran', + 'Ozaeta', + 'Pacha', + 'Palese', + 'Perala', + 'Pisarcik', + 'Pobanz', + 'Pommer', + 'Pontrelli', + 'Prabhakar', + 'Rehmann', + 'Scheunemann', + 'Severini', + 'Skalla', + 'Srinivas', + 'Stadtmiller', + 'Trentman', + 'Trinka', + 'Tutterow', + 'Vari', + 'Wence', + 'Zeff', + 'Anagnos', + 'Arvayo', + 'Bihl', + 'Darbyshire', + 'Deeg', + 'Domagalski', + 'Estenson', + 'Finkenbinder', + 'Gaboriault', + 'Kastens', + 'Lacek', + 'Merkin', + 'Mersman', + 'Nicolaus', + 'Offerdahl', + 'Pallett', + 'Platten', + 'Quesnell', + 'Skene', + 'Sondag', + 'Wolfrom', + 'Mineer', + 'Sor', + 'Canard', + 'Mcmeen', + 'Tur', + 'Giner', + 'Mackrell', + 'Alic', + 'Sampath', + 'Baby', + 'Beales', + 'Kadri', + 'Minot', + 'Bienvenue', + 'Millirons', + 'Woodstock', + 'Landing', + 'Limehouse', + 'Andonian', + 'Armentor', + 'Asai', + 'Cutaia', + 'Darji', + 'Delsanto', + 'Deutch', + 'Droge', + 'Emme', + 'Flenner', + 'Gaida', + 'Gladd', + 'Guettler', + 'Guggisberg', + 'Guier', + 'Habenicht', + 'Heininger', + 'Helfman', + 'Hiscox', + 'Holtorf', + 'Hovious', + 'Juul', + 'Lacock', + 'Lepisto', + 'Malanowski', + 'Marineau', + 'Matza', + 'Meffert', + 'Nuon', + 'Oneto', + 'Padmanabhan', + 'Pantuso', + 'Pesci', + 'Rosenbluth', + 'Rubano', + 'Sedlar', + 'Sferrazza', + 'Sifuentez', + 'Simione', + 'Torossian', + 'Vaux', + 'Weilbacher', + 'Wiatrek', + 'Brzoska', + 'Caltabiano', + 'Csaszar', + 'Eyerman', + 'Geissinger', + 'Gioffre', + 'Grilliot', + 'Grotz', + 'Harrower', + 'Jaroszewski', + 'Jokerst', + 'Kamali', + 'Kampmann', + 'Klemz', + 'Koike', + 'Lista', + 'Mcconkie', + 'Mencia', + 'Missler', + 'Olshefski', + 'Omdahl', + 'Penunuri', + 'Scheckel', + 'Schreiter', + 'Swackhammer', + 'Taflinger', + 'Tegethoff', + 'Ummel', + 'Wetsel', + 'Wissmann', + 'Porr', + 'Ramser', + 'Russett', + 'Clucas', + 'Matlin', + 'Noblet', + 'Boyan', + 'Koman', + 'Lope', + 'Deman', + 'Latendresse', + 'Bound', + 'Rijos', + 'Bouillon', + 'Crunkleton', + 'Jayson', + 'Anne', + 'Staude', + 'Sturn', + 'Burdell', + 'Arther', + 'Yett', + 'Woolcock', + 'Clemon', + 'Saintjean', + 'Sainvil', + 'Coverson', + 'Barroga', + 'Benedicto', + 'Borin', + 'Budrow', + 'Cuddihy', + 'Forness', + 'Gohman', + 'Hepker', + 'Hilscher', + 'Holien', + 'Holstad', + 'Hopfer', + 'Hulburt', + 'Kalter', + 'Kuehnle', + 'Lachica', + 'Macioce', + 'Massimo', + 'Matsubara', + 'Meaker', + 'Mehmedovic', + 'Minckler', + 'Miralles', + 'Mostek', + 'Oshita', + 'Parthasarathy', + 'Roszak', + 'Rottenberg', + 'Rydman', + 'Shankman', + 'Sprong', + 'Stenerson', + 'Strubel', + 'Tavano', + 'Thornberg', + 'Trumpower', + 'Whittinghill', + 'Altenhofen', + 'Bartolucci', + 'Debski', + 'Dekoning', + 'Dottavio', + 'Emminger', + 'Hodkinson', + 'Hurtubise', + 'Lauridsen', + 'Leinberger', + 'Luskin', + 'Pask', + 'Rehfeld', + 'Spagna', + 'Szumski', + 'Szymborski', + 'Teem', + 'Tritschler', + 'Tschantz', + 'Tsutsui', + 'Vanecek', + 'Haddaway', + 'Colombe', + 'Mayol', + 'Shivley', + 'Maturin', + 'Babe', + 'Bovey', + 'Bathe', + 'Belliard', + 'Loner', + 'Arrow', + 'Billa', + 'Mcneish', + 'Kinton', + 'Scarber', + 'Donson', + 'Atherley', + 'Abdulaziz', + 'Age', + 'Carreker', + 'Tory', + 'Leduff', + 'Wattley', + 'Altergott', + 'Belitz', + 'Bidinger', + 'Blauch', + 'Cariker', + 'Condren', + 'Curiale', + 'Dronet', + 'Elstad', + 'Esquerra', + 'Fread', + 'Gilb', + 'Goga', + 'Gonyo', + 'Grudzien', + 'Hino', + 'Ishler', + 'Jacober', + 'Kilty', + 'Kuhrt', + 'Lairmore', + 'Lamba', + 'Lorek', + 'Lucich', + 'Marcou', + 'Mcgath', + 'Menze', + 'Mindel', + 'Nabb', + 'Ottosen', + 'Pann', + 'Ratkowski', + 'Saurer', + 'Sedore', + 'Shonka', + 'Soberano', + 'Sossamon', + 'Stdennis', + 'Stillinger', + 'Tager', + 'Tersigni', + 'Tissue', + 'Trampe', + 'Twite', + 'Whitling', + 'Wiebusch', + 'Abundez', + 'Bisping', + 'Candella', + 'Dahill', + 'Groebner', + 'Gulbrandsen', + 'Hasenauer', + 'Heesch', + 'Hipwell', + 'Kamrowski', + 'Keyworth', + 'Kleinschmit', + 'Legorreta', + 'Minium', + 'Mixter', + 'Neiswonger', + 'Purk', + 'Rinkenberger', + 'Rosenkrans', + 'Rozenberg', + 'Simenson', + 'Soltes', + 'Storino', + 'Viereck', + 'Schaafsma', + 'Craigie', + 'Amorin', + 'Latner', + 'Bowmer', + 'Nasby', + 'Bada', + 'Rami', + 'Mcglashan', + 'Reede', + 'Police', + 'Cobey', + 'Dahir', + 'Dirden', + 'Destine', + 'Akkerman', + 'Azzopardi', + 'Blankenhorn', + 'Bolio', + 'Brandhorst', + 'Buchter', + 'Canul', + 'Cocozza', + 'Collantes', + 'Cronic', + 'Cullifer', + 'Delpizzo', + 'Demoranville', + 'Dolder', + 'Dvorsky', + 'Eggett', + 'Elgersma', + 'Episcopo', + 'Esses', + 'Fehlman', + 'Gansen', + 'Garciamartinez', + 'Goldwater', + 'Gushue', + 'Hittner', + 'Igel', + 'Jupin', + 'Kostoff', + 'Kruschke', + 'Kuechler', + 'Labs', + 'Lacerte', + 'Lagle', + 'Leischner', + 'Linders', + 'Marulanda', + 'Meindl', + 'Melman', + 'Menden', + 'Orbach', + 'Patak', + 'Patras', + 'Petroni', + 'Rabenold', + 'Rapisarda', + 'Rodenburg', + 'Roelle', + 'Schar', + 'Scherbarth', + 'Simar', + 'Thoen', + 'Trana', + 'Tuch', + 'Turko', + 'Wamser', + 'Weinfeld', + 'Wirz', + 'Zatorski', + 'Zbinden', + 'Aksamit', + 'Asebedo', + 'Biello', + 'Bouchey', + 'Callejo', + 'Espanol', + 'Flathers', + 'Kunka', + 'Liaw', + 'Mckowen', + 'Mitrano', + 'Needler', + 'Och', + 'Paolella', + 'Patricelli', + 'Recine', + 'Rengel', + 'Spinler', + 'Wagenaar', + 'Winnicki', + 'Eichert', + 'Dabb', + 'Imrie', + 'Antoni', + 'Lardner', + 'Maund', + 'Schou', + 'Brittin', + 'Anthon', + 'Was', + 'Nevis', + 'Delamar', + 'Mcnorton', + 'Tankard', + 'Boardley', + 'Garcon', + 'Wimes', + 'Antell', + 'Belmarez', + 'Boff', + 'Boughan', + 'Cando', + 'Carrender', + 'Carrieri', + 'Charnley', + 'Cittadino', + 'Cwynar', + 'Deupree', + 'Doepke', + 'Fasone', + 'Fauteux', + 'Foody', + 'Fornal', + 'Fust', + 'Gasner', + 'Gloe', + 'Gorter', + 'Grumbine', + 'Hancher', + 'Hapke', + 'Heckendorn', + 'Heinlen', + 'Hilgeman', + 'Kahre', + 'Kakos', + 'Kops', + 'Lahn', + 'Leiferman', + 'Lothamer', + 'Mallis', + 'Napierkowski', + 'Orbin', + 'Panno', + 'Piacente', + 'Posas', + 'Ragasa', + 'Sonora', + 'Stupka', + 'Tio', + 'Valido', + 'Weyrick', + 'Argall', + 'Arrighi', + 'Bohlken', + 'Desrocher', + 'Distad', + 'Erkkila', + 'Gherardi', + 'Goughnour', + 'Koltz', + 'Koperski', + 'Lafalce', + 'Lucken', + 'Meleski', + 'Mortellaro', + 'Nagorski', + 'Pedrotti', + 'Pruyn', + 'Revard', + 'Saffran', + 'Schnoebelen', + 'Sermersheim', + 'Skroch', + 'Vandervliet', + 'Alwood', + 'Bosso', + 'Hor', + 'Licerio', + 'Septer', + 'Labo', + 'Lessa', + 'Ooley', + 'Gorgas', + 'Medal', + 'Coull', + 'Creely', + 'Bolland', + 'Ishaq', + 'Legore', + 'Alicia', + 'Fillingame', + 'Levers', + 'Flight', + 'Woodrick', + 'Berrie', + 'Buckels', + 'Pigue', + 'Crosse', + 'Speakes', + 'Wynes', + 'Mussa', + 'Highbaugh', + 'Venning', + 'Dupas', + 'Mccastle', + 'Andreoni', + 'Bakula', + 'Besemer', + 'Blier', + 'Braaksma', + 'Brocco', + 'Cajas', + 'Campano', + 'Crapser', + 'Dentinger', + 'Deziel', + 'Dragos', + 'Ekblad', + 'Gargis', + 'Gilberto', + 'Guadron', + 'Hollern', + 'Leibensperger', + 'Lindaman', + 'Lumadue', + 'Mault', + 'Mieses', + 'Nanninga', + 'Nudd', + 'Ouch', + 'Ramin', + 'Reggio', + 'Ruttan', + 'Saccomanno', + 'Scheaffer', + 'Sohm', + 'Spaniol', + 'Stenner', + 'Strieter', + 'Takashima', + 'Vaid', + 'Venzke', + 'Wallwork', + 'Zaffuto', + 'Zaucha', + 'Zemel', + 'Zinni', + 'Alltop', + 'Ciolek', + 'Empie', + 'Flitton', + 'Gullikson', + 'Hassebrock', + 'Kanitz', + 'Kirschenmann', + 'Krivanek', + 'Loseke', + 'Mckercher', + 'Melching', + 'Nham', + 'Ormerod', + 'Randlett', + 'Reifel', + 'Sawada', + 'Sofranko', + 'Stoia', + 'Umeda', + 'Eagon', + 'Hucker', + 'Kenniston', + 'Salus', + 'Ayyad', + 'Camey', + 'Dacy', + 'Joa', + 'Peerson', + 'Rossy', + 'Aure', + 'Keetch', + 'Sprigg', + 'Southgate', + 'Parden', + 'Andris', + 'Bossman', + 'Blondell', + 'Carmickle', + 'Pelly', + 'Mceachron', + 'Marry', + 'Burel', + 'Shark', + 'Flash', + 'Rickenbacker', + 'Foots', + 'Sillah', + 'Almgren', + 'Awtrey', + 'Berganza', + 'Boehne', + 'Bralley', + 'Brosnahan', + 'Caddick', + 'Chandonnet', + 'Cullimore', + 'Darroch', + 'Eimers', + 'Flam', + 'Howerter', + 'Jerzak', + 'Kabler', + 'Kirkes', + 'Kopper', + 'Krakow', + 'Linskey', + 'Lizzi', + 'Luria', + 'Marcrum', + 'Mathy', + 'Matulich', + 'Miskin', + 'Moghadam', + 'Nagarajan', + 'Packham', + 'Papania', + 'Paup', + 'Rippeon', + 'Rolli', + 'Rubey', + 'Scherzinger', + 'Scrima', + 'Sharar', + 'Shoberg', + 'Stupar', + 'Tendler', + 'Tobiason', + 'Vanvooren', + 'Zisa', + 'Bindel', + 'Flasch', + 'Graetz', + 'Heintzman', + 'Kosanke', + 'Longden', + 'Mahfouz', + 'Mormile', + 'Nannini', + 'Olaes', + 'Panik', + 'Putzier', + 'Radilla', + 'Schaedler', + 'Schoepf', + 'Sianez', + 'Taucher', + 'Wiebelhaus', + 'Banka', + 'Console', + 'Derego', + 'Vile', + 'Colgin', + 'Drage', + 'Josten', + 'Luckadoo', + 'Ryen', + 'Bako', + 'Ow', + 'Patient', + 'Elmes', + 'Mossa', + 'Colee', + 'Comber', + 'Tippy', + 'Perrell', + 'Axon', + 'Rickson', + 'Postlewaite', + 'Lafargue', + 'Guffin', + 'Cains', + 'Dewindt', + 'Cathy', + 'Tallie', + 'Ausby', + 'Alires', + 'Baz', + 'Bergeman', + 'Bodensteiner', + 'Borghi', + 'Dematos', + 'Denzler', + 'Dorko', + 'Duffett', + 'Dykas', + 'Emerton', + 'Fenger', + 'Fosberg', + 'Gwinner', + 'Kniess', + 'Lerew', + 'Lohner', + 'Lun', + 'Maita', + 'Mandler', + 'Marcoe', + 'Nikolov', + 'Paschen', + 'Paver', + 'Prosperi', + 'Rackliff', + 'Roever', + 'Ruberg', + 'Ruest', + 'Schnick', + 'Schuur', + 'Sowash', + 'Zanca', + 'Brecheen', + 'Brusky', + 'Chauca', + 'Debernardi', + 'Froio', + 'Gadway', + 'Karoly', + 'Kintzel', + 'Kneisley', + 'Kruser', + 'Lindfors', + 'Lwin', + 'Oursler', + 'Peruski', + 'Petteys', + 'Rottmann', + 'Schroeck', + 'Stenglein', + 'Vigen', + 'Wempe', + 'Zehren', + 'Wollen', + 'Dismore', + 'Santalucia', + 'Laza', + 'Pesnell', + 'Litle', + 'Markson', + 'Piercefield', + 'Jerrett', + 'Virginia', + 'Demonbreun', + 'Tugman', + 'Ramoutar', + 'Bazin', + 'Ola', + 'Alamin', + 'Adebayo', + 'Berkland', + 'Bernt', + 'Briguglio', + 'Bulnes', + 'Burack', + 'Cantoran', + 'Giardini', + 'Goetzke', + 'Graziosi', + 'Guberman', + 'Kamaka', + 'Karvonen', + 'Kitz', + 'Kopera', + 'Krempa', + 'Linkenhoker', + 'Mascioli', + 'Matlick', + 'Mcmahill', + 'Medaglia', + 'Mirarchi', + 'Mondry', + 'Muhlestein', + 'Murty', + 'Orender', + 'Pesantez', + 'Postiglione', + 'Reisen', + 'Riff', + 'Scarantino', + 'Seelinger', + 'Seher', + 'Sharum', + 'Sorice', + 'Staebler', + 'Tanney', + 'Tech', + 'Tramontano', + 'Trude', + 'Vasudevan', + 'Wareing', + 'Westerhold', + 'Wohlfarth', + 'Achorn', + 'Boesel', + 'Calabaza', + 'Dunkleberger', + 'Erck', + 'Fanger', + 'Felmlee', + 'Friebel', + 'Gabrys', + 'Godsil', + 'Goldhammer', + 'Gourneau', + 'Kaseman', + 'Keysor', + 'Mccargar', + 'Mittag', + 'Narum', + 'Schoeneck', + 'Stenquist', + 'Sunderlin', + 'Tarazon', + 'Tietze', + 'Wemmer', + 'Witthuhn', + 'Durango', + 'Simerson', + 'Beber', + 'Bjorn', + 'Neuville', + 'Preas', + 'Reitter', + 'Senf', + 'Mcclatchy', + 'Sanor', + 'Benney', + 'Sarrazin', + 'Woodliff', + 'Bramlet', + 'Cullin', + 'Wessells', + 'Higgens', + 'Rout', + 'Craigen', + 'Ackers', + 'Wickliff', + 'Hofler', + 'Pilgram', + 'Mcfayden', + 'Dillworth', + 'Robenson', + 'Mateen', + 'Ambrogio', + 'Aoun', + 'Aranas', + 'Balsiger', + 'Bonzo', + 'Busam', + 'Casassa', + 'Ciborowski', + 'Cotterill', + 'Cressler', + 'Cristales', + 'Crumpacker', + 'Daloisio', + 'Damasco', + 'Depolo', + 'Diguglielmo', + 'Dominik', + 'Esbenshade', + 'Fineran', + 'Formisano', + 'Gandolfi', + 'Geidel', + 'Gerwitz', + 'Grammatico', + 'Idleman', + 'Iwinski', + 'Kerth', + 'Lacouture', + 'Lafoy', + 'Lapid', + 'Lardizabal', + 'Lembcke', + 'Maga', + 'Mahrt', + 'Maniatis', + 'Martinezlopez', + 'Martinovich', + 'Milham', + 'Muscatello', + 'Perezperez', + 'Quiocho', + 'Rickner', + 'Sackrider', + 'Schwarm', + 'Schwebke', + 'Scollard', + 'Seader', + 'Shutters', + 'Skare', + 'Slothower', + 'Steeber', + 'Want', + 'Cherubini', + 'Coslett', + 'Degener', + 'Dulak', + 'Faull', + 'Freyman', + 'Gatchel', + 'Ginzburg', + 'Gronberg', + 'Landeck', + 'Lehenbauer', + 'Lubke', + 'Mcconaughey', + 'Mendonsa', + 'Minnehan', + 'Palaguachi', + 'Peedin', + 'Raithel', + 'Rezabek', + 'Rolfson', + 'Schuitema', + 'Sjodin', + 'Underkoffler', + 'Verrilli', + 'Yogi', + 'Zimpfer', + 'Zingaro', + 'Butrum', + 'Ritson', + 'Martinka', + 'Cashatt', + 'Kearn', + 'Sawtell', + 'Boyster', + 'Broyhill', + 'Cockerell', + 'Thane', + 'Resende', + 'Pealer', + 'Perrot', + 'Everhardt', + 'Breach', + 'Bry', + 'Juma', + 'Mclaine', + 'Paddy', + 'Hennesy', + 'Ledee', + 'Web', + 'Delone', + 'Louison', + 'Hamiel', + 'Tutson', + 'Bellingham', + 'Brenn', + 'Bussen', + 'Charrette', + 'Denenberg', + 'Depascale', + 'Derner', + 'Dondlinger', + 'Favro', + 'Frana', + 'Goeser', + 'Guerrini', + 'Hamideh', + 'Hetu', + 'Hnat', + 'Hollerbach', + 'Kenagy', + 'Kregel', + 'Lammi', + 'Laubacher', + 'Madarang', + 'Mangine', + 'Marut', + 'Mcmahen', + 'Memoli', + 'Milko', + 'Morash', + 'Mulvehill', + 'Nelles', + 'Perfecto', + 'Perkes', + 'Pesantes', + 'Peschke', + 'Polyakov', + 'Preheim', + 'Prust', + 'Reha', + 'Richardt', + 'Rockers', + 'Sartwell', + 'Schedler', + 'Scheler', + 'Skop', + 'Stefko', + 'Tatlock', + 'Tiley', + 'Waldecker', + 'Weinbaum', + 'Aguallo', + 'Benassi', + 'Bezio', + 'Bockover', + 'Dobesh', + 'Encina', + 'Eversman', + 'Haverfield', + 'Heigl', + 'Holzhauser', + 'Liebenow', + 'Mesenbrink', + 'Mittendorf', + 'Normoyle', + 'Pickart', + 'Rosselot', + 'Shigley', + 'Skufca', + 'Stroot', + 'Walth', + 'Wernert', + 'Lahood', + 'Ragain', + 'Stumpe', + 'Kolle', + 'Minerd', + 'Dickeson', + 'Koone', + 'Stoessel', + 'Kington', + 'Soe', + 'Wailes', + 'Monet', + 'Mccullars', + 'Huguenin', + 'Warnell', + 'Calip', + 'Sandles', + 'Fayson', + 'Balik', + 'Bauermeister', + 'Bianculli', + 'Bin', + 'Bring', + 'Busenbark', + 'Canevari', + 'Crile', + 'Dyment', + 'Egelhoff', + 'Elbe', + 'Estudillo', + 'Feigel', + 'Flammer', + 'Folta', + 'Ghuman', + 'Hefferan', + 'Hennick', + 'Hosner', + 'Kilner', + 'Liuzzi', + 'Maj', + 'Massing', + 'Nicolaisen', + 'Ohlrich', + 'Ozdemir', + 'Piccininni', + 'Prem', + 'Primiano', + 'Reek', + 'Riling', + 'Rohweder', + 'Rosasco', + 'Sandau', + 'Santarsiero', + 'Schuhmacher', + 'Stenseth', + 'Stilts', + 'Strohmeier', + 'Thorell', + 'Torr', + 'Vaswani', + 'Yono', + 'Amadon', + 'Ballowe', + 'Betke', + 'Borgwardt', + 'Decelle', + 'Dibiasio', + 'Fieldhouse', + 'Hegyi', + 'Heuberger', + 'Kreiling', + 'Montney', + 'Sammut', + 'Senseney', + 'Takenaka', + 'Tramonte', + 'Zalesky', + 'Zumstein', + 'Bents', + 'Vandersluis', + 'Wieringa', + 'Houlton', + 'Lippens', + 'Maino', + 'Keeny', + 'Bethards', + 'Guillette', + 'Lenn', + 'Minge', + 'Masley', + 'Christley', + 'Gabrielle', + 'Bruington', + 'Perren', + 'Ander', + 'Leeb', + 'Callicott', + 'Peaster', + 'Hardister', + 'Daughtridge', + 'Mclauchlin', + 'Culliver', + 'Missouri', + 'Aloisi', + 'Barua', + 'Bezek', + 'Broshears', + 'Busbin', + 'Cajamarca', + 'Dellarocco', + 'Dezeeuw', + 'Ferrelli', + 'Fieber', + 'Fredin', + 'Giovannoni', + 'Glasner', + 'Grenda', + 'Haberl', + 'Heimsoth', + 'Heinl', + 'Hellickson', + 'Hernandezlopez', + 'Huckeby', + 'Jungman', + 'Langhans', + 'Lingelbach', + 'Manera', + 'Maneri', + 'Marzella', + 'Mennen', + 'Molesworth', + 'Nagano', + 'Narula', + 'Niner', + 'Nordhoff', + 'Olazabal', + 'Perfect', + 'Plonka', + 'Pund', + 'Reincke', + 'Schimek', + 'Seegert', + 'Summar', + 'Tanori', + 'Trethewey', + 'Wehler', + 'Wirthlin', + 'Wolaver', + 'Zuver', + 'Bendure', + 'Bither', + 'Bungert', + 'Chaviano', + 'Derhammer', + 'Disbro', + 'Facchini', + 'Hoefle', + 'Hoepner', + 'Kimmes', + 'Korus', + 'Manfredonia', + 'Neuser', + 'Samarin', + 'Sanghera', + 'Sherburn', + 'Shiplett', + 'Steckelberg', + 'Faist', + 'Cardy', + 'Colan', + 'Goodbar', + 'Boro', + 'Moden', + 'Hardick', + 'Esteve', + 'Rawling', + 'Benet', + 'Nabers', + 'Atkerson', + 'Countess', + 'Thwaites', + 'Caroline', + 'Whisonant', + 'Alridge', + 'Pamphile', + 'Abdelnour', + 'Allebach', + 'Armenti', + 'Baudendistel', + 'Biers', + 'Bockrath', + 'Borgert', + 'Bovino', + 'Burgamy', + 'Cadiente', + 'Calabretta', + 'Cariveau', + 'Christoffel', + 'Daigler', + 'Dannels', + 'Darnold', + 'Decock', + 'Dominski', + 'Fest', + 'Forren', + 'Freise', + 'Galperin', + 'Hackbart', + 'Holtzer', + 'Idell', + 'Kapala', + 'Kohlenberg', + 'Kolton', + 'Lemburg', + 'Lievanos', + 'Maranan', + 'Marchitto', + 'Masini', + 'Mayabb', + 'Mccrossen', + 'Metrick', + 'Molinelli', + 'Oehlert', + 'Parlee', + 'Pizzini', + 'Polachek', + 'Salmans', + 'Selbe', + 'Sickman', + 'Stegmaier', + 'Sulek', + 'Thall', + 'Tiznado', + 'Tonini', + 'Trostel', + 'Warshawsky', + 'Aument', + 'Byrer', + 'Dechaine', + 'Fearnow', + 'Gallicchio', + 'Gertler', + 'Greubel', + 'Hironaka', + 'Kashner', + 'Kleffner', + 'Korthals', + 'Kundinger', + 'Lenger', + 'Lingafelter', + 'Luczynski', + 'Ostermeier', + 'Petrasek', + 'Righetti', + 'Tvedt', + 'Weindel', + 'Wurtzel', + 'Zumbro', + 'Wikel', + 'Burdi', + 'Ozturk', + 'Parmele', + 'Oteri', + 'Alexa', + 'Erven', + 'Keng', + 'Fare', + 'Sade', + 'Saw', + 'Jaquay', + 'Pillay', + 'Kearsley', + 'Kirkby', + 'Game', + 'Herst', + 'Vallie', + 'Bayon', + 'Whitler', + 'Pe', + 'Lockerman', + 'Cogle', + 'Rouzer', + 'Curling', + 'Mandley', + 'Kleckley', + 'Buckson', + 'Risby', + 'Averhart', + 'Almendariz', + 'Angelopoulos', + 'Brallier', + 'Decaire', + 'Deloria', + 'Derham', + 'Drudge', + 'Eckelberry', + 'Ehling', + 'Engebretsen', + 'Ercole', + 'Fiscal', + 'Gabino', + 'Gelvin', + 'Giannetto', + 'Godeaux', + 'Goshert', + 'Hedrich', + 'Ioannou', + 'Jungbluth', + 'Kia', + 'Krusemark', + 'Lader', + 'Lythgoe', + 'Malinak', + 'Mcinvale', + 'Melis', + 'Metsker', + 'Minasyan', + 'Nuhfer', + 'Omana', + 'Parco', + 'Pha', + 'Phanthavong', + 'Proa', + 'Sarli', + 'Schirtzinger', + 'Schlotter', + 'Sharrar', + 'Spielberg', + 'Stelzner', + 'Tschudy', + 'Utke', + 'Weipert', + 'Yera', + 'Berkemeier', + 'Bothun', + 'Dalporto', + 'Deschler', + 'Dragonetti', + 'Hasz', + 'Holtzinger', + 'Kallal', + 'Kesinger', + 'Kilfoyle', + 'Kobylinski', + 'Kramme', + 'Kreh', + 'Lindseth', + 'Plaugher', + 'Rehfeldt', + 'Repine', + 'Roudabush', + 'Swoveland', + 'Teper', + 'Tucek', + 'Wadding', + 'Wenzlick', + 'Ghobrial', + 'Golberg', + 'Soyka', + 'Matura', + 'Moras', + 'Natter', + 'Apps', + 'Imran', + 'Rossel', + 'Harne', + 'Les', + 'Silla', + 'Deblanc', + 'Rhinehardt', + 'Delaware', + 'Alkins', + 'Laidley', + 'Maree', + 'Cassells', + 'Abdulrahman', + 'Cange', + 'Devone', + 'Eustache', + 'Negash', + 'Tanks', + 'Sivels', + 'Cabbagestalk', + 'Ahlin', + 'Akard', + 'Barbaree', + 'Bielat', + 'Bressman', + 'Capurro', + 'Cortazar', + 'Dauphinee', + 'Dornak', + 'Eckl', + 'Eisenhuth', + 'Fazzini', + 'Fraim', + 'Glaab', + 'Glod', + 'Guedea', + 'Hearty', + 'Hinostroza', + 'Honold', + 'Jostes', + 'Korzeniewski', + 'Lobell', + 'Lopardo', + 'Middlekauff', + 'Monfils', + 'Oshana', + 'Schiappa', + 'Schubach', + 'Servantez', + 'Shaler', + 'Siverson', + 'Slimp', + 'Slovacek', + 'Staat', + 'Strassman', + 'Waffle', + 'Wuebker', + 'Beigel', + 'Berardo', + 'Berkery', + 'Bloyer', + 'Cronkright', + 'Cuautle', + 'Devenny', + 'Ghrist', + 'Gipple', + 'Gwilliam', + 'Hunzeker', + 'Ierardi', + 'Kathol', + 'Kienle', + 'Krack', + 'Loeper', + 'Minchey', + 'Pecht', + 'Schaberg', + 'Schollmeyer', + 'Siniscalchi', + 'Toback', + 'Tramp', + 'Vandaele', + 'Witzig', + 'Wivell', + 'Moros', + 'Saso', + 'Gares', + 'Heagle', + 'Murrillo', + 'Stankey', + 'Shamon', + 'Avram', + 'Achor', + 'Ovens', + 'Rames', + 'Perris', + 'Kernes', + 'Semmes', + 'Thaw', + 'Stevison', + 'Clemetson', + 'Belmar', + 'Guster', + 'Bascomb', + 'Adrien', + 'Jeanpaul', + 'Alabi', + 'Jallow', + 'Atamian', + 'Basque', + 'Bubier', + 'Casad', + 'Czekaj', + 'Dejoy', + 'Dulworth', + 'Fatula', + 'Favale', + 'Feutz', + 'Freundlich', + 'Frid', + 'Gagan', + 'Gaughran', + 'Guderian', + 'Hagemeister', + 'Haser', + 'Leibman', + 'Meddings', + 'Narlock', + 'Offenberger', + 'Pesa', + 'Poupard', + 'Raus', + 'Repetti', + 'Revello', + 'Robarts', + 'Rowin', + 'Saltarelli', + 'Sanghvi', + 'Schleyer', + 'Silba', + 'Steuck', + 'Stoffers', + 'Tangredi', + 'Taussig', + 'Tiso', + 'Wehmeier', + 'Zwiefelhofer', + 'Bartelson', + 'Brabender', + 'Cornfield', + 'Davtyan', + 'Delnero', + 'Frontino', + 'Gathman', + 'Graessle', + 'Hinchcliff', + 'Houdeshell', + 'Kapler', + 'Karabin', + 'Kerestes', + 'Lemmen', + 'Merkt', + 'Mitro', + 'Nahm', + 'Nancarrow', + 'Novakowski', + 'Parraz', + 'Revolorio', + 'Schamel', + 'Scowden', + 'Steever', + 'Suastegui', + 'Villarin', + 'Wuellner', + 'Dooly', + 'Erno', + 'Arbelo', + 'Groshek', + 'Boliver', + 'Gane', + 'Bees', + 'Dowds', + 'Newmann', + 'Kewley', + 'Stile', + 'Lobe', + 'Skeet', + 'Burgen', + 'Mckamie', + 'Hubanks', + 'Suleman', + 'Billey', + 'Efferson', + 'Mcleary', + 'Housen', + 'Shambley', + 'Fanfan', + 'Bacca', + 'Battaglini', + 'Bonfanti', + 'Bongers', + 'Butzin', + 'Caira', + 'Councilman', + 'Crounse', + 'Dadisman', + 'Donais', + 'Estabrooks', + 'Fornoff', + 'Froh', + 'Gaige', + 'Garofolo', + 'Grivas', + 'Jacuinde', + 'Kalmus', + 'Kientz', + 'Kostenko', + 'Kras', + 'Lagoy', + 'Larzelere', + 'Lizer', + 'Maric', + 'Mayette', + 'Mcfeeters', + 'Meadowcroft', + 'Newgent', + 'Parpart', + 'Pauwels', + 'Perriello', + 'Persichetti', + 'Proietti', + 'Siefring', + 'Simones', + 'Taliercio', + 'Thilges', + 'Thumann', + 'Thun', + 'Tuomi', + 'Uhde', + 'Umscheid', + 'Uran', + 'Velador', + 'Veltkamp', + 'Waddoups', + 'Yeley', + 'Bihn', + 'Bladow', + 'Boeh', + 'Chadderdon', + 'Ensing', + 'Fasbender', + 'Folkert', + 'Goellner', + 'Heitmeyer', + 'Iovine', + 'Klinke', + 'Nessel', + 'Perleberg', + 'Rajagopal', + 'Sackmann', + 'Sapio', + 'Schickling', + 'Schliep', + 'Siminski', + 'Sirrine', + 'Sporn', + 'Stockburger', + 'Tangonan', + 'Tarkowski', + 'Tartaglione', + 'Traum', + 'Vanoverbeke', + 'Weirauch', + 'Wellendorf', + 'Wonnacott', + 'Camplin', + 'Leth', + 'Meltz', + 'Cavero', + 'Florido', + 'Tremont', + 'Riviello', + 'Piotter', + 'Munce', + 'Trescott', + 'Eben', + 'Vaillant', + 'Furches', + 'Bazen', + 'Esse', + 'Losier', + 'Zahir', + 'Lazier', + 'Lightell', + 'Christal', + 'Behe', + 'Blayney', + 'Buchalter', + 'Demarsh', + 'Dhondt', + 'Diefendorf', + 'Dillavou', + 'Dombkowski', + 'Duchow', + 'Fettes', + 'Gallaga', + 'Gallet', + 'Haaf', + 'Hartinger', + 'Jech', + 'Klas', + 'Kostal', + 'Kubler', + 'Leisey', + 'Leisinger', + 'Marinas', + 'Mcpeck', + 'Miccio', + 'Mikkola', + 'Morath', + 'Olthoff', + 'Pacific', + 'Penado', + 'Petronio', + 'Pirani', + 'Pitones', + 'Pociask', + 'Ratay', + 'Riesberg', + 'Ruberto', + 'Sabet', + 'Sabic', + 'Simonich', + 'Skains', + 'Skarzynski', + 'Spreeman', + 'Steig', + 'Struckhoff', + 'Trolinger', + 'Uliano', + 'Vaquerano', + 'Zukas', + 'Zwahlen', + 'Amborn', + 'Amspacher', + 'Azzaro', + 'Bartoletti', + 'Berkstresser', + 'Buboltz', + 'Ekstein', + 'Fohl', + 'Heinzel', + 'Hellmer', + 'Kapfer', + 'Kurka', + 'Mccreless', + 'Miyahira', + 'Nebergall', + 'Orlosky', + 'Pajor', + 'Quartararo', + 'Rahilly', + 'Rzasa', + 'Sabas', + 'Slutz', + 'Speros', + 'Stumpp', + 'Tamburo', + 'Tesler', + 'Tonkovich', + 'Urbieta', + 'Vallandingham', + 'Youngdahl', + 'Juliana', + 'Rienstra', + 'Prideaux', + 'Coval', + 'Hausen', + 'Seith', + 'Ny', + 'Bian', + 'Gressman', + 'Yanick', + 'Mannina', + 'Nater', + 'Gurry', + 'Vaile', + 'Sortor', + 'Woodington', + 'Apollo', + 'Mozley', + 'Patience', + 'Hearron', + 'Milloy', + 'Huntsberry', + 'Polidore', + 'Ridges', + 'Bonton', + 'Mercadel', + 'Alikhan', + 'Antis', + 'Bartosiewicz', + 'Brems', + 'Clopper', + 'Colato', + 'Collver', + 'Daino', + 'Degrande', + 'Dellis', + 'Depner', + 'Disantis', + 'Dolecki', + 'Dollens', + 'Eliasen', + 'Fasig', + 'Favinger', + 'Furuta', + 'Gharibian', + 'Gombar', + 'Gordo', + 'Gornik', + 'Gulas', + 'Khoshaba', + 'Laurita', + 'Liby', + 'Linhardt', + 'Lookabaugh', + 'Lorincz', + 'Mautner', + 'Mcquigg', + 'Meine', + 'Melaragno', + 'Meroney', + 'Mikesh', + 'Miu', + 'Monasterio', + 'Navarete', + 'Orendain', + 'Puricelli', + 'Riede', + 'Rubis', + 'Sandness', + 'Schellhase', + 'Stehlin', + 'Sunder', + 'Teaney', + 'Terman', + 'Tith', + 'Totino', + 'Tudisco', + 'Urwin', + 'Vandrunen', + 'Vasicek', + 'Youtz', + 'Berwald', + 'Bilow', + 'Bubolz', + 'Cieslewicz', + 'Denbleyker', + 'Ensinger', + 'Gantenbein', + 'Gurnsey', + 'Herceg', + 'Kless', + 'Kollias', + 'Leppek', + 'Naeve', + 'Oncale', + 'Pastran', + 'Pinyan', + 'Porrata', + 'Pustejovsky', + 'Renko', + 'Scioli', + 'Sinkhorn', + 'Sporrer', + 'Tomkiewicz', + 'Weisbeck', + 'Gautam', + 'Gleed', + 'Shave', + 'Crotzer', + 'Demarr', + 'Reckard', + 'Coyt', + 'Norberto', + 'Ury', + 'Crispen', + 'Parcells', + 'Meiklejohn', + 'Risden', + 'Bracker', + 'Askari', + 'Hyneman', + 'Auberry', + 'Bruney', + 'Weakly', + 'Ysaguirre', + 'Calender', + 'Benison', + 'Nazaire', + 'Pondexter', + 'Fryson', + 'Aguino', + 'Antonino', + 'Babilonia', + 'Banfill', + 'Beger', + 'Berardino', + 'Bizub', + 'Contractor', + 'Convey', + 'Cossairt', + 'Cruzen', + 'Dible', + 'Dorning', + 'Ellena', + 'Fafard', + 'Fano', + 'Favaro', + 'Feeler', + 'Foulger', + 'Gulbrandson', + 'Heckaman', + 'Heimerman', + 'Herms', + 'Hotchkin', + 'Jinright', + 'Kisler', + 'Kontz', + 'Kryder', + 'Lopezperez', + 'Lumm', + 'Mcelravy', + 'Meditz', + 'Melucci', + 'Meras', + 'Miyahara', + 'Musella', + 'Nelis', + 'Nhem', + 'Olivan', + 'Popson', + 'Presgraves', + 'Reindel', + 'Riege', + 'Rivenburgh', + 'Sahl', + 'Selberg', + 'Tashiro', + 'Todorov', + 'Toutant', + 'Turski', + 'Vankuren', + 'Westrup', + 'Beeney', + 'Bickhart', + 'Borkenhagen', + 'Bukoski', + 'Citrin', + 'Civello', + 'Forstrom', + 'Froning', + 'Geiler', + 'Hargadon', + 'Hemric', + 'Jeffus', + 'Klingele', + 'Kooiker', + 'Lizalde', + 'Nardiello', + 'Pestka', + 'Pignato', + 'Pudwill', + 'Rabelo', + 'Remund', + 'Skluzacek', + 'Stegenga', + 'Steidle', + 'Stenz', + 'Terlecki', + 'Vanselow', + 'Waskey', + 'Azhar', + 'Wroe', + 'Tool', + 'Leibert', + 'Vary', + 'Scovell', + 'Derick', + 'Arrey', + 'Cavness', + 'Garley', + 'Sholtz', + 'Legard', + 'Heyliger', + 'Thorns', + 'Sowells', + 'Alemu', + 'Aragones', + 'Ayllon', + 'Baab', + 'Blankenbeckler', + 'Brengle', + 'Burick', + 'Deuser', + 'Disabato', + 'Doddridge', + 'Dolinski', + 'Economy', + 'Ems', + 'Hagenow', + 'Iwen', + 'Kiesler', + 'Lehrmann', + 'Loisel', + 'Mallicoat', + 'Mansouri', + 'Marse', + 'Mccartt', + 'Menninger', + 'Montee', + 'Nappa', + 'Ohanesian', + 'Podgurski', + 'Prosch', + 'Puder', + 'Ritthaler', + 'Rodelo', + 'Shipper', + 'Shorkey', + 'Sirna', + 'Smedberg', + 'Smink', + 'Strahle', + 'Troeger', + 'Twaddell', + 'Vandyk', + 'Wandrey', + 'Yaworski', + 'Zagami', + 'Duecker', + 'Finlinson', + 'Frysinger', + 'Grush', + 'Knackstedt', + 'Morozov', + 'Murgia', + 'Naffziger', + 'Ontko', + 'Piltz', + 'Roskelley', + 'Sonderman', + 'Garrand', + 'Kopack', + 'Theys', + 'Sanseverino', + 'Budai', + 'Selwyn', + 'Assante', + 'Nary', + 'Fildes', + 'Tano', + 'Hogen', + 'Gennett', + 'Melka', + 'Thorner', + 'Grandjean', + 'Dury', + 'Gerrald', + 'Quilling', + 'Mccallon', + 'Preister', + 'Kydd', + 'Cranshaw', + 'Folson', + 'Roker', + 'Dockett', + 'Stfort', + 'Haymer', + 'Njie', + 'Adamik', + 'Aredondo', + 'Bathrick', + 'Beldin', + 'Blackwater', + 'Branscom', + 'Cappucci', + 'Cartelli', + 'Carullo', + 'Cunneen', + 'Davee', + 'Deboy', + 'Defrates', + 'Esham', + 'Furio', + 'Garverick', + 'Gimlin', + 'Gosline', + 'Gromer', + 'Halbig', + 'Hasbrook', + 'Holgerson', + 'Hupfer', + 'Jochem', + 'Kihn', + 'Klotzbach', + 'Lantagne', + 'Leichter', + 'Lerette', + 'Lupu', + 'Machorro', + 'Mieles', + 'Mikulec', + 'Mirante', + 'Nasrallah', + 'Piccini', + 'Pinkhasov', + 'Poplaski', + 'Pottenger', + 'Rahrig', + 'Ranganathan', + 'Ravan', + 'Righi', + 'Rogacki', + 'Sadlon', + 'Salafia', + 'Schlitz', + 'Slayback', + 'Stetzel', + 'Tamargo', + 'Tenore', + 'Verkuilen', + 'Vuncannon', + 'Waggle', + 'Bacorn', + 'Boerema', + 'Cimorelli', + 'Ciresi', + 'Dethlefs', + 'Dimarzo', + 'Ficco', + 'Floresca', + 'Gnau', + 'Hefel', + 'Holbein', + 'Klepacki', + 'Konigsberg', + 'Lienau', + 'Malsam', + 'Meidl', + 'Nawabi', + 'Netzley', + 'Renbarger', + 'Rumbold', + 'Sarafian', + 'Sonnenfeld', + 'Tindol', + 'Trettin', + 'Tuckerman', + 'Vanderweele', + 'Weppler', + 'Westbay', + 'Zaveri', + 'Boran', + 'Deighan', + 'Rothery', + 'Yom', + 'Gatley', + 'Caldron', + 'Lucado', + 'Dromgoole', + 'Novell', + 'Sherriff', + 'Gerrick', + 'Balgobin', + 'Danger', + 'Sookram', + 'Daron', + 'Knibbs', + 'Faggart', + 'Beidleman', + 'Russey', + 'Lagrand', + 'Bluett', + 'Glaspy', + 'Baldon', + 'Trueheart', + 'Cradle', + 'Asfaw', + 'Ballinas', + 'Bogdon', + 'Brizzi', + 'Carrio', + 'Cherny', + 'Crogan', + 'Depierro', + 'Dhami', + 'Dresden', + 'Finnicum', + 'Geltz', + 'Granade', + 'Granieri', + 'Guia', + 'Hashagen', + 'Hollick', + 'Jicha', + 'Jollie', + 'Kathan', + 'Malara', + 'Manabat', + 'Mehall', + 'Midcap', + 'Mitre', + 'Newburg', + 'Parveen', + 'Pianka', + 'Plouff', + 'Posillico', + 'Ransier', + 'Reano', + 'Roskam', + 'Rufer', + 'Schnetzer', + 'Scorsone', + 'Sitterly', + 'Skilton', + 'Sohail', + 'Starin', + 'Stavish', + 'Tufaro', + 'Vano', + 'Vinsant', + 'Vlahakis', + 'Vondrasek', + 'Waldroop', + 'Wamboldt', + 'Achatz', + 'Bomkamp', + 'Fetzner', + 'Gemmer', + 'Haroutunian', + 'Hurtig', + 'Juncaj', + 'Kleban', + 'Knier', + 'Kopischke', + 'Kugelman', + 'Lacoss', + 'Meulemans', + 'Neyens', + 'Niccoli', + 'Oberhaus', + 'Penkala', + 'Podoll', + 'Roupp', + 'Scozzari', + 'Siverling', + 'Uhls', + 'Werber', + 'Grealish', + 'Montieth', + 'Haik', + 'Kuri', + 'Kanaan', + 'Prenatt', + 'Dingledine', + 'Mccamy', + 'Balin', + 'Droney', + 'Clyatt', + 'Ramone', + 'Anglen', + 'Mathus', + 'Bagent', + 'Lamarque', + 'Arscott', + 'Romes', + 'Speigner', + 'Latouche', + 'Tripplett', + 'Eversley', + 'Aquirre', + 'Bernales', + 'Bouthillier', + 'Cavendish', + 'Detienne', + 'Dewbre', + 'Dimuro', + 'Dosh', + 'Dunklee', + 'Duyck', + 'Emilio', + 'Ence', + 'Garofano', + 'Gellis', + 'Haertel', + 'Handyside', + 'Hornburg', + 'Jenniges', + 'Kallhoff', + 'Klontz', + 'Langsdorf', + 'Leabo', + 'Lorette', + 'Maracle', + 'Merta', + 'Muoio', + 'Nierenberg', + 'Oborn', + 'Osorto', + 'Ruscitti', + 'Santaella', + 'Spinnato', + 'Stentz', + 'Stocke', + 'Sundt', + 'Thorup', + 'Tresch', + 'Urdaneta', + 'Uttech', + 'Vosler', + 'Wieand', + 'Zacharia', + 'Zeleznik', + 'Zoucha', + 'Zuch', + 'Abrell', + 'Atiyeh', + 'Aydt', + 'Cleeton', + 'Crisan', + 'Cwikla', + 'Denz', + 'Diesing', + 'Emmi', + 'Fringer', + 'Gibbard', + 'Graunke', + 'Gschwind', + 'Hafele', + 'Hoogland', + 'Howsare', + 'Kesecker', + 'Kilgallon', + 'Kleyman', + 'Kufahl', + 'Laut', + 'Malstrom', + 'Michetti', + 'Nosbisch', + 'Rasner', + 'Rosekrans', + 'Schnebly', + 'Staebell', + 'Theilen', + 'Tieszen', + 'Mellone', + 'Burcher', + 'Feister', + 'Hoage', + 'Irmen', + 'Derwin', + 'Dien', + 'Markins', + 'Egnew', + 'Dunlow', + 'Brickel', + 'Curt', + 'Smyly', + 'Whedbee', + 'Larman', + 'Boisselle', + 'Jaquess', + 'Bowns', + 'Nile', + 'Boyson', + 'Phillipps', + 'Weech', + 'Pillars', + 'Cauldwell', + 'Wynns', + 'Toca', + 'Scorza', + 'Ramsaran', + 'Arkwright', + 'Gurganious', + 'Jubert', + 'Beed', + 'Kellem', + 'Gervin', + 'Yarn', + 'Bookhart', + 'Sullen', + 'Moncrieffe', + 'Eze', + 'Agyeman', + 'Aldea', + 'Amodei', + 'Attig', + 'Bergthold', + 'Blaskowski', + 'Blitzer', + 'Bowring', + 'Brenning', + 'Chappuis', + 'Cordasco', + 'Cosens', + 'Denoble', + 'Dochterman', + 'Domek', + 'Embleton', + 'Georgiades', + 'Gintz', + 'Grooters', + 'Hoell', + 'Honse', + 'Jagiello', + 'Jaskulski', + 'Kaluzny', + 'Keske', + 'Khiev', + 'Koeneman', + 'Majestic', + 'Mandile', + 'Marandola', + 'Mcinroy', + 'Nienhaus', + 'Peckenpaugh', + 'Raquel', + 'Rossler', + 'Rusconi', + 'Schaffert', + 'Schipani', + 'Sittner', + 'Sweezey', + 'Swenor', + 'Tagliaferro', + 'Tubby', + 'Ulep', + 'Vallette', + 'Westergren', + 'Yaros', + 'Yasui', + 'Anway', + 'Bannick', + 'Biasi', + 'Breitling', + 'Catarino', + 'Dunaj', + 'Giovanelli', + 'Hemmerich', + 'Iott', + 'Knotek', + 'Kraeger', + 'Laskaris', + 'Lomboy', + 'Oleski', + 'Reibel', + 'Rightmyer', + 'Salmela', + 'Salow', + 'Siebels', + 'Spielvogel', + 'Streitmatter', + 'Ucci', + 'Windmiller', + 'Wojtkiewicz', + 'Zirkel', + 'Markie', + 'Nedeau', + 'Froehle', + 'Jesson', + 'Regala', + 'Boody', + 'Hayen', + 'Ose', + 'Loewy', + 'Radliff', + 'Davia', + 'Sky', + 'Halker', + 'Alu', + 'Ey', + 'Badawi', + 'Yeargain', + 'Jeanette', + 'Doublin', + 'Nolton', + 'Streety', + 'Blueford', + 'Abeles', + 'Aldava', + 'Alsteen', + 'Altadonna', + 'Apa', + 'Behlke', + 'Bellisario', + 'Bienstock', + 'Brenan', + 'Capley', + 'Castoro', + 'Demir', + 'Evinger', + 'Gartside', + 'Gellatly', + 'Goldinger', + 'Grabel', + 'Henkin', + 'Herrle', + 'Honegger', + 'Kunin', + 'Larmer', + 'Lizano', + 'Lorino', + 'Malcomson', + 'Matesic', + 'Mathiasen', + 'Mccolm', + 'Meenach', + 'Mullady', + 'Neiderer', + 'Ogier', + 'Omura', + 'Plog', + 'Pomplun', + 'Procida', + 'Raisbeck', + 'Rastetter', + 'Reither', + 'Rettberg', + 'Roblee', + 'Rossitto', + 'Scahill', + 'Schmoker', + 'Segreto', + 'Shelstad', + 'Shwartz', + 'Sondgeroth', + 'Supnet', + 'Swartzbaugh', + 'Tkachenko', + 'Urbani', + 'Vanslooten', + 'Varricchio', + 'Villarino', + 'Whiston', + 'Wyffels', + 'Yehle', + 'Basinski', + 'Belvedere', + 'Bernabei', + 'Bolotin', + 'Bresett', + 'Dabkowski', + 'Dalsanto', + 'Gotwalt', + 'Hellberg', + 'Hunke', + 'Kroenke', + 'Leppla', + 'Luginbuhl', + 'Mimnaugh', + 'Mullenbach', + 'Nearhood', + 'Raser', + 'Resendis', + 'Seydel', + 'Sozio', + 'Stillions', + 'Stormont', + 'Strimple', + 'Toruno', + 'Trouten', + 'Tryba', + 'Vandalen', + 'Wilhelmy', + 'Orland', + 'Loui', + 'Morcos', + 'Radell', + 'Artus', + 'Truxillo', + 'Copelan', + 'Bress', + 'Unthank', + 'Sudlow', + 'Branden', + 'Rowzee', + 'Montreuil', + 'Sollers', + 'Umar', + 'Coulibaly', + 'Allegretto', + 'Andreen', + 'Bielicki', + 'Bustard', + 'Cardosi', + 'Carkhuff', + 'Cetina', + 'Clouthier', + 'Dolata', + 'Fiola', + 'Fjeld', + 'Gawthrop', + 'Glastetter', + 'Hamlyn', + 'Hanten', + 'Huerter', + 'Kreiss', + 'Lestrange', + 'Litzau', + 'Luberto', + 'Menconi', + 'Milosevic', + 'Munera', + 'Nachtigal', + 'Nethers', + 'Nicolaou', + 'Olund', + 'Paddack', + 'Pfiester', + 'Pilley', + 'Polendo', + 'Porcayo', + 'Preast', + 'Runquist', + 'Saccente', + 'Santoli', + 'Saragoza', + 'Selway', + 'Smestad', + 'Stebner', + 'Toben', + 'Trapnell', + 'Urschel', + 'Verno', + 'Vidovich', + 'Walterscheid', + 'Yoh', + 'Zmijewski', + 'Allwein', + 'Bessire', + 'Broering', + 'Budzik', + 'Denherder', + 'Goerner', + 'Goldbaum', + 'Grussing', + 'Huaracha', + 'Ippoliti', + 'Kanak', + 'Kaucher', + 'Kious', + 'Kirkner', + 'Kratzke', + 'Kubisiak', + 'Kueny', + 'Mazzilli', + 'Mazzo', + 'Mcclenathan', + 'Mehlberg', + 'Miotke', + 'Nihiser', + 'Olheiser', + 'Oravetz', + 'Radwanski', + 'Shinsato', + 'Vandekamp', + 'Zagata', + 'Abert', + 'Llera', + 'Thommen', + 'Wirkkala', + 'Brasuell', + 'Shawler', + 'Mourey', + 'Gavia', + 'Morgano', + 'Newill', + 'Rathel', + 'Wist', + 'Braner', + 'Soman', + 'Koskey', + 'Searson', + 'Brocksmith', + 'Peale', + 'Couzens', + 'Shall', + 'Anis', + 'Stanly', + 'Cauthorn', + 'Kinkle', + 'Laughinghouse', + 'Mellette', + 'Rox', + 'Demetrius', + 'Cullars', + 'Summons', + 'Banwart', + 'Bartl', + 'Bebb', + 'Bobier', + 'Bogdanoff', + 'Bollmann', + 'Borrowman', + 'Borseth', + 'Buttitta', + 'Canelo', + 'Cassedy', + 'Cata', + 'Crivelli', + 'Daane', + 'Dhingra', + 'Dipple', + 'Dovidio', + 'Duesler', + 'Eissler', + 'Ent', + 'Falotico', + 'Goodrick', + 'Goupil', + 'Huels', + 'Keithly', + 'Killilea', + 'Klausing', + 'Kludt', + 'Licitra', + 'Llerenas', + 'Merolla', + 'Oatley', + 'Osmanovic', + 'Poudrier', + 'Raben', + 'Realmuto', + 'Reczek', + 'Ricchio', + 'Rossner', + 'Rozak', + 'Sandora', + 'Schuenemann', + 'Seres', + 'Shoptaw', + 'Splitt', + 'Tonkinson', + 'Willardson', + 'Winterberg', + 'Zayac', + 'Bobzien', + 'Buhman', + 'Carotenuto', + 'Chynoweth', + 'Defenbaugh', + 'Dipiero', + 'Duve', + 'Goonan', + 'Gragert', + 'Hangartner', + 'Heemstra', + 'Hensch', + 'Hollatz', + 'Jakubowicz', + 'Kapaun', + 'Kiener', + 'Landesman', + 'Lenzini', + 'Longbottom', + 'Parde', + 'Pincock', + 'Schlicker', + 'Shankel', + 'Vidas', + 'Waisner', + 'Zilberman', + 'Allcock', + 'Durban', + 'Javid', + 'Shoda', + 'Edes', + 'Boxwell', + 'Dezern', + 'Rubley', + 'Angelica', + 'Jeannette', + 'Planer', + 'Pata', + 'Lothridge', + 'Lucks', + 'Bais', + 'Sandra', + 'Enwright', + 'Maxton', + 'Radway', + 'Hoof', + 'Morisset', + 'Danzey', + 'Ancar', + 'Mcwright', + 'Leggs', + 'Monestime', + 'Massaquoi', + 'Barkow', + 'Bastyr', + 'Bautz', + 'Behanna', + 'Bewick', + 'Bezdek', + 'Bielby', + 'Bretschneider', + 'Bugher', + 'Carchi', + 'Chapp', + 'Conser', + 'Crete', + 'Derflinger', + 'Elsbernd', + 'Freimark', + 'Gerwin', + 'Grunfeld', + 'Harpham', + 'Hoeschen', + 'Holmlund', + 'Horch', + 'Hulsebus', + 'Kassabian', + 'Konczal', + 'Korell', + 'Lacuesta', + 'Lantier', + 'Larowe', + 'Lietzke', + 'Lunny', + 'Masin', + 'Massicotte', + 'Michalsky', + 'Notarianni', + 'Pautsch', + 'Poppy', + 'Sukup', + 'Suleski', + 'Tafel', + 'Wanninger', + 'Zaffino', + 'Zody', + 'Arganbright', + 'Bohmer', + 'Cintora', + 'Connatser', + 'Dlugos', + 'Fariello', + 'Fedie', + 'Felicetti', + 'Garno', + 'Gottsch', + 'Gratzer', + 'Gubser', + 'Kappelman', + 'Kuechle', + 'Laningham', + 'Latsch', + 'Longie', + 'Luscher', + 'Lybeck', + 'Rhude', + 'Setterlund', + 'Sobh', + 'Sonneborn', + 'Villamizar', + 'Wolstenholme', + 'Zacek', + 'Leppanen', + 'Casdorph', + 'Pinsker', + 'Reutov', + 'Rede', + 'Sheck', + 'Bakley', + 'Radde', + 'Moher', + 'Khader', + 'Rossie', + 'Scriver', + 'Provine', + 'Debarge', + 'Darke', + 'Griswell', + 'Naji', + 'Frere', + 'Cheevers', + 'Schnyder', + 'Curb', + 'Luten', + 'Cashaw', + 'Agerton', + 'Barnier', + 'Bluestone', + 'Boward', + 'Boyar', + 'Briano', + 'Bryngelson', + 'Calef', + 'Caraher', + 'Castelluccio', + 'Conk', + 'Crewse', + 'Demarzo', + 'Deutschman', + 'Eckrote', + 'Edmister', + 'Ferg', + 'Ghan', + 'Giampaolo', + 'Goedecke', + 'Gonet', + 'Gradel', + 'Gregston', + 'Grzesiak', + 'Guallpa', + 'Hanline', + 'Hardyman', + 'Hogate', + 'Houg', + 'Justiss', + 'Kaps', + 'Klopf', + 'Kniskern', + 'Laneve', + 'Lenhoff', + 'Lojewski', + 'Melott', + 'Milillo', + 'Passage', + 'Pereyda', + 'Plack', + 'Poet', + 'Prospero', + 'Quadros', + 'Revelo', + 'Rogier', + 'Sanabia', + 'Tragesser', + 'Vanarsdall', + 'Vanausdal', + 'Verbrugge', + 'Wandler', + 'Zoss', + 'Balzarini', + 'Brotz', + 'Bulin', + 'Bumann', + 'Cancro', + 'Centner', + 'Deblasi', + 'Duesing', + 'Friedley', + 'Frieling', + 'Heinke', + 'Holzheimer', + 'Klinck', + 'Knouff', + 'Kuczek', + 'Leible', + 'Lerum', + 'Liddicoat', + 'Mikowski', + 'Nonaka', + 'Ohlman', + 'Picaso', + 'Plamann', + 'Porretta', + 'Prajapati', + 'Rancour', + 'Stepka', + 'Studzinski', + 'Vaysman', + 'Wallenstein', + 'Wunderlin', + 'Pattinson', + 'Siskind', + 'Sitzer', + 'Thuman', + 'Barella', + 'Brillon', + 'Arnholt', + 'Karge', + 'Dohman', + 'Morone', + 'Macie', + 'Aken', + 'Lye', + 'Student', + 'Westen', + 'Bonsell', + 'Komara', + 'Hafiz', + 'Stickland', + 'Morina', + 'Creekmur', + 'Hussien', + 'Walrond', + 'Louischarles', + 'Alkema', + 'Angert', + 'Arcidiacono', + 'Ashkar', + 'Bookbinder', + 'Bootz', + 'Cilia', + 'Devilla', + 'Difatta', + 'Enberg', + 'Enderby', + 'Forbess', + 'Frutiger', + 'Graefe', + 'Guenette', + 'Hauschildt', + 'Keirsey', + 'Kolka', + 'Kopelman', + 'Lewan', + 'Mcluckie', + 'Mia', + 'Moebius', + 'Oestreicher', + 'Oprea', + 'Ortolano', + 'Padovani', + 'Pensabene', + 'Phimmasone', + 'Pointon', + 'Punches', + 'Schertzer', + 'Seoane', + 'Skramstad', + 'Sorlie', + 'Syfert', + 'Tasca', + 'Townzen', + 'Wernli', + 'Wurzel', + 'Yazdi', + 'Devendorf', + 'Featherly', + 'Frush', + 'Heringer', + 'Iwai', + 'Kallenberger', + 'Kobashigawa', + 'Langbehn', + 'Livecchi', + 'Middlesworth', + 'Niess', + 'Osterlund', + 'Ruz', + 'Seiwert', + 'Vanwieren', + 'Wernet', + 'Grabbe', + 'Gaugh', + 'Mcclarren', + 'Raudales', + 'Urry', + 'Clere', + 'Lacer', + 'Mathia', + 'Mccrumb', + 'Cotrell', + 'Mannor', + 'Medine', + 'Tittsworth', + 'Hughston', + 'Buick', + 'Limes', + 'Hams', + 'Thagard', + 'Leavelle', +]; + +export const maxStringLength = 15; diff --git a/drizzle-seed/src/datasets/loremIpsumSentences.ts b/drizzle-seed/src/datasets/loremIpsumSentences.ts new file mode 100644 index 000000000..64fe59f71 --- /dev/null +++ b/drizzle-seed/src/datasets/loremIpsumSentences.ts @@ -0,0 +1,1641 @@ +/** + * Data was generated, using https://www.lipsum.com/ + */ +export default [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Nam porta quis ex a blandit.', + 'Donec ullamcorper erat sed diam luctus, eu euismod nibh eleifend.', + 'Curabitur sit amet tortor vehicula lacus mollis efficitur eu feugiat tortor.', + 'Quisque in erat vitae nisl tristique blandit.', + 'Vivamus in lectus tellus.', + 'Donec quis neque sit amet diam elementum accumsan.', + 'Sed vitae sollicitudin tellus, sed rhoncus magna.', + 'Aliquam eu interdum purus, sed viverra lorem.', + 'Etiam eget viverra dui.', + 'Morbi vel risus dolor.', + 'Donec laoreet, ipsum sed vestibulum venenatis, ligula leo fermentum enim, in pharetra lorem massa volutpat metus.', + 'Aliquam egestas mi in urna blandit, quis viverra justo condimentum.', + 'Maecenas pulvinar quam sapien, sed euismod enim rhoncus quis.', + 'Maecenas at quam non elit varius rutrum.', + 'Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.', + 'Quisque et malesuada erat.', + 'Maecenas eleifend tellus eu luctus tempor.', + 'Cras at scelerisque massa, quis dapibus urna.', + 'Aliquam porttitor a risus quis luctus.', + 'Aenean mollis ex tempor ligula cursus, interdum porttitor nibh fringilla.', + 'Donec aliquet ac nulla nec scelerisque.', + 'Curabitur neque diam, posuere nec tortor a, posuere pretium odio.', + 'Nullam et vehicula ante.', + 'Etiam mattis, odio quis sodales maximus, nisl lectus sagittis ligula, quis ornare urna nibh ac est.', + 'Pellentesque eget finibus eros.', + 'Maecenas gravida risus vitae vestibulum facilisis.', + 'Sed rhoncus libero fringilla arcu viverra tempus.', + 'Suspendisse non lacus vitae urna viverra vehicula.', + 'Pellentesque eu elementum enim.', + 'Morbi aliquet nisl eu accumsan rhoncus.', + 'Ut fringilla dolor ut odio blandit, et dignissim lectus placerat.', + 'Aliquam vulputate mauris elit, in semper purus accumsan tempor.', + 'Sed at elit ut ligula bibendum tincidunt.', + 'Maecenas ut tristique ipsum, ac sollicitudin quam.', + 'Pellentesque ut ante quis tellus pellentesque tempus.', + 'Nulla suscipit ex eget ex cursus accumsan.', + 'Sed at purus sapien.', + 'Fusce feugiat ante ac massa aliquam, maximus bibendum arcu convallis.', + 'Interdum et malesuada fames ac ante ipsum primis in faucibus.', + 'Cras vitae dignissim leo, ac pretium est.', + 'Aliquam lectus lectus, varius in eros eget, tempus sollicitudin ex.', + 'Nunc gravida mi lectus, tincidunt ultrices sapien lobortis cursus.', + 'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', + 'Maecenas aliquam vulputate justo vel lacinia.', + 'Nam facilisis augue vitae dolor mattis, sit amet maximus orci molestie.', + 'Etiam et nibh id lorem viverra aliquet.', + 'Quisque et mauris et odio finibus ullamcorper id eget odio.', + 'Duis sit amet varius purus.', + 'In congue posuere libero, nec tincidunt dui suscipit ac.', + 'Vivamus suscipit risus vel massa commodo pulvinar vitae eu diam.', + 'Mauris porta non orci at dapibus.', + 'Sed ullamcorper, sem ac fringilla tristique, purus massa hendrerit turpis, at elementum massa nulla nec quam.', + 'Praesent sed felis vitae felis vestibulum hendrerit vel at ipsum.', + 'Nunc egestas, lectus feugiat consequat auctor, erat mauris pretium sapien, et consequat magna ex id purus.', + 'Maecenas nibh ex, bibendum at augue eget, pulvinar cursus libero.', + 'Quisque ultricies vestibulum neque, in sollicitudin felis euismod in.', + 'Maecenas viverra mauris sit amet neque vulputate, sed suscipit sapien laoreet.', + 'Sed vitae sapien maximus, faucibus enim a, placerat erat.', + 'Cras maximus ipsum nec dui fermentum, eu facilisis augue fringilla.', + 'Sed eget nibh ante.', + 'Praesent pellentesque sodales tellus non consectetur.', + 'Suspendisse pulvinar, massa id gravida facilisis, diam nulla molestie metus, et convallis purus elit quis sapien.', + 'Mauris fermentum nec metus id consectetur.', + 'Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.', + 'Nulla vel maximus nunc.', + 'Duis dolor orci, tempor nec odio at, gravida congue ex.', + 'Pellentesque faucibus, est et eleifend commodo, ipsum nunc lobortis felis, at aliquam erat leo eu massa.', + 'Morbi egestas vehicula lacus, in pharetra nulla dictum in.', + 'Ut facilisis, erat eu suscipit mollis, ipsum ex sagittis augue, sit amet vehicula neque nunc ut leo.', + 'Donec dapibus non odio non auctor.', + 'Donec vitae ipsum eget risus vulputate vestibulum.', + 'Cras vestibulum purus leo, in porttitor erat finibus quis.', + 'Vivamus tincidunt justo diam, placerat maximus orci congue ac.', + 'Curabitur pulvinar congue accumsan.', + 'Vivamus eget velit dictum, sagittis diam eu, elementum diam.', + 'Quisque pharetra pellentesque purus at tristique.', + 'Cras dignissim arcu massa, eu pellentesque eros tristique id.', + 'Proin efficitur turpis vel sem ultricies molestie.', + 'Curabitur rhoncus viverra nibh ut sollicitudin.', + 'Vestibulum ut magna dolor.', + 'Suspendisse placerat eleifend lorem, at aliquet enim lacinia ut.', + 'Integer at nisi eu ex viverra viverra.', + 'Morbi finibus bibendum volutpat.', + 'Donec facilisis sem id eros tempor vehicula.', + 'Phasellus a dolor in dolor finibus iaculis et at quam.', + 'Phasellus volutpat nulla eget mauris blandit pharetra ut sit amet augue.', + 'Phasellus leo urna, ornare ut mauris ultrices, posuere imperdiet dui.', + 'Morbi accumsan bibendum neque, sit amet eleifend nunc bibendum nec.', + 'Praesent dapibus tristique tempor.', + 'Duis dapibus nulla in lectus luctus, nec blandit sem tristique.', + 'In odio dolor, consectetur eget sapien egestas, viverra pharetra urna.', + 'Nam risus est, suscipit fermentum tincidunt id, vehicula vitae arcu.', + 'Aenean venenatis pretium condimentum.', + 'Mauris lobortis blandit dapibus.', + 'Phasellus aliquet efficitur condimentum.', + 'Nam pulvinar ullamcorper metus ac vehicula.', + 'Donec eget auctor tellus.', + 'Morbi quis diam ultrices, tristique lectus eu, vehicula dolor.', + 'Aenean malesuada lorem sed vestibulum rutrum.', + 'Praesent quis metus id quam facilisis blandit.', + 'Proin venenatis eleifend augue nec gravida.', + 'Nulla eget vehicula mauris, vel rutrum ligula.', + 'Ut interdum aliquam fermentum.', + 'Morbi elementum metus ut velit pellentesque lacinia.', + 'Suspendisse malesuada est sed varius rhoncus.', + 'Sed eu porta ex.', + 'Nullam dignissim egestas dapibus.', + 'Vestibulum a pharetra ipsum.', + 'Donec in interdum diam.', + 'Morbi viverra id sem quis mollis.', + 'In eget porta lorem.', + 'Aliquam tincidunt feugiat magna, vel finibus odio rutrum sit amet.', + 'Morbi faucibus metus at vehicula efficitur.', + 'Cras metus lectus, egestas lacinia leo vitae, lacinia dapibus quam.', + 'Morbi tincidunt ut velit sed hendrerit.', + 'Vivamus eleifend at leo porttitor blandit.', + 'Morbi egestas diam augue, vel condimentum odio pulvinar id.', + 'Morbi porta vulputate ante sed lacinia.', + 'Fusce massa est, varius et lacinia sit amet, dictum at turpis.', + 'Vestibulum viverra augue elit, eget tristique ipsum accumsan vitae.', + 'Sed sit amet ex sapien.', + 'Mauris dapibus tincidunt scelerisque.', + 'Aliquam nunc libero, vestibulum id facilisis in, sollicitudin vitae nulla.', + 'Aenean a nulla commodo, rutrum orci eget, pellentesque erat.', + 'Aenean ut sem felis.', + 'Donec sapien ante, ornare sit amet ornare id, mattis lobortis tellus.', + 'Nam ut placerat metus.', + 'Vivamus in cursus eros, sit amet scelerisque mauris.', + 'Integer tempus, justo vel aliquet aliquam, mi libero iaculis leo, placerat sollicitudin mauris ipsum faucibus justo.', + 'Cras at vehicula urna.', + 'Phasellus id nunc eu enim ultricies hendrerit.', + 'Nulla sodales sodales orci in placerat.', + 'Donec placerat, justo in imperdiet euismod, nulla metus pharetra nibh, nec auctor tortor mauris ac augue.', + 'Donec at elit non odio malesuada consequat non id velit.', + 'Morbi pellentesque eleifend iaculis.', + 'Aliquam ullamcorper lacinia vulputate.', + 'Nulla commodo risus et efficitur mollis.', + 'In venenatis consectetur metus, in iaculis ligula bibendum fermentum.', + 'Nullam ac finibus nisl.', + 'Aenean blandit sagittis justo, ut cursus tortor vehicula vel.', + 'Integer at pulvinar eros, sed dictum ex.', + 'Phasellus bibendum interdum porttitor.', + 'Fusce blandit egestas nisl, quis mattis elit commodo in.', + 'Donec in ex justo.', + 'Aenean elementum tristique eros, vel mattis tellus malesuada nec.', + 'Quisque euismod tincidunt erat.', + 'Proin turpis orci, vehicula vitae ipsum et, auctor ornare ex.', + 'Nunc efficitur nisl sit amet justo faucibus, eu bibendum diam pretium.', + 'Nullam consectetur finibus dui at malesuada.', + 'Ut elementum, ante vitae gravida feugiat, orci enim molestie libero, ut vehicula purus ipsum a eros.', + 'Ut mi neque, vestibulum nec nibh eu, imperdiet elementum ipsum.', + 'Donec cursus augue quis ex rutrum lacinia.', + 'Mauris purus mi, pellentesque at leo in, auctor ultrices massa.', + 'Maecenas finibus quam quis arcu mattis porttitor.', + 'Suspendisse ac urna ac odio aliquet congue.', + 'Integer suscipit, odio in ullamcorper ornare, diam nibh elementum eros, a aliquam lacus velit vel mauris.', + 'Quisque ut bibendum risus.', + 'Suspendisse bibendum augue pellentesque, dapibus leo ac, luctus purus.', + 'Phasellus interdum ipsum sit amet elit rhoncus varius.', + 'Pellentesque pharetra lorem et nibh aliquam, vel luctus elit sodales.', + 'Maecenas ornare cursus metus in efficitur.', + 'Phasellus laoreet ipsum nec erat mattis, vitae vulputate risus auctor.', + 'Maecenas augue magna, mattis elementum dapibus sed, vulputate venenatis ante.', + 'Fusce non lorem vitae velit molestie auctor.', + 'Etiam sodales, orci sed consequat luctus, ante urna hendrerit ipsum, at ultrices mauris neque in velit.', + 'Vestibulum a egestas ipsum.', + 'Donec sit amet laoreet mi.', + 'Quisque varius ligula dolor.', + 'Morbi sodales volutpat nulla, et ullamcorper lacus bibendum at.', + 'In mattis in dui quis facilisis.', + 'Cras pulvinar, massa eu rhoncus rhoncus, mi mi ultricies turpis, eu iaculis elit nulla et metus.', + 'Donec et neque suscipit, iaculis ipsum at, maximus eros.', + 'Quisque eu accumsan risus.', + 'Phasellus in consectetur nisl.', + 'Nullam interdum porttitor enim fermentum bibendum.', + 'Vestibulum consequat fermentum mollis.', + 'Quisque id velit sit amet magna posuere aliquam vel in nunc.', + 'Maecenas nisl lectus, sollicitudin eu auctor nec, cursus vel quam.', + 'Proin elementum efficitur velit vel vestibulum.', + 'Nunc non tincidunt ex.', + 'Fusce nec nisl eget nunc fringilla dignissim vel nec ex.', + 'Cras malesuada erat quis ligula lacinia consectetur.', + 'Aliquam semper elit ante, sed accumsan lacus molestie in.', + 'Vivamus porttitor enim eros, eu ultricies lectus pulvinar eget.', + 'Nullam consequat tincidunt ligula, eu luctus nisi congue id.', + 'Aenean lacinia lobortis ante, fermentum vulputate turpis eleifend faucibus.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus tempus libero non ipsum cursus rhoncus.', + 'Sed euismod molestie augue vitae fringilla.', + 'Pellentesque mi tortor, tempor quis condimentum quis, lobortis quis quam.', + 'Sed in vestibulum purus, in vestibulum neque.', + 'Etiam pellentesque ligula ligula, sit amet varius mi venenatis fringilla.', + 'Morbi vitae est ac diam convallis sagittis in facilisis ligula.', + 'Sed vel consequat diam.', + 'Nunc ac tempor felis.', + 'Aenean luctus tristique urna lacinia venenatis.', + 'Suspendisse vehicula auctor accumsan.', + 'Suspendisse ultrices rhoncus nisi a pellentesque.', + 'Sed sollicitudin id orci ut laoreet.', + 'Cras pulvinar lorem ut ipsum malesuada, sed euismod turpis placerat.', + 'Ut vitae massa quis augue posuere ultricies.', + 'In quis erat posuere, posuere dolor ac, tempus tortor.', + 'Aliquam aliquet nisl eu tortor mollis, id dictum nisi congue.', + 'Etiam pulvinar, ex a tincidunt bibendum, nisl elit venenatis lacus, nec dictum odio ligula non nulla.', + 'Etiam sit amet nunc vestibulum, pharetra diam ac, lacinia felis.', + 'Quisque volutpat laoreet lorem, sit amet porta justo ultrices aliquet.', + 'Praesent aliquet nisi elit, ut facilisis orci accumsan vitae.', + 'Quisque vehicula augue at leo varius, ac dictum tortor viverra.', + 'Proin eu bibendum diam.', + 'Aliquam blandit, erat et feugiat varius, erat mauris convallis ipsum, ut convallis massa erat vel neque.', + 'Sed commodo nec ipsum in maximus.', + 'Pellentesque ligula nisl, tincidunt volutpat convallis non, interdum quis felis.', + 'Nunc ultrices neque ut diam congue, non tristique metus tempor.', + 'Pellentesque sodales metus leo, at eleifend dui pretium at.', + 'Suspendisse sit amet metus at est viverra fermentum.', + 'Donec ac odio vitae urna blandit consectetur.', + 'Vivamus tincidunt cursus nunc in mollis.', + 'Nullam malesuada quis odio eu imperdiet.', + 'Integer convallis sapien vitae semper varius.', + 'Nullam malesuada tincidunt lacus elementum condimentum.', + 'Nam eget neque vitae leo convallis aliquam id eu quam.', + 'Quisque aliquet elementum lectus, vitae pharetra nisl facilisis at.', + 'Fusce ut velit porttitor, porta erat ac, vehicula odio.', + 'Sed tempor est at nulla mollis aliquet.', + 'Quisque luctus dolor eu placerat ultrices.', + 'Vivamus luctus ex non ante pretium venenatis.', + 'Ut non arcu vitae velit pellentesque accumsan eget id risus.', + 'Pellentesque accumsan elementum turpis, a aliquam dui sodales nec.', + 'Donec quis semper tortor, scelerisque venenatis velit.', + 'Morbi tempus lacus pretium risus rhoncus, tincidunt lacinia diam dapibus.', + 'Donec libero neque, aliquet non aliquet et, mollis at est.', + 'Fusce mauris tortor, molestie ut porttitor nec, euismod consequat metus.', + 'Maecenas in nunc blandit, sagittis orci sed, fringilla risus.', + 'Suspendisse vel vulputate velit.', + 'Nulla aliquam facilisis velit.', + 'Donec placerat porttitor sapien.', + 'Quisque non pharetra mi.', + 'Suspendisse mattis justo nec arcu efficitur, nec suscipit mi tempor.', + 'Sed et dui vitae nisi accumsan faucibus nec vel odio.', + 'Donec at lacus eget nisi ultricies efficitur.', + 'Aenean ultricies elit eget mi consectetur imperdiet.', + 'Ut lorem magna, ullamcorper sit amet dui quis, pulvinar cursus felis.', + 'Morbi ligula nibh, fermentum nec pellentesque eget, sodales in sapien.', + 'Sed eu vehicula mi.', + 'Vestibulum et erat erat.', + 'Maecenas eleifend ultricies erat eget vehicula.', + 'Donec varius lectus ut metus finibus pellentesque.', + 'Aliquam nec orci scelerisque, elementum odio non, aliquet ante.', + 'Nulla eget nisi ac magna aliquet efficitur vitae sed felis.', + 'Suspendisse purus erat, blandit eget leo quis, iaculis vestibulum sapien.', + 'Vivamus rutrum, leo ac suscipit tincidunt, ipsum sem volutpat purus, quis sodales augue lacus id mi.', + 'Aenean interdum ac turpis eu viverra.', + 'Suspendisse rhoncus rutrum augue.', + 'Ut dolor lectus, rutrum et metus et, volutpat sagittis urna.', + 'Donec blandit tortor sed pellentesque maximus.', + 'Phasellus molestie congue erat, ut euismod leo pulvinar nec.', + 'Nulla elementum vestibulum libero vehicula aliquet.', + 'Sed venenatis enim eu nisi laoreet, sit amet sagittis magna gravida.', + 'Suspendisse semper molestie ligula sit amet lobortis.', + 'Nulla urna eros, condimentum a odio id, aliquet scelerisque justo.', + 'Suspendisse sit amet orci ante.', + 'Sed congue sem sapien, ac ornare nibh porta efficitur.', + 'Nullam suscipit, lectus ac gravida ultrices, lectus neque viverra sem, sit amet eleifend purus felis vulputate odio.', + 'In velit lacus, facilisis quis nunc vitae, imperdiet bibendum mauris.', + 'Duis iaculis sodales turpis, vestibulum rutrum eros efficitur ac.', + 'Aenean interdum congue libero vel suscipit.', + 'Quisque pharetra semper lorem ac posuere.', + 'Mauris viverra neque pellentesque, semper augue id, placerat arcu.', + 'Mauris sit amet bibendum nisi, a laoreet ipsum.', + 'Proin tristique auctor massa convallis imperdiet.', + 'Curabitur congue sed neque vel imperdiet.', + 'Quisque egestas metus at diam feugiat, vestibulum ornare nulla venenatis.', + 'Donec non hendrerit urna.', + 'Curabitur id justo ex.', + 'Sed consectetur urna a purus egestas, a fringilla leo scelerisque.', + 'Morbi interdum massa sed ligula dapibus semper.', + 'Nam sit amet condimentum erat.', + 'Nam vel magna porta, ultrices nisl eu, lacinia lorem.', + 'Pellentesque accumsan, felis sit amet elementum tincidunt, risus arcu bibendum eros, vitae commodo justo orci vitae ex.', + 'Vestibulum eu fermentum lacus.', + 'Nulla quis pulvinar metus.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Curabitur malesuada non erat vitae dapibus.', + 'Donec sit amet facilisis ante, vitae tristique risus.', + 'Vestibulum maximus vehicula purus sed tincidunt.', + 'Fusce facilisis odio et fermentum dapibus.', + 'In hac habitasse platea dictumst.', + 'Etiam et viverra felis, in vulputate enim.', + 'Donec vel pulvinar elit.', + 'Mauris quis velit suscipit, accumsan libero eget, consectetur sapien.', + 'Maecenas vel placerat justo, sit amet eleifend sem.', + 'Pellentesque cursus felis enim, vitae convallis lectus finibus et.', + 'Aliquam eu dolor eros.', + 'Cras dictum, est quis porttitor semper, turpis lacus maximus eros, at luctus diam orci et elit.', + 'Nulla id augue tincidunt, sollicitudin mi vel, pellentesque mi.', + 'Vestibulum ultricies turpis a congue rutrum.', + 'Praesent sed dictum nunc.', + 'Donec finibus commodo ligula non tincidunt.', + 'Aliquam eget tellus velit.', + 'Proin tempor elit at nulla commodo molestie.', + 'Morbi ultricies sit amet tortor eu porttitor.', + 'Duis congue elit ac porttitor lobortis.', + 'Sed fringilla mi pretium lacinia finibus.', + 'Maecenas faucibus metus sed ipsum tristique, sed vulputate odio bibendum.', + 'Nulla facilisi.', + 'Nulla non dapibus nibh, id ultricies augue.', + 'Vestibulum vitae lorem neque.', + 'Maecenas non augue nibh.', + 'Suspendisse laoreet dapibus auctor.', + 'In sit amet lorem eget purus ultrices tincidunt ut at neque.', + 'Fusce congue, nunc sit amet lobortis pellentesque, sapien ex rutrum elit, vel gravida nulla velit sed dui.', + 'Cras nec hendrerit sapien, eu euismod elit.', + 'Maecenas lobortis egestas interdum.', + 'Nunc sagittis bibendum erat sit amet varius.', + 'Mauris varius nunc at odio facilisis facilisis.', + 'Sed maximus sit amet urna vitae aliquam.', + 'Donec vel ipsum sed sapien aliquet pharetra id vitae leo.', + 'Sed vitae diam elit.', + 'Nam tempor, risus nec gravida mollis, velit neque efficitur leo, sit amet porta purus magna euismod ipsum.', + 'Suspendisse potenti.', + 'Donec interdum vulputate lorem, vitae pellentesque sem mollis at.', + 'Vivamus vitae faucibus libero.', + 'Etiam laoreet semper accumsan.', + 'Morbi imperdiet, ex ut fringilla fermentum, sapien ipsum efficitur magna, a ultrices purus massa at nibh.', + 'Sed a dolor euismod, facilisis ex eget, efficitur est.', + 'Phasellus eleifend, felis quis convallis semper, dui magna accumsan leo, a mattis magna urna in ligula.', + 'Donec porta sollicitudin vestibulum.', + 'Nunc et nisl in ligula iaculis rutrum id consequat neque.', + 'Nunc sed purus quis felis aliquet accumsan.', + 'Aliquam congue placerat condimentum.', + 'Proin ultrices condimentum facilisis.', + 'Aenean in faucibus odio.', + 'Pellentesque enim ex, mattis non risus ut, euismod imperdiet lorem.', + 'Integer quis magna non risus luctus posuere.', + 'Vestibulum pellentesque suscipit arcu, id dignissim leo rhoncus nec.', + 'Praesent vitae sodales quam.', + 'Morbi viverra nibh quam, quis dapibus nibh consequat sed.', + 'Morbi sed risus sollicitudin, tincidunt augue vel, posuere orci.', + 'Nunc nisi nisi, varius ac commodo ac, placerat hendrerit nisi.', + 'Donec sapien magna, elementum eu mi vitae, laoreet euismod turpis.', + 'Cras eget pretium eros.', + 'Sed tincidunt ante id tortor porta, ac pellentesque erat suscipit.', + 'Fusce consequat nisi dolor, eget tincidunt tellus imperdiet at.', + 'Nullam scelerisque commodo eleifend.', + 'Mauris et nisl bibendum, varius sem at, sollicitudin libero.', + 'Quisque purus felis, tristique id ligula in, ullamcorper pellentesque felis.', + 'Phasellus et tortor ut sem rhoncus suscipit ac eget elit.', + 'Donec rhoncus ex finibus neque volutpat, ut placerat metus gravida.', + 'Suspendisse at sem id diam efficitur dapibus.', + 'Aliquam erat volutpat.', + 'Nunc ac nibh eget augue sodales ornare.', + 'Integer ultricies neque at felis aliquam, vel interdum felis mollis.', + 'Nulla iaculis libero velit, a consequat eros hendrerit venenatis.', + 'Etiam aliquet eros magna, ut ultricies metus feugiat vitae.', + 'Maecenas est orci, accumsan eu eleifend vitae, sollicitudin vitae metus.', + 'Sed aliquet, tellus sed euismod sodales, lectus leo imperdiet dui, eu luctus mauris turpis id turpis.', + 'Sed eget accumsan felis, viverra euismod nulla.', + 'Nullam convallis odio consectetur nisl tempus, sed dictum urna tempor.', + 'Proin scelerisque elit tortor, a ultricies odio ullamcorper vel.', + 'Etiam ultrices congue neque ac sollicitudin.', + 'Ut placerat consectetur sapien ut rhoncus.', + 'Ut aliquam quam nec ornare fermentum.', + 'Vivamus aliquet facilisis magna.', + 'Vestibulum dictum sed leo non cursus.', + 'Morbi egestas et augue fringilla bibendum.', + 'Etiam vel maximus tellus.', + 'Praesent et turpis justo.', + 'Morbi a hendrerit diam, cursus posuere lorem.', + 'In sed sem id eros dignissim tincidunt.', + 'Nullam porta varius risus at ullamcorper.', + 'Nam varius sodales dolor, dapibus rutrum ligula vulputate at.', + 'Nam ultricies sed ante eget convallis.', + 'Duis ultrices est ac orci auctor, et malesuada neque sodales.', + 'Aliquam venenatis sodales aliquam.', + 'Phasellus ut lectus id sapien dictum luctus a vitae nibh.', + 'Sed euismod varius malesuada.', + 'Ut faucibus ultricies posuere.', + 'Nunc vitae diam in mi pellentesque vehicula eu elementum lectus.', + 'Vivamus gravida felis eget ipsum consectetur tincidunt.', + 'Nullam nunc eros, blandit ut finibus sit amet, porta nec lectus.', + 'Vestibulum non orci neque.', + 'Praesent velit massa, pulvinar quis mauris sit amet, consequat tincidunt mauris.', + 'Quisque id cursus magna.', + 'Donec eros ante, placerat at efficitur in, placerat id turpis.', + 'Morbi non dui tortor.', + 'Quisque at turpis sodales, pharetra justo ut, accumsan est.', + 'Sed molestie dolor mi, ac feugiat elit blandit et.', + 'Nullam libero ex, rutrum ac ultrices vitae, tincidunt ut velit.', + 'Proin pharetra placerat eros, eget mattis risus semper at.', + 'In feugiat congue risus.', + 'Curabitur non odio ligula.', + 'Nulla sit amet ligula facilisis, venenatis ante eget, porttitor libero.', + 'In eu sodales sem.', + 'In iaculis ex eget nisi euismod, eget porta libero condimentum.', + 'Vivamus tristique faucibus nunc.', + 'Nam sit amet cursus erat.', + 'Suspendisse ligula velit, molestie ac nisl quis, cursus sodales nunc.', + 'Nunc vel semper odio, at scelerisque felis.', + 'Fusce egestas purus id enim accumsan ultrices.', + 'Curabitur porttitor justo urna, nec ultricies magna varius non.', + 'Nullam euismod, nunc varius efficitur viverra, sem justo scelerisque elit, a pretium ante sem id mi.', + 'Sed sagittis faucibus urna, eu sollicitudin magna.', + 'Nam pretium velit quis lectus viverra sodales.', + 'Cras lectus mi, accumsan non vulputate et, hendrerit ac libero.', + 'Integer nec faucibus risus.', + 'Vestibulum a finibus nulla.', + 'Quisque consequat nisi varius, laoreet justo et, elementum dui.', + 'Praesent cursus quam nec vestibulum porta.', + 'Nunc fermentum semper molestie.', + 'Cras fermentum, sem et lobortis iaculis, magna erat bibendum sapien, in sollicitudin erat metus in lectus.', + 'Nullam ligula est, tincidunt vitae consectetur vel, rutrum at erat.', + 'Etiam in rhoncus nisl, ut tempor ex.', + 'Phasellus mollis tempus urna, vel hendrerit felis aliquam sit amet.', + 'Aliquam eget mi tellus.', + 'Maecenas consectetur enim diam, a fringilla nunc suscipit egestas.', + 'Phasellus ac efficitur dolor.', + 'Nullam efficitur metus a risus sodales, quis vestibulum urna lacinia.', + 'Nam eu risus vulputate, commodo purus quis, ullamcorper nunc.', + 'Maecenas in urna tortor.', + 'Duis a purus volutpat ligula tristique suscipit.', + 'Sed id libero accumsan, finibus ipsum et, sagittis justo.', + 'Suspendisse malesuada lectus in ligula interdum condimentum.', + 'Fusce viverra ipsum lacus, et condimentum nisi tincidunt vitae.', + 'Ut pulvinar sodales nisl non dictum.', + 'Proin et efficitur tortor.', + 'Cras viverra lacinia dolor, a condimentum mauris rhoncus eu.', + 'Quisque non nunc lobortis, iaculis neque et, venenatis eros.', + 'Etiam posuere, risus quis feugiat gravida, velit nisi ornare enim, vitae dictum leo massa id orci.', + 'Sed sit amet ligula nisi.', + 'Cras et velit eget urna pulvinar dignissim.', + 'Phasellus feugiat enim eu dolor molestie, vitae molestie dui consectetur.', + 'Morbi scelerisque sapien et diam tincidunt volutpat.', + 'Praesent viverra lobortis tristique.', + 'Vestibulum placerat rutrum congue.', + 'Sed vel leo eu odio feugiat bibendum.', + 'Mauris lobortis ante tortor, ac mattis nunc consequat sit amet.', + 'Aenean sollicitudin faucibus purus, ut facilisis neque convallis quis.', + 'Aenean sit amet risus in libero eleifend pellentesque nec non lacus.', + 'Maecenas iaculis at ligula eget rutrum.', + 'Aliquam vitae tristique justo.', + 'Aliquam enim nibh, porta accumsan tortor at, condimentum feugiat tellus.', + 'Morbi dignissim egestas maximus.', + 'Sed dui risus, vulputate ac accumsan vel, rhoncus vitae nunc.', + 'Phasellus elementum ac enim a tincidunt.', + 'Curabitur vulputate enim ut leo suscipit rhoncus.', + 'Ut vitae dapibus dui.', + 'Proin sed nulla purus.', + 'Etiam a eros elementum, fringilla orci nec, cursus magna.', + 'Curabitur egestas ultricies risus, vitae ultrices dolor.', + 'Donec et tempus leo.', + 'Phasellus justo tellus, lacinia ut lorem sed, pretium hendrerit dolor.', + 'Mauris in mattis libero, sed pulvinar lorem.', + 'Cras quis auctor velit.', + 'Vestibulum vitae hendrerit tortor.', + 'Cras dictum ligula eget arcu malesuada suscipit ac sed arcu.', + 'Suspendisse vel enim sit amet metus eleifend venenatis.', + 'Quisque eget purus in lorem vulputate congue.', + 'Curabitur non enim vulputate, accumsan purus nec, suscipit lacus.', + 'Donec eros est, pretium blandit semper eu, dignissim vitae leo.', + 'Maecenas molestie erat ac magna finibus, quis sollicitudin dui euismod.', + 'Curabitur rutrum dolor ut nibh suscipit luctus.', + 'Fusce venenatis orci nulla, eget semper libero dictum sed.', + 'Sed id justo id est eleifend tristique.', + 'Phasellus eleifend eget lectus vitae luctus.', + 'Fusce vitae dolor id dui aliquam gravida ac id ex.', + 'Maecenas elementum, mi sed suscipit malesuada, magna ex laoreet lorem, lacinia pellentesque erat nisl quis augue.', + 'Ut egestas tincidunt tincidunt.', + 'Nullam consectetur magna id dictum varius.', + 'Pellentesque mattis, velit ac volutpat euismod, ipsum magna volutpat tellus, ac pharetra dolor erat vel felis.', + 'Nunc pretium, tortor blandit gravida pretium, neque nulla vehicula diam, eget aliquet turpis eros eu orci.', + 'Integer non arcu eget odio eleifend tempor quis eget elit.', + 'Ut molestie nulla ornare, congue nulla vel, eleifend tellus.', + 'Pellentesque ultrices diam ut risus convallis viverra.', + 'Integer ex erat, molestie in rhoncus ornare, rhoncus id ipsum.', + 'Fusce non nulla id augue molestie malesuada.', + 'Proin in ornare ligula.', + 'Fusce sit amet augue eget orci imperdiet consequat.', + 'Maecenas sodales est dui, vel feugiat orci aliquam a.', + 'Aenean pulvinar quam in nunc fringilla, et convallis turpis congue.', + 'Proin ex erat, vehicula tristique mi vehicula, finibus congue ex.', + 'Nunc ornare fermentum convallis.', + 'Quisque scelerisque orci non dignissim sodales.', + 'Donec eget facilisis enim.', + 'Maecenas at libero at urna vestibulum mattis.', + 'Curabitur fringilla ex purus, quis egestas tortor lacinia nec.', + 'Sed vel est consequat, sagittis lacus at, ullamcorper augue.', + 'Sed eget dui ac nisi hendrerit auctor.', + 'Nulla consectetur placerat magna, at mattis felis rutrum in.', + 'Nullam vitae risus viverra, faucibus lacus a, eleifend eros.', + 'Nullam tempus sit amet eros vitae semper.', + 'Nullam vestibulum sem sed purus congue, hendrerit porttitor leo maximus.', + 'Praesent fringilla aliquet efficitur.', + 'Aliquam quis metus at ante posuere gravida.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam sapien dui, mollis sed hendrerit id, fermentum sed tortor.', + 'Ut eget velit a urna interdum volutpat.', + 'Maecenas rhoncus dui vitae tempus rhoncus.', + 'Phasellus eu molestie sem.', + 'Mauris quis aliquam lectus, nec vehicula dui.', + 'Aenean eget imperdiet odio.', + 'Vestibulum non ullamcorper lacus.', + 'Etiam eu augue eget massa tempus rutrum eu sit amet leo.', + 'Vestibulum nec nulla quis tellus lacinia scelerisque quis vitae felis.', + 'Donec convallis, elit sit amet viverra fermentum, libero eros auctor elit, a tincidunt erat eros vitae ligula.', + 'Cras ornare placerat ultrices.', + 'Mauris blandit, nunc eu viverra interdum, est odio bibendum urna, eget mattis purus nisl sollicitudin mauris.', + 'Cras ornare velit ac facilisis rhoncus.', + 'Donec lacus lectus, consectetur in fringilla ac, aliquet et nisl.', + 'Morbi sit amet nulla vitae arcu porttitor elementum ac vitae nulla.', + 'Duis sed tristique velit, non rutrum tellus.', + 'Cras et imperdiet nisl.', + 'Fusce id ipsum a dui volutpat volutpat.', + 'Nam eget augue et lectus placerat vehicula.', + 'In egestas condimentum mi, id efficitur nunc mattis sit amet.', + 'Nunc pulvinar tortor nec dolor vehicula, at porta elit porttitor.', + 'Praesent ac auctor erat.', + 'Donec eu faucibus eros, eu varius tortor.', + 'Sed eget sapien at est lacinia molestie eu in ligula.', + 'Duis mollis vehicula cursus.', + 'Duis finibus auctor pellentesque.', + 'Nullam sed urna diam.', + 'Nulla quis ipsum lacinia, placerat nisl et, dignissim est.', + 'Vivamus hendrerit, urna vel pulvinar maximus, elit turpis placerat dolor, quis sodales erat turpis sed lorem.', + 'Nulla dapibus porta odio, at porta ligula convallis a.', + 'Morbi rhoncus tempor libero, lobortis facilisis ligula convallis at.', + 'Etiam dapibus lacinia massa a finibus.', + 'Nam eget convallis mi.', + 'Integer posuere consectetur nisl eu ullamcorper.', + 'Cras et lorem sit amet dui venenatis pellentesque non laoreet quam.', + 'Sed eget fermentum diam, eget suscipit nulla.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam luctus eros quis metus faucibus luctus.', + 'Suspendisse rutrum libero magna, eu porttitor purus pretium vel.', + 'Curabitur ultricies pellentesque dui sit amet volutpat.', + 'Fusce tincidunt vulputate elit et efficitur.', + 'Nunc tincidunt malesuada dignissim.', + 'Nullam ornare venenatis purus semper porta.', + 'Nullam nisl massa, porttitor non gravida id, bibendum at dui.', + 'Curabitur rhoncus at massa ac ultricies.', + 'Phasellus aliquet ex in quam placerat feugiat.', + 'Nullam placerat in quam vitae venenatis.', + 'Vivamus quis mi accumsan, egestas nunc ac, laoreet arcu.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed faucibus imperdiet tortor vitae dapibus.', + 'Morbi neque sapien, aliquam eget leo in, maximus feugiat eros.', + 'In interdum ipsum quis dictum posuere.', + 'Vestibulum pellentesque ullamcorper rutrum.', + 'Vivamus sed lacus imperdiet, vehicula augue id, aliquet magna.', + 'Donec mollis auctor consectetur.', + 'Proin ultrices orci tellus, in volutpat leo pellentesque id.', + 'Donec sapien mauris, sagittis iaculis iaculis non, iaculis at nunc.', + 'In dapibus leo ac elit gravida, in ornare tortor mattis.', + 'Nam ac auctor arcu, id semper ex.', + 'Sed vitae mauris sagittis, vestibulum sapien sed, consectetur tortor.', + 'Sed dapibus metus vitae condimentum sagittis.', + 'Integer purus leo, pretium id vulputate sit amet, tempus at quam.', + 'Donec egestas laoreet efficitur.', + 'Phasellus et ante vehicula, consectetur lacus ut, vehicula nunc.', + 'Vestibulum ultricies, ligula et consequat fermentum, nisi mauris imperdiet turpis, tincidunt mollis felis tellus sed quam.', + 'Praesent id arcu dui.', + 'Suspendisse suscipit, velit vel tempus ultricies, turpis magna facilisis lectus, et varius enim sem vel enim.', + 'Vivamus id elit turpis.', + 'Duis porta, nibh sit amet pharetra dapibus, massa neque elementum turpis, et porta ante ex at libero.', + 'In tempus, sem a pulvinar viverra, turpis nulla malesuada metus, vitae posuere augue odio at nunc.', + 'Donec lectus sem, cursus ut vestibulum vitae, facilisis ac mi.', + 'Maecenas bibendum nisl eu libero hendrerit, vel mollis lorem tristique.', + 'Nulla cursus cursus mauris.', + 'Vestibulum laoreet augue ac nunc pulvinar, vestibulum ullamcorper purus volutpat.', + 'Vivamus iaculis euismod accumsan.', + 'Vestibulum eu dui feugiat, consectetur libero id, consectetur arcu.', + 'Nam lobortis ut turpis ac convallis.', + 'Duis dapibus turpis nec aliquet porta.', + 'Suspendisse id gravida nunc.', + 'Nullam tristique risus et magna dictum bibendum.', + 'Morbi semper tellus id arcu dictum, vitae accumsan purus sollicitudin.', + 'Nunc id laoreet diam.', + 'Praesent congue, elit ac molestie pharetra, nulla orci viverra turpis, quis tristique dolor sem quis magna.', + 'Donec eu ligula mauris.', + 'Etiam nec pretium arcu, et vehicula leo.', + 'Pellentesque non libero eu justo aliquet dictum.', + 'Mauris vel aliquet nulla.', + 'Aliquam vitae pharetra purus.', + 'Morbi aliquam aliquet malesuada.', + 'Duis id luctus arcu.', + 'In rutrum mattis leo.', + 'Sed mattis augue sed nulla porta, sit amet efficitur libero aliquet.', + 'Vivamus quis dignissim nibh.', + 'Donec accumsan vitae nisl ac placerat.', + 'Maecenas in neque nunc.', + 'Donec placerat quis ex id egestas.', + 'Quisque aliquam ex vel convallis eleifend.', + 'Mauris sodales elementum risus, quis fermentum augue vulputate a.', + 'Suspendisse sollicitudin finibus tellus, vitae sollicitudin eros tincidunt et.', + 'Nulla ut ex in eros consectetur aliquam.', + 'Ut maximus nulla tellus, eget tincidunt nulla auctor a.', + 'Vivamus accumsan dictum felis, sit amet tempor sapien pulvinar at.', + 'Morbi interdum, justo in scelerisque varius, purus enim vulputate nisi, consectetur interdum odio lorem vel justo.', + 'Aenean vitae hendrerit tortor.', + 'Suspendisse maximus imperdiet mi eu pellentesque.', + 'Quisque ultrices ultrices tortor, eget tristique odio pretium nec.', + 'Fusce metus dui, ultrices vel bibendum non, elementum sit amet magna.', + 'Nullam euismod ligula non ligula pulvinar, eu elementum velit cursus.', + 'Donec neque quam, feugiat nec vehicula non, ultricies non ex.', + 'Vivamus felis dui, vulputate in sollicitudin id, blandit nec odio.', + 'Pellentesque risus elit, mattis a purus vitae, varius auctor eros.', + 'Donec non rhoncus magna.', + 'Vestibulum volutpat orci enim, sed cursus massa vehicula ut.', + 'Vestibulum in rhoncus mi.', + 'Fusce vitae pulvinar nunc.', + 'Nam sit amet urna et lacus auctor accumsan et malesuada mauris.', + 'Curabitur ac eleifend urna.', + 'Maecenas a justo sed augue consequat blandit nec sit amet lacus.', + 'Phasellus vehicula est diam, in pharetra turpis porta ut.', + 'Nunc arcu lorem, pretium vitae feugiat in, elementum ac purus.', + 'Phasellus eget mollis diam, eu lacinia mi.', + 'Aenean eget lectus nulla.', + 'Integer sapien nibh, blandit quis libero mattis, ultricies consequat nisl.', + 'Nunc ac sagittis ligula, vel varius ante.', + 'Proin ut turpis a erat viverra lobortis eu quis quam.', + 'Mauris eu augue vel nisl interdum accumsan.', + 'Ut ut euismod leo.', + 'Aliquam urna turpis, blandit nec suscipit non, convallis at ante.', + 'Nulla quis molestie erat, ut venenatis nunc.', + 'Fusce pellentesque sit amet felis eu mattis.', + 'Mauris felis elit, gravida at dapibus ut, cursus at ante.', + 'Praesent in lacus euismod, porta ipsum id, feugiat mauris.', + 'Maecenas in eros nec arcu aliquet hendrerit.', + 'Duis bibendum rutrum mi.', + 'Donec elementum, felis eu fringilla placerat, mi lacus molestie tortor, et varius libero justo in tortor.', + 'Integer rutrum at neque et luctus.', + 'Nullam faucibus tempus metus, vitae dignissim leo viverra a.', + 'Integer augue erat, aliquet id tortor a, convallis dignissim tellus.', + 'Morbi elementum mollis tellus, id interdum arcu sodales sit amet.', + 'Suspendisse a sapien quis nibh convallis laoreet eu eget sem.', + 'Aliquam blandit odio vel nulla rhoncus dapibus.', + 'Nullam mollis tristique ligula, in tristique mauris eleifend id.', + 'Pellentesque eget consequat nunc.', + 'Donec et orci hendrerit, lacinia nisl in, pretium ex.', + 'Quisque sit amet eleifend tortor.', + 'Proin viverra dui eget tortor faucibus hendrerit.', + 'Etiam pretium fringilla justo, quis ornare risus posuere quis.', + 'Sed commodo maximus mauris, eu vulputate metus consectetur eget.', + 'Maecenas in arcu porttitor, condimentum arcu at, pretium ante.', + 'Ut vel urna viverra diam gravida condimentum non at enim.', + 'In ligula orci, malesuada convallis est a, egestas auctor elit.', + 'Sed blandit sagittis ipsum, id tempus diam ornare at.', + 'Mauris volutpat faucibus magna ac sodales.', + 'Fusce eget arcu et nunc rhoncus egestas et vel metus.', + 'Vestibulum egestas euismod bibendum.', + 'Ut id ex cursus, congue nulla ac, finibus mi.', + 'Suspendisse vitae tempor arcu.', + 'Integer ultricies orci purus, non finibus nisi laoreet in.', + 'Vestibulum metus purus, sodales rutrum tincidunt pharetra, eleifend vel dui.', + 'Pellentesque convallis dolor sed consequat pretium.', + 'Fusce suscipit ex ante, ac accumsan quam eleifend non.', + 'Donec porta, neque eu cursus cursus, enim arcu consequat neque, cursus laoreet dolor nunc at sem.', + 'Praesent sit amet arcu eros.', + 'Donec congue tellus nec mi ullamcorper pretium.', + 'Duis aliquet, orci eu facilisis dapibus, mauris erat congue neque, in commodo diam libero maximus urna.', + 'Curabitur interdum et metus at ullamcorper.', + 'Nulla a dui purus.', + 'Proin sed dapibus risus.', + 'Nunc consectetur posuere maximus.', + 'In sodales sem nisl, ut varius est venenatis at.', + 'Vestibulum non consectetur lorem.', + 'Nunc id risus sapien.', + 'Sed tristique condimentum tortor, tincidunt vulputate turpis.', + 'Ut neque purus, molestie eu varius vitae, venenatis sit amet nulla.', + 'Quisque dapibus libero libero, sed blandit lectus blandit sed.', + 'Nulla congue urna quis metus molestie vehicula.', + 'Vivamus sed aliquet felis, volutpat dignissim metus.', + 'Duis id augue neque.', + 'Nullam placerat mollis malesuada.', + 'Aenean malesuada elit ac ante imperdiet posuere sed et nibh.', + 'Vivamus id interdum ligula.', + 'Nunc nisl tellus, aliquam nec tristique et, elementum non est.', + 'Praesent fermentum gravida dolor, eu viverra justo posuere id.', + 'Ut nec iaculis velit, quis pretium mi.', + 'Vivamus scelerisque dolor sit amet erat dapibus consectetur.', + 'Pellentesque dolor risus, maximus quis gravida non, viverra sit amet dui.', + 'Phasellus placerat lectus velit, id ultrices augue posuere id.', + 'In consequat aliquet justo, in ornare turpis tempus sed.', + 'Fusce condimentum rhoncus condimentum.', + 'Phasellus convallis, sem molestie scelerisque congue, est justo lobortis diam, id commodo ligula ipsum eu nibh.', + 'Etiam id dolor vehicula, vehicula orci vitae, elementum dolor.', + 'Proin sit amet massa et dui varius auctor at nec ex.', + 'Donec ultrices sem vel nisi fermentum vulputate.', + 'In viverra quam ante, sit amet efficitur est varius sed.', + 'Sed vestibulum, tortor sed tincidunt congue, eros turpis mollis ipsum, id dictum nibh ipsum et metus.', + 'Proin nec sem eros.', + 'Donec vulputate lacus nisl, nec consectetur nulla blandit ac.', + 'Nulla sapien ipsum, tristique a mi sed, iaculis accumsan sapien.', + 'Etiam vitae est quis purus rhoncus blandit.', + 'Proin suscipit nec tortor sit amet malesuada.', + 'Aenean felis odio, facilisis sit amet enim in, condimentum sollicitudin ante.', + 'Nullam porttitor vel nulla sit amet feugiat.', + 'Phasellus mi libero, vulputate eu magna at, molestie aliquet erat.', + 'Mauris eros lorem, malesuada nec ligula vitae, pellentesque consequat est.', + 'Donec sodales pellentesque mi vitae pulvinar.', + 'Morbi sapien nisi, commodo nec ultricies et, iaculis nec nisl.', + 'Vestibulum maximus luctus elit malesuada tincidunt.', + 'Duis eget varius orci, eget sagittis eros.', + 'Quisque eu eros nisl.', + 'Pellentesque eleifend aliquam metus, a finibus neque sodales eu.', + 'Sed tempus ligula sapien, eu scelerisque nisl porttitor gravida.', + 'Sed vitae lectus sit amet lectus ornare interdum.', + 'Aenean arcu diam, porta sit amet lorem eget, sagittis iaculis erat.', + 'Nulla pharetra, sem non auctor ultrices, ex metus dictum magna, at sollicitudin ex justo ut turpis.', + 'Sed aliquam luctus semper.', + 'Nullam vitae malesuada sapien, tempor scelerisque lorem.', + 'Integer congue lorem ligula, ac volutpat lorem sagittis id.', + 'Nam nec pretium elit.', + 'Quisque volutpat ex dui, a sagittis augue consequat tincidunt.', + 'Cras tristique felis arcu, at vestibulum enim scelerisque sollicitudin.', + 'Nunc est metus, semper vel magna et, tincidunt scelerisque felis.', + 'Morbi lacus nisl, porttitor ac justo non, ultricies fringilla tellus.', + 'Ut eu metus metus.', + 'Proin vehicula vestibulum sollicitudin.', + 'Sed iaculis sem non ante ultricies, at semper libero porttitor.', + 'Ut dapibus laoreet sem ac consectetur.', + 'Etiam hendrerit, odio sit amet iaculis semper, ligula sapien rhoncus lorem, sit amet cursus eros ante sed nunc.', + 'Etiam interdum pellentesque enim id dapibus.', + 'Fusce luctus orci tortor.', + 'Cras id bibendum risus.', + 'Nam pretium felis nec ante tincidunt interdum.', + 'Nam cursus nibh non justo pharetra tristique.', + 'Nam facilisis dapibus lacus sit amet volutpat.', + 'Morbi felis ex, semper sed tortor eu, finibus aliquet lacus.', + 'Integer a orci augue.', + 'Vivamus lobortis tellus sed est fringilla ornare.', + 'Mauris tempus ante tortor, ac feugiat dui viverra et.', + 'Vivamus risus leo, ultrices at eleifend vel, luctus non arcu.', + 'Nullam dictum purus commodo turpis maximus, a mollis nibh efficitur.', + 'Nullam elementum dapibus suscipit.', + 'Nullam blandit sem lacus, vel blandit eros fermentum et.', + 'Sed cursus fermentum augue sit amet rutrum.', + 'Maecenas posuere, ante sed tristique ultrices, nisl orci molestie metus, sed vehicula mauris odio quis neque.', + 'Aliquam quis elit enim.', + 'Aenean cursus, turpis vel rhoncus imperdiet, dolor est consectetur metus, nec egestas elit orci ac ex.', + 'Fusce convallis justo eget dui ultrices, nec venenatis felis venenatis.', + 'Praesent eleifend, arcu eget auctor venenatis, nibh mi aliquet ligula, ac sagittis risus mi sed est.', + 'Phasellus pharetra, leo sed elementum aliquam, nisl justo lacinia ante, eu vehicula justo diam a sem.', + 'Praesent ac convallis neque, a dictum ipsum.', + 'Quisque venenatis nulla porta nunc semper sollicitudin.', + 'Duis a lectus velit.', + 'Phasellus lobortis, arcu et feugiat imperdiet, magna ipsum vestibulum massa, eget ullamcorper libero eros sed justo.', + 'Vivamus eget augue a ligula ultrices condimentum.', + 'Maecenas at eros a eros dictum mollis.', + 'Sed pellentesque tempor purus rhoncus pulvinar.', + 'Vestibulum faucibus urna quis convallis rutrum.', + 'Donec ultricies tempor nunc, a facilisis velit elementum non.', + 'Sed in risus in ante eleifend congue sed non nisl.', + 'Vestibulum in ante quis libero rhoncus sollicitudin id in quam.', + 'Sed imperdiet dapibus purus.', + 'Sed blandit, enim a cursus scelerisque, ante purus ornare massa, in dictum lacus neque id quam.', + 'Suspendisse tincidunt consectetur eros ac imperdiet.', + 'Sed ac justo feugiat, convallis ligula eget, interdum erat.', + 'Integer fermentum id arcu quis blandit.', + 'Curabitur iaculis, mauris at efficitur condimentum, justo lectus ornare quam, a euismod enim dolor gravida sem.', + 'Vivamus eu diam et nisl tincidunt pretium.', + 'Nullam sodales pulvinar urna in ultricies.', + 'Phasellus in condimentum enim.', + 'Vivamus eu mollis lacus.', + 'Etiam id ante eget dolor vehicula porta.', + 'Etiam purus urna, bibendum sollicitudin viverra quis, tristique id felis.', + 'Morbi porttitor tortor eget lorem commodo, nec pharetra urna vestibulum.', + 'Integer id faucibus massa.', + 'Duis interdum, quam quis ornare vulputate, eros est sodales ligula, a tincidunt mauris turpis ac diam.', + 'Mauris dictum nisl a vulputate cursus.', + 'Nulla cursus accumsan nisi, sit amet vulputate sapien semper ac.', + 'Vestibulum malesuada sodales condimentum.', + 'Sed tincidunt iaculis interdum.', + 'Maecenas quis nulla arcu.', + 'Fusce lacus urna, vulputate eu fringilla ut, vulputate quis magna.', + 'Vivamus a commodo neque.', + 'Donec maximus erat libero, eget sagittis magna tincidunt ac.', + 'Proin at dui id orci ornare vestibulum.', + 'Ut porttitor eget urna non efficitur.', + 'Nullam ut ligula est.', + 'Vestibulum vulputate, nulla sit amet laoreet egestas, ex mauris mattis metus, ut bibendum elit diam nec felis.', + 'Duis tempus finibus lorem, sit amet consequat dolor porta eu.', + 'Phasellus vel fringilla orci.', + 'Curabitur nec elit pulvinar mi vestibulum accumsan id quis augue.', + 'Ut ante neque, malesuada sed lacinia id, faucibus eget dui.', + 'In dui nibh, dignissim sed nisi at, feugiat vestibulum odio.', + 'Nunc ac lacus eleifend, laoreet tellus sit amet, condimentum nulla.', + 'Sed ac elit a lacus fermentum auctor in ac mi.', + 'Donec a justo in tellus laoreet facilisis.', + 'Pellentesque a enim dui.', + 'Vivamus sit amet rhoncus neque.', + 'Vivamus risus leo, aliquet ut sollicitudin vel, blandit in risus.', + 'Pellentesque eget tincidunt urna.', + 'Duis iaculis suscipit diam a tempus.', + 'Etiam posuere eu est vel congue.', + 'Nunc vel accumsan justo.', + 'Sed pharetra arcu vitae mauris eleifend, eu fermentum elit fermentum.', + 'In suscipit quam neque, ut dapibus urna venenatis in.', + 'Vivamus ultrices consequat risus, ac vestibulum orci porta finibus.', + 'Fusce sem orci, egestas ut feugiat sit amet, molestie quis arcu.', + 'Fusce vel ex nec justo ullamcorper viverra sit amet sed justo.', + 'Nunc sit amet leo consequat, commodo mauris quis, euismod mi.', + 'Ut finibus sapien ut dictum maximus.', + 'Suspendisse sed dui urna.', + 'Ut mattis et ex sit amet sagittis.', + 'Nulla vitae condimentum metus.', + 'Fusce sodales nulla metus, lacinia tincidunt sapien lobortis tincidunt.', + 'Vestibulum dapibus urna diam, nec ultricies neque feugiat et.', + 'Vestibulum eget quam ac lectus fermentum sagittis eu sit amet massa.', + 'Suspendisse quis diam eget felis faucibus tempus.', + 'Nullam nec consectetur urna.', + 'Aenean pharetra ullamcorper nibh, in maximus dui molestie non.', + 'Mauris laoreet, ex id volutpat bibendum, magna purus sodales lacus, eu fringilla ante ex ac metus.', + 'Suspendisse ac sapien massa.', + 'Donec pellentesque, ipsum in mollis accumsan, nisi risus sodales nisl, sed malesuada enim nibh quis mi.', + 'Duis varius viverra lacus non ultricies.', + 'Vestibulum venenatis id odio eget gravida.', + 'Suspendisse at quam et justo sollicitudin ornare eget ut felis.', + 'Etiam rhoncus ornare nisl ac tincidunt.', + 'Etiam vitae maximus tellus.', + 'In nibh leo, mattis vel tellus id, facilisis imperdiet lacus.', + 'Mauris sagittis ut erat eget porta.', + 'Nam a lectus laoreet, consequat elit ac, porta magna.', + 'Aenean commodo suscipit lorem, quis iaculis risus interdum ut.', + 'In cursus ullamcorper quam vel imperdiet.', + 'Nullam id mattis lectus.', + 'Cras sagittis massa urna, vitae mollis elit tincidunt a.', + 'Aliquam commodo urna quis nunc elementum ultricies at interdum massa.', + 'Sed pharetra urna eros, eget aliquam sem interdum id.', + 'Integer vestibulum dolor eget urna suscipit ultricies.', + 'Nam facilisis velit non mi pulvinar, eu mattis massa interdum.', + 'Sed faucibus, velit id tempor maximus, magna odio sodales ligula, vitae efficitur massa nunc non odio.', + 'Integer mattis lorem vitae turpis molestie faucibus.', + 'Praesent sagittis, quam quis placerat viverra, orci eros interdum mauris, ac iaculis erat lectus vel augue.', + 'Donec dapibus leo leo, accumsan tincidunt augue aliquet et.', + 'Ut non erat sed odio gravida blandit.', + 'Praesent placerat ante nulla, a iaculis magna lobortis in.', + 'Nulla erat nisi, pharetra at luctus vitae, cursus eget neque.', + 'Vivamus diam ante, pulvinar elementum vestibulum ut, fringilla quis lacus.', + 'Pellentesque ultrices odio placerat sollicitudin tincidunt.', + 'Nunc accumsan nisl nunc, tempor egestas odio elementum a.', + 'Vestibulum ut leo euismod, hendrerit tortor sed, eleifend purus.', + 'Aenean venenatis viverra elementum.', + 'Vivamus eu mattis quam, sit amet iaculis mi.', + 'Proin ultricies, arcu et tempus blandit, neque tortor vehicula felis, quis mattis magna metus non velit.', + 'Mauris imperdiet eu quam nec efficitur.', + 'Nam sodales sem at nulla laoreet, a luctus odio viverra.', + 'Morbi non lectus semper, interdum lectus ac, elementum purus.', + 'Vestibulum efficitur faucibus volutpat.', + 'Aliquam pellentesque, ex id laoreet gravida, sapien orci sodales dolor, vitae pretium turpis dolor a metus.', + 'Nulla metus tellus, porttitor in placerat non, laoreet eget nunc.', + 'Maecenas euismod massa et viverra consectetur.', + 'Curabitur est sapien, commodo vel urna lacinia, accumsan viverra libero.', + 'Maecenas fringilla, odio vitae congue malesuada, elit mi rhoncus erat, at hendrerit metus magna et eros.', + 'Suspendisse efficitur cursus purus quis commodo.', + 'Duis egestas sem urna.', + 'Suspendisse consectetur posuere purus id dignissim.', + 'Aenean ut congue velit.', + 'Etiam suscipit, tellus non laoreet maximus, orci dolor faucibus velit, quis vulputate leo dui in lacus.', + 'Ut iaculis metus ante, sed sollicitudin sem consequat ut.', + 'Fusce semper mattis turpis, vel elementum leo ornare eget.', + 'Donec magna nulla, vulputate quis lacinia sit amet, tempor ac mauris.', + 'Aenean interdum purus ligula, ut porttitor arcu aliquam vitae.', + 'Morbi venenatis sem in velit venenatis rutrum.', + 'Nullam porta leo convallis, molestie massa non, sagittis metus.', + 'Nunc posuere pretium augue, eu condimentum augue sollicitudin sit amet.', + 'Pellentesque elementum ipsum nec tincidunt aliquam.', + 'Pellentesque massa enim, vehicula quis euismod non, lobortis eget magna.', + 'Mauris posuere risus non velit vestibulum pretium non non ipsum.', + 'Cras vel ornare turpis, vel feugiat purus.', + 'Quisque odio eros, porttitor nec vulputate vitae, sollicitudin pretium purus.', + 'Maecenas imperdiet lacus a urna finibus fringilla sit amet et felis.', + 'Aenean quis ipsum tempus, pellentesque nunc mattis, tristique diam.', + 'Vestibulum vitae nunc hendrerit, gravida sem eu, tempus risus.', + 'Donec condimentum bibendum ipsum, a hendrerit neque posuere nec.', + 'Donec a dolor a massa maximus efficitur.', + 'Praesent velit massa, tempus ac semper quis, scelerisque vitae ante.', + 'Aliquam purus urna, hendrerit vitae sagittis at, porta vel justo.', + 'Curabitur pellentesque consectetur lobortis.', + 'Vivamus scelerisque hendrerit venenatis.', + 'Integer tincidunt ut diam sed congue.', + 'Sed ut aliquam nisi.', + 'Nullam nec eros id nunc semper luctus.', + 'Aliquam maximus eleifend dui, nec blandit massa bibendum eget.', + 'Donec interdum placerat tincidunt.', + 'Quisque non nulla sapien.', + 'Etiam tincidunt eros eget elit bibendum gravida.', + 'Quisque fringilla facilisis tortor quis ullamcorper.', + 'Integer gravida justo in iaculis posuere.', + 'Praesent sed tincidunt sapien.', + 'Sed euismod vitae ex vel scelerisque.', + 'Aenean nisi felis, ornare et feugiat eget, sodales vitae odio.', + 'Aenean libero sapien, lacinia ac sapien ac, laoreet dignissim dui.', + 'Nunc nibh massa, convallis in augue et, efficitur mattis elit.', + 'Suspendisse id nisl luctus, sollicitudin justo non, luctus arcu.', + 'Aliquam a est massa.', + 'Pellentesque dignissim mattis arcu.', + 'Aliquam efficitur ante metus, ut pellentesque felis suscipit eu.', + 'Ut facilisis vestibulum arcu elementum dignissim.', + 'Cras non sapien mauris.', + 'Maecenas interdum libero eu libero luctus, sit amet efficitur leo porttitor.', + 'Duis ut mollis ex.', + 'Quisque mi eros, suscipit vitae tempus ut, condimentum sit amet est.', + 'Ut non posuere erat.', + 'Curabitur bibendum magna turpis, non tincidunt risus dictum at.', + 'Etiam tempus magna at odio auctor, ac euismod nibh pulvinar.', + 'Morbi dapibus et sapien in elementum.', + 'Ut finibus est odio, eu convallis nunc viverra id.', + 'Quisque rhoncus mollis est sit amet semper.', + 'Ut dapibus urna sed diam ornare, eu efficitur leo feugiat.', + 'Vestibulum vel felis et erat molestie volutpat.', + 'Maecenas molestie lorem eget quam porta, a venenatis felis accumsan.', + 'In id auctor risus.', + 'Phasellus at diam sed orci porttitor tempus.', + 'Nunc dapibus, massa id ornare condimentum, mi sem ullamcorper nunc, et auctor felis felis id sem.', + 'Suspendisse nec vehicula augue.', + 'Aliquam imperdiet sagittis justo at iaculis.', + 'Praesent vulputate tellus ornare malesuada faucibus.', + 'Sed sed tristique ante.', + 'Curabitur ac augue et leo fermentum commodo eu in nulla.', + 'Aliquam pellentesque risus velit, a consequat dolor vestibulum ac.', + 'Fusce in dolor porttitor arcu viverra gravida.', + 'Maecenas ex orci, pretium vitae dui eget, scelerisque dapibus arcu.', + 'Praesent efficitur efficitur imperdiet.', + 'Curabitur eget tortor finibus, elementum orci quis, viverra arcu.', + 'Aenean ac tincidunt lacus.', + 'Donec vel ultricies est.', + 'Mauris mollis nisi a efficitur scelerisque.', + 'Nam dictum lacinia odio at pharetra.', + 'Quisque ultricies arcu in venenatis rutrum.', + 'In odio ipsum, euismod in posuere eget, placerat et felis.', + 'Curabitur mi est, placerat quis egestas non, mollis sed urna.', + 'Praesent malesuada, dolor in fermentum faucibus, tellus velit accumsan ipsum, tincidunt luctus turpis nulla sed enim.', + 'Integer rhoncus, turpis id tincidunt pulvinar, metus orci cursus mi, nec feugiat lorem elit ut enim.', + 'Aliquam et felis vel elit porta cursus in sit amet diam.', + 'In a vehicula eros, eu ullamcorper turpis.', + 'Vivamus eleifend libero non nulla accumsan porttitor.', + 'Suspendisse vel neque ultrices, scelerisque lacus in, ornare massa.', + 'Duis dolor leo, ullamcorper vel lacinia eget, aliquam rhoncus risus.', + 'Vivamus dapibus ac elit sed imperdiet.', + 'Vestibulum eget auctor dui, at tempus dolor.', + 'Sed consequat placerat libero, et sodales sapien porttitor non.', + 'Aenean arcu diam, imperdiet sit amet purus a, ornare sodales metus.', + 'Integer accumsan ante sem, at facilisis ipsum egestas et.', + 'Nulla non orci dolor.', + 'Sed rhoncus facilisis condimentum.', + 'Nulla vitae maximus nisl.', + 'Praesent a rhoncus ante, a pharetra ante.', + 'Fusce volutpat eu risus nec eleifend.', + 'Suspendisse nibh leo, semper in egestas eget, placerat vel nulla.', + 'Ut malesuada condimentum eros, id dignissim nunc imperdiet ac.', + 'Praesent posuere tortor vitae augue convallis malesuada.', + 'Donec congue sem eu leo dignissim, at blandit felis blandit.', + 'In auctor, sapien quis hendrerit auctor, arcu tellus aliquam ante, quis vulputate purus metus eget mauris.', + 'Proin eget purus purus.', + 'Vestibulum pretium pharetra egestas.', + 'Proin vulputate augue non odio commodo, eu varius sem porta.', + 'Quisque porta massa quis finibus dignissim.', + 'Sed sit amet lectus sit amet elit porta rutrum.', + 'Nunc ornare vulputate tellus, eu rutrum turpis sagittis rutrum.', + 'Nam elit justo, laoreet a tortor et, tempus dapibus sapien.', + 'Sed velit augue, maximus et dignissim sed, mollis id mi.', + 'Integer eget libero consequat, placerat massa maximus, efficitur dui.', + 'Praesent quis ipsum a ex ultricies euismod a sit amet mi.', + 'Curabitur at accumsan urna.', + 'Cras pulvinar leo sit amet ligula suscipit mattis.', + 'Morbi dapibus facilisis euismod.', + 'Quisque efficitur venenatis eros ac elementum.', + 'Suspendisse imperdiet nunc non libero consectetur, eget blandit libero mattis.', + 'Vivamus tempor ullamcorper sapien vitae aliquam.', + 'In eget tempus ante.', + 'Vestibulum accumsan enim sed est eleifend, non commodo orci tristique.', + 'Nam pellentesque nisi a laoreet lobortis.', + 'Sed faucibus eros nec urna elementum, sed tincidunt est elementum.', + 'Suspendisse bibendum, velit nec tempus ullamcorper, urna elit auctor mauris, et tempus dui purus ut sapien.', + 'Aliquam posuere pulvinar lorem, ac vulputate neque congue eget.', + 'Vivamus sit amet elit id ante pellentesque rhoncus.', + 'Maecenas sagittis dui justo, id bibendum nulla egestas ac.', + 'Mauris placerat sapien urna, eget porta nisi molestie eu.', + 'Pellentesque metus arcu, gravida eget erat et, condimentum sodales quam.', + 'Ut finibus tortor ac dui vestibulum, et interdum elit fringilla.', + 'Aliquam a fringilla augue.', + 'Aenean placerat, enim nec dapibus viverra, arcu leo pharetra nibh, id pretium nisl purus et orci.', + 'Praesent felis odio, bibendum ut rutrum non, vehicula mollis tortor.', + 'Nam id vulputate magna.', + 'Vivamus ultrices purus vitae risus vehicula, et varius magna fringilla.', + 'Integer mollis, arcu in aliquet eleifend, augue felis suscipit augue, vitae consectetur sem elit non neque.', + 'Suspendisse dictum, lectus et sagittis dictum, turpis lorem ultrices odio, vitae tincidunt ex dolor vitae leo.', + 'Nulla aliquet risus ut augue finibus, ac egestas ante varius.', + 'In eget est a urna pulvinar bibendum quis eget dui.', + 'In sodales auctor imperdiet.', + 'Aenean et ipsum commodo, gravida erat vel, molestie nunc.', + 'Etiam maximus nibh finibus ex aliquet aliquam.', + 'Aliquam ultricies tellus lectus, ac suscipit tellus congue ac.', + 'Ut id ullamcorper urna.', + 'Aliquam ut faucibus nunc.', + 'Morbi vel elit dapibus, faucibus sem quis, feugiat lacus.', + 'Sed euismod diam mi, ac lacinia diam ornare sed.', + 'Duis dictum sodales turpis at feugiat.', + 'Donec id orci maximus, venenatis metus quis, tincidunt sapien.', + 'Etiam et justo non orci elementum bibendum ut sed elit.', + 'Donec imperdiet porta augue eget suscipit.', + 'Donec posuere dui eget quam faucibus hendrerit.', + 'Nunc non nibh mi.', + 'Nullam augue ex, tincidunt nec turpis pretium, porttitor tempor neque.', + 'Integer vel neque commodo, consectetur nulla a, blandit risus.', + 'Quisque maximus est condimentum hendrerit placerat.', + 'Morbi sagittis posuere feugiat.', + 'Mauris nec lacinia dolor, eu accumsan ante.', + 'Sed eu lorem auctor, gravida ligula vitae, auctor arcu.', + 'Curabitur porttitor et lacus nec tempor.', + 'Nulla sed posuere lorem, at facilisis quam.', + 'Sed ornare leo vitae ipsum condimentum, in ullamcorper magna rhoncus.', + 'Nullam quis augue tristique, scelerisque turpis sit amet, placerat tortor.', + 'Suspendisse aliquam magna a suscipit egestas.', + 'Fusce dictum consectetur mattis.', + 'Etiam commodo iaculis neque quis scelerisque.', + 'Nulla eu condimentum lectus, vitae tincidunt nisi.', + 'Vestibulum lacinia ac ligula quis fringilla.', + 'Pellentesque aliquam posuere nunc sed malesuada.', + 'Fusce a sem lobortis, egestas tortor eu, mollis sapien.', + 'Praesent malesuada consequat ante in hendrerit.', + 'Pellentesque lorem ligula, sodales quis suscipit at, venenatis in risus.', + 'Phasellus sapien nibh, tincidunt ut suscipit non, hendrerit id turpis.', + 'Maecenas ut lorem non risus efficitur tristique ac at arcu.', + 'Phasellus consequat urna ligula, a luctus justo cursus ac.', + 'Praesent et augue augue.', + 'Morbi accumsan neque id nisl malesuada, quis cursus sapien blandit.', + 'Phasellus ultrices dignissim neque a posuere.', + 'Mauris eu vehicula nunc.', + 'Sed egestas nisi dui, at lobortis dui condimentum vitae.', + 'Quisque sit amet dui eu massa molestie malesuada iaculis in nulla.', + 'Phasellus et molestie lacus.', + 'Pellentesque egestas iaculis tortor, ac tempus ante commodo non.', + 'Maecenas ullamcorper dictum tortor ut luctus.', + 'Curabitur id dui quis felis pharetra elementum vitae nec elit.', + 'Nunc dictum malesuada ante, in elementum nulla ornare ut.', + 'Vivamus luctus lacus id venenatis eleifend.', + 'Suspendisse a venenatis turpis.', + 'Sed suscipit feugiat massa sed molestie.', + 'Suspendisse vitae ornare quam.', + 'Vivamus tincidunt metus sed aliquet sodales.', + 'Ut quis massa a magna vulputate dictum in vitae ex.', + 'Phasellus nulla elit, volutpat eu tincidunt eu, fringilla interdum metus.', + 'Praesent dignissim felis nec est molestie, in commodo lectus tempor.', + 'Donec quis mi venenatis, suscipit nibh eget, euismod dui.', + 'Morbi enim elit, tempor at euismod quis, lacinia sit amet risus.', + 'Vestibulum vitae pharetra magna, non luctus mauris.', + 'Nullam vel luctus arcu, condimentum posuere libero.', + 'Ut ullamcorper, dolor ac interdum auctor, massa leo vehicula urna, sed interdum lacus magna in quam.', + 'Integer pretium pulvinar sem, eget vehicula sem egestas vel.', + 'Aenean malesuada odio eget fermentum facilisis.', + 'Suspendisse finibus, tortor sit amet porta ultricies, sapien dolor sodales metus, a commodo risus nulla vel justo.', + 'Integer nec justo at neque aliquam ultrices a quis libero.', + 'Ut sem ligula, facilisis ut turpis quis, convallis porta ex.', + 'Nam sed ullamcorper ipsum, ut tincidunt risus.', + 'Curabitur nisi nisl, finibus et maximus id, bibendum hendrerit risus.', + 'Suspendisse molestie laoreet quam, in lobortis ipsum sodales id.', + 'Integer et suscipit nisi.', + 'Mauris mattis porta malesuada.', + 'Vivamus interdum blandit dolor, vitae egestas turpis rutrum eget.', + 'In a est sit amet dolor tristique vehicula.', + 'Sed et vehicula magna, eget laoreet dui.', + 'In convallis sem in maximus luctus.', + 'Vestibulum vestibulum convallis diam, nec tristique nisl cursus vel.', + 'Suspendisse nec lorem eget ligula venenatis placerat et et massa.', + 'Vestibulum efficitur ac libero vel fermentum.', + 'Cras condimentum diam turpis, sit amet egestas neque hendrerit ac.', + 'Pellentesque mattis mollis dignissim.', + 'Donec eleifend magna ac pulvinar sagittis.', + 'Vestibulum accumsan vulputate odio et molestie.', + 'Vestibulum accumsan volutpat interdum.', + 'Fusce vestibulum feugiat odio bibendum bibendum.', + 'Mauris non blandit nibh, id mattis dolor.', + 'Nunc interdum ipsum quis sem scelerisque sagittis.', + 'Integer sit amet dapibus velit, sit amet facilisis sem.', + 'In hendrerit sapien vel nulla rutrum, at lobortis quam gravida.', + 'Etiam euismod est quis leo convallis, eu interdum risus viverra.', + 'Nam ullamcorper imperdiet erat, ut efficitur nunc molestie a.', + 'Sed eget tellus cursus, ullamcorper odio sit amet, semper est.', + 'Aliquam suscipit urna ex, quis sodales nisi dapibus non.', + 'Nulla quis leo non tellus sollicitudin suscipit.', + 'Fusce in aliquet nulla.', + 'Donec risus tellus, imperdiet sed vulputate ut, pulvinar malesuada quam.', + 'Morbi id ligula leo.', + 'Fusce varius mauris dui, vel placerat magna efficitur eget.', + 'Morbi vulputate et lectus a porta.', + 'Quisque porta tortor sapien, quis rhoncus libero maximus volutpat.', + 'Cras sodales ex nec tortor finibus, aliquet scelerisque sem pellentesque.', + 'Aliquam ornare sodales quam.', + 'Donec eleifend ornare velit, in mollis elit.', + 'Nunc bibendum venenatis dui, sit amet scelerisque ex blandit eget.', + 'In non lacus iaculis, dictum urna id, placerat lorem.', + 'Vestibulum quis sem imperdiet, pellentesque neque ac, varius justo.', + 'Phasellus porta, augue at mattis dignissim, erat tortor porttitor leo, eu pretium purus lectus quis diam.', + 'Nam scelerisque, turpis eget pharetra sollicitudin, erat lacus tincidunt odio, at condimentum augue eros nec lectus.', + 'Proin vestibulum, tortor non maximus sodales, quam nibh gravida risus, vitae porta ex nisi eget velit.', + 'Cras at orci eu tortor vulputate facilisis nec in ex.', + 'Donec a turpis pulvinar, gravida nisl ut, suscipit justo.', + 'Vestibulum pharetra, lacus eu sodales vestibulum, eros lectus ullamcorper odio, in vulputate dui leo a enim.', + 'Aenean at consectetur quam, in elementum ipsum.', + 'Vestibulum maximus aliquam leo, vitae accumsan felis hendrerit varius.', + 'Sed bibendum vestibulum nibh, scelerisque dictum ex feugiat et.', + 'Suspendisse placerat dolor quis aliquam maximus.', + 'Sed sed enim convallis, sodales nulla id, molestie nisi.', + 'Aliquam at iaculis ante.', + 'Cras blandit hendrerit accumsan.', + 'Vestibulum convallis nisi vel dui luctus, sit amet malesuada mi tincidunt.', + 'Nunc tempor eget massa porta dignissim.', + 'Proin ut congue neque, sit amet maximus felis.', + 'Mauris ultrices eleifend nunc.', + 'Maecenas maximus mauris ac sagittis volutpat.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras ac pulvinar diam.', + 'Donec mollis mi eu arcu convallis mattis.', + 'Donec ipsum lectus, placerat sed consectetur id, ultricies nec tortor.', + 'Cras tellus augue, faucibus et felis ut, vehicula pretium sem.', + 'Fusce sit amet cursus leo, nec cursus dui.', + 'Curabitur massa leo, varius ut consectetur in, sodales sit amet nisi.', + 'In faucibus nibh id massa porttitor, vitae sollicitudin metus pretium.', + 'Phasellus ultrices erat enim, vitae mollis justo tincidunt at.', + 'Donec accumsan commodo quam non iaculis.', + 'Pellentesque viverra et magna eget sollicitudin.', + 'Suspendisse at dui eu diam mattis congue sagittis in magna.', + 'Fusce fermentum commodo arcu sed consectetur.', + 'Fusce nec orci lacus.', + 'Aliquam eu mauris accumsan, ullamcorper massa eu, facilisis augue.', + 'Curabitur vel tincidunt felis, vitae faucibus nibh.', + 'Cras mattis dignissim viverra.', + 'Phasellus sed erat congue, maximus quam id, blandit lectus.', + 'Pellentesque a volutpat magna.', + 'Aenean tempus, tortor sit amet porttitor consectetur, ante libero pulvinar urna, at euismod purus erat a turpis.', + 'Vestibulum congue interdum laoreet.', + 'Morbi auctor sollicitudin lacus nec feugiat.', + 'Etiam et justo eget elit egestas bibendum eget varius nibh.', + 'Phasellus tempor ullamcorper tellus, fermentum lobortis velit luctus vel.', + 'In commodo ac ligula sit amet maximus.', + 'Duis consectetur nibh velit, vitae tristique urna mattis at.', + 'Cras vitae risus at metus finibus vestibulum.', + 'Ut sit amet suscipit mauris.', + 'Fusce euismod dolor non nibh consequat viverra.', + 'Duis viverra orci magna, vel volutpat turpis pretium vel.', + 'Integer ultricies tempus augue ut ultrices.', + 'Ut gravida ante venenatis commodo dapibus.', + 'Fusce tincidunt id nisl nec tincidunt.', + 'Phasellus sed diam bibendum, tincidunt felis ac, malesuada augue.', + 'In convallis mauris non turpis convallis auctor.', + 'Vivamus dolor tortor, suscipit at vulputate vitae, ullamcorper vitae ipsum.', + 'Sed dictum eros neque, sit amet cursus felis condimentum ac.', + 'Sed laoreet diam eu euismod tempus.', + 'Phasellus ultricies suscipit lacus, at faucibus est varius ac.', + 'Praesent aliquam tristique interdum.', + 'Quisque quis porttitor ipsum, sed pellentesque arcu.', + 'Ut convallis eros sed tellus euismod posuere.', + 'Proin eget dictum lacus.', + 'Mauris scelerisque ex ac faucibus maximus.', + 'Donec at leo sed libero iaculis gravida sed ac enim.', + 'Proin a consequat ligula.', + 'In euismod tempus velit vel condimentum.', + 'Proin viverra convallis ipsum sit amet accumsan.', + 'Ut mi nisl, consequat a neque eu, aliquam placerat quam.', + 'Duis tempus ullamcorper risus ut finibus.', + 'Duis ac enim eros.', + 'In blandit malesuada tellus, in bibendum massa condimentum in.', + 'Vivamus quis orci libero.', + 'Cras eu condimentum ipsum.', + 'Maecenas lacinia lobortis euismod.', + 'Nulla at imperdiet nibh, eget lacinia augue.', + 'Curabitur sit amet pulvinar ipsum.', + 'Praesent tincidunt velit in nunc congue dictum.', + 'Cras arcu arcu, elementum sit amet molestie non, pellentesque id orci.', + 'Vestibulum eleifend faucibus magna pellentesque imperdiet.', + 'Curabitur convallis non nunc nec consectetur.', + 'Integer at maximus tellus, ac pellentesque velit.', + 'Etiam nec elementum turpis, et blandit sapien.', + 'Mauris iaculis pulvinar ipsum, ac vulputate lacus maximus sit amet.', + 'Mauris quis turpis in orci ornare posuere at quis lorem.', + 'Etiam egestas aliquam rhoncus.', + 'Maecenas metus ex, lobortis malesuada rutrum viverra, pellentesque quis ligula.', + 'Pellentesque a nunc orci.', + 'Integer vitae elit sodales nisl aliquet luctus nec eu augue.', + 'Pellentesque feugiat eget urna eu molestie.', + 'Quisque dolor sem, gravida id nisl nec, sodales hendrerit sapien.', + 'Vivamus vehicula neque lacus.', + 'Duis non justo et nunc consequat sagittis non in eros.', + 'Morbi nulla diam, interdum et massa at, eleifend lobortis nibh.', + 'Nunc sollicitudin pharetra tincidunt.', + 'Pellentesque nulla diam, bibendum ac dictum a, facilisis gravida est.', + 'Fusce tempus turpis fringilla pellentesque pretium.', + 'Aenean ultricies sapien dolor, ullamcorper auctor libero interdum eu.', + 'Duis quis velit in urna laoreet imperdiet id ut sem.', + 'Phasellus fermentum odio at tempor scelerisque.', + 'Donec semper viverra ex, ut hendrerit ante tristique vel.', + 'Cras vel tempor massa.', + 'Sed lacinia viverra vestibulum.', + 'Suspendisse libero elit, porta at enim eu, iaculis consectetur lectus.', + 'Pellentesque aliquet lorem vehicula sapien sagittis, vel tristique augue venenatis.', + 'Nunc felis diam, iaculis vitae tortor at, rutrum efficitur orci.', + 'Pellentesque mi metus, luctus a tellus eget, eleifend elementum tortor.', + 'Praesent aliquet quam efficitur urna blandit lacinia.', + 'Pellentesque euismod sodales ultrices.', + 'Quisque non arcu ut arcu molestie dapibus.', + 'Aenean euismod lacus mi.', + 'Morbi sodales massa sed nisl luctus, eget posuere tortor vehicula.', + 'Etiam interdum convallis enim eu sagittis.', + 'Mauris sollicitudin nisi eget diam placerat, ac malesuada ligula vestibulum.', + 'Phasellus in vulputate elit.', + 'Phasellus porta consequat scelerisque.', + 'Ut tincidunt eget quam et faucibus.', + 'Integer mi elit, blandit at vehicula non, porta ut odio.', + 'Donec sollicitudin varius finibus.', + 'Nam a venenatis massa.', + 'Vivamus sit amet porta arcu.', + 'Maecenas dui nunc, venenatis ac sem ac, elementum molestie tellus.', + 'Aliquam scelerisque, velit ac venenatis vestibulum, nisi ante semper risus, in eleifend diam lorem ac ligula.', + 'Aliquam pulvinar dui porttitor magna sagittis volutpat.', + 'Sed posuere tortor a tellus tincidunt semper.', + 'Nulla rhoncus id nunc vitae condimentum.', + 'Donec efficitur faucibus ex eget varius.', + 'Cras fermentum vestibulum tellus eget iaculis.', + 'Vestibulum vestibulum nec purus eget semper.', + 'Sed commodo purus arcu, a consequat felis sollicitudin a.', + 'Suspendisse mollis lectus sed nulla dapibus gravida.', + 'Sed fermentum sem et nunc venenatis luctus.', + 'Phasellus dapibus est non magna iaculis, vel venenatis ex ornare.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec malesuada commodo nibh, id vulputate enim viverra at.', + 'Sed venenatis viverra mattis.', + 'Aliquam tincidunt dignissim sem, et sodales purus mollis id.', + 'Nam vitae pellentesque augue, a congue magna.', + 'Quisque vitae blandit nisi, sed vehicula magna.', + 'Aliquam id diam ac purus sagittis pharetra mattis eget ipsum.', + 'Praesent pulvinar gravida mi, a ornare velit cursus ac.', + 'Aliquam bibendum vulputate consequat.', + 'Nulla elit ante, ultrices quis eleifend eu, egestas quis ipsum.', + 'Aliquam non auctor nulla.', + 'Curabitur in condimentum ex.', + 'Mauris vestibulum ligula sit amet tortor porttitor iaculis.', + 'Ut sollicitudin sed nunc eu iaculis.', + 'Integer mattis egestas tellus, et volutpat ligula placerat non.', + 'Aliquam id sem interdum, scelerisque arcu in, tempus elit.', + 'Etiam tristique, mi et euismod dictum, leo augue posuere tellus, vitae malesuada libero dolor sed urna.', + 'Donec interdum dolor metus, ac euismod magna pulvinar in.', + 'Cras at nunc at nibh sollicitudin tristique.', + 'Nullam nec quam porta, rhoncus diam et, porta erat.', + 'Vestibulum ut vestibulum ante.', + 'Aliquam tincidunt blandit metus ac varius.', + 'Vivamus sollicitudin, quam a suscipit volutpat, leo eros commodo ex, sed sodales urna diam sed nulla.', + 'Sed a nibh ac felis efficitur luctus sit amet mollis leo.', + 'Vestibulum tempor urna quis erat accumsan hendrerit.', + 'Etiam euismod mauris non est iaculis dignissim.', + 'Nunc in dui ultrices, placerat tellus ut, sollicitudin magna.', + 'Aliquam efficitur nunc ac elit viverra laoreet.', + 'Pellentesque dui nisl, viverra vitae venenatis eu, ultricies vel risus.', + 'Integer volutpat quam non erat condimentum placerat.', + 'Maecenas molestie odio vel ultrices porta.', + 'Integer ullamcorper mollis elementum.', + 'Suspendisse mi sapien, mattis ut posuere vitae, vulputate a nisi.', + 'Nulla et ullamcorper odio.', + 'Mauris nec arcu massa.', + 'Aliquam quis eleifend ante.', + 'Nulla dignissim pulvinar hendrerit.', + 'Aenean lobortis tempus condimentum.', + 'Sed rhoncus metus quis mi ullamcorper tincidunt.', + 'Phasellus augue nisi, auctor quis posuere sed, mattis eu libero.', + 'Mauris pharetra ac libero at sodales.', + 'Cras efficitur enim ut tempor convallis.', + 'Donec lectus lorem, consectetur quis felis sed, vehicula suscipit massa.', + 'Vestibulum posuere viverra ultrices.', + 'Vivamus mollis cursus nibh sed fermentum.', + 'Vestibulum in varius ligula.', + 'Donec sit amet est scelerisque, sodales odio a, dictum ligula.', + 'Suspendisse consequat laoreet est, sit amet pulvinar elit sodales vitae.', + 'Nunc fermentum sodales eros, at vestibulum arcu vulputate ut.', + 'Integer faucibus aliquet eros.', + 'Suspendisse metus quam, placerat nec rutrum a, feugiat vel sapien.', + 'Donec malesuada, eros id blandit scelerisque, tellus libero ultricies leo, ac accumsan arcu metus eu nisl.', + 'Integer vitae arcu turpis.', + 'Sed eget congue orci, vel porta tellus.', + 'Integer ligula nisl, finibus eu sollicitudin ac, malesuada vitae nisi.', + 'Donec sit amet vulputate metus.', + 'Phasellus enim sem, varius ac vulputate ut, dapibus id tellus.', + 'Nam non malesuada metus.', + 'Nulla scelerisque magna ut est imperdiet, ac luctus sem sodales.', + 'In auctor neque enim, eu hendrerit eros fringilla nec.', + 'Duis quis purus rhoncus, malesuada enim vitae, bibendum nisl.', + 'Donec ut libero lacinia, tempus enim sed, volutpat metus.', + 'In commodo posuere nisi vitae faucibus.', + 'Maecenas felis odio, vehicula ac ullamcorper non, maximus id elit.', + 'Curabitur congue urna in mi venenatis euismod.', + 'Suspendisse hendrerit lacus ac risus tempor, non tristique urna venenatis.', + 'Suspendisse cursus urna ornare, varius risus sit amet, lobortis eros.', + 'Fusce consequat porttitor tortor in dignissim.', + 'Ut nulla magna, semper posuere ex quis, pharetra tempor quam.', + 'Morbi enim ligula, tincidunt id ligula ac, imperdiet pretium arcu.', + 'Maecenas quis risus malesuada nisl efficitur pretium.', + 'Integer at ante congue, luctus neque sed, dictum sem.', + 'Phasellus luctus diam nec risus porttitor posuere.', + 'Vestibulum ultrices tristique ex.', + 'Nulla id lacus erat.', + 'Vestibulum eu orci turpis.', + 'Ut feugiat auctor interdum.', + 'Donec at convallis dui.', + 'Phasellus placerat vitae dui eu tincidunt.', + 'Vestibulum metus lacus, fermentum id dignissim a, sagittis a nulla.', + 'In fermentum turpis in dui dignissim iaculis.', + 'Suspendisse vehicula ex vel imperdiet vestibulum.', + 'Suspendisse lobortis felis non augue lacinia ornare.', + 'Cras porta neque tellus, sed aliquet purus vulputate tincidunt.', + 'Nullam non purus tellus.', + 'Vivamus ex diam, condimentum ut ornare sit amet, venenatis pharetra turpis.', + 'Aliquam dui tortor, volutpat ut molestie eu, efficitur id nibh.', + 'Morbi vel ante et tellus rhoncus mattis id at sem.', + 'In vel tellus sapien.', + 'Aenean nec est finibus, iaculis orci a, molestie turpis.', + 'Proin ornare eget odio luctus sodales.', + 'Etiam hendrerit a nisl at pellentesque.', + 'Nunc blandit blandit mauris, eget blandit nisi luctus eget.', + 'Ut justo justo, imperdiet nec magna maximus, venenatis vestibulum leo.', + 'Cras a consequat quam.', + 'Vestibulum ac lectus ullamcorper, efficitur lacus quis, pulvinar est.', + 'Aenean nec odio elit.', + 'Nulla quis viverra odio, nec cursus erat.', + 'Vestibulum lobortis est nec sem dignissim, id luctus tortor congue.', + 'Morbi sollicitudin massa et justo aliquam volutpat.', + 'Aliquam bibendum tristique lacus sit amet mattis.', + 'Sed hendrerit lorem feugiat est feugiat ultricies.', + 'Cras dictum, turpis id imperdiet volutpat, nisl orci facilisis mi, in tincidunt arcu diam et dui.', + 'Mauris congue neque libero, vitae sagittis elit consectetur in.', + 'Cras commodo at sem et fermentum.', + 'Nullam consequat ligula in est pellentesque malesuada.', + 'In sit amet elit ac erat varius posuere scelerisque in lectus.', + 'Nunc ac odio vitae orci placerat mollis at in velit.', + 'Vivamus interdum nunc quis velit viverra consectetur.', + 'Nam sit amet semper arcu.', + 'Donec pellentesque feugiat lorem nec elementum.', + 'Quisque tincidunt maximus vehicula.', + 'Curabitur fermentum, ligula in sollicitudin tempus, odio libero euismod augue, vulputate pellentesque lorem nisi vehicula nulla.', + 'Aenean quis mauris et neque venenatis laoreet.', + 'Fusce id porta augue.', + 'Integer ac nunc vel enim ultrices consequat.', + 'Mauris magna purus, congue ut sapien in, molestie luctus nulla.', + 'Donec consequat, augue in tristique scelerisque, nisi sem tincidunt lorem, varius tempus est nunc in ipsum.', + 'Duis dictum ut est ac viverra.', + 'Aliquam id tincidunt ligula.', + 'Aliquam vitae tortor ut massa eleifend imperdiet sed et ipsum.', + 'Maecenas aliquet tellus ac nisl molestie, vel ullamcorper lacus vulputate.', + 'Mauris nec lacus sapien.', + 'Integer sagittis dolor sit amet velit mollis, quis sodales odio sollicitudin.', + 'Vivamus varius, libero a cursus imperdiet, lorem diam vestibulum risus, eget dictum lectus dolor eget eros.', + 'Nullam vitae orci varius, suscipit magna vitae, efficitur tellus.', + 'Praesent sagittis mi nec rutrum tempus.', + 'Aenean eu eros in erat gravida consectetur.', + 'Nulla lacinia vitae urna commodo ultricies.', + 'In interdum fermentum malesuada.', + 'Nullam placerat vel velit vel blandit.', + 'Praesent blandit tortor nec nisl viverra rutrum.', + 'Fusce ac euismod dui.', + 'Curabitur auctor quam dui, quis tristique ligula ornare et.', + 'Vivamus tellus diam, fringilla a congue quis, porta sit amet diam.', + 'Proin consectetur pulvinar malesuada.', + 'Duis tempor maximus libero non fringilla.', + 'Maecenas et elit leo.', + 'Fusce porttitor ex tortor, iaculis mollis sapien scelerisque eu.', + 'Etiam in dignissim tellus, nec egestas mauris.', + 'Aenean lacinia nec sapien quis suscipit.', + 'Ut id libero nec ligula laoreet rutrum.', + 'In id hendrerit nisl, sed luctus tortor.', + 'Morbi interdum augue justo, tincidunt suscipit sem lacinia vel.', + 'Aliquam a massa tortor.', + 'Sed eget libero id est pharetra laoreet.', + 'Morbi molestie, ex eu aliquet maximus, ante felis efficitur ligula, ac commodo justo augue quis velit.', + 'Integer lorem nulla, rhoncus a magna placerat, semper faucibus quam.', + 'Curabitur imperdiet aliquet diam nec scelerisque.', + 'Sed sed pellentesque risus.', + 'Sed tortor odio, vestibulum et augue a, maximus congue turpis.', + 'Donec pulvinar mi a enim rutrum, eu blandit neque molestie.', + 'Nullam tristique pulvinar sapien, ut consectetur velit mollis eu.', + 'Suspendisse vestibulum nisi leo, et ornare mi congue id.', + 'Integer urna nulla, molestie vitae arcu in, finibus aliquet felis.', + 'Maecenas laoreet venenatis felis, eget finibus urna pharetra sed.', + 'Pellentesque in turpis a nulla rutrum egestas vel in augue.', + 'Curabitur a lectus ac nulla porta vehicula.', + 'Cras quis euismod massa.', + 'Sed consequat arcu vitae gravida pellentesque.', + 'Donec rhoncus rhoncus imperdiet.', + 'Praesent pulvinar risus sed orci dignissim, sed tincidunt leo viverra.', + 'Aenean euismod maximus posuere.', + 'Quisque odio dolor, suscipit ut semper sed, molestie vel nulla.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque auctor, leo at vulputate tempus, est nisi imperdiet arcu, nec dignissim nunc mauris eget enim.', + 'Aliquam at posuere mauris, ac dignissim nibh.', + 'Donec lacinia lobortis tempus.', + 'Sed blandit varius enim et sodales.', + 'Morbi pretium vulputate ipsum et congue.', + 'Quisque scelerisque justo eget ullamcorper vestibulum.', + 'Cras quis erat ut turpis dictum aliquam rutrum vel ligula.', + 'Sed ipsum elit, congue quis mi sit amet, semper porttitor magna.', + 'Nulla vel tellus condimentum, accumsan ex ac, gravida mauris.', + 'Donec cursus, nisi in vestibulum suscipit, magna tellus interdum tortor, a tincidunt mi turpis vitae libero.', + 'Etiam consectetur, odio ut iaculis convallis, ex nisl rhoncus enim, vitae placerat sem dui eu elit.', + 'Curabitur libero tellus, posuere nec fringilla sed, lobortis nec quam.', + 'Curabitur a augue ligula.', + 'Mauris pharetra eu ante non porta.', + 'Cras efficitur velit sem.', + 'Proin at dapibus arcu.', + 'Integer nec purus vitae lorem aliquam placerat.', + 'Integer sem risus, ultricies sit amet magna eget, viverra vehicula augue.', + 'Morbi blandit ligula vitae interdum euismod.', + 'Vestibulum egestas eleifend pulvinar.', + 'Ut euismod ex rutrum, viverra augue non, molestie libero.', + 'Proin fringilla, urna eu condimentum pharetra, dolor quam sollicitudin mi, vitae consequat sapien purus id magna.', + 'Phasellus pulvinar vel massa eu ullamcorper.', + 'Etiam nunc leo, vestibulum a tortor quis, laoreet gravida odio.', + 'Nullam tristique consequat lacus nec aliquam.', + 'Pellentesque aliquet augue facilisis felis pretium faucibus.', + 'Integer vitae ultrices diam, id lobortis ipsum.', + 'In at augue nibh.', + 'In non gravida ante.', + 'Aenean euismod eros augue.', + 'Nunc ac metus eget mauris bibendum cursus.', + 'Etiam et massa eu lacus molestie dignissim.', + 'Proin dictum ante non urna sollicitudin eleifend.', + 'Mauris convallis ultricies neque sit amet semper.', + 'Morbi venenatis euismod quam sed gravida.', + 'Nulla elementum orci at justo scelerisque, vel dapibus eros scelerisque.', + 'Integer accumsan augue posuere, suscipit odio vitae, tincidunt nisi.', + 'Quisque in orci malesuada, lobortis neque sit amet, pretium odio.', + 'Integer tempus eget sapien non sodales.', + 'Nunc ut dapibus justo.', + 'Quisque id nulla elit.', + 'Nam quis justo eget velit convallis facilisis.', + 'Quisque iaculis ex lectus, sit amet dictum neque pellentesque volutpat.', + 'Aenean quis tellus lacinia, gravida elit non, interdum quam.', + 'Donec interdum orci ut mauris molestie lacinia.', + 'Donec risus elit, mattis et viverra placerat, viverra et turpis.', + 'Morbi in sodales urna.', + 'Nam lorem ligula, tempus vitae sapien eget, vulputate fermentum purus.', + 'Aenean vulputate, nulla id euismod aliquet, leo ligula ornare tortor, sed tincidunt ante metus non felis.', + 'Cras volutpat, dolor consectetur luctus volutpat, mauris eros laoreet augue, at vehicula purus enim in tortor.', + 'Cras mi est, tincidunt in sapien nec, tempor cursus purus.', + 'Phasellus rutrum nibh a sagittis feugiat.', + 'Aenean sit amet tincidunt enim, at tincidunt justo.', + 'Pellentesque maximus nisi vitae nibh porttitor gravida.', + 'Aliquam ac sem mollis, pulvinar ligula id, rhoncus turpis.', + 'Ut placerat turpis ac finibus fringilla.', + 'Sed sit amet est eu tortor efficitur dignissim.', + 'Mauris iaculis, dolor et lobortis congue, augue massa scelerisque libero, in lobortis augue dolor et libero.', + 'Mauris pharetra finibus turpis, non maximus quam pellentesque sit amet.', + 'Praesent feugiat, ex eu aliquam tempor, sem nibh tempus est, eget tincidunt velit quam eget est.', + 'Donec imperdiet enim tellus, vel tincidunt felis accumsan rutrum.', + 'Ut ac sem sit amet sem posuere luctus at in enim.', + 'Nulla euismod, libero vel finibus lobortis, nulla libero porttitor tellus, a interdum odio quam nec nunc.', + 'Phasellus quis efficitur risus, ullamcorper condimentum nisl.', + 'Aliquam id neque a nunc vehicula euismod.', + 'In nec ultrices eros.', + 'Nunc sit amet purus neque.', + 'Pellentesque pharetra nisl augue, sed sollicitudin eros faucibus eget.', + 'Morbi sit amet lorem eget enim cursus vehicula.', + 'Etiam euismod ante in venenatis bibendum.', + 'Curabitur eu efficitur tortor.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla interdum nibh in vehicula tincidunt.', + 'Sed fringilla, magna non malesuada dignissim, quam enim mollis tellus, non faucibus purus tellus a ipsum.', + 'Ut placerat varius diam in vulputate.', + 'Mauris aliquet lectus id lacus iaculis, nec viverra ante porttitor.', + 'Maecenas finibus posuere risus.', + 'Suspendisse sit amet lectus quis nunc tincidunt bibendum eget in felis.', + 'In mattis dolor purus, id tempus nibh rutrum ut.', + 'Phasellus ornare diam ac elit laoreet, ac egestas ante maximus.', + 'In et lacus leo.', + 'Nam volutpat id nulla non dictum.', + 'Nunc blandit eu libero at consequat.', + 'In pretium lacus ac leo malesuada condimentum.', + 'Maecenas congue dictum ultricies.', + 'Sed id tincidunt ex.', + 'Praesent gravida lectus id ante facilisis, nec pharetra justo rutrum.', + 'Praesent cursus lobortis accumsan.', + 'Aenean sed lacus ac arcu blandit placerat.', + 'Quisque vitae ligula vitae elit congue dictum.', + 'Nulla condimentum fermentum nulla ac porta.', + 'Cras ornare diam vitae augue maximus, quis faucibus dui fermentum.', + 'Maecenas porttitor porttitor felis, sit amet facilisis diam sodales vitae.', + 'Aliquam dictum arcu tortor, et fringilla leo euismod vel.', + 'Phasellus diam mauris, feugiat aliquet fermentum in, porttitor et tortor.', + 'Maecenas ex justo, sagittis sed magna sed, efficitur venenatis felis.', + 'In placerat et nunc et malesuada.', + 'Aenean vehicula neque odio, vitae molestie mauris aliquet quis.', + 'Vestibulum gravida dolor vel velit semper rhoncus.', + 'Ut egestas sodales nulla quis rutrum.', + 'Integer quis nisi nec enim vestibulum convallis.', + 'Fusce eget nisi rutrum justo porttitor gravida nec nec ipsum.', + 'Nam blandit nec leo ut porttitor.', + 'Suspendisse a nunc sed ante fringilla fermentum consectetur vitae orci.', + 'Nunc bibendum arcu erat, eu ullamcorper est placerat ac.', + 'Sed facilisis, enim a tempor ullamcorper, metus nunc interdum lacus, id pellentesque mauris magna ut augue.', + 'Curabitur eu dignissim velit.', + 'Sed ut quam erat.', + 'Sed faucibus sapien felis, ac malesuada nunc cursus et.', + 'Sed vestibulum lacus nec sapien ultrices, at euismod tellus dignissim.', + 'Integer sagittis vulputate lectus, sed scelerisque tortor pulvinar id.', + 'Integer finibus venenatis massa, eget fringilla arcu placerat et.', + 'Proin blandit neque a quam blandit mollis.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse potenti.', + 'Ut nec risus nisl.', + 'Vestibulum rhoncus pellentesque augue fringilla molestie.', + 'Praesent sodales eget sapien eu tristique.', + 'In nec purus leo.', + 'Vivamus vitae sem sed massa bibendum tempor eu id lectus.', + 'Ut id pulvinar nunc.', + 'Nam tempus dignissim lectus, ac pellentesque neque porttitor non.', + 'Suspendisse lobortis rhoncus dui, ac ullamcorper dolor dignissim sed.', + 'Vestibulum sollicitudin faucibus nisl a laoreet.', + 'Aenean consequat purus et lorem suscipit, sed efficitur magna facilisis.', + 'Mauris fermentum malesuada tortor et tempus.', + 'Maecenas interdum rutrum pretium.', + 'Morbi non neque eget lorem suscipit congue id eget quam.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse malesuada purus nibh, volutpat facilisis lacus iaculis a.', + 'Aenean eu pharetra ligula.', + 'Nulla nec leo porta, sollicitudin nunc et, lacinia quam.', + 'Nunc placerat tristique pellentesque.', + 'Ut enim nisi, condimentum quis felis ut, vestibulum cursus mi.', + 'Vestibulum ut ipsum eros.', + 'Proin vehicula bibendum enim, vitae laoreet ante auctor vitae.', + 'Donec imperdiet vestibulum congue.', + 'Sed velit mi, dictum in commodo at, semper a neque.', + 'Aliquam lectus turpis, vulputate at nunc et, laoreet porttitor turpis.', + 'Nullam eu mauris eget augue tincidunt ornare.', + 'Pellentesque sit amet leo eu nibh placerat sagittis.', + 'Curabitur et finibus odio.', + 'Fusce tristique non tellus sed egestas.', + 'Donec metus nunc, consequat non lorem sit amet, vehicula venenatis nisl.', + 'Aenean sodales molestie posuere.', + 'Mauris ut sollicitudin orci.', + 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc sit amet condimentum mauris, eu fermentum risus.', + 'Cras quis ante felis.', + 'Pellentesque id interdum neque.', + 'Phasellus erat tellus, tempus quis pulvinar volutpat, efficitur in odio.', + 'Maecenas a venenatis ex, at tempor libero.', + 'Pellentesque suscipit ipsum eget tincidunt faucibus.', + 'Aenean sollicitudin est odio, vitae vulputate ligula dapibus sit amet.', + 'Nam finibus arcu at dui blandit ornare.', + 'Integer id dapibus est, ut fringilla lorem.', + 'Aenean interdum quis dui nec pharetra.', + 'Maecenas non diam non elit fringilla tempor id sollicitudin purus.', + 'Nullam odio metus, aliquam eu dolor non, rutrum sodales mauris.', + 'Maecenas nibh sem, faucibus at turpis vitae, tristique vestibulum eros.', + 'Duis ac dolor eget augue luctus dignissim.', + 'Fusce eget libero hendrerit, placerat purus id, volutpat tellus.', + 'Fusce viverra, arcu ut molestie ultricies, dui tortor lacinia risus, eu auctor elit mauris eu orci.', + 'Praesent semper mi eu mi dignissim, imperdiet dictum urna tincidunt.', + 'Nulla in lorem id sapien rhoncus ultrices.', + 'Quisque imperdiet, tortor ac viverra blandit, massa sapien auctor tortor, at fermentum ex tortor sit amet sem.', + 'Fusce nisl libero, sollicitudin vel feugiat iaculis, consectetur ac diam.', + 'Aliquam velit arcu, faucibus id ornare a, pharetra et leo.', + 'Nullam eu fringilla felis.', + 'Donec dignissim risus facilisis bibendum tincidunt.', + 'Donec sed orci nunc.', + 'Fusce at metus lobortis, iaculis ante laoreet, aliquam nisi.', + 'Integer imperdiet ornare turpis, sed viverra felis mattis non.', + 'Nulla tincidunt purus sed fermentum volutpat.', + 'Phasellus eu dignissim massa, non consectetur lorem.', + 'Praesent venenatis, nulla nec ornare vulputate, velit ipsum lobortis nisl, sed consequat nibh turpis vel diam.', + 'Pellentesque pharetra odio lobortis, congue enim eu, sagittis velit.', + 'Mauris sed molestie tortor.', + 'Suspendisse consectetur nunc vel dolor aliquet, vel venenatis eros laoreet.', + 'Nullam malesuada nulla libero, dapibus pretium mi iaculis a.', + 'Donec placerat nibh leo, id tristique enim tincidunt sed.', + 'Nunc elementum, arcu nec ultrices convallis, urna turpis finibus velit, eu euismod nibh orci ac eros.', + 'Proin cursus pellentesque turpis, vel tempor lorem feugiat et.', + 'Duis finibus aliquet lacus, at accumsan tortor posuere nec.', + 'Aliquam id sem justo.', + 'Suspendisse eget dignissim eros.', + 'Suspendisse feugiat scelerisque ante id fermentum.', + 'Quisque pellentesque libero et sem pellentesque ultrices.', + 'Nunc dignissim magna interdum, feugiat odio sit amet, pharetra lacus.', + 'Duis in sapien aliquam, volutpat est sit amet, semper risus.', + 'Proin blandit enim at fermentum mattis.', + 'Proin pharetra, orci a semper porttitor, purus felis placerat lorem, nec mollis orci tellus sit amet elit.', + 'Duis sed convallis turpis, a pellentesque nibh.', + 'Proin et est vel nisl dictum aliquam et et sem.', + 'Donec sed augue laoreet, suscipit sem quis, consectetur est.', + 'Sed hendrerit arcu quis porttitor vestibulum.', + 'Pellentesque interdum lorem urna, et vestibulum enim pharetra et.', + 'Cras sed neque sit amet erat aliquam semper.', + 'Fusce sit amet eros lacus.', + 'Cras commodo, nibh et sodales vehicula, eros magna pellentesque orci, et laoreet lacus dolor nec enim.', + 'Donec et quam ultrices, porta justo tempor, auctor odio.', + 'Morbi eget iaculis nisi, ut finibus orci.', + 'Sed tellus lectus, consequat nec dapibus ac, interdum vitae lectus.', + 'Praesent accumsan vehicula lacus ut efficitur.', + 'Integer sit amet nisl non odio tincidunt sodales eu ut mauris.', + 'Duis euismod risus quis iaculis sodales.', + 'Pellentesque eleifend vel lorem sed iaculis.', + 'Duis a mollis ex.', + 'Nunc ullamcorper id felis non aliquam.', + 'Etiam ut posuere lacus.', + 'Duis quis elementum ligula.', + 'Aenean pulvinar elit a eleifend luctus.', + 'Vivamus accumsan tincidunt dui in pellentesque.', + 'Cras vulputate metus at suscipit vestibulum.', + 'Phasellus ultrices consequat lectus, ac tristique nunc mattis sit amet.', + 'Donec arcu diam, mollis ut euismod sit amet, tincidunt vel ipsum.', + 'Maecenas aliquet orci ac rutrum laoreet.', + 'Quisque maximus nisl sed sapien posuere pulvinar.', + 'Sed interdum, ante et laoreet iaculis, arcu ligula pretium mi, eget bibendum lorem urna sit amet odio.', + 'Praesent consequat nisl quis tellus mollis eleifend.', + 'Suspendisse facilisis sem vitae sapien rhoncus, ac dignissim nunc pharetra.', + 'Quisque id lacus ut neque vehicula pellentesque.', + 'Quisque scelerisque, risus sit amet condimentum consectetur, quam erat pretium tellus, ut tempor metus lectus suscipit risus.', + 'Vestibulum eu lectus eget nunc aliquet laoreet.', + 'Phasellus ornare neque auctor pulvinar pellentesque.', + 'Aliquam vitae dignissim mi, ac mattis nibh.', + 'Duis vitae porta velit, ac efficitur mauris.', + 'Sed id nisi nisl.', + 'Donec malesuada odio in posuere eleifend.', + 'Cras ut mi quis tortor mollis iaculis sed a dolor.', + 'Nunc a nisi vitae orci consectetur semper.', + 'Mauris tristique pharetra mattis.', + 'Sed iaculis fermentum mauris a tempus.', + 'Quisque cursus, mauris vel tristique molestie, massa libero suscipit orci, ut sodales lacus massa vel odio.', + 'Sed eu mi sapien.', + 'Sed tincidunt ullamcorper mauris nec lobortis.', + 'Suspendisse at est in diam elementum commodo.', + 'Ut felis lectus, hendrerit sit amet vulputate sit amet, cursus sit amet quam.', + 'Quisque porta arcu ac purus scelerisque, ac aliquam mi ornare.', + 'Sed ultricies vitae purus et convallis.', + 'Nunc ligula nisl, malesuada ut scelerisque ac, maximus quis odio.', + 'Duis nisi arcu, commodo ac purus ac, dignissim convallis arcu.', + 'Suspendisse eget scelerisque libero.', + 'Morbi dolor purus, vehicula id sem sed, condimentum viverra eros.', + 'Etiam bibendum commodo enim, ac vulputate ex consequat aliquet.', + 'Pellentesque porta nisl sit amet tortor dignissim, quis aliquet arcu consectetur.', + 'Integer et pretium lectus.', + 'Fusce efficitur posuere ipsum, ut convallis nisl sollicitudin ultrices.', + 'Sed suscipit elementum est, vitae laoreet tellus dignissim ultricies.', + 'Praesent viverra nisl ut odio posuere, tristique finibus nibh rhoncus.', + 'Aliquam consequat ornare orci ac ultrices.', + 'Vivamus vel lacinia velit.', + 'Vestibulum sit amet sapien sit amet velit tincidunt suscipit.', + 'Etiam ornare molestie aliquet.', + 'Sed gravida enim quis nunc interdum imperdiet.', + 'Proin cursus odio ac dolor blandit, quis sollicitudin ante rutrum.', +]; + +export const maxStringLength = 190; diff --git a/drizzle-seed/src/datasets/phonesInfo.ts b/drizzle-seed/src/datasets/phonesInfo.ts new file mode 100644 index 000000000..3412857ed --- /dev/null +++ b/drizzle-seed/src/datasets/phonesInfo.ts @@ -0,0 +1,851 @@ +/** + * The original source for the phones info data was taken from https://www.kaggle.com/datasets/leighplt/country-code?select=mobile_telephone_prefixes_by_country.csv + * + * Data format is: ["country prefix, operator prefix, number length including operator prefix and excluding country prefix"] + */ +export default [ + '93,70,9', + '93,71,9', + '93,72,9', + '93,73,9', + '93,74,9', + '93,75,9', + '93,76,9', + '93,77,9', + '93,78,9', + '93,79,9', + '355,66,9', + '355,67,9', + '355,68,9', + '355,69,9', + '213,5,9', + '213,6,9', + '213,7,9', + '1,684,10', + '1,264 772,10', + '1,268 7,10', + '374,55,6', + '374,95,6', + '374,41,6', + '374,44,6', + '374,77,6', + '374,93,6', + '374,94,6', + '374,98,6', + '374,91,6', + '374,99,6', + '374,43,6', + '374,97,6', + '61,4,9', + '672,1,9', + '43,650,10', + '43,660,10', + '43,664,10', + '43,676,10', + '43,680,10', + '43,677,11', + '43,681,11', + '43,688,11', + '43,699,11', + '994,41,9', + '994,50,9', + '994,51,9', + '994,55,9', + '994,70,9', + '994,77,9', + '994,99,9', + '1,242 35,10', + '1,242 45,10', + '1,242 55,10', + '973,31,8', + '973,322,8', + '973,33,8', + '973,340,8', + '973,341,8', + '973,343,8', + '973,344,8', + '973,345,8', + '973,353,8', + '973,355,8', + '973,36,8', + '973,377,8', + '973,383,8', + '973,384,8', + '973,388,8', + '973,39,8', + '973,663,8', + '973,666,8', + '973,669,8', + '880,13,10', + '880,14,10', + '880,15,10', + '880,16,10', + '880,17,10', + '880,18,10', + '880,19,10', + '1,246,10', + '32,456,9', + '32,47,9', + '32,48,9', + '32,49,9', + '501,6,7', + '1,441,10', + '387,60,8', + '387,69,8', + '387,62,8', + '387,63,8', + '387,64,8', + '387,65,8', + '387,66,8', + '246,387,7', + '1,284,10', + '359,87,9', + '359,88,9', + '359,89,9', + '359,988,9', + '226,70,8', + '226,71,8', + '226,72,8', + '226,74,8', + '226,75,8', + '226,77,8', + '226,78,8', + '226,79,8', + '855,92,9', + '855,12,9', + '855,11,9', + '855,77,9', + '855,99,9', + '1,345,10', + '235,66,8', + '235,63,8', + '235,65,8', + '235,99,8', + '235,95,8', + '235,93,8', + '235,90,8', + '235,77,8', + '56,9,9', + '86,13,11', + '86,15,11', + '86,18,11', + '86,19,11', + '57,30,10', + '57,310,10', + '57,311,10', + '57,312,10', + '57,313,10', + '57,314,10', + '57,315,10', + '57,316,10', + '57,317,10', + '57,318,10', + '57,319,10', + '57,32,10', + '682,5,5', + '682,7,5', + '506,6,8', + '506,7,8', + '506,8,8', + '385,91,9', + '385,92,9', + '385,95,9', + '385,97,9', + '385,98,9', + '385,99,9', + '357,94,8', + '357,95,8', + '357,96,8', + '357,97,8', + '357,99,8', + '420,601,9', + '420,602,9', + '420,603,9', + '420,604,9', + '420,605,9', + '420,606,9', + '420,607,9', + '420,608,9', + '420,702,9', + '420,72,9', + '420,73,9', + '420,77,9', + '420,790,9', + '45,2,8', + '45,30,8', + '45,31,8', + '45,40,8', + '45,41,8', + '45,42,8', + '45,50,8', + '45,51,8', + '45,52,8', + '45,53,8', + '45,60,8', + '45,61,8', + '45,71,8', + '45,81,8', + '1,767 2,10', + '1,809,10', + '1,829,10', + '1,849,10', + '670,77,8', + '670,78,8', + '593,9,9', + '20,10,10', + '20,11,10', + '20,12,10', + '503,6,8', + '503,7,8', + '268,7,8', + '500,5,5', + '500,6,5', + '298,21,5', + '298,22,5', + '298,23,5', + '298,24,5', + '298,25,5', + '298,26,5', + '298,27,5', + '298,28,5', + '298,29,5', + '298,5,5', + '298,71,5', + '298,72,5', + '298,73,5', + '298,74,5', + '298,75,5', + '298,76,5', + '298,77,5', + '298,78,5', + '298,79,5', + '298,91,5', + '298,92,5', + '298,93,5', + '298,94,5', + '298,95,5', + '298,96,5', + '298,97,5', + '298,98,5', + '298,99,5', + '691,92,7', + '691,93,7', + '691,95,7', + '691,97,7', + '358,457,10', + '33,6,9', + '33,700,9', + '33,73,9', + '33,74,9', + '33,75,9', + '33,76,9', + '33,77,9', + '33,78,9', + '594,694,9', + '241,2,7', + '241,3,7', + '241,4,7', + '241,5,7', + '241,6,7', + '241,7,7', + '995,544,9', + '995,514,9', + '995,551,9', + '995,555,9', + '995,557,9', + '995,558,9', + '995,568,9', + '995,570,9', + '995,571,9', + '995,574,9', + '995,577,9', + '995,578,9', + '995,579,9', + '995,591,9', + '995,592,9', + '995,593,9', + '995,595,9', + '995,596,9', + '995,597,9', + '995,598,9', + '995,599,9', + '49,151,10', + '49,152,10', + '49,155,10', + '49,157,10', + '49,159,10', + '49,162,10', + '49,163,10', + '49,170,10', + '49,171,10', + '49,172,10', + '49,173,10', + '49,174,10', + '49,175,10', + '49,176,10', + '49,177,10', + '49,178,10', + '49,179,10', + '233,20,9', + '233,50,9', + '233,23,9', + '233,24,9', + '233,54,9', + '233,55,9', + '233,59,9', + '233,26,9', + '233,56,9', + '233,27,9', + '233,57,9', + '233,28,9', + '30,690,10', + '30,693,10', + '30,694,10', + '30,695,10', + '30,697,10', + '30,698,10', + '30,699,10', + '1,473 41,10', + '1,671,10', + '502,231,8', + '502,2324,8', + '502,2326,8', + '502,2327,8', + '502,2328,8', + '502,2329,8', + '502,2428,8', + '502,2429,8', + '502,30,8', + '502,310,8', + '502,311,8', + '502,448,8', + '502,449,8', + '502,45,8', + '502,46,8', + '502,478,8', + '502,479,8', + '502,480,8', + '502,481,8', + '502,49,8', + '502,5,8', + '1,808,10', + '504,3,8', + '504,7,8', + '504,8,8', + '504,9,8', + '36,20,9', + '36,30,9', + '36,31,9', + '36,38,9', + '36,50,9', + '36,60,9', + '36,70,9', + '91,7,10', + '91,8,10', + '91,90,10', + '91,91,10', + '91,92,10', + '91,93,10', + '91,94,10', + '91,95,10', + '91,96,10', + '91,97,10', + '91,98,10', + '91,99,10', + '62,811,9', + '62,813,11', + '62,814,11', + '62,815,10', + '62,818,9', + '62,819,10', + '62,838,10', + '62,852,11', + '62,853,11', + '62,855,10', + '62,858,11', + '62,859,11', + '62,878,11', + '62,896,10', + '62,897,10', + '62,898,10', + '62,899,10', + '98,91,10', + '98,990,10', + '353,8,9', + '353,83,9', + '353,85,9', + '353,86,9', + '353,87,9', + '353,89,9', + '972,50,9', + '972,52,9', + '972,53,9', + '972,54,9', + '972,556,9', + '972,558,9', + '972,559,9', + '972,58,9', + '39,310,10', + '39,31100,10', + '39,31101,10', + '39,31105,10', + '39,313,10', + '39,319,10', + '39,320,10', + '39,324,10', + '39,327,10', + '39,328,10', + '39,329,10', + '39,331,10', + '39,333,10', + '39,334,10', + '39,338,10', + '39,339,10', + '39,340,10', + '39,342,10', + '39,344,10', + '39,345,10', + '39,346,10', + '39,347,10', + '39,348,10', + '39,349,10', + '39,3505,10', + '39,3510,10', + '39,3512,10', + '39,366,10', + '39,370,10', + '39,3710,10', + '39,3711,10', + '39,373,10', + '39,377,10', + '39,380,10', + '39,385,10', + '39,388,10', + '39,389,10', + '39,391,10', + '39,392,10', + '39,393,10', + '1,876,10', + '81,060,11', + '81,070,11', + '81,080,11', + '81,090,11', + '254,10,10', + '254,11,10', + '254,70,10', + '254,71,10', + '254,72,10', + '254,73,10', + '254,74,10', + '254,75,10', + '254,763,10', + '254,77,10', + '254,78,10', + '686,63,8', + '686,7,8', + '383,44,8', + '383,45,8', + '383,49,8', + '965,5,8', + '965,6,8', + '965,9,8', + '371,2,8', + '231,46,7', + '231,47,7', + '231,5,7', + '231,64,7', + '231,65,7', + '231,7,8', + '218,91,10', + '218,92,10', + '218,94,10', + '370,6,8', + '352,621,9', + '352,628,9', + '352,661,9', + '352,668,9', + '352,691,9', + '352,698,9', + '60,11,7', + '60,12,7', + '60,13,7', + '60,14,7', + '60,16,7', + '60,17,7', + '60,18,7', + '60,19,7', + '960,7,7', + '960,9,7', + '223,6,8', + '223,7,8', + '596,696,9', + '230,57,8', + '230,58,8', + '230,59,8', + '230,54,8', + '52,1,10', + '373,60,8', + '373,65,8', + '373,67,8', + '373,68,8', + '373,69,8', + '373,78,8', + '373,79,8', + '976,70,8', + '976,88,8', + '976,89,8', + '976,91,8', + '976,93,8', + '976,94,8', + '976,95,8', + '976,96,8', + '976,98,8', + '976,99,8', + '382,60,8', + '382,63,8', + '382,66,8', + '382,67,8', + '382,68,8', + '382,69,8', + '1,664,10', + '95,92,8', + '95,925,10', + '95,926,10', + '95,943,9', + '95,94,10', + '95,944,9', + '95,95,8', + '95,96,8', + '95,973,9', + '95,991,9', + '95,93,9', + '95,996,10', + '95,997,10', + '95,977,10', + '95,978,10', + '95,979,10', + '977,98,10', + '31,6,9', + '687,7,6', + '687,8,6', + '687,9,6', + '64,22,9', + '64,27,9', + '505,8,8', + '227,9,8', + '234,804,8', + '234,805,8', + '234,803,8', + '234,802,8', + '234,809,8', + '683,1,4', + '683,3,4', + '683,4,4', + '672,38,6', + '389,70,8', + '389,71,8', + '389,72,8', + '389,74,8', + '389,75,8', + '389,76,8', + '389,77,8', + '389,78,8', + '389,79,8', + '90,533,7', + '1,670,10', + '47,4,8', + '47,59,8', + '47,9,8', + '968,91,8', + '92,30,10', + '92,31,10', + '92,32,10', + '92,33,10', + '92,34,10', + '507,6,8', + '595,9,9', + '51,9,9', + '63,973,10', + '63,974,10', + '63,905,10', + '63,906,10', + '63,977,10', + '63,915,10', + '63,916,10', + '63,926,10', + '63,927,10', + '63,935,10', + '63,936,10', + '63,937,10', + '63,996,10', + '63,997,10', + '63,917,10', + '63,979,10', + '63,920,10', + '63,930,10', + '63,938,10', + '63,939,10', + '63,907,10', + '63,908,10', + '63,909,10', + '63,910,10', + '63,912,10', + '63,919,10', + '63,921,10', + '63,928,10', + '63,929,10', + '63,947,10', + '63,948,10', + '63,949,10', + '63,989,10', + '63,918,10', + '63,999,10', + '63,922,10', + '63,923,10', + '63,932,10', + '63,933,10', + '63,942,10', + '63,943,10', + '48,50,9', + '48,45,9', + '48,51,9', + '48,53,9', + '48,57,9', + '48,60,9', + '48,66,9', + '48,69,9', + '48,72,9', + '48,73,9', + '48,78,9', + '48,79,9', + '48,88,9', + '351,91,9', + '351,921,9', + '351,922,9', + '351,924,9', + '351,925,9', + '351,926,9', + '351,927,9', + '351,9290,9', + '351,9291,9', + '351,9292,9', + '351,9293,9', + '351,9294,9', + '351,93,9', + '351,96,9', + '1,787,10', + '1,939,10', + '974,33,8', + '974,55,8', + '974,66,8', + '974,77,8', + '1,869,10', + '1,758,10', + '1,784,10', + '685,77,5', + '966,50,9', + '966,51,9', + '966,53,9', + '966,54,9', + '966,55,9', + '966,56,9', + '966,57,9', + '966,58,9', + '966,59,9', + '381,60,9', + '381,61,9', + '381,62,9', + '381,63,9', + '381,64,9', + '381,65,9', + '381,66,9', + '381,677,9', + '381,68,9', + '381,69,8', + '65,8,8', + '65,9,8', + '1,721,10', + '421,901,9', + '421,902,9', + '421,903,9', + '421,904,9', + '421,905,9', + '421,906,9', + '421,907,9', + '421,908,9', + '421,910,9', + '421,911,9', + '421,912,9', + '421,914,9', + '421,915,9', + '421,916,9', + '421,917,9', + '421,918,9', + '421,940,9', + '421,944,9', + '421,948,9', + '421,949,9', + '421,950,9', + '421,951,9', + '386,20,8', + '386,21,8', + '386,30,8', + '386,31,8', + '386,40,8', + '386,41,8', + '386,49,8', + '386,50,8', + '386,51,8', + '386,60,8', + '386,61,8', + '386,64,8', + '386,70,8', + '386,71,8', + '677,74,7', + '677,75,7', + '27,60,9', + '27,710,9', + '27,711,9', + '27,712,9', + '27,713,9', + '27,714,9', + '27,715,9', + '27,716,9', + '27,717,9', + '27,718,9', + '27,719,9', + '27,72,9', + '27,73,9', + '27,74,9', + '27,741,9', + '27,76,9', + '27,78,9', + '27,79,9', + '27,811,9', + '27,812,9', + '27,813,9', + '27,814,9', + '27,82,9', + '27,83,9', + '27,84,9', + '34,6,9', + '34,7,9', + '94,70,7', + '94,71,7', + '94,72,7', + '94,75,7', + '94,76,7', + '94,77,7', + '94,78,7', + '46,70,7', + '46,71 0,10', + '46,73 00,7', + '46,73 01,7', + '46,73 10,7', + '46,73 11,7', + '46,73 12,7', + '46,73 13,7', + '46,73 16,7', + '46,73 170,7', + '46,73 18,7', + '46,73 19,7', + '46,73 20,7', + '46,73 23,7', + '46,73 27,7', + '46,73 28,7', + '46,73 29,7', + '46,73 3,7', + '46,73 455,7', + '46,73 456,7', + '46,73 6,7', + '46,73 85,7', + '46,73 86,7', + '46,73 87,7', + '46,73 88,7', + '46,73 89,7', + '46,73 9,7', + '41,74,9', + '41,75,9', + '41,76,9', + '41,77,9', + '41,78,9', + '41,79,9', + '963,93,9', + '963,98,9', + '963,99,9', + '963,94,9', + '963,95,9', + '963,96,9', + '886,9,9', + '66,6,9', + '66,8,9', + '66,9,9', + '228,90,8', + '228,91,8', + '228,92,8', + '228,97,8', + '228,98,8', + '228,99,8', + '1,868,10', + '216,2,8', + '216,3,8', + '216,4,8', + '216,5,8', + '216,9,8', + '90,50,11', + '90,53,11', + '90,54,11', + '90,55,11', + '1,649,10', + '380,39,9', + '380,50,9', + '380,63,9', + '380,66,9', + '380,67,9', + '380,68,9', + '380,91,9', + '380,92,9', + '380,93,9', + '380,94,9', + '380,95,9', + '380,96,9', + '380,97,9', + '380,98,9', + '380,99,9', + '971,50,9', + '971,52,9', + '971,54,9', + '971,55,9', + '971,56,9', + '971,58,9', + '44,71,10', + '44,72,10', + '44,73,10', + '44,74,10', + '44,75,10', + '44,7624,10', + '44,77,10', + '44,78,10', + '44,79,10', + '598,91,8', + '598,93,8', + '598,94,8', + '598,95,8', + '598,96,8', + '598,97,8', + '598,98,8', + '598,99,8', + '39,06 698,10', + '58,4,7', + '58,412,7', + '58,414,7', + '58,416,7', + '58,424,7', + '58,426,7', + '1,340,10', + '967,7,9', + '967,70,9', + '967,71,9', + '967,73,9', + '967,77,9', +]; diff --git a/drizzle-seed/src/datasets/states.ts b/drizzle-seed/src/datasets/states.ts new file mode 100644 index 000000000..cd66cf330 --- /dev/null +++ b/drizzle-seed/src/datasets/states.ts @@ -0,0 +1,54 @@ +export default [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; + +export const maxStringLength = 14; diff --git a/drizzle-seed/src/datasets/streetSuffix.ts b/drizzle-seed/src/datasets/streetSuffix.ts new file mode 100644 index 000000000..90a70c2c6 --- /dev/null +++ b/drizzle-seed/src/datasets/streetSuffix.ts @@ -0,0 +1,202 @@ +/** + * The original data was taken from the сopycat library: https://github.com/supabase-community/copycat/blob/main/src/locales/en/address/street_suffix.ts + */ +export default [ + 'Alley', + 'Avenue', + 'Branch', + 'Bridge', + 'Brook', + 'Brooks', + 'Burg', + 'Burgs', + 'Bypass', + 'Camp', + 'Canyon', + 'Cape', + 'Causeway', + 'Center', + 'Centers', + 'Circle', + 'Circles', + 'Cliff', + 'Cliffs', + 'Club', + 'Common', + 'Corner', + 'Corners', + 'Course', + 'Court', + 'Courts', + 'Cove', + 'Coves', + 'Creek', + 'Crescent', + 'Crest', + 'Crossing', + 'Crossroad', + 'Curve', + 'Dale', + 'Dam', + 'Divide', + 'Drive', + 'Drives', + 'Estate', + 'Estates', + 'Expressway', + 'Extension', + 'Extensions', + 'Fall', + 'Falls', + 'Ferry', + 'Field', + 'Fields', + 'Flat', + 'Flats', + 'Ford', + 'Fords', + 'Forest', + 'Forge', + 'Forges', + 'Fork', + 'Forks', + 'Fort', + 'Freeway', + 'Garden', + 'Gardens', + 'Gateway', + 'Glen', + 'Glens', + 'Green', + 'Greens', + 'Grove', + 'Groves', + 'Harbor', + 'Harbors', + 'Haven', + 'Heights', + 'Highway', + 'Hill', + 'Hills', + 'Hollow', + 'Inlet', + 'Island', + 'Islands', + 'Isle', + 'Junction', + 'Junctions', + 'Key', + 'Keys', + 'Knoll', + 'Knolls', + 'Lake', + 'Lakes', + 'Land', + 'Landing', + 'Lane', + 'Light', + 'Lights', + 'Loaf', + 'Lock', + 'Locks', + 'Lodge', + 'Loop', + 'Mall', + 'Manor', + 'Manors', + 'Meadow', + 'Meadows', + 'Mews', + 'Mill', + 'Mills', + 'Mission', + 'Motorway', + 'Mount', + 'Mountain', + 'Mountains', + 'Neck', + 'Orchard', + 'Oval', + 'Overpass', + 'Park', + 'Parks', + 'Parkway', + 'Parkways', + 'Pass', + 'Passage', + 'Path', + 'Pike', + 'Pine', + 'Pines', + 'Place', + 'Plain', + 'Plains', + 'Plaza', + 'Point', + 'Points', + 'Port', + 'Ports', + 'Prairie', + 'Radial', + 'Ramp', + 'Ranch', + 'Rapid', + 'Rapids', + 'Rest', + 'Ridge', + 'Ridges', + 'River', + 'Road', + 'Roads', + 'Route', + 'Row', + 'Rue', + 'Run', + 'Shoal', + 'Shoals', + 'Shore', + 'Shores', + 'Skyway', + 'Spring', + 'Springs', + 'Spur', + 'Spurs', + 'Square', + 'Squares', + 'Station', + 'Stravenue', + 'Stream', + 'Street', + 'Streets', + 'Summit', + 'Terrace', + 'Throughway', + 'Trace', + 'Track', + 'Trafficway', + 'Trail', + 'Tunnel', + 'Turnpike', + 'Underpass', + 'Union', + 'Unions', + 'Valley', + 'Valleys', + 'Via', + 'Viaduct', + 'View', + 'Views', + 'Village', + 'Villages', + 'Ville', + 'Vista', + 'Walk', + 'Walks', + 'Wall', + 'Way', + 'Ways', + 'Well', + 'Wells', +]; + +export const maxStringLength = 10; diff --git a/drizzle-seed/src/index.ts b/drizzle-seed/src/index.ts new file mode 100644 index 000000000..cc416c84d --- /dev/null +++ b/drizzle-seed/src/index.ts @@ -0,0 +1,1463 @@ +/* eslint-disable drizzle-internal/require-entity-kind */ +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + getTableName, + is, + One, + Relations, + sql, +} from 'drizzle-orm'; + +import type { MySqlColumn, MySqlSchema } from 'drizzle-orm/mysql-core'; +import { getTableConfig as getMysqlTableConfig, MySqlDatabase, MySqlTable } from 'drizzle-orm/mysql-core'; + +import type { PgArray, PgColumn, PgSchema } from 'drizzle-orm/pg-core'; +import { getTableConfig as getPgTableConfig, PgDatabase, PgTable } from 'drizzle-orm/pg-core'; + +import type { SQLiteColumn } from 'drizzle-orm/sqlite-core'; +import { BaseSQLiteDatabase, getTableConfig as getSqliteTableConfig, SQLiteTable } from 'drizzle-orm/sqlite-core'; + +import { generatorsFuncs, generatorsFuncsV2 } from './services/GeneratorFuncs.ts'; +import type { AbstractGenerator } from './services/Generators.ts'; +import { SeedService } from './services/SeedService.ts'; +import type { DrizzleStudioObjectType, DrizzleStudioRelationType } from './types/drizzleStudio.ts'; +import type { RefinementsType } from './types/seedService.ts'; +import type { Column, Relation, RelationWithReferences, Table } from './types/tables.ts'; + +type InferCallbackType< + DB extends + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + SCHEMA extends { + [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable | Relations; + }, +> = DB extends PgDatabase ? SCHEMA extends { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations; + } ? { + // iterates through schema fields. example -> schema: {"tableName": PgTable} + [ + table in keyof SCHEMA as SCHEMA[table] extends PgTable ? table + : never + ]?: { + count?: number; + columns?: { + // iterates through table fields. example -> table: {"columnName": PgColumn} + [ + column in keyof SCHEMA[table] as SCHEMA[table][column] extends PgColumn ? column + : never + ]?: AbstractGenerator; + }; + with?: { + [ + refTable in keyof SCHEMA as SCHEMA[refTable] extends PgTable ? refTable + : never + ]?: + | number + | { weight: number; count: number | number[] }[]; + }; + }; + } + : {} + : DB extends MySqlDatabase ? SCHEMA extends { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations; + } ? { + // iterates through schema fields. example -> schema: {"tableName": MySqlTable} + [ + table in keyof SCHEMA as SCHEMA[table] extends MySqlTable ? table + : never + ]?: { + count?: number; + columns?: { + // iterates through table fields. example -> table: {"columnName": MySqlColumn} + [ + column in keyof SCHEMA[table] as SCHEMA[table][column] extends MySqlColumn ? column + : never + ]?: AbstractGenerator; + }; + with?: { + [ + refTable in keyof SCHEMA as SCHEMA[refTable] extends MySqlTable ? refTable + : never + ]?: + | number + | { weight: number; count: number | number[] }[]; + }; + }; + } + : {} + : DB extends BaseSQLiteDatabase ? SCHEMA extends { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations; + } ? { + // iterates through schema fields. example -> schema: {"tableName": SQLiteTable} + [ + table in keyof SCHEMA as SCHEMA[table] extends SQLiteTable ? table + : never + ]?: { + count?: number; + columns?: { + // iterates through table fields. example -> table: {"columnName": SQLiteColumn} + [ + column in keyof SCHEMA[table] as SCHEMA[table][column] extends SQLiteColumn ? column + : never + ]?: AbstractGenerator; + }; + with?: { + [ + refTable in keyof SCHEMA as SCHEMA[refTable] extends SQLiteTable ? refTable + : never + ]?: + | number + | { weight: number; count: number | number[] }[]; + }; + }; + } + : {} + : {}; + +class SeedPromise< + DB extends + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + SCHEMA extends { + [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable | Relations; + }, + VERSION extends string | undefined, +> implements Promise { + static readonly entityKind: string = 'SeedPromise'; + + [Symbol.toStringTag] = 'SeedPromise'; + + constructor( + private db: DB, + private schema: SCHEMA, + private options?: { count?: number; seed?: number; version?: VERSION }, + ) {} + + then( + onfulfilled?: + | ((value: void) => TResult1 | PromiseLike) + | null + | undefined, + onrejected?: + | ((reason: any) => TResult2 | PromiseLike) + | null + | undefined, + ): Promise { + return seedFunc(this.db, this.schema, this.options).then( + onfulfilled, + onrejected, + ); + } + + catch( + onrejected?: + | ((reason: any) => TResult | PromiseLike) + | null + | undefined, + ): Promise { + return this.then(undefined, onrejected); + } + + finally(onfinally?: (() => void) | null | undefined): Promise { + return this.then( + (value) => { + onfinally?.(); + return value; + }, + (reason) => { + onfinally?.(); + throw reason; + }, + ); + } + + async refine( + callback: ( + funcs: FunctionsVersioning, + ) => InferCallbackType, + ): Promise { + const refinements = this.options?.version === undefined || this.options.version === '2' + ? callback(generatorsFuncsV2 as FunctionsVersioning) as RefinementsType + : callback(generatorsFuncs as FunctionsVersioning) as RefinementsType; + + await seedFunc(this.db, this.schema, this.options, refinements); + } +} + +type FunctionsVersioning = VERSION extends `1` ? typeof generatorsFuncs + : VERSION extends `2` ? typeof generatorsFuncsV2 + : typeof generatorsFuncsV2; + +export function getGeneratorsFunctions() { + return generatorsFuncs; +} + +export async function seedForDrizzleStudio( + { sqlDialect, drizzleStudioObject, drizzleStudioRelations, schemasRefinements, options }: { + sqlDialect: 'postgresql' | 'mysql' | 'sqlite'; + drizzleStudioObject: DrizzleStudioObjectType; + drizzleStudioRelations: DrizzleStudioRelationType[]; + schemasRefinements?: { [schemaName: string]: RefinementsType }; + options?: { count?: number; seed?: number }; + }, +) { + const generatedSchemas: { + [schemaName: string]: { + tables: { + tableName: string; + rows: { + [columnName: string]: string | number | boolean | undefined; + }[]; + }[]; + }; + } = {}; + + let tables: Table[], relations: Relation[], refinements: RefinementsType | undefined; + drizzleStudioRelations = drizzleStudioRelations.filter((rel) => rel.type === 'one'); + for (const [schemaName, { tables: drizzleStudioTables }] of Object.entries(drizzleStudioObject)) { + tables = []; + for (const [tableName, table] of Object.entries(drizzleStudioTables)) { + const drizzleStudioColumns = Object.values(table.columns); + const columns = drizzleStudioColumns.map((col) => ({ + name: col.name, + dataType: 'string', + columnType: col.type, + // TODO: revise later + typeParams: {}, + default: col.default, + hasDefault: col.default === undefined ? false : true, + isUnique: col.isUnique === undefined ? false : col.isUnique, + notNull: col.notNull, + primary: col.primaryKey, + })); + tables.push( + { + name: tableName, + columns, + primaryKeys: drizzleStudioColumns.filter((col) => col.primaryKey === true).map((col) => col.name), + }, + ); + } + + relations = drizzleStudioRelations.filter((rel) => rel.schema === schemaName && rel.refSchema === schemaName); + const isCyclicRelations = relations.map( + (reli) => { + if (relations.some((relj) => reli.table === relj.refTable && reli.refTable === relj.table)) { + return { ...reli, isCyclic: true }; + } + return { ...reli, isCyclic: false }; + }, + ); + + refinements = schemasRefinements !== undefined && schemasRefinements[schemaName] !== undefined + ? schemasRefinements[schemaName] + : undefined; + + const seedService = new SeedService(); + + const generatedTablesGenerators = seedService.generatePossibleGenerators( + sqlDialect, + tables, + isCyclicRelations, + refinements, + options, + ); + + const generatedTables = await seedService.generateTablesValues( + isCyclicRelations, + generatedTablesGenerators, + undefined, + undefined, + { ...options, preserveData: true, insertDataInDb: false }, + ); + + generatedSchemas[schemaName] = { tables: generatedTables }; + } + + return generatedSchemas; +} + +/** + * @param db - database you would like to seed. + * @param schema - object that contains all your database tables you would like to seed. + * @param options - object that contains properties `count` and `seed`: + * + * `count` - number of rows you want to generate. + * + * `seed` - a number that controls the state of generated data. (if the `seed` number is the same and nothing is changed in the seeding script, generated data will remain the same each time you seed database) + * + * @returns SeedPromise - a class object that has a refine method that is used to change generators for columns. + * + * @example + * ```ts + * // base seeding + * await seed(db, schema); + * + * // seeding with count specified + * await seed(db, schema, { count: 100000 }); + * + * // seeding with count and seed specified + * await seed(db, schema, { count: 100000, seed: 1 }); + * + * //seeding using refine + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * name: funcs.firstName({ isUnique: true }), + * email: funcs.email(), + * phone: funcs.phoneNumber({ template: "+380 99 ###-##-##" }), + * password: funcs.string({ isUnique: true }), + * }, + * count: 100000, + * }, + * posts: { + * columns: { + * title: funcs.valuesFromArray({ + * values: ["Title1", "Title2", "Title3", "Title4", "Title5"], + * }), + * content: funcs.loremIpsum({ sentencesCount: 3 }), + * }, + * }, + * })); + * + * ``` + */ +export function seed< + DB extends + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + SCHEMA extends { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; + }, + VERSION extends '2' | '1' | undefined, +>(db: DB, schema: SCHEMA, options?: { count?: number; seed?: number; version?: VERSION }) { + return new SeedPromise(db, schema, options); +} + +const seedFunc = async ( + db: PgDatabase | MySqlDatabase | BaseSQLiteDatabase, + schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; + }, + options: { count?: number; seed?: number; version?: string } = {}, + refinements?: RefinementsType, +) => { + let version: number | undefined; + if (options?.version !== undefined) { + version = Number(options?.version); + } + + if (is(db, PgDatabase)) { + await seedPostgres(db, schema, { ...options, version }, refinements); + } else if (is(db, MySqlDatabase)) { + await seedMySql(db, schema, { ...options, version }, refinements); + } else if (is(db, BaseSQLiteDatabase)) { + await seedSqlite(db, schema, { ...options, version }, refinements); + } else { + throw new Error( + 'The drizzle-seed package currently supports only PostgreSQL, MySQL, and SQLite databases. Please ensure your database is one of these supported types', + ); + } + + return; +}; + +/** + * deletes all data from specified tables + * + * @param db - database you would like to reset. + * @param schema - object that contains all your database tables you would like to delete data from. + * + * `If db is a PgDatabase object`, we will execute sql query and delete data from your tables the following way: + * ```sql + * truncate tableName1, tableName2, ... cascade; + * ``` + * + * `If db is a MySqlDatabase object`, we will execute sql queries and delete data from your tables the following way: + * ```sql + * SET FOREIGN_KEY_CHECKS = 0; + * truncate tableName1; + * truncate tableName2; + * . + * . + * . + * + * SET FOREIGN_KEY_CHECKS = 1; + * ``` + * + * `If db is a BaseSQLiteDatabase object`, we will execute sql queries and delete data from your tables the following way: + * ```sql + * PRAGMA foreign_keys = OFF; + * delete from tableName1; + * delete from tableName2; + * . + * . + * . + * + * PRAGMA foreign_keys = ON; + * ``` + * + * @example + * ```ts + * await reset(db, schema); + * ``` + */ +export async function reset< + DB extends + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + SCHEMA extends { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | any; + }, +>(db: DB, schema: SCHEMA) { + if (is(db, PgDatabase)) { + const { pgTables } = filterPgSchema(schema); + + if (Object.entries(pgTables).length > 0) { + await resetPostgres(db, pgTables); + } + } else if (is(db, MySqlDatabase)) { + const { mysqlTables } = filterMysqlTables(schema); + + if (Object.entries(mysqlTables).length > 0) { + await resetMySql(db, mysqlTables); + } + } else if (is(db, BaseSQLiteDatabase)) { + const { sqliteTables } = filterSqliteTables(schema); + + if (Object.entries(sqliteTables).length > 0) { + await resetSqlite(db, sqliteTables); + } + } else { + throw new Error( + 'The drizzle-seed package currently supports only PostgreSQL, MySQL, and SQLite databases. Please ensure your database is one of these supported types', + ); + } +} + +// Postgres----------------------------------------------------------------------------------------------------------- +const resetPostgres = async ( + db: PgDatabase, + pgTables: { [key: string]: PgTable }, +) => { + const tablesToTruncate = Object.entries(pgTables).map(([_, table]) => { + const config = getPgTableConfig(table); + config.schema = config.schema === undefined ? 'public' : config.schema; + + return `"${config.schema}"."${config.name}"`; + }); + + await db.execute(sql.raw(`truncate ${tablesToTruncate.join(',')} cascade;`)); +}; + +const filterPgSchema = (schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; +}) => { + const pgSchema = Object.fromEntries( + Object.entries(schema).filter((keyValue): keyValue is [string, PgTable | Relations] => + is(keyValue[1], PgTable) || is(keyValue[1], Relations) + ), + ); + + const pgTables = Object.fromEntries( + Object.entries(schema).filter((keyValue): keyValue is [string, PgTable] => is(keyValue[1], PgTable)), + ); + + return { pgSchema, pgTables }; +}; + +const seedPostgres = async ( + db: PgDatabase, + schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; + }, + options: { count?: number; seed?: number; version?: number } = {}, + refinements?: RefinementsType, +) => { + const seedService = new SeedService(); + + const { pgSchema, pgTables } = filterPgSchema(schema); + + const { tables, relations } = getPostgresInfo(pgSchema, pgTables); + const generatedTablesGenerators = seedService.generatePossibleGenerators( + 'postgresql', + tables, + relations, + refinements, + options, + ); + + const preserveCyclicTablesData = relations.some((rel) => rel.isCyclic === true); + + const tablesValues = await seedService.generateTablesValues( + relations, + generatedTablesGenerators, + db, + pgTables, + { ...options, preserveCyclicTablesData }, + ); + + const { filteredTablesGenerators, tablesUniqueNotNullColumn } = seedService.filterCyclicTables( + generatedTablesGenerators, + ); + const updateDataInDb = filteredTablesGenerators.length === 0 ? false : true; + + await seedService.generateTablesValues( + relations, + filteredTablesGenerators, + db, + pgTables, + { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, + ); +}; + +const getPostgresInfo = ( + pgSchema: { [key: string]: PgTable | Relations }, + pgTables: { [key: string]: PgTable }, +) => { + let tableConfig: ReturnType; + let dbToTsColumnNamesMap: { [key: string]: string }; + const dbToTsTableNamesMap: { [key: string]: string } = Object.fromEntries( + Object.entries(pgTables).map(([key, value]) => [getTableName(value), key]), + ); + + const tables: Table[] = []; + const relations: RelationWithReferences[] = []; + const dbToTsColumnNamesMapGlobal: { + [tableName: string]: { [dbColumnName: string]: string }; + } = {}; + const tableRelations: { [tableName: string]: RelationWithReferences[] } = {}; + + const getDbToTsColumnNamesMap = (table: PgTable) => { + let dbToTsColumnNamesMap: { [dbColName: string]: string } = {}; + + const tableName = getTableName(table); + if (Object.hasOwn(dbToTsColumnNamesMapGlobal, tableName)) { + dbToTsColumnNamesMap = dbToTsColumnNamesMapGlobal[tableName]!; + return dbToTsColumnNamesMap; + } + + const tableConfig = getPgTableConfig(table); + for (const [tsCol, col] of Object.entries(tableConfig.columns[0]!.table)) { + dbToTsColumnNamesMap[col.name] = tsCol; + } + dbToTsColumnNamesMapGlobal[tableName] = dbToTsColumnNamesMap; + + return dbToTsColumnNamesMap; + }; + + const transformFromDrizzleRelation = ( + schema: Record, + getDbToTsColumnNamesMap: (table: PgTable) => { + [dbColName: string]: string; + }, + tableRelations: { + [tableName: string]: RelationWithReferences[]; + }, + ) => { + const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); + const relations: RelationWithReferences[] = []; + for (const table of Object.values(schemaConfig.tables)) { + if (table.relations !== undefined) { + for (const drizzleRel of Object.values(table.relations)) { + if (is(drizzleRel, One)) { + const tableConfig = getPgTableConfig(drizzleRel.sourceTable as PgTable); + const tableDbSchema = tableConfig.schema ?? 'public'; + const tableDbName = tableConfig.name; + const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getPgTableConfig(drizzleRel.referencedTable as PgTable); + const refTableDbSchema = refTableConfig.schema ?? 'public'; + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; + } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); + } + } + } + } + return relations; + }; + + for (const table of Object.values(pgTables)) { + tableConfig = getPgTableConfig(table); + + dbToTsColumnNamesMap = {}; + for (const [tsCol, col] of Object.entries(tableConfig.columns[0]!.table)) { + dbToTsColumnNamesMap[col.name] = tsCol; + } + + // might be empty list + const newRelations = tableConfig.foreignKeys.map((fk) => { + const table = dbToTsTableNamesMap[tableConfig.name] as string; + const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( + fk.reference().foreignTable, + ); + + if (tableRelations[refTable] === undefined) { + tableRelations[refTable] = []; + } + return { + table, + columns: fk + .reference() + .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), + refTable, + refColumns: fk + .reference() + .foreignColumns.map( + (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, + ), + refTableRels: tableRelations[refTable], + }; + }); + + relations.push( + ...newRelations, + ); + + if (tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] === undefined) { + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] = []; + } + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string]!.push(...newRelations); + + const getAllBaseColumns = ( + baseColumn: PgArray['baseColumn'] & { baseColumn?: PgArray['baseColumn'] }, + ): Column['baseColumn'] => { + const baseColumnResult: Column['baseColumn'] = { + name: baseColumn.name, + columnType: baseColumn.getSQLType(), + typeParams: getTypeParams(baseColumn.getSQLType()), + dataType: baseColumn.dataType, + size: (baseColumn as PgArray).size, + hasDefault: baseColumn.hasDefault, + enumValues: baseColumn.enumValues, + default: baseColumn.default, + isUnique: baseColumn.isUnique, + notNull: baseColumn.notNull, + primary: baseColumn.primary, + baseColumn: baseColumn.baseColumn === undefined ? undefined : getAllBaseColumns(baseColumn.baseColumn), + }; + + return baseColumnResult; + }; + + const getTypeParams = (sqlType: string) => { + // get type params + const typeParams: Column['typeParams'] = {}; + + // handle dimensions + if (sqlType.includes('[')) { + const match = sqlType.match(/\[\w*]/g); + if (match) { + typeParams['dimensions'] = match.length; + } + } + + if ( + sqlType.startsWith('numeric') + || sqlType.startsWith('decimal') + || sqlType.startsWith('double precision') + || sqlType.startsWith('real') + ) { + const match = sqlType.match(/\((\d+), *(\d+)\)/); + if (match) { + typeParams['precision'] = Number(match[1]); + typeParams['scale'] = Number(match[2]); + } + } else if ( + sqlType.startsWith('varchar') + || sqlType.startsWith('bpchar') + || sqlType.startsWith('char') + || sqlType.startsWith('bit') + || sqlType.startsWith('time') + || sqlType.startsWith('timestamp') + || sqlType.startsWith('interval') + ) { + const match = sqlType.match(/\((\d+)\)/); + if (match) { + typeParams['length'] = Number(match[1]); + } + } + + return typeParams; + }; + + // console.log(tableConfig.columns); + tables.push({ + name: dbToTsTableNamesMap[tableConfig.name] as string, + columns: tableConfig.columns.map((column) => ({ + name: dbToTsColumnNamesMap[column.name] as string, + columnType: column.getSQLType(), + typeParams: getTypeParams(column.getSQLType()), + dataType: column.dataType, + size: (column as PgArray).size, + hasDefault: column.hasDefault, + default: column.default, + enumValues: column.enumValues, + isUnique: column.isUnique, + notNull: column.notNull, + primary: column.primary, + generatedIdentityType: column.generatedIdentity?.type, + baseColumn: ((column as PgArray).baseColumn === undefined) + ? undefined + : getAllBaseColumns((column as PgArray).baseColumn), + })), + primaryKeys: tableConfig.columns + .filter((column) => column.primary) + .map((column) => dbToTsColumnNamesMap[column.name] as string), + }); + } + + const transformedDrizzleRelations = transformFromDrizzleRelation(pgSchema, getDbToTsColumnNamesMap, tableRelations); + relations.push( + ...transformedDrizzleRelations, + ); + + const isCyclicRelations = relations.map( + (relI) => { + // if (relations.some((relj) => relI.table === relj.refTable && relI.refTable === relj.table)) { + const tableRel = tableRelations[relI.table]!.find((relJ) => relJ.refTable === relI.refTable)!; + if (isRelationCyclic(relI)) { + tableRel['isCyclic'] = true; + return { ...relI, isCyclic: true }; + } + tableRel['isCyclic'] = false; + return { ...relI, isCyclic: false }; + }, + ); + + return { tables, relations: isCyclicRelations, tableRelations }; +}; + +const isRelationCyclic = ( + startRel: RelationWithReferences, +) => { + // self relation + if (startRel.table === startRel.refTable) return false; + + // DFS + const targetTable = startRel.table; + const queue = [startRel]; + let path: string[] = []; + while (queue.length !== 0) { + const currRel = queue.shift(); + + if (path.includes(currRel!.table)) { + const idx = path.indexOf(currRel!.table); + path = path.slice(0, idx); + } + path.push(currRel!.table); + + for (const rel of currRel!.refTableRels) { + // self relation + if (rel.table === rel.refTable) continue; + + if (rel.refTable === targetTable) return true; + + // found cycle, but not the one we are looking for + if (path.includes(rel.refTable)) continue; + queue.unshift(rel); + } + } + + return false; +}; + +// MySql----------------------------------------------------------------------------------------------------- +const resetMySql = async ( + db: MySqlDatabase, + schema: { [key: string]: MySqlTable }, +) => { + const tablesToTruncate = Object.entries(schema).map(([_tsTableName, table]) => { + const dbTableName = getTableName(table); + return dbTableName; + }); + + await db.execute(sql.raw('SET FOREIGN_KEY_CHECKS = 0;')); + + for (const tableName of tablesToTruncate) { + const sqlQuery = `truncate \`${tableName}\`;`; + await db.execute(sql.raw(sqlQuery)); + } + + await db.execute(sql.raw('SET FOREIGN_KEY_CHECKS = 1;')); +}; + +const filterMysqlTables = (schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | any; +}) => { + const mysqlSchema = Object.fromEntries( + Object.entries(schema).filter( + (keyValue): keyValue is [string, MySqlTable | Relations] => + is(keyValue[1], MySqlTable) || is(keyValue[1], Relations), + ), + ); + + const mysqlTables = Object.fromEntries( + Object.entries(schema).filter( + (keyValue): keyValue is [string, MySqlTable] => is(keyValue[1], MySqlTable), + ), + ); + + return { mysqlSchema, mysqlTables }; +}; + +const seedMySql = async ( + db: MySqlDatabase, + schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; + }, + options: { count?: number; seed?: number; version?: number } = {}, + refinements?: RefinementsType, +) => { + const { mysqlSchema, mysqlTables } = filterMysqlTables(schema); + const { tables, relations } = getMySqlInfo(mysqlSchema, mysqlTables); + + const seedService = new SeedService(); + + const generatedTablesGenerators = seedService.generatePossibleGenerators( + 'mysql', + tables, + relations, + refinements, + options, + ); + + const preserveCyclicTablesData = relations.some((rel) => rel.isCyclic === true); + + const tablesValues = await seedService.generateTablesValues( + relations, + generatedTablesGenerators, + db, + mysqlTables, + { ...options, preserveCyclicTablesData }, + ); + + const { filteredTablesGenerators, tablesUniqueNotNullColumn } = seedService.filterCyclicTables( + generatedTablesGenerators, + ); + const updateDataInDb = filteredTablesGenerators.length === 0 ? false : true; + + await seedService.generateTablesValues( + relations, + filteredTablesGenerators, + db, + mysqlTables, + { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, + ); +}; + +const getMySqlInfo = ( + mysqlSchema: { [key: string]: MySqlTable | Relations }, + mysqlTables: { [key: string]: MySqlTable }, +) => { + let tableConfig: ReturnType; + let dbToTsColumnNamesMap: { [key: string]: string }; + + const dbToTsTableNamesMap: { [key: string]: string } = Object.fromEntries( + Object.entries(mysqlTables).map(([key, value]) => [getTableName(value), key]), + ); + + const tables: Table[] = []; + const relations: RelationWithReferences[] = []; + const dbToTsColumnNamesMapGlobal: { + [tableName: string]: { [dbColumnName: string]: string }; + } = {}; + const tableRelations: { [tableName: string]: RelationWithReferences[] } = {}; + + const getDbToTsColumnNamesMap = (table: MySqlTable) => { + let dbToTsColumnNamesMap: { [dbColName: string]: string } = {}; + + const tableName = getTableName(table); + if (Object.hasOwn(dbToTsColumnNamesMapGlobal, tableName)) { + dbToTsColumnNamesMap = dbToTsColumnNamesMapGlobal[tableName]!; + return dbToTsColumnNamesMap; + } + + const tableConfig = getMysqlTableConfig(table); + for (const [tsCol, col] of Object.entries(tableConfig.columns[0]!.table)) { + dbToTsColumnNamesMap[col.name] = tsCol; + } + dbToTsColumnNamesMapGlobal[tableName] = dbToTsColumnNamesMap; + + return dbToTsColumnNamesMap; + }; + + const transformFromDrizzleRelation = ( + schema: Record, + getDbToTsColumnNamesMap: (table: MySqlTable) => { + [dbColName: string]: string; + }, + tableRelations: { + [tableName: string]: RelationWithReferences[]; + }, + ) => { + const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); + const relations: RelationWithReferences[] = []; + for (const table of Object.values(schemaConfig.tables)) { + if (table.relations !== undefined) { + for (const drizzleRel of Object.values(table.relations)) { + if (is(drizzleRel, One)) { + const tableConfig = getMysqlTableConfig(drizzleRel.sourceTable as MySqlTable); + const tableDbSchema = tableConfig.schema ?? 'public'; + const tableDbName = tableConfig.name; + const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as MySqlTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getMysqlTableConfig(drizzleRel.referencedTable as MySqlTable); + const refTableDbSchema = refTableConfig.schema ?? 'public'; + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as MySqlTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; + } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); + } + } + } + } + return relations; + }; + + for (const table of Object.values(mysqlTables)) { + tableConfig = getMysqlTableConfig(table); + + dbToTsColumnNamesMap = {}; + for (const [tsCol, col] of Object.entries(tableConfig.columns[0]!.table)) { + dbToTsColumnNamesMap[col.name] = tsCol; + } + + const newRelations = tableConfig.foreignKeys.map((fk) => { + const table = dbToTsTableNamesMap[tableConfig.name] as string; + const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( + fk.reference().foreignTable, + ); + + if (tableRelations[refTable] === undefined) { + tableRelations[refTable] = []; + } + return { + table, + columns: fk + .reference() + .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), + refTable, + refColumns: fk + .reference() + .foreignColumns.map( + (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, + ), + refTableRels: tableRelations[refTable], + }; + }); + relations.push( + ...newRelations, + ); + + if (tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] === undefined) { + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] = []; + } + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string]!.push(...newRelations); + + const getTypeParams = (sqlType: string) => { + // get type params and set only type + const typeParams: Column['typeParams'] = {}; + + if ( + sqlType.startsWith('decimal') + || sqlType.startsWith('real') + || sqlType.startsWith('double') + || sqlType.startsWith('float') + ) { + const match = sqlType.match(/\((\d+), *(\d+)\)/); + if (match) { + typeParams['precision'] = Number(match[1]); + typeParams['scale'] = Number(match[2]); + } + } else if ( + sqlType.startsWith('char') + || sqlType.startsWith('varchar') + || sqlType.startsWith('binary') + || sqlType.startsWith('varbinary') + ) { + const match = sqlType.match(/\((\d+)\)/); + if (match) { + typeParams['length'] = Number(match[1]); + } + } + + return typeParams; + }; + + tables.push({ + name: dbToTsTableNamesMap[tableConfig.name] as string, + columns: tableConfig.columns.map((column) => ({ + name: dbToTsColumnNamesMap[column.name] as string, + columnType: column.getSQLType(), + typeParams: getTypeParams(column.getSQLType()), + dataType: column.dataType, + hasDefault: column.hasDefault, + default: column.default, + enumValues: column.enumValues, + isUnique: column.isUnique, + notNull: column.notNull, + primary: column.primary, + })), + primaryKeys: tableConfig.columns + .filter((column) => column.primary) + .map((column) => dbToTsColumnNamesMap[column.name] as string), + }); + } + + const transformedDrizzleRelations = transformFromDrizzleRelation( + mysqlSchema, + getDbToTsColumnNamesMap, + tableRelations, + ); + relations.push( + ...transformedDrizzleRelations, + ); + + const isCyclicRelations = relations.map( + (relI) => { + const tableRel = tableRelations[relI.table]!.find((relJ) => relJ.refTable === relI.refTable)!; + if (isRelationCyclic(relI)) { + tableRel['isCyclic'] = true; + return { ...relI, isCyclic: true }; + } + tableRel['isCyclic'] = false; + return { ...relI, isCyclic: false }; + }, + ); + + return { tables, relations: isCyclicRelations, tableRelations }; +}; + +// Sqlite------------------------------------------------------------------------------------------------------------------------ +const resetSqlite = async ( + db: BaseSQLiteDatabase, + schema: { [key: string]: SQLiteTable }, +) => { + const tablesToTruncate = Object.entries(schema).map(([_tsTableName, table]) => { + const dbTableName = getTableName(table); + return dbTableName; + }); + + await db.run(sql.raw('PRAGMA foreign_keys = OFF')); + + for (const tableName of tablesToTruncate) { + const sqlQuery = `delete from \`${tableName}\`;`; + await db.run(sql.raw(sqlQuery)); + } + + await db.run(sql.raw('PRAGMA foreign_keys = ON')); +}; + +const filterSqliteTables = (schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | any; +}) => { + const sqliteSchema = Object.fromEntries( + Object.entries(schema).filter( + (keyValue): keyValue is [string, SQLiteTable | Relations] => + is(keyValue[1], SQLiteTable) || is(keyValue[1], Relations), + ), + ); + + const sqliteTables = Object.fromEntries( + Object.entries(schema).filter( + (keyValue): keyValue is [string, SQLiteTable] => is(keyValue[1], SQLiteTable), + ), + ); + + return { sqliteSchema, sqliteTables }; +}; + +const seedSqlite = async ( + db: BaseSQLiteDatabase, + schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; + }, + options: { count?: number; seed?: number; version?: number } = {}, + refinements?: RefinementsType, +) => { + const { sqliteSchema, sqliteTables } = filterSqliteTables(schema); + + const { tables, relations } = getSqliteInfo(sqliteSchema, sqliteTables); + + const seedService = new SeedService(); + + const generatedTablesGenerators = seedService.generatePossibleGenerators( + 'sqlite', + tables, + relations, + refinements, + options, + ); + + const preserveCyclicTablesData = relations.some((rel) => rel.isCyclic === true); + + const tablesValues = await seedService.generateTablesValues( + relations, + generatedTablesGenerators, + db, + sqliteTables, + { ...options, preserveCyclicTablesData }, + ); + + const { filteredTablesGenerators, tablesUniqueNotNullColumn } = seedService.filterCyclicTables( + generatedTablesGenerators, + ); + const updateDataInDb = filteredTablesGenerators.length === 0 ? false : true; + + await seedService.generateTablesValues( + relations, + filteredTablesGenerators, + db, + sqliteTables, + { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, + ); +}; + +const getSqliteInfo = ( + sqliteSchema: { [key: string]: SQLiteTable | Relations }, + sqliteTables: { [key: string]: SQLiteTable }, +) => { + let tableConfig: ReturnType; + let dbToTsColumnNamesMap: { [key: string]: string }; + const dbToTsTableNamesMap: { [key: string]: string } = Object.fromEntries( + Object.entries(sqliteTables).map(([key, value]) => [getTableName(value), key]), + ); + + const tables: Table[] = []; + const relations: RelationWithReferences[] = []; + const dbToTsColumnNamesMapGlobal: { + [tableName: string]: { [dbColumnName: string]: string }; + } = {}; + const tableRelations: { [tableName: string]: RelationWithReferences[] } = {}; + + const getDbToTsColumnNamesMap = (table: SQLiteTable) => { + let dbToTsColumnNamesMap: { [dbColName: string]: string } = {}; + + const tableName = getTableName(table); + if (Object.hasOwn(dbToTsColumnNamesMapGlobal, tableName)) { + dbToTsColumnNamesMap = dbToTsColumnNamesMapGlobal[tableName]!; + return dbToTsColumnNamesMap; + } + + const tableConfig = getSqliteTableConfig(table); + for (const [tsCol, col] of Object.entries(tableConfig.columns[0]!.table)) { + dbToTsColumnNamesMap[col.name] = tsCol; + } + dbToTsColumnNamesMapGlobal[tableName] = dbToTsColumnNamesMap; + + return dbToTsColumnNamesMap; + }; + + const transformFromDrizzleRelation = ( + schema: Record, + getDbToTsColumnNamesMap: (table: SQLiteTable) => { + [dbColName: string]: string; + }, + tableRelations: { + [tableName: string]: RelationWithReferences[]; + }, + ) => { + const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); + const relations: RelationWithReferences[] = []; + for (const table of Object.values(schemaConfig.tables)) { + if (table.relations !== undefined) { + for (const drizzleRel of Object.values(table.relations)) { + if (is(drizzleRel, One)) { + const tableConfig = getSqliteTableConfig(drizzleRel.sourceTable as SQLiteTable); + const tableDbName = tableConfig.name; + // TODO: tableNamesMap: have {public.customer: 'customer'} structure in sqlite + const tableTsName = schemaConfig.tableNamesMap[`public.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as SQLiteTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getSqliteTableConfig(drizzleRel.referencedTable as SQLiteTable); + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`public.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as SQLiteTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; + } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); + } + } + } + } + return relations; + }; + + for (const table of Object.values(sqliteTables)) { + tableConfig = getSqliteTableConfig(table); + + dbToTsColumnNamesMap = {}; + for (const [tsCol, col] of Object.entries(tableConfig.columns[0]!.table)) { + dbToTsColumnNamesMap[col.name] = tsCol; + } + + const newRelations = tableConfig.foreignKeys.map((fk) => { + const table = dbToTsTableNamesMap[tableConfig.name] as string; + const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( + fk.reference().foreignTable, + ); + + if (tableRelations[refTable] === undefined) { + tableRelations[refTable] = []; + } + return { + table, + columns: fk + .reference() + .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), + refTable, + refColumns: fk + .reference() + .foreignColumns.map( + (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, + ), + refTableRels: tableRelations[refTable], + }; + }); + + relations.push( + ...newRelations, + ); + + if (tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] === undefined) { + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] = []; + } + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string]!.push(...newRelations); + + const getTypeParams = (sqlType: string) => { + // get type params and set only type + const typeParams: Column['typeParams'] = {}; + + if ( + sqlType.startsWith('decimal') + ) { + const match = sqlType.match(/\((\d+), *(\d+)\)/); + if (match) { + typeParams['precision'] = Number(match[1]); + typeParams['scale'] = Number(match[2]); + } + } else if ( + sqlType.startsWith('char') + || sqlType.startsWith('varchar') + || sqlType.startsWith('text') + ) { + const match = sqlType.match(/\((\d+)\)/); + if (match) { + typeParams['length'] = Number(match[1]); + } + } + + return typeParams; + }; + + tables.push({ + name: dbToTsTableNamesMap[tableConfig.name] as string, + columns: tableConfig.columns.map((column) => ({ + name: dbToTsColumnNamesMap[column.name] as string, + columnType: column.getSQLType(), + typeParams: getTypeParams(column.getSQLType()), + dataType: column.dataType, + hasDefault: column.hasDefault, + default: column.default, + enumValues: column.enumValues, + isUnique: column.isUnique, + notNull: column.notNull, + primary: column.primary, + })), + primaryKeys: tableConfig.columns + .filter((column) => column.primary) + .map((column) => dbToTsColumnNamesMap[column.name] as string), + }); + } + + const transformedDrizzleRelations = transformFromDrizzleRelation( + sqliteSchema, + getDbToTsColumnNamesMap, + tableRelations, + ); + relations.push( + ...transformedDrizzleRelations, + ); + + const isCyclicRelations = relations.map( + (relI) => { + const tableRel = tableRelations[relI.table]!.find((relJ) => relJ.refTable === relI.refTable)!; + if (isRelationCyclic(relI)) { + tableRel['isCyclic'] = true; + return { ...relI, isCyclic: true }; + } + tableRel['isCyclic'] = false; + return { ...relI, isCyclic: false }; + }, + ); + + return { tables, relations: isCyclicRelations, tableRelations }; +}; + +export { default as cities } from './datasets/cityNames.ts'; +export { default as countries } from './datasets/countries.ts'; +export { default as firstNames } from './datasets/firstNames.ts'; +export { default as lastNames } from './datasets/lastNames.ts'; +export { SeedService } from './services/SeedService.ts'; diff --git a/drizzle-seed/src/services/GeneratorFuncs.ts b/drizzle-seed/src/services/GeneratorFuncs.ts new file mode 100644 index 000000000..10d0d10f7 --- /dev/null +++ b/drizzle-seed/src/services/GeneratorFuncs.ts @@ -0,0 +1,918 @@ +import type { AbstractGenerator } from './Generators.ts'; +import { + GenerateArray, + GenerateBoolean, + GenerateCity, + GenerateCompanyName, + GenerateCountry, + GenerateDate, + GenerateDatetime, + GenerateDefault, + GenerateEmail, + GenerateEnum, + GenerateFirstName, + GenerateFullName, + GenerateInt, + GenerateInterval, + GenerateIntPrimaryKey, + GenerateJobTitle, + GenerateJson, + GenerateLastName, + GenerateLine, + GenerateLoremIpsum, + GenerateNumber, + GeneratePhoneNumber, + GeneratePoint, + GeneratePostcode, + GenerateSelfRelationsValuesFromArray, + GenerateState, + GenerateStreetAddress, + GenerateString, + GenerateTime, + GenerateTimestamp, + GenerateUniqueCity, + GenerateUniqueCompanyName, + GenerateUniqueCountry, + GenerateUniqueFirstName, + GenerateUniqueFullName, + GenerateUniqueInt, + GenerateUniqueInterval, + GenerateUniqueLastName, + GenerateUniqueLine, + GenerateUniqueNumber, + GenerateUniquePoint, + GenerateUniquePostcode, + GenerateUniqueStreetAddress, + GenerateUniqueString, + GenerateUUID, + GenerateValuesFromArray, + GenerateWeightedCount, + GenerateYear, + HollowGenerator, + WeightedRandomGenerator, +} from './Generators.ts'; +import { GenerateStringV2, GenerateUniqueIntervalV2, GenerateUniqueStringV2 } from './versioning/v2.ts'; + +function createGenerator, T>( + generatorConstructor: new(params?: T) => GeneratorType, +) { + return ( + ...args: GeneratorType extends GenerateValuesFromArray | GenerateDefault | WeightedRandomGenerator ? [T] + : ([] | [T]) + ): GeneratorType => { + let params = args[0]; + if (params === undefined) params = {} as T; + return new generatorConstructor(params); + }; +} + +export const generatorsFuncs = { + /** + * generates same given value each time the generator is called. + * @param defaultValue - value you want to generate + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * posts: { + * columns: { + * content: funcs.default({ defaultValue: "post content" }), + * }, + * }, + * })); + * ``` + */ + default: createGenerator(GenerateDefault), + + /** + * generates values from given array + * @param values - array of values you want to generate. can be array of weighted values. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * posts: { + * columns: { + * title: funcs.valuesFromArray({ + * values: ["Title1", "Title2", "Title3", "Title4", "Title5"], + * isUnique: true + * }), + * }, + * }, + * })); + * + * ``` + * weighted values example + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * posts: { + * columns: { + * title: funcs.valuesFromArray({ + * values: [ + * { weight: 0.35, values: ["Title1", "Title2"] }, + * { weight: 0.5, values: ["Title3", "Title4"] }, + * { weight: 0.15, values: ["Title5"] }, + * ], + * isUnique: false + * }), + * }, + * }, + * })); + * + * ``` + */ + valuesFromArray: createGenerator(GenerateValuesFromArray), + + /** + * generates sequential integers starting with 1. + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * posts: { + * columns: { + * id: funcs.intPrimaryKey(), + * }, + * }, + * })); + * + * ``` + */ + intPrimaryKey: createGenerator(GenerateIntPrimaryKey), + + /** + * generates numbers with floating point in given range. + * @param minValue - lower border of range. + * @param maxValue - upper border of range. + * @param precision - precision of generated number: + * precision equals 10 means that values will be accurate to one tenth (1.2, 34.6); + * precision equals 100 means that values will be accurate to one hundredth (1.23, 34.67). + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * products: { + * columns: { + * unitPrice: funcs.number({ minValue: 10, maxValue: 120, precision: 100, isUnique: false }), + * }, + * }, + * })); + * + * ``` + */ + number: createGenerator(GenerateNumber), + // uniqueNumber: createGenerator(GenerateUniqueNumber), + + /** + * generates integers within given range. + * @param minValue - lower border of range. + * @param maxValue - upper border of range. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * products: { + * columns: { + * unitsInStock: funcs.number({ minValue: 0, maxValue: 100, isUnique: false }), + * }, + * }, + * })); + * + * ``` + */ + int: createGenerator(GenerateInt), + // uniqueInt: createGenerator(GenerateUniqueInt), + + /** + * generates boolean values(true or false) + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * isAvailable: funcs.boolean() + * }, + * }, + * })); + * + * ``` + */ + boolean: createGenerator(GenerateBoolean), + + /** + * generates date within given range. + * @param minDate - lower border of range. + * @param maxDate - upper border of range. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * birthDate: funcs.date({ minDate: "1990-01-01", maxDate: "2010-12-31" }) + * }, + * }, + * })); + * + * ``` + */ + date: createGenerator(GenerateDate), + + /** + * generates time in 24 hours style. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * birthTime: funcs.time() + * }, + * }, + * })); + * + * ``` + */ + time: createGenerator(GenerateTime), + + /** + * generates timestamps. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * orders: { + * columns: { + * shippedDate: funcs.timestamp() + * }, + * }, + * })); + * + * ``` + */ + timestamp: createGenerator(GenerateTimestamp), + + /** + * generates datetime objects. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * orders: { + * columns: { + * shippedDate: funcs.datetime() + * }, + * }, + * })); + * + * ``` + */ + datetime: createGenerator(GenerateDatetime), + + /** + * generates years. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * birthYear: funcs.year() + * }, + * }, + * })); + * + * ``` + */ + year: createGenerator(GenerateYear), + + /** + * generates json objects with fixed structure. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * json structure can equal this: + * ``` + * { + * email, + * name, + * isGraduated, + * hasJob, + * salary, + * startedWorking, + * visitedCountries, + * } + * ``` + * or this + * ``` + * { + * email, + * name, + * isGraduated, + * hasJob, + * visitedCountries, + * } + * ``` + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * metadata: funcs.json() + * }, + * }, + * })); + * ``` + */ + json: createGenerator(GenerateJson), + // jsonb: createGenerator(GenerateJsonb), + + /** + * generates time intervals. + * + * interval example: "1 years 12 days 5 minutes" + * + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * @param fields - range of values you want to see in your intervals. + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * timeSpentOnWebsite: funcs.interval() + * }, + * }, + * })); + * ``` + */ + interval: createGenerator(GenerateInterval), + // uniqueInterval: createGenerator(GenerateUniqueInterval), + + /** + * generates random strings. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * hashedPassword: funcs.string({isUnique: false}) + * }, + * }, + * })); + * ``` + */ + string: createGenerator(GenerateString), + // uniqueString: createGenerator(GenerateUniqueString), + + /** + * generates v4 UUID strings if arraySize is not specified, or v4 UUID 1D arrays if it is. + * + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * uuid: funcs.uuid({ + * arraySize: 4 + * }) + * }, + * }, + * })); + * ``` + */ + uuid: createGenerator(GenerateUUID), + + /** + * generates person's first names. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * firstName: funcs.firstName({isUnique: true}) + * }, + * }, + * })); + * ``` + */ + firstName: createGenerator(GenerateFirstName), + // uniqueFirstName: createGenerator(GenerateUniqueName), + + /** + * generates person's last names. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * lastName: funcs.lastName({isUnique: false}) + * }, + * }, + * })); + * ``` + */ + lastName: createGenerator(GenerateLastName), + // uniqueLastName: createGenerator(GenerateUniqueSurname), + + /** + * generates person's full names. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * fullName: funcs.fullName({isUnique: true}) + * }, + * }, + * })); + * ``` + */ + fullName: createGenerator(GenerateFullName), + // uniqueFullName: createGenerator(GenerateUniqueFullName), + + /** + * generates unique emails. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * email: funcs.email() + * }, + * }, + * })); + * ``` + */ + email: createGenerator(GenerateEmail), + + /** + * generates unique phone numbers. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @param template - phone number template, where all '#' symbols will be substituted with generated digits. + * @param prefixes - array of any string you want to be your phone number prefixes.(not compatible with template property) + * @param generatedDigitsNumbers - number of digits that will be added at the end of prefixes.(not compatible with template property) + * @example + * ```ts + * //generate phone number using template property + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * phoneNumber: funcs.phoneNumber({template: "+(380) ###-####"}) + * }, + * }, + * })); + * + * //generate phone number using prefixes and generatedDigitsNumbers properties + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * phoneNumber: funcs.phoneNumber({prefixes: [ "+380 99", "+380 67" ], generatedDigitsNumbers: 7}) + * }, + * }, + * })); + * + * //generate phone number using prefixes and generatedDigitsNumbers properties but with different generatedDigitsNumbers for prefixes + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * phoneNumber: funcs.phoneNumber({prefixes: [ "+380 99", "+380 67", "+1" ], generatedDigitsNumbers: [7, 7, 10]}) + * }, + * }, + * })); + * + * ``` + */ + phoneNumber: createGenerator(GeneratePhoneNumber), + + /** + * generates country's names. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * country: funcs.country({isUnique: false}) + * }, + * }, + * })); + * ``` + */ + country: createGenerator(GenerateCountry), + // uniqueCountry: createGenerator(GenerateUniqueCountry), + + /** + * generates city's names. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * city: funcs.city({isUnique: false}) + * }, + * }, + * })); + * ``` + */ + city: createGenerator(GenerateCity), + // uniqueCity: createGenerator(GenerateUniqueCityName), + + /** + * generates street address. + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * streetAddress: funcs.streetAddress({isUnique: true}) + * }, + * }, + * })); + * ``` + */ + streetAddress: createGenerator(GenerateStreetAddress), + // uniqueStreetAddress: createGenerator(GenerateUniqueStreetAddress), + + /** + * generates job titles. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * jobTitle: funcs.jobTitle() + * }, + * }, + * })); + * ``` + */ + jobTitle: createGenerator(GenerateJobTitle), + + /** + * generates postal codes. + * + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * postcode: funcs.postcode({isUnique: true}) + * }, + * }, + * })); + * ``` + */ + postcode: createGenerator(GeneratePostcode), + // uniquePostcoe: createGenerator(GenerateUniquePostcode), + + /** + * generates states of America. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * state: funcs.state() + * }, + * }, + * })); + * ``` + */ + state: createGenerator(GenerateState), + + /** + * generates company's names. + * + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * company: funcs.companyName({isUnique: true}) + * }, + * }, + * })); + * ``` + */ + companyName: createGenerator(GenerateCompanyName), + // uniqueCompanyName: createGenerator(GenerateUniqueCompanyName), + + /** + * generates 'lorem ipsum' text sentences. + * + * @param sentencesCount - number of sentences you want to generate as one generated value(string). + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * posts: { + * columns: { + * content: funcs.loremIpsum({sentencesCount: 2}) + * }, + * }, + * })); + * ``` + */ + loremIpsum: createGenerator(GenerateLoremIpsum), + + /** + * generates 2D points within specified ranges for x and y coordinates. + * + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param minXValue - lower bound of range for x coordinate. + * @param maxXValue - upper bound of range for x coordinate. + * @param minYValue - lower bound of range for y coordinate. + * @param maxYValue - upper bound of range for y coordinate. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * triangles: { + * columns: { + * pointCoords: funcs.point({ + * isUnique: true, + * minXValue: -5, maxXValue:20, + * minYValue: 0, maxYValue: 30 + * }) + * }, + * }, + * })); + * ``` + */ + point: createGenerator(GeneratePoint), + // uniquePoint: createGenerator(GenerateUniquePoint), + + /** + * generates 2D lines within specified ranges for a, b and c parameters of line. + * + * ``` + * line equation: a*x + b*y + c = 0 + * ``` + * + * @param isUnique - property that controls if generated values gonna be unique or not. + * @param minAValue - lower bound of range for a parameter. + * @param maxAValue - upper bound of range for x parameter. + * @param minBValue - lower bound of range for y parameter. + * @param maxBValue - upper bound of range for y parameter. + * @param minCValue - lower bound of range for y parameter. + * @param maxCValue - upper bound of range for y parameter. + * @param arraySize - number of elements in each one-dimensional array. (If specified, arrays will be generated.) + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * lines: { + * columns: { + * lineParams: funcs.point({ + * isUnique: true, + * minAValue: -5, maxAValue:20, + * minBValue: 0, maxBValue: 30, + * minCValue: 0, maxCValue: 10 + * }) + * }, + * }, + * })); + * ``` + */ + line: createGenerator(GenerateLine), + // uniqueLine: createGenerator(GenerateUniqueLine), + + /** + * gives you the opportunity to call different generators with different probabilities to generate values for one column. + * @param params - array of generators with probabilities you would like to call them to generate values. + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * posts: { + * columns: { + * content: funcs.weightedRandom([ + * { + * weight: 0.6, + * value: funcs.loremIpsum({ sentencesCount: 3 }), + * }, + * { + * weight: 0.4, + * value: funcs.default({ defaultValue: "TODO" }), + * }, + * ]), + * }, + * }, + * })); + * ``` + */ + weightedRandom: createGenerator(WeightedRandomGenerator), +}; + +// so far, version changes don’t affect generator parameters. +export const generatorsFuncsV2 = { + ...generatorsFuncs, +}; + +export const generatorsMap = { + HollowGenerator: [ + HollowGenerator, + ], + GenerateDefault: [ + GenerateDefault, + ], + GenerateValuesFromArray: [ + GenerateValuesFromArray, + ], + GenerateSelfRelationsValuesFromArray: [ + GenerateSelfRelationsValuesFromArray, + ], + GenerateIntPrimaryKey: [ + GenerateIntPrimaryKey, + ], + GenerateNumber: [ + GenerateNumber, + ], + GenerateUniqueNumber: [ + GenerateUniqueNumber, + ], + GenerateInt: [ + GenerateInt, + ], + GenerateUniqueInt: [ + GenerateUniqueInt, + ], + GenerateBoolean: [ + GenerateBoolean, + ], + GenerateDate: [ + GenerateDate, + ], + GenerateTime: [ + GenerateTime, + ], + GenerateTimestamp: [ + GenerateTimestamp, + ], + GenerateDatetime: [ + GenerateDatetime, + ], + GenerateYear: [ + GenerateYear, + ], + GenerateJson: [ + GenerateJson, + ], + GenerateEnum: [ + GenerateEnum, + ], + GenerateInterval: [ + GenerateInterval, + ], + GenerateUniqueInterval: [ + GenerateUniqueInterval, + GenerateUniqueIntervalV2, + ], + GenerateString: [ + GenerateString, + GenerateStringV2, + ], + GenerateUniqueString: [ + GenerateUniqueString, + GenerateUniqueStringV2, + ], + GenerateUUID: [ + GenerateUUID, + ], + GenerateFirstName: [ + GenerateFirstName, + ], + GenerateUniqueFirstName: [ + GenerateUniqueFirstName, + ], + GenerateLastName: [ + GenerateLastName, + ], + GenerateUniqueLastName: [ + GenerateUniqueLastName, + ], + GenerateFullName: [ + GenerateFullName, + ], + GenerateUniqueFullName: [ + GenerateUniqueFullName, + ], + GenerateEmail: [ + GenerateEmail, + ], + GeneratePhoneNumber: [ + GeneratePhoneNumber, + ], + GenerateCountry: [ + GenerateCountry, + ], + GenerateUniqueCountry: [ + GenerateUniqueCountry, + ], + GenerateCity: [ + GenerateCity, + ], + GenerateUniqueCity: [ + GenerateUniqueCity, + ], + GenerateStreetAddress: [ + GenerateStreetAddress, + ], + GenerateUniqueStreetAddress: [ + GenerateUniqueStreetAddress, + ], + GenerateJobTitle: [ + GenerateJobTitle, + ], + GeneratePostcode: [ + GeneratePostcode, + ], + GenerateUniquePostcode: [ + GenerateUniquePostcode, + ], + GenerateState: [ + GenerateState, + ], + GenerateCompanyName: [ + GenerateCompanyName, + ], + GenerateUniqueCompanyName: [ + GenerateUniqueCompanyName, + ], + GenerateLoremIpsum: [ + GenerateLoremIpsum, + ], + GeneratePoint: [ + GeneratePoint, + ], + GenerateUniquePoint: [ + GenerateUniquePoint, + ], + GenerateLine: [ + GenerateLine, + ], + GenerateUniqueLine: [ + GenerateUniqueLine, + ], + WeightedRandomGenerator: [ + WeightedRandomGenerator, + ], + GenerateArray: [ + GenerateArray, + ], + GenerateWeightedCount: [ + GenerateWeightedCount, + ], +} as const; diff --git a/drizzle-seed/src/services/Generators.ts b/drizzle-seed/src/services/Generators.ts new file mode 100644 index 000000000..0d285540e --- /dev/null +++ b/drizzle-seed/src/services/Generators.ts @@ -0,0 +1,3119 @@ +/* eslint-disable drizzle-internal/require-entity-kind */ +import prand from 'pure-rand'; +import adjectives, { maxStringLength as maxAdjectiveLength } from '../datasets/adjectives.ts'; +import cityNames, { maxStringLength as maxCityNameLength } from '../datasets/cityNames.ts'; +import companyNameSuffixes, { maxStringLength as maxCompanyNameSuffixLength } from '../datasets/companyNameSuffixes.ts'; +import countries, { maxStringLength as maxCountryLength } from '../datasets/countries.ts'; +import emailDomains, { maxStringLength as maxEmailDomainLength } from '../datasets/emailDomains.ts'; +import firstNames, { maxStringLength as maxFirstNameLength } from '../datasets/firstNames.ts'; +import jobsTitles, { maxStringLength as maxJobTitleLength } from '../datasets/jobsTitles.ts'; +import lastNames, { maxStringLength as maxLastNameLength } from '../datasets/lastNames.ts'; +import loremIpsumSentences, { maxStringLength as maxLoremIpsumLength } from '../datasets/loremIpsumSentences.ts'; +import phonesInfo from '../datasets/phonesInfo.ts'; +import states, { maxStringLength as maxStateLength } from '../datasets/states.ts'; +import streetSuffix, { maxStringLength as maxStreetSuffixLength } from '../datasets/streetSuffix.ts'; +import { fastCartesianProduct, fillTemplate, getWeightedIndices, isObject } from './utils.ts'; + +export abstract class AbstractGenerator { + static readonly entityKind: string = 'AbstractGenerator'; + static readonly version: number = 1; + + public isUnique = false; + public notNull = false; + + // param for generators which have a unique version of themselves + public uniqueVersionOfGen?: new(params: T) => AbstractGenerator; + + public dataType?: string; + public timeSpent?: number; + + // + public arraySize?: number; + public baseColumnDataType?: string; + + // param for text-like generators + public stringLength?: number; + + // params for GenerateValuesFromArray + public weightedCountSeed?: number | undefined; + public maxRepeatedValuesCount?: number | { weight: number; count: number | number[] }[] | undefined; + + public params: T; + + constructor(params?: T) { + this.params = params === undefined ? {} as T : params as T; + } + + init(params: { count: number | { weight: number; count: number | number[] }[]; seed: number }): void; + init() { + this.updateParams(); + } + + updateParams() { + if ((this.params as any).arraySize !== undefined) { + this.arraySize = (this.params as any).arraySize; + } + + if ((this.params as any).isUnique !== undefined) { + if ((this.params as any).isUnique === false && this.isUnique === true) { + throw new Error('specifying non unique generator to unique column.'); + } + + this.isUnique = (this.params as any).isUnique; + } + } + + abstract generate(params: { i: number }): number | string | boolean | unknown | undefined | void; + + getEntityKind(): string { + const constructor = this.constructor as typeof AbstractGenerator; + return constructor.entityKind; + } + + replaceIfUnique() { + this.updateParams(); + if ( + this.uniqueVersionOfGen !== undefined + && this.isUnique === true + ) { + const uniqueGen = new this.uniqueVersionOfGen({ + ...this.params, + }); + + uniqueGen.isUnique = this.isUnique; + uniqueGen.dataType = this.dataType; + + return uniqueGen; + } + return; + } + + replaceIfArray() { + this.updateParams(); + if (!(this.getEntityKind() === 'GenerateArray') && this.arraySize !== undefined) { + const uniqueGen = this.replaceIfUnique(); + const baseColumnGen = uniqueGen === undefined ? this : uniqueGen; + baseColumnGen.dataType = this.baseColumnDataType; + const arrayGen = new GenerateArray( + { + baseColumnGen, + size: this.arraySize, + }, + ); + + return arrayGen; + } + + return; + } +} + +// Generators Classes ----------------------------------------------------------------------------------------------------------------------- +export class GenerateArray extends AbstractGenerator<{ baseColumnGen: AbstractGenerator; size?: number }> { + static override readonly entityKind: string = 'GenerateArray'; + public override arraySize = 10; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + this.arraySize = this.params.size === undefined ? this.arraySize : this.params.size; + this.params.baseColumnGen.init({ count: count * this.arraySize, seed }); + } + + generate() { + const array = []; + for (let i = 0; i < this.arraySize; i++) { + array.push(this.params.baseColumnGen.generate({ i })); + } + + return array; + } +} + +export class GenerateWeightedCount extends AbstractGenerator<{}> { + static override readonly entityKind: string = 'GenerateWeightedCount'; + + private state: { + rng: prand.RandomGenerator; + weightedIndices: number[]; + weightedCount: { weight: number; count: number | number[] }[]; + } | undefined; + + override init({ seed, count }: { count: { weight: number; count: number | number[] }[]; seed: number }) { + const rng = prand.xoroshiro128plus(seed); + const weightedIndices = getWeightedIndices(count.map((val) => val.weight)); + this.state = { rng, weightedIndices, weightedCount: count }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + // logic for this generator + let idx: number; + const weightedCount = this.state.weightedCount; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, this.state.weightedIndices.length - 1, this.state.rng); + const objIdx = this.state.weightedIndices[idx] as number; + + if (typeof weightedCount[objIdx]!.count === 'number') { + return weightedCount[objIdx]!.count as number; + } else { + // typeof weightedCount[objIdx]!.count === 'object' // number[] + const possCounts = weightedCount[objIdx]!.count as number[]; + [idx, this.state.rng] = prand.uniformIntDistribution(0, possCounts.length - 1, this.state.rng); + return possCounts[idx]!; + } + } +} + +export class HollowGenerator extends AbstractGenerator<{}> { + static override readonly entityKind: string = 'HollowGenerator'; + + override init() {} + + generate() {} +} + +export class GenerateDefault extends AbstractGenerator<{ + defaultValue: unknown; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateDefault'; + + generate() { + return this.params.defaultValue; + } +} + +export class GenerateValuesFromArray extends AbstractGenerator< + { + values: + | (number | string | boolean | undefined)[] + | { weight: number; values: (number | string | boolean | undefined)[] }[]; + isUnique?: boolean; + arraySize?: number; + } +> { + static override readonly entityKind: string = 'GenerateValuesFromArray'; + + private state: { + rng: prand.RandomGenerator; + values: + | (number | string | boolean | undefined)[] + | { weight: number; values: (number | string | boolean | undefined)[] }[]; + genIndicesObj: GenerateUniqueInt | undefined; + genIndicesObjList: GenerateUniqueInt[] | undefined; + valuesWeightedIndices: number[] | undefined; + genMaxRepeatedValuesCount: GenerateDefault | GenerateWeightedCount | undefined; + } | undefined; + public override timeSpent: number = 0; + + checks({ count }: { count: number }) { + const { values } = this.params; + const { maxRepeatedValuesCount, notNull, isUnique } = this; + if (values.length === 0) { + throw new Error('Values length equals zero.'); + } + + if ( + isObject(values[0]) + && !(values as { weight: number; values: any[] }[]).every((val) => val.values.length !== 0) + ) { + throw new Error('One of weighted values length equals zero.'); + } + + if ( + maxRepeatedValuesCount !== undefined && ( + (typeof maxRepeatedValuesCount === 'number' && maxRepeatedValuesCount <= 0) + || (typeof maxRepeatedValuesCount === 'object' && !maxRepeatedValuesCount + .every((obj) => + (typeof obj.count) === 'number' + ? (obj.count as number) > 0 + : (obj.count as number[]).every((count) => count > 0) + )) + ) + ) { + throw new Error('maxRepeatedValuesCount should be greater than zero.'); + } + + let allValuesCount = values.length; + if (isObject(values[0])) { + allValuesCount = (values as { values: any[] }[]).reduce((acc, currVal) => acc + currVal.values.length, 0); + } + + if ( + notNull === true + && maxRepeatedValuesCount !== undefined + && ( + (!isObject(values[0]) && typeof maxRepeatedValuesCount === 'number' + && maxRepeatedValuesCount * values.length < count) + || (isObject(values[0]) && typeof maxRepeatedValuesCount === 'number' + && maxRepeatedValuesCount * allValuesCount < count) + ) + ) { + throw new Error("Can't fill notNull column with null values."); + } + + if ( + isUnique === true && maxRepeatedValuesCount !== undefined && ( + (typeof maxRepeatedValuesCount === 'number' && maxRepeatedValuesCount > 1) + || (typeof maxRepeatedValuesCount === 'object' && !maxRepeatedValuesCount + .every((obj) => + (typeof obj.count) === 'number' + ? obj.count === 1 + : (obj.count as number[]).every((count) => count === 1) + )) + ) + ) { + throw new Error("Can't be greater than 1 if column is unique."); + } + + if ( + isUnique === true && notNull === true && ( + (!isObject(values[0]) && values.length < count) + || (isObject(values[0]) && allValuesCount < count) + ) + ) { + // console.log(maxRepeatedValuesCount, values.length, allValuesCount, count) + throw new Error('There are no enough values to fill unique column.'); + } + } + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + this.checks({ count }); + + let { maxRepeatedValuesCount } = this; + const { params, isUnique, notNull, weightedCountSeed } = this; + + const values = params.values; + + let valuesWeightedIndices; + if (isObject(values[0])) { + valuesWeightedIndices = getWeightedIndices((values as { weight: number }[]).map((val) => val.weight)); + if (isUnique === true && notNull === true) { + let idx: number, valueIdx: number, rng = prand.xoroshiro128plus(seed); + const indicesCounter: { [key: number]: number } = {}; + for (let i = 0; i < count; i++) { + [idx, rng] = prand.uniformIntDistribution(0, valuesWeightedIndices.length - 1, rng); + valueIdx = valuesWeightedIndices[idx]!; + if (!Object.hasOwn(indicesCounter, valueIdx)) indicesCounter[valueIdx] = 0; + indicesCounter[valueIdx]! += 1; + } + + for (const [idx, value] of values.entries()) { + if ((value as { values: (number | string | boolean | undefined)[] }).values.length < indicesCounter[idx]!) { + throw new Error( + 'weighted values arrays is too small to generate values with specified probability for unique not null column.' + + `it's planned to generate: ${ + Object.entries(indicesCounter).map(([idx, count]) => { + return `${count} values with probability ${(values as { weight: number }[])[Number(idx)]?.weight}`; + }).join(',') + }`, + ); + } + } + } + } + if (isUnique === true && maxRepeatedValuesCount === undefined) { + maxRepeatedValuesCount = 1; + } + let genMaxRepeatedValuesCount: GenerateDefault | GenerateWeightedCount | undefined; + if (typeof maxRepeatedValuesCount === 'number') { + genMaxRepeatedValuesCount = new GenerateDefault({ defaultValue: maxRepeatedValuesCount }); + } else if (typeof maxRepeatedValuesCount === 'object') { + genMaxRepeatedValuesCount = new GenerateWeightedCount({}); + (genMaxRepeatedValuesCount as GenerateWeightedCount).init( + { + count: maxRepeatedValuesCount, + seed: weightedCountSeed === undefined ? seed : weightedCountSeed, + }, + ); + } + + let genIndicesObj: GenerateUniqueInt | undefined; + let genIndicesObjList: GenerateUniqueInt[] | undefined; + + if (maxRepeatedValuesCount !== undefined) { + if (!isObject(values[0])) { + genIndicesObj = new GenerateUniqueInt({ minValue: 0, maxValue: values.length - 1 }); + genIndicesObj.genMaxRepeatedValuesCount = genMaxRepeatedValuesCount; + genIndicesObj.skipCheck = true; + genIndicesObj.init({ count, seed }); + } else if (isObject(values[0])) { + genIndicesObjList = []; + for (const obj of values as { weight: number; values: (number | string | boolean | undefined)[] }[]) { + const genIndicesObj = new GenerateUniqueInt({ minValue: 0, maxValue: obj.values.length - 1 }); + genIndicesObj.genMaxRepeatedValuesCount = genMaxRepeatedValuesCount; + genIndicesObj.skipCheck = true; + genIndicesObj.init({ count, seed }); + genIndicesObjList.push(genIndicesObj); + } + } + } + + const rng = prand.xoroshiro128plus(seed); + + this.state = { rng, values, valuesWeightedIndices, genMaxRepeatedValuesCount, genIndicesObj, genIndicesObjList }; + } + + generate() { + const t0 = new Date(); + + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx: number, + value: string | number | boolean | undefined; + let valueIdx: number; + + if (this.state.valuesWeightedIndices === undefined) { + if (this.state.genIndicesObj === undefined) { + [idx, this.state.rng] = prand.uniformIntDistribution(0, this.state.values.length - 1, this.state.rng); + } else { + idx = this.state.genIndicesObj.generate() as number; + } + + value = (this.state.values as (number | string | boolean | undefined)[])[idx]; + } else { + // weighted values + [idx, this.state.rng] = prand.uniformIntDistribution( + 0, + this.state.valuesWeightedIndices.length - 1, + this.state.rng, + ); + valueIdx = this.state.valuesWeightedIndices[idx] as number; + const currValues = + (this.state.values![valueIdx] as { weight: number; values: (number | string | boolean | undefined)[] }).values; + if (this.state.genIndicesObjList === undefined) { + // isUnique !== true + [idx, this.state.rng] = prand.uniformIntDistribution(0, currValues.length - 1, this.state.rng); + } else { + // isUnique === true + idx = this.state.genIndicesObjList[valueIdx]!.generate() as number; + } + value = currValues[idx]; + } + + this.timeSpent += (Date.now() - t0.getTime()) / 1000; + return value; + } +} + +export class GenerateSelfRelationsValuesFromArray extends AbstractGenerator<{ values: (number | string | boolean)[] }> { + static override readonly entityKind: string = 'GenerateSelfRelationsValuesFromArray'; + + private state: { + rng: prand.RandomGenerator; + firstValuesCount: number; + firstValues: (string | number | boolean)[]; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + let rng = prand.xoroshiro128plus(seed); + + // generate 15-40 % values with the same value as reference column + let percent = 30; + [percent, rng] = prand.uniformIntDistribution(20, 40, rng); + const firstValuesCount = Math.floor((percent / 100) * count), firstValues: (string | number | boolean)[] = []; + + this.state = { rng, firstValuesCount, firstValues }; + } + + generate({ i }: { i: number }) { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const { values } = this.params; + let idx: number; + + if (i < this.state.firstValuesCount) { + this.state.firstValues.push(values[i]!); + return values[i]; + } else { + [idx, this.state.rng] = prand.uniformIntDistribution(0, this.state.firstValues.length - 1, this.state.rng); + return this.state.firstValues[idx]; + } + } +} + +export class GenerateIntPrimaryKey extends AbstractGenerator<{}> { + static override readonly entityKind: string = 'GenerateIntPrimaryKey'; + + public maxValue?: number | bigint; + + override init({ count }: { count: number; seed: number }) { + if (this.maxValue !== undefined && count > this.maxValue) { + throw new Error('count exceeds max number for this column type.'); + } + } + + generate({ i }: { i: number }) { + if (this.dataType === 'bigint') { + return BigInt(i + 1); + } + + return i + 1; + } +} + +export class GenerateNumber extends AbstractGenerator< + { + minValue?: number; + maxValue?: number; + precision?: number; + isUnique?: boolean; + arraySize?: number; + } +> { + static override readonly entityKind: string = 'GenerateNumber'; + + private state: { + rng: prand.RandomGenerator; + minValue: number; + maxValue: number; + precision: number; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueNumber; + + override init({ count, seed }: { seed: number; count: number }) { + super.init({ count, seed }); + + let { minValue, maxValue, precision } = this.params; + if (precision === undefined) { + precision = 100; + } + + if (maxValue === undefined) { + maxValue = precision * 1000; + } else { + maxValue *= precision; + } + + if (minValue === undefined) { + minValue = -maxValue; + } else { + minValue *= precision; + } + + const rng = prand.xoroshiro128plus(seed); + + this.state = { rng, minValue, maxValue, precision }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let value: number; + + [value, this.state.rng] = prand.uniformIntDistribution(this.state.minValue, this.state.maxValue, this.state.rng); + return value / this.state.precision; + } +} + +export class GenerateUniqueNumber extends AbstractGenerator< + { + minValue?: number; + maxValue?: number; + precision?: number; + isUnique?: boolean; + } +> { + static override readonly entityKind: string = 'GenerateUniqueNumber'; + + private state: { + genUniqueIntObj: GenerateUniqueInt; + minValue: number; + maxValue: number; + precision: number; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + let { minValue, maxValue, precision } = this.params; + + if (precision === undefined) { + precision = 100; + } + + if (maxValue === undefined) { + maxValue = count * precision; + } else { + maxValue *= precision; + } + + if (minValue === undefined) { + minValue = -maxValue; + } else { + minValue *= precision; + } + + const genUniqueIntObj = new GenerateUniqueInt({ minValue, maxValue }); + genUniqueIntObj.init({ count, seed }); + + this.state = { genUniqueIntObj, minValue, maxValue, precision }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const value = this.state.genUniqueIntObj.generate() as number / this.state.precision; + + return value; + } +} + +export class GenerateInt extends AbstractGenerator<{ + minValue?: number | bigint; + maxValue?: number | bigint; + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateInt'; + + private state: { + rng: prand.RandomGenerator; + minValue: number | bigint; + maxValue: number | bigint; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueInt; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + let { minValue, maxValue } = this.params; + + if (maxValue === undefined) { + maxValue = 1000; + } + + if (minValue === undefined) { + minValue = -maxValue; + } + + if (typeof minValue === 'number' && typeof maxValue === 'number') { + minValue = minValue >= 0 ? Math.ceil(minValue) : Math.floor(minValue); + maxValue = maxValue >= 0 ? Math.floor(maxValue) : Math.ceil(maxValue); + } + + const rng = prand.xoroshiro128plus(seed); + + this.state = { rng, minValue, maxValue }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let value: number | bigint; + if (typeof this.state.minValue === 'bigint' && typeof this.state.maxValue === 'bigint') { + [value, this.state.rng] = prand.uniformBigIntDistribution( + this.state.minValue, + this.state.maxValue, + this.state.rng, + ); + } else { + [value, this.state.rng] = prand.uniformIntDistribution( + this.state.minValue as number, + this.state.maxValue as number, + this.state.rng, + ); + } + + if (this.dataType === 'string') { + return String(value); + } + + if (this.dataType === 'bigint') { + value = BigInt(value); + } + return value; + } +} + +export class GenerateUniqueInt extends AbstractGenerator<{ + minValue?: number | bigint; + maxValue?: number | bigint; + isUnique?: boolean; +}> { + static override readonly entityKind: string = 'GenerateUniqueInt'; + + public genMaxRepeatedValuesCount: GenerateDefault | GenerateWeightedCount | undefined; + public skipCheck?: boolean = false; + public state: { + rng: prand.RandomGenerator; + minValue: number | bigint; + maxValue: number | bigint; + intervals: (number | bigint)[][]; + integersCount: Map; + } | undefined; + public override isUnique = true; + public override timeSpent = 0; + + override init({ count, seed }: { count: number; seed: number }) { + const rng = prand.xoroshiro128plus(seed); + let { minValue, maxValue } = this.params; + + if (maxValue === undefined) { + maxValue = count * 10; + } + if (minValue === undefined) { + minValue = -maxValue; + } + + const intervals = [[minValue, maxValue]]; + + const integersCount = new Map(); + + if (typeof minValue === 'bigint' && typeof maxValue === 'bigint') { + if (this.skipCheck === false && maxValue - minValue + BigInt(1) < count) { + throw new Error( + 'count exceeds max number of unique integers in given range(min, max), try to make range wider.', + ); + } + } else if (typeof minValue === 'number' && typeof maxValue === 'number') { + minValue = minValue >= 0 ? Math.ceil(minValue) : Math.floor(minValue); + maxValue = maxValue >= 0 ? Math.floor(maxValue) : Math.ceil(maxValue); + if (this.skipCheck === false && maxValue - minValue + 1 < count) { + throw new Error( + 'count exceeds max number of unique integers in given range(min, max), try to make range wider.', + ); + } + } else { + throw new Error( + 'minValue and maxValue should be the same type.', + ); + } + + this.state = { rng, minValue, maxValue, intervals, integersCount }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let intervalIdx: number, + numb: number | bigint | undefined; + + const intervalsToAdd: (number | bigint)[][] = []; + + if (this.state.intervals.length === 0) { + if (this.skipCheck === false) { + throw new RangeError( + 'generateUniqueInt: count exceeds max number of unique integers in given range(min, max), try to increase range.', + ); + } else { + return; + } + } + + [intervalIdx, this.state.rng] = prand.uniformIntDistribution( + 0, + this.state.intervals.length - 1, + this.state.rng, + ); + + const interval = this.state.intervals[intervalIdx] as (number | bigint)[]; + const [currMinNumb, currMaxNumb] = [interval[0] as number | bigint, interval[1] as number | bigint]; + + if (typeof currMinNumb === 'number' && typeof currMaxNumb === 'number') { + numb = this.generateNumber(currMinNumb, currMaxNumb, intervalsToAdd as number[][], intervalIdx); + } else if (typeof currMinNumb === 'bigint' && typeof currMaxNumb === 'bigint') { + numb = this.generateBigint( + currMinNumb as bigint, + currMaxNumb as bigint, + intervalsToAdd as bigint[][], + intervalIdx, + ); + } + + if (this.dataType === 'string') { + return String(numb); + } + + if (this.dataType === 'bigint' && numb !== undefined) { + numb = BigInt(numb); + } + return numb; + } + + generateNumber(currMinNumb: number, currMaxNumb: number, intervalsToAdd: number[][], intervalIdx: number) { + let numb: number; + + [numb, this.state!.rng] = prand.uniformIntDistribution(currMinNumb, currMaxNumb, this.state!.rng); + + if (this.genMaxRepeatedValuesCount !== undefined) { + if (this.state!.integersCount.get(numb) === undefined) { + this.state!.integersCount.set(numb, this.genMaxRepeatedValuesCount.generate() as number); + } + this.state!.integersCount.set(numb, this.state!.integersCount.get(numb)! - 1); + } + + if (this.state!.integersCount.get(numb) === undefined || this.state!.integersCount.get(numb) === 0) { + if (numb === currMinNumb) { + intervalsToAdd = numb + 1 <= currMaxNumb ? [[numb + 1, currMaxNumb]] : []; + } else if (numb === currMaxNumb) { + intervalsToAdd = [[currMinNumb, numb - 1]]; + } else { + intervalsToAdd = [ + [currMinNumb, numb - 1], + [numb + 1, currMaxNumb], + ]; + } + + const t0 = new Date(); + this.state!.intervals[intervalIdx] = this.state!.intervals[this.state!.intervals.length - 1]!; + this.state?.intervals.pop(); + this.timeSpent += (Date.now() - t0.getTime()) / 1000; + this.state!.intervals.push(...intervalsToAdd); + } + + return numb; + } + + generateBigint(currMinNumb: bigint, currMaxNumb: bigint, intervalsToAdd: bigint[][], intervalIdx: number) { + let numb: bigint; + [numb, this.state!.rng] = prand.uniformBigIntDistribution(currMinNumb, currMaxNumb, this.state!.rng); + if (this.genMaxRepeatedValuesCount !== undefined) { + if (this.state!.integersCount.get(numb) === undefined) { + this.state!.integersCount.set(numb, this.genMaxRepeatedValuesCount.generate() as number); + } + this.state!.integersCount.set(numb, this.state!.integersCount.get(numb)! - 1); + } + + if (this.state!.integersCount.get(numb) === undefined || this.state!.integersCount.get(numb) === 0) { + if (numb === currMinNumb) { + intervalsToAdd = numb + BigInt(1) <= currMaxNumb ? [[numb + BigInt(1), currMaxNumb]] : []; + } else if (numb === currMaxNumb) { + intervalsToAdd = [[currMinNumb, numb - BigInt(1)]]; + } else { + intervalsToAdd = [ + [currMinNumb, numb - BigInt(1)], + [numb + BigInt(1), currMaxNumb], + ]; + } + + this.state!.intervals[intervalIdx] = this.state!.intervals[this.state!.intervals.length - 1]!; + this.state?.intervals.pop(); + this.state!.intervals.push(...intervalsToAdd); + } + + return numb; + } +} + +export class GenerateBoolean extends AbstractGenerator<{ arraySize?: number }> { + static override readonly entityKind: string = 'GenerateBoolean'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let value: number; + + [value, this.state.rng] = prand.uniformIntDistribution(0, 1, this.state.rng); + return value === 1; + } +} + +export class GenerateDate extends AbstractGenerator<{ + minDate?: string | Date; + maxDate?: string | Date; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateDate'; + + private state: { + rng: prand.RandomGenerator; + minDate: Date; + maxDate: Date; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); + + let { minDate, maxDate } = this.params; + + const anchorDate = new Date('2024-05-08'); + const deltaMilliseconds = 4 * 31536000000; + + if (typeof minDate === 'string') { + minDate = new Date(minDate); + } + + if (typeof maxDate === 'string') { + maxDate = new Date(maxDate); + } + + if (minDate === undefined) { + if (maxDate === undefined) { + minDate = new Date(anchorDate.getTime() - deltaMilliseconds); + maxDate = new Date(anchorDate.getTime() + deltaMilliseconds); + } else { + minDate = new Date(maxDate.getTime() - (2 * deltaMilliseconds)); + } + } + + if (maxDate === undefined) { + maxDate = new Date(minDate.getTime() + (2 * deltaMilliseconds)); + } + + this.state = { rng, minDate, maxDate }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let milliseconds: number; + + [milliseconds, this.state.rng] = prand.uniformIntDistribution( + this.state.minDate.getTime(), + this.state.maxDate.getTime(), + this.state.rng, + ); + const date = new Date(milliseconds); + + if (this.dataType === 'string') { + return date.toISOString().replace(/T.+/, ''); + } + return date; + } +} +export class GenerateTime extends AbstractGenerator<{ arraySize?: number }> { + static override readonly entityKind: string = 'GenerateTime'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const anchorDateTime = new Date('2024-05-08T12:00:00.000Z'); + const oneDayInMilliseconds = 86400000; + + let date = new Date(); + let milliseconds: number; + + [milliseconds, this.state.rng] = prand.uniformIntDistribution( + -oneDayInMilliseconds, + oneDayInMilliseconds, + this.state.rng, + ); + date = new Date(date.setTime(anchorDateTime.getTime() + milliseconds)); + + return date.toISOString().replace(/(\d{4}-\d{2}-\d{2}T)|(\.\d{3}Z)/g, ''); + } +} +export class GenerateTimestampInt extends AbstractGenerator<{ unitOfTime?: 'seconds' | 'milliseconds' }> { + static override readonly entityKind: string = 'GenerateTimestampInt'; + + private state: { + generateTimestampObj: GenerateTimestamp; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + const generateTimestampObj = new GenerateTimestamp({}); + generateTimestampObj.dataType = 'date'; + generateTimestampObj.init({ count, seed }); + + this.state = { generateTimestampObj }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const date = this.state.generateTimestampObj.generate() as Date; + + if (this.params.unitOfTime === 'seconds') { + return Math.floor(date.getTime() / 1000); + } else if (this.params.unitOfTime === 'milliseconds') { + return date.getTime(); + } else { + // this.params.unitOfTime === undefined + return Math.floor(date.getTime() / 1000); + } + } +} + +export class GenerateTimestamp extends AbstractGenerator<{ arraySize?: number }> { + static override readonly entityKind: string = 'GenerateTimestamp'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const anchorTimestamp = new Date('2024-05-08'); + const twoYearsInMilliseconds = 2 * 31536000000; + + let date = new Date(); + let milliseconds: number; + + [milliseconds, this.state.rng] = prand.uniformIntDistribution( + -twoYearsInMilliseconds, + twoYearsInMilliseconds, + this.state.rng, + ); + date = new Date(date.setTime(anchorTimestamp.getTime() + milliseconds)); + + if (this.dataType === 'string') { + return date + .toISOString() + .replace('T', ' ') + .replace(/\.\d{3}Z/, ''); + } + + return date; + } +} + +export class GenerateDatetime extends AbstractGenerator<{ arraySize?: number }> { + static override readonly entityKind: string = 'GenerateDatetime'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const anchorDate = new Date('2024-05-08'); + const twoYearsInMilliseconds = 2 * 31536000000; + + let date = new Date(); + let milliseconds: number; + + [milliseconds, this.state.rng] = prand.uniformIntDistribution( + -twoYearsInMilliseconds, + twoYearsInMilliseconds, + this.state.rng, + ); + date = new Date(date.setTime(anchorDate.getTime() + milliseconds)); + + if (this.dataType === 'string') { + return date + .toISOString() + .replace('T', ' ') + .replace(/\.\d{3}Z/, ''); + } + + return date; + } +} + +export class GenerateYear extends AbstractGenerator<{ arraySize?: number }> { + static override readonly entityKind: string = 'GenerateYear'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const anchorDate = new Date('2024-05-08'); + const tenYears = 10; + + let date = new Date(); + let years: number; + + [years, this.state.rng] = prand.uniformIntDistribution(-tenYears, tenYears, this.state.rng); + date = new Date(date.setFullYear(anchorDate.getFullYear() + years)); + + return date + .toISOString() + .replace(/(-\d{2}-\d{2}T)|(\d{2}:\d{2}:\d{2}\.\d{3}Z)/g, ''); + } +} + +export class GenerateJson extends AbstractGenerator<{ arraySize?: number }> { + static override readonly entityKind: string = 'GenerateJson'; + + private state: { + emailGeneratorObj: GenerateEmail; + nameGeneratorObj: GenerateFirstName; + booleanGeneratorObj: GenerateBoolean; + salaryGeneratorObj: GenerateInt; + dateGeneratorObj: GenerateDate; + visitedCountriesNumberGeneratorObj: GenerateInt; + seed: number; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const emailGeneratorObj = new GenerateEmail({}); + emailGeneratorObj.init({ count, seed }); + + const nameGeneratorObj = new GenerateFirstName({}); + nameGeneratorObj.init({ count, seed }); + + const booleanGeneratorObj = new GenerateBoolean({}); + booleanGeneratorObj.init({ + count, + seed, + }); + + const salaryGeneratorObj = new GenerateInt({ minValue: 200, maxValue: 4000 }); + salaryGeneratorObj.init({ + count, + seed, + ...salaryGeneratorObj.params, + }); + + const dateGeneratorObj = new GenerateDate({}); + dateGeneratorObj.dataType = 'string'; + dateGeneratorObj.init({ count, seed }); + + const visitedCountriesNumberGeneratorObj = new GenerateInt({ minValue: 0, maxValue: 4 }); + visitedCountriesNumberGeneratorObj.init( + { count, seed, ...visitedCountriesNumberGeneratorObj.params }, + ); + + this.state = { + emailGeneratorObj, + nameGeneratorObj, + booleanGeneratorObj, + salaryGeneratorObj, + dateGeneratorObj, + visitedCountriesNumberGeneratorObj, + seed, + }; + } + + generate({ i }: { i: number }) { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const visitedCountries: string[] = []; + const email = this.state.emailGeneratorObj.generate(); + const name = this.state.nameGeneratorObj.generate(); + const isGraduated = this.state.booleanGeneratorObj.generate(); + const hasJob = this.state.booleanGeneratorObj.generate(); + const salary = this.state.salaryGeneratorObj.generate() as number; + const startedWorking = this.state.dateGeneratorObj.generate() as string; + const visitedCountriesNumber = this.state.visitedCountriesNumberGeneratorObj.generate() as number; + + const uniqueCountriesGeneratorObj = new GenerateUniqueCountry({}); + uniqueCountriesGeneratorObj.init({ + count: visitedCountriesNumber, + seed: this.state.seed + i, + }); + for (let j = 0; j < visitedCountriesNumber; j++) { + visitedCountries.push(uniqueCountriesGeneratorObj.generate()); + } + + const returnJson = hasJob + ? { + email, + name, + isGraduated, + hasJob, + salary, + startedWorking, + visitedCountries, + } + : { + email, + name, + isGraduated, + hasJob, + visitedCountries, + }; + + if (this.dataType === 'string') { + return JSON.stringify(returnJson); + } + + return returnJson; + } +} + +export class GenerateEnum extends AbstractGenerator<{ enumValues: (string | number | boolean)[] }> { + static override readonly entityKind: string = 'GenerateEnum'; + + private state: { + enumValuesGenerator: GenerateValuesFromArray; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + const { enumValues } = this.params; + const enumValuesGenerator = new GenerateValuesFromArray({ values: enumValues }); + enumValuesGenerator.init({ count, seed }); + this.state = { enumValuesGenerator }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + // logic for this generator + return this.state.enumValuesGenerator.generate(); + } +} + +export class GenerateInterval extends AbstractGenerator<{ + fields?: + | 'year' + | 'month' + | 'day' + | 'hour' + | 'minute' + | 'second' + | 'year to month' + | 'day to hour' + | 'day to minute' + | 'day to second' + | 'hour to minute' + | 'hour to second' + | 'minute to second'; + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateInterval'; + + private state: { + rng: prand.RandomGenerator; + fieldsToGenerate: string[]; + } | undefined; + override uniqueVersionOfGen: new(params: any) => AbstractGenerator = GenerateUniqueInterval; + private config: { [key: string]: { from: number; to: number } } = { + year: { + from: 0, + to: 5, + }, + month: { + from: 0, + to: 12, + }, + day: { + from: 1, + to: 29, + }, + hour: { + from: 0, + to: 24, + }, + minute: { + from: 0, + to: 60, + }, + second: { + from: 0, + to: 60, + }, + }; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const allFields = ['year', 'month', 'day', 'hour', 'minute', 'second']; + let fieldsToGenerate: string[] = allFields; + + if (this.params.fields !== undefined && this.params.fields?.includes(' to ')) { + const tokens = this.params.fields.split(' to '); + const endIdx = allFields.indexOf(tokens[1]!); + fieldsToGenerate = allFields.slice(0, endIdx + 1); + } else if (this.params.fields !== undefined) { + const endIdx = allFields.indexOf(this.params.fields); + fieldsToGenerate = allFields.slice(0, endIdx + 1); + } + + const rng = prand.xoroshiro128plus(seed); + this.state = { rng, fieldsToGenerate }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let interval = '', numb: number; + + for (const field of this.state.fieldsToGenerate) { + const from = this.config[field]!.from, to = this.config[field]!.to; + [numb, this.state.rng] = prand.uniformIntDistribution(from, to, this.state.rng); + interval += `${numb} ${field} `; + } + + return interval; + } +} + +// has a newer version +export class GenerateUniqueInterval extends AbstractGenerator<{ + fields?: + | 'year' + | 'month' + | 'day' + | 'hour' + | 'minute' + | 'second' + | 'year to month' + | 'day to hour' + | 'day to minute' + | 'day to second' + | 'hour to minute' + | 'hour to second' + | 'minute to second'; + isUnique?: boolean; +}> { + static override readonly 'entityKind': string = 'GenerateUniqueInterval'; + + private state: { + rng: prand.RandomGenerator; + fieldsToGenerate: string[]; + intervalSet: Set; + } | undefined; + public override isUnique = true; + private config: { [key: string]: { from: number; to: number } } = { + year: { + from: 0, + to: 5, + }, + month: { + from: 0, + to: 12, + }, + day: { + from: 1, + to: 29, + }, + hour: { + from: 0, + to: 24, + }, + minute: { + from: 0, + to: 60, + }, + second: { + from: 0, + to: 60, + }, + }; + + override init({ count, seed }: { count: number; seed: number }) { + const allFields = ['year', 'month', 'day', 'hour', 'minute', 'second']; + let fieldsToGenerate: string[] = allFields; + + if (this.params.fields !== undefined && this.params.fields?.includes(' to ')) { + const tokens = this.params.fields.split(' to '); + const endIdx = allFields.indexOf(tokens[1]!); + fieldsToGenerate = allFields.slice(0, endIdx + 1); + } else if (this.params.fields !== undefined) { + const endIdx = allFields.indexOf(this.params.fields); + fieldsToGenerate = allFields.slice(0, endIdx + 1); + } + + let maxUniqueIntervalsNumber = 1; + for (const field of fieldsToGenerate) { + const from = this.config[field]!.from, to = this.config[field]!.to; + maxUniqueIntervalsNumber *= from - to + 1; + } + + if (count > maxUniqueIntervalsNumber) { + throw new RangeError(`count exceeds max number of unique intervals(${maxUniqueIntervalsNumber})`); + } + + const rng = prand.xoroshiro128plus(seed); + const intervalSet = new Set(); + this.state = { rng, fieldsToGenerate, intervalSet }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let interval, numb: number; + + for (;;) { + interval = ''; + + for (const field of this.state.fieldsToGenerate) { + const from = this.config[field]!.from, to = this.config[field]!.to; + [numb, this.state.rng] = prand.uniformIntDistribution(from, to, this.state.rng); + interval += `${numb} ${field} `; + } + + if (!this.state.intervalSet.has(interval)) { + this.state.intervalSet.add(interval); + break; + } + } + + return interval; + } +} + +// has a newer version +export class GenerateString extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateString'; + + private state: { rng: prand.RandomGenerator } | undefined; + override uniqueVersionOfGen = GenerateUniqueString; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const minStringLength = 7; + const maxStringLength = 20; + const stringChars = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let idx: number, + strLength: number, + currStr: string; + + currStr = ''; + [strLength, this.state.rng] = prand.uniformIntDistribution( + minStringLength, + maxStringLength, + this.state.rng, + ); + for (let j = 0; j < strLength; j++) { + [idx, this.state.rng] = prand.uniformIntDistribution( + 0, + stringChars.length - 1, + this.state.rng, + ); + currStr += stringChars[idx]; + } + return currStr; + } +} + +// has a newer version +export class GenerateUniqueString extends AbstractGenerator<{ isUnique?: boolean }> { + static override readonly entityKind: string = 'GenerateUniqueString'; + + private state: { rng: prand.RandomGenerator } | undefined; + public override isUnique = true; + + override init({ seed }: { seed: number }) { + const rng = prand.xoroshiro128plus(seed); + this.state = { rng }; + } + + generate({ i }: { i: number }) { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const minStringLength = 7; + const maxStringLength = 20; + const stringChars = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let idx: number, + strLength: number; + let currStr: string; + + currStr = ''; + const uniqueStr = i.toString(16); + [strLength, this.state.rng] = prand.uniformIntDistribution( + minStringLength, + maxStringLength - uniqueStr.length, + this.state.rng, + ); + for (let j = 0; j < strLength - uniqueStr.length; j++) { + [idx, this.state.rng] = prand.uniformIntDistribution( + 0, + stringChars.length - 1, + this.state.rng, + ); + currStr += stringChars[idx]; + } + + return currStr.slice(0, 4) + uniqueStr + currStr.slice(4); + } +} + +export class GenerateUUID extends AbstractGenerator<{ + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateUUID'; + + public override isUnique = true; + + private state: { rng: prand.RandomGenerator } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + // TODO generate uuid using string generator + const stringChars = '1234567890abcdef'; + let idx: number, + currStr: string; + const strLength = 36; + + // uuid v4 + const uuidTemplate = '########-####-4###-####-############'; + currStr = ''; + for (let i = 0; i < strLength; i++) { + [idx, this.state.rng] = prand.uniformIntDistribution( + 0, + stringChars.length - 1, + this.state.rng, + ); + + if (uuidTemplate[i] === '#') { + currStr += stringChars[idx]; + continue; + } + currStr += uuidTemplate[i]; + } + return currStr; + } +} + +export class GenerateFirstName extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateFirstName'; + + override timeSpent: number = 0; + private state: { + rng: prand.RandomGenerator; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueFirstName; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + if (this.stringLength !== undefined && this.stringLength < maxFirstNameLength) { + throw new Error( + `You can't use first name generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxFirstNameLength}.`, + ); + } + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + // logic for this generator + // names dataset contains about 30000 unique names. + let idx: number; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, firstNames.length - 1, this.state.rng); + return firstNames[idx] as string; + } +} + +export class GenerateUniqueFirstName extends AbstractGenerator<{ + isUnique?: boolean; +}> { + static override readonly entityKind: string = 'GenerateUniqueFirstName'; + + private state: { + genIndicesObj: GenerateUniqueInt; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + if (count > firstNames.length) { + throw new Error('count exceeds max number of unique first names.'); + } + + if (this.stringLength !== undefined && this.stringLength < maxFirstNameLength) { + throw new Error( + `You can't use first name generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxFirstNameLength}.`, + ); + } + + const genIndicesObj = new GenerateUniqueInt({ minValue: 0, maxValue: firstNames.length - 1 }); + genIndicesObj.init({ count, seed }); + + this.state = { genIndicesObj }; + } + + generate() { + // names dataset contains about 30000 unique names. + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const nameIdx = this.state.genIndicesObj.generate() as number; + const name = firstNames[nameIdx] as string; + + return name; + } +} + +export class GenerateLastName extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateLastName'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueLastName; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + if (this.stringLength !== undefined && this.stringLength < maxLastNameLength) { + throw new Error( + `You can't use last name generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxLastNameLength}.`, + ); + } + + this.state = { rng }; + } + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx: number; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, lastNames.length - 1, this.state.rng); + return lastNames[idx]; + } +} + +export class GenerateUniqueLastName extends AbstractGenerator<{ isUnique?: boolean }> { + static override readonly entityKind: string = 'GenerateUniqueLastName'; + + private state: { + genIndicesObj: GenerateUniqueInt; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + if (count > lastNames.length) { + throw new Error('count exceeds max number of unique last names.'); + } + + if (this.stringLength !== undefined && this.stringLength < maxLastNameLength) { + throw new Error( + `You can't use last name generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxLastNameLength}.`, + ); + } + + const genIndicesObj = new GenerateUniqueInt({ minValue: 0, maxValue: lastNames.length - 1 }); + genIndicesObj.init({ count, seed }); + + this.state = { genIndicesObj }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const surnameIdx = this.state.genIndicesObj.generate() as number; + const surname = lastNames[surnameIdx] as string; + + return surname; + } +} + +export class GenerateFullName extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateFullName'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueFullName; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + if (this.stringLength !== undefined && this.stringLength < (maxFirstNameLength + maxLastNameLength + 1)) { + throw new Error( + `You can't use full name generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${ + maxFirstNameLength + maxLastNameLength + 1 + }.`, + ); + } + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx: number; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, firstNames.length - 1, this.state.rng); + const name = firstNames[idx] as string; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, lastNames.length - 1, this.state.rng); + const surname = lastNames[idx] as string; + + const fullName = `${name} ${surname}`; + + return fullName; + } +} + +export class GenerateUniqueFullName extends AbstractGenerator<{ + isUnique?: boolean; +}> { + static override readonly entityKind: string = 'GenerateUniqueFullName'; + + private state: { + fullnameSet: Set; + rng: prand.RandomGenerator; + } | undefined; + public override isUnique = true; + public override timeSpent = 0; + + override init({ count, seed }: { count: number; seed: number }) { + const t0 = new Date(); + + const maxUniqueFullNamesNumber = firstNames.length * lastNames.length; + if (count > maxUniqueFullNamesNumber) { + throw new RangeError( + `count exceeds max number of unique full names(${maxUniqueFullNamesNumber}).`, + ); + } + + if (this.stringLength !== undefined && this.stringLength < (maxFirstNameLength + maxLastNameLength + 1)) { + throw new Error( + `You can't use full name generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${ + maxFirstNameLength + maxLastNameLength + 1 + }.`, + ); + } + + const rng = prand.xoroshiro128plus(seed); + const fullnameSet = new Set(); + + this.state = { rng, fullnameSet }; + this.timeSpent += (Date.now() - t0.getTime()) / 1000; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let fullname: string, name: string, surname: string, idx: number; + + const t0 = new Date(); + for (;;) { + [idx, this.state.rng] = prand.uniformIntDistribution(0, firstNames.length - 1, this.state.rng); + name = firstNames[idx] as string; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, lastNames.length - 1, this.state.rng); + surname = lastNames[idx] as string; + + fullname = `${name} ${surname}`; + + if (!this.state.fullnameSet.has(fullname)) { + this.state.fullnameSet.add(fullname); + break; + } + } + + this.timeSpent += (Date.now() - t0.getTime()) / 1000; + return fullname; + } +} + +export class GenerateEmail extends AbstractGenerator<{ + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateEmail'; + + private state: { + genIndicesObj: GenerateUniqueInt; + arraysToGenerateFrom: string[][]; + } | undefined; + public override timeSpent: number = 0; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const domainsArray = emailDomains; + const adjectivesArray = adjectives; + const namesArray = firstNames; + + const maxUniqueEmailsNumber = adjectivesArray.length * namesArray.length * domainsArray.length; + if (count > maxUniqueEmailsNumber) { + throw new RangeError( + `count exceeds max number of unique emails(${maxUniqueEmailsNumber}).`, + ); + } + + const maxEmailLength = maxAdjectiveLength + maxFirstNameLength + maxEmailDomainLength + 2; + if (this.stringLength !== undefined && this.stringLength < maxEmailLength) { + throw new Error( + `You can't use email generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxEmailLength}.`, + ); + } + + const arraysToGenerateFrom = [adjectivesArray, namesArray, domainsArray]; + const genIndicesObj = new GenerateUniqueInt({ + minValue: 0, + maxValue: maxUniqueEmailsNumber - 1, + }); + genIndicesObj.init({ count, seed }); + + this.state = { genIndicesObj, arraysToGenerateFrom }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const t0 = new Date(); + const emailIndex = this.state.genIndicesObj.generate() as number; + this.timeSpent += (Date.now() - t0.getTime()) / 1000; + const tokens = fastCartesianProduct( + this.state.arraysToGenerateFrom, + emailIndex, + ) as string[]; + + const [adjective, name, domain] = [tokens[0] as string, tokens[1] as string, tokens[2] as string]; + + const email = `${adjective}_${name.toLowerCase()}@${domain}`; + + return email; + } +} + +export class GeneratePhoneNumber extends AbstractGenerator<{ + template?: string; + prefixes?: string[]; + generatedDigitsNumbers?: number | number[]; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GeneratePhoneNumber'; + + private state: { + rng: prand.RandomGenerator; + placeholdersCount?: number; + prefixesArray: string[]; + generatedDigitsNumbers: number[]; + generatorsMap: Map; + phoneNumbersSet: Set; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + let { generatedDigitsNumbers } = this.params; + const { prefixes, template } = this.params; + + const rng = prand.xoroshiro128plus(seed); + + if (template !== undefined) { + if (this.stringLength !== undefined && this.stringLength < template.length) { + throw new Error( + `Length of phone number template is shorter than db column length restriction: ${this.stringLength}. + Set the maximum string length to at least ${template.length}.`, + ); + } + + const iterArray = [...template.matchAll(/#/g)]; + const placeholdersCount = iterArray.length; + + const maxUniquePhoneNumbersCount = Math.pow(10, placeholdersCount); + if (maxUniquePhoneNumbersCount < count) { + throw new RangeError( + `count exceeds max number of unique phone numbers(${maxUniquePhoneNumbersCount}).`, + ); + } + + const generatorsMap = new Map(); + const genObj = new GenerateUniqueInt({ minValue: 0, maxValue: maxUniquePhoneNumbersCount - 1 }); + genObj.init({ + count, + seed, + }); + + generatorsMap.set( + template, + genObj, + ); + + const prefixesArray: string[] = []; + const generatedDigitsNumbers: number[] = []; + const phoneNumbersSet = new Set(); + + this.state = { rng, placeholdersCount, generatorsMap, prefixesArray, generatedDigitsNumbers, phoneNumbersSet }; + return; + } + + let prefixesArray: string[]; + if (prefixes === undefined || prefixes.length === 0) { + prefixesArray = phonesInfo.map((phoneInfo) => phoneInfo.split(',').slice(0, -1).join(' ')); + generatedDigitsNumbers = phonesInfo.map((phoneInfo) => { + // tokens = ["380","99","9"] = + // = ["country prefix", "operator prefix", "number length including operator prefix and excluding country prefix"] + const tokens = phoneInfo.split(','); + const operatorPrefixLength = tokens[1]!.replaceAll(' ', '').length; + + return Number(tokens[2]) - operatorPrefixLength; + }); + } else { + prefixesArray = prefixes; + if (typeof generatedDigitsNumbers === 'number') { + generatedDigitsNumbers = Array.from({ length: prefixes.length }).fill( + generatedDigitsNumbers, + ); + } else if ( + generatedDigitsNumbers === undefined + || generatedDigitsNumbers.length === 0 + ) { + generatedDigitsNumbers = Array.from({ length: prefixes.length }).fill(7); + } + } + + const maxPrefixLength = Math.max(...prefixesArray.map((prefix) => prefix.length)); + const maxGeneratedDigits = Math.max(...generatedDigitsNumbers); + + if (this.stringLength !== undefined && this.stringLength < (maxPrefixLength + maxGeneratedDigits)) { + throw new Error( + `You can't use phone number generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${ + maxPrefixLength + maxGeneratedDigits + }.`, + ); + } + + if (new Set(prefixesArray).size !== prefixesArray.length) { + throw new Error('prefixes are not unique.'); + } + + const maxUniquePhoneNumbersCount = generatedDigitsNumbers.reduce( + (a, b) => a + Math.pow(10, b), + 0, + ); + if (maxUniquePhoneNumbersCount < count) { + throw new RangeError( + `count exceeds max number of unique phone numbers(${maxUniquePhoneNumbersCount}).`, + ); + } + + const generatorsMap = new Map(); + let maxValue: number, prefix: string, generatedDigitsNumber: number; + for (const [i, element] of prefixesArray.entries()) { + prefix = element as string; + generatedDigitsNumber = generatedDigitsNumbers[i] as number; + maxValue = Math.pow(10, generatedDigitsNumber) - 1; + + if (!generatorsMap.has(prefix)) { + const genObj = new GenerateUniqueInt({ minValue: 0, maxValue }); + genObj.init({ + count: Math.min(count, maxValue + 1), + seed, + }); + genObj.skipCheck = true; + generatorsMap.set( + prefix, + genObj, + ); + } + } + const phoneNumbersSet = new Set(); + + this.state = { rng, prefixesArray, generatedDigitsNumbers, generatorsMap, phoneNumbersSet }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let prefix: string, generatedDigitsNumber: number, numberBody: string, phoneNumber: string, idx: number; + + if (this.params.template === undefined) { + for (;;) { + [idx, this.state.rng] = prand.uniformIntDistribution( + 0, + this.state.prefixesArray.length - 1, + this.state.rng, + ); + prefix = this.state.prefixesArray[idx] as string; + generatedDigitsNumber = this.state.generatedDigitsNumbers[idx] as number; + + numberBody = String(this.state.generatorsMap.get(prefix)?.generate()); + if (numberBody === 'undefined') { + this.state.prefixesArray!.splice(idx, 1); + this.state.generatedDigitsNumbers.splice(idx, 1); + + this.state.generatorsMap.delete(prefix); + + continue; + } + + if (this.state.phoneNumbersSet.has(numberBody)) { + continue; + } + this.state.phoneNumbersSet.add(numberBody); + + break; + } + + const digitsNumberDiff = generatedDigitsNumber - numberBody.length; + if (digitsNumberDiff > 0) { + numberBody = '0'.repeat(digitsNumberDiff) + numberBody; + } + + phoneNumber = prefix + '' + numberBody; + + return phoneNumber; + } else { + numberBody = String(this.state.generatorsMap.get(this.params.template)?.generate()); + phoneNumber = fillTemplate({ + template: this.params.template, + values: [...numberBody], + defaultValue: '0', + placeholdersCount: this.state.placeholdersCount, + }); + + return phoneNumber; + } + } +} + +export class GenerateCountry extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateCountry'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueCountry; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + if (this.stringLength !== undefined && this.stringLength < maxCountryLength) { + throw new Error( + `You can't use country generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxCountryLength}.`, + ); + } + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx: number; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, countries.length - 1, this.state.rng); + const country = countries[idx] as string; + + return country; + } +} + +export class GenerateUniqueCountry extends AbstractGenerator<{ isUnique?: boolean }> { + static override readonly entityKind: string = 'GenerateUniqueCountry'; + + private state: { + genIndicesObj: GenerateUniqueInt; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + if (count > countries.length) { + throw new Error('count exceeds max number of unique countries.'); + } + + if (this.stringLength !== undefined && this.stringLength < maxCountryLength) { + throw new Error( + `You can't use country generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxCountryLength}.`, + ); + } + + const genIndicesObj = new GenerateUniqueInt({ minValue: 0, maxValue: countries.length - 1 }); + genIndicesObj.init({ count, seed }); + + this.state = { genIndicesObj }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const countryIdx = this.state.genIndicesObj.generate() as number; + const country = countries[countryIdx] as string; + + return country; + } +} + +export class GenerateJobTitle extends AbstractGenerator<{ + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateJobTitle'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + if (this.stringLength !== undefined && this.stringLength < maxJobTitleLength) { + throw new Error( + `You can't use job title generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxJobTitleLength}.`, + ); + } + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + let idx; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, jobsTitles.length - 1, this.state.rng); + + return jobsTitles[idx]; + } +} + +export class GenerateStreetAddress extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateStreetAddress'; + + private state: { + rng: prand.RandomGenerator; + possStreetNames: string[][]; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueStreetAddress; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + const possStreetNames = [firstNames, lastNames]; + + const maxStreetAddressLength = 4 + Math.max(maxFirstNameLength, maxLastNameLength) + 1 + maxStreetSuffixLength; + if (this.stringLength !== undefined && this.stringLength < maxStreetAddressLength) { + throw new Error( + `You can't use street address generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxStreetAddressLength}.`, + ); + } + + this.state = { rng, possStreetNames }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx, streetBaseNameIdx, streetSuffixIdx, streetNumber; + [idx, this.state.rng] = prand.uniformIntDistribution(0, this.state.possStreetNames.length - 1, this.state.rng); + + [streetBaseNameIdx, this.state.rng] = prand.uniformIntDistribution( + 0, + this.state.possStreetNames[idx]!.length - 1, + this.state.rng, + ); + [streetSuffixIdx, this.state.rng] = prand.uniformIntDistribution(0, streetSuffix.length - 1, this.state.rng); + const streetName = `${this.state.possStreetNames[idx]![streetBaseNameIdx]} ${streetSuffix[streetSuffixIdx]}`; + + [streetNumber, this.state.rng] = prand.uniformIntDistribution(1, 999, this.state.rng); + + return `${streetNumber} ${streetName}`; + } +} + +export class GenerateUniqueStreetAddress extends AbstractGenerator<{ isUnique?: boolean }> { + static override readonly entityKind: string = 'GenerateUniqueStreetAddress'; + + private state: { + rng: prand.RandomGenerator; + possStreetNameObjs: { + indicesGen: GenerateUniqueInt; + maxUniqueStreetNamesNumber: number; + count: number; + arraysToChooseFrom: string[][]; + }[]; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + const streetNumberStrs = Array.from({ length: 999 }, (_, i) => String(i + 1)); + const maxUniqueStreetnamesNumber = streetNumberStrs.length * firstNames.length * streetSuffix.length + + streetNumberStrs.length * firstNames.length * streetSuffix.length; + + if (count > maxUniqueStreetnamesNumber) { + throw new RangeError( + `count exceeds max number of unique street names(${maxUniqueStreetnamesNumber}).`, + ); + } + + const maxStreetAddressLength = 4 + Math.max(maxFirstNameLength, maxLastNameLength) + 1 + maxStreetSuffixLength; + if (this.stringLength !== undefined && this.stringLength < maxStreetAddressLength) { + throw new Error( + `You can't use street address generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxStreetAddressLength}.`, + ); + } + + const rng = prand.xoroshiro128plus(seed); + // ["1", "2", ..., "999"] + + const possStreetNameObjs = [ + { + indicesGen: new GenerateUniqueInt({ + minValue: 0, + maxValue: streetNumberStrs.length * firstNames.length * streetSuffix.length - 1, + }), + maxUniqueStreetNamesNumber: streetNumberStrs.length * firstNames.length * streetSuffix.length, + count: 0, + arraysToChooseFrom: [streetNumberStrs, firstNames, streetSuffix], + }, + { + indicesGen: new GenerateUniqueInt({ + minValue: 0, + maxValue: streetNumberStrs.length * lastNames.length * streetSuffix.length - 1, + }), + maxUniqueStreetNamesNumber: streetNumberStrs.length * firstNames.length * streetSuffix.length, + count: 0, + arraysToChooseFrom: [streetNumberStrs, lastNames, streetSuffix], + }, + ]; + + for (const possStreetNameObj of possStreetNameObjs) { + possStreetNameObj.indicesGen.skipCheck = true; + possStreetNameObj.indicesGen.init({ count, seed }); + } + + this.state = { rng, possStreetNameObjs }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let streetNameObjIdx; + [streetNameObjIdx, this.state.rng] = prand.uniformIntDistribution( + 0, + this.state.possStreetNameObjs.length - 1, + this.state.rng, + ); + const streetNameObj = this.state.possStreetNameObjs[streetNameObjIdx]!; + + const idx = streetNameObj.indicesGen.generate() as number; + const values = fastCartesianProduct(streetNameObj.arraysToChooseFrom, idx) as string[]; + + streetNameObj.count += 1; + if (streetNameObj.count === streetNameObj.maxUniqueStreetNamesNumber) { + this.state.possStreetNameObjs[streetNameObjIdx] = this.state + .possStreetNameObjs.at(-1)!; + this.state.possStreetNameObjs.pop(); + } + + const streetName = fillTemplate({ template: '# # #', values, placeholdersCount: 3 }); + + return streetName; + } +} + +export class GenerateCity extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateCity'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueCity; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + if (this.stringLength !== undefined && this.stringLength < maxCityNameLength) { + throw new Error( + `You can't use city generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxCityNameLength}.`, + ); + } + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx; + [idx, this.state.rng] = prand.uniformIntDistribution(0, cityNames.length - 1, this.state.rng); + + return cityNames[idx]; + } +} + +export class GenerateUniqueCity extends AbstractGenerator<{ isUnique?: boolean }> { + static override readonly entityKind: string = 'GenerateUniqueCity'; + + private state: { + genIndicesObj: GenerateUniqueInt; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + if (count > cityNames.length) { + throw new Error('count exceeds max number of unique cities.'); + } + + if (this.stringLength !== undefined && this.stringLength < maxCityNameLength) { + throw new Error( + `You can't use city generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxCityNameLength}.`, + ); + } + + const genIndicesObj = new GenerateUniqueInt({ minValue: 0, maxValue: cityNames.length - 1 }); + genIndicesObj.init({ count, seed }); + + this.state = { genIndicesObj }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const cityIdx = this.state.genIndicesObj.generate() as number; + const city = cityNames[cityIdx] as string; + + return city; + } +} + +export class GeneratePostcode extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GeneratePostcode'; + + private state: { + rng: prand.RandomGenerator; + templates: string[]; + } | undefined; + override uniqueVersionOfGen = GenerateUniquePostcode; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + const templates = ['#####', '#####-####']; + + const maxPostcodeLength = Math.max(...templates.map((template) => template.length)); + if (this.stringLength !== undefined && this.stringLength < maxPostcodeLength) { + throw new Error( + `You can't use postcode generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxPostcodeLength}.`, + ); + } + + this.state = { rng, templates }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx: number, postcodeNumber: number; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, this.state.templates.length - 1, this.state.rng); + const template = this.state.templates[idx]!; + + const iterArray = [...template.matchAll(/#/g)]; + const placeholdersCount = iterArray.length; + + [postcodeNumber, this.state.rng] = prand.uniformIntDistribution( + 0, + Math.pow(10, placeholdersCount) - 1, + this.state.rng, + ); + const postcode = fillTemplate({ + template, + placeholdersCount, + values: [...String(postcodeNumber)], + defaultValue: '0', + }); + + return postcode; + } +} + +export class GenerateUniquePostcode extends AbstractGenerator<{ isUnique?: boolean }> { + static override readonly entityKind: string = 'GenerateUniquePostcode'; + + private state: { + rng: prand.RandomGenerator; + templates: { + template: string; + indicesGen: GenerateUniqueInt; + placeholdersCount: number; + count: number; + maxUniquePostcodeNumber: number; + }[]; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + const maxUniquePostcodeNumber = Math.pow(10, 5) + Math.pow(10, 9); + if (count > maxUniquePostcodeNumber) { + throw new RangeError( + `count exceeds max number of unique postcodes(${maxUniquePostcodeNumber}).`, + ); + } + + const rng = prand.xoroshiro128plus(seed); + const templates = [ + { + template: '#####', + indicesGen: new GenerateUniqueInt({ minValue: 0, maxValue: Math.pow(10, 5) - 1 }), + placeholdersCount: 5, + count: 0, + maxUniquePostcodeNumber: Math.pow(10, 5), + }, + { + template: '#####-####', + indicesGen: new GenerateUniqueInt({ minValue: 0, maxValue: Math.pow(10, 9) - 1 }), + placeholdersCount: 9, + count: 0, + maxUniquePostcodeNumber: Math.pow(10, 9), + }, + ]; + + const maxPostcodeLength = Math.max(...templates.map((template) => template.template.length)); + if (this.stringLength !== undefined && this.stringLength < maxPostcodeLength) { + throw new Error( + `You can't use postcode generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxPostcodeLength}.`, + ); + } + + for (const templateObj of templates) { + templateObj.indicesGen.skipCheck = true; + templateObj.indicesGen.init({ count, seed }); + } + + this.state = { rng, templates }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx: number; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, this.state.templates.length - 1, this.state.rng); + const templateObj = this.state.templates[idx]!; + + const postcodeNumber = templateObj.indicesGen.generate() as number; + + templateObj.count += 1; + if (templateObj.count === templateObj.maxUniquePostcodeNumber) { + this.state.templates[idx] = this.state.templates.at(-1)!; + this.state.templates.pop(); + } + + const postcode = fillTemplate({ + template: templateObj.template, + placeholdersCount: templateObj.placeholdersCount, + values: [...String(postcodeNumber)], + defaultValue: '0', + }); + + return postcode; + } +} + +export class GenerateState extends AbstractGenerator<{ + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateState'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + + if (this.stringLength !== undefined && this.stringLength < maxStateLength) { + throw new Error( + `You can't use state generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxStateLength}.`, + ); + } + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx; + [idx, this.state.rng] = prand.uniformIntDistribution(0, states.length - 1, this.state.rng); + + return states[idx]; + } +} + +export class GenerateCompanyName extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateCompanyName'; + + private state: { + rng: prand.RandomGenerator; + templates: { template: string; placeholdersCount: number }[]; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueCompanyName; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + const templates = [ + { template: '#', placeholdersCount: 1 }, + { template: '# - #', placeholdersCount: 2 }, + { template: '# and #', placeholdersCount: 2 }, + { template: '#, # and #', placeholdersCount: 3 }, + ]; + + // max( { template: '#', placeholdersCount: 1 }, { template: '#, # and #', placeholdersCount: 3 } ) + const maxCompanyNameLength = Math.max( + maxLastNameLength + maxCompanyNameSuffixLength + 1, + 3 * maxLastNameLength + 7, + ); + if (this.stringLength !== undefined && this.stringLength < maxCompanyNameLength) { + throw new Error( + `You can't use company name generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxCompanyNameLength}.`, + ); + } + + this.state = { rng, templates }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let templateIdx, idx, lastName, companyNameSuffix, companyName; + [templateIdx, this.state.rng] = prand.uniformIntDistribution(0, this.state.templates.length - 1, this.state.rng); + const templateObj = this.state.templates[templateIdx]!; + + if (templateObj.template === '#') { + [idx, this.state.rng] = prand.uniformIntDistribution(0, lastNames.length - 1, this.state.rng); + lastName = lastNames[idx]; + + [idx, this.state.rng] = prand.uniformIntDistribution(0, companyNameSuffixes.length - 1, this.state.rng); + companyNameSuffix = companyNameSuffixes[idx]; + + companyName = `${lastName} ${companyNameSuffix}`; + return companyName; + } + + const values = []; + for (let i = 0; i < templateObj.placeholdersCount; i++) { + [idx, this.state.rng] = prand.uniformIntDistribution(0, lastNames.length - 1, this.state.rng); + values.push(lastNames[idx]!); + } + + companyName = fillTemplate({ + template: templateObj.template, + values, + placeholdersCount: templateObj.placeholdersCount, + }); + return companyName; + } +} + +export class GenerateUniqueCompanyName extends AbstractGenerator<{ isUnique?: boolean }> { + static override readonly entityKind: string = 'GenerateUniqueCompanyName'; + + private state: { + rng: prand.RandomGenerator; + templates: { + template: string; + placeholdersCount: number; + indicesGen: GenerateUniqueInt; + maxUniqueCompanyNameNumber: number; + count: number; + arraysToChooseFrom: string[][]; + }[]; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + const maxUniqueCompanyNameNumber = lastNames.length * companyNameSuffixes.length + Math.pow(lastNames.length, 2) + + Math.pow(lastNames.length, 2) + Math.pow(lastNames.length, 3); + if (count > maxUniqueCompanyNameNumber) { + throw new RangeError( + `count exceeds max number of unique company names(${maxUniqueCompanyNameNumber}).`, + ); + } + + // max( { template: '#', placeholdersCount: 1 }, { template: '#, # and #', placeholdersCount: 3 } ) + const maxCompanyNameLength = Math.max( + maxLastNameLength + maxCompanyNameSuffixLength + 1, + 3 * maxLastNameLength + 7, + ); + if (this.stringLength !== undefined && this.stringLength < maxCompanyNameLength) { + throw new Error( + `You can't use company name generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxCompanyNameLength}.`, + ); + } + + const rng = prand.xoroshiro128plus(seed); + // when count reach maxUniqueCompanyNameNumber template will be deleted from array + const templates = [ + { + template: '# - #', + placeholdersCount: 1, + indicesGen: new GenerateUniqueInt({ minValue: 0, maxValue: lastNames.length * companyNameSuffixes.length - 1 }), + maxUniqueCompanyNameNumber: lastNames.length * companyNameSuffixes.length, + count: 0, + arraysToChooseFrom: [lastNames, companyNameSuffixes], + }, + { + template: '# - #', + placeholdersCount: 2, + indicesGen: new GenerateUniqueInt({ minValue: 0, maxValue: Math.pow(lastNames.length, 2) - 1 }), + maxUniqueCompanyNameNumber: Math.pow(lastNames.length, 2), + count: 0, + arraysToChooseFrom: [lastNames, lastNames], + }, + { + template: '# and #', + placeholdersCount: 2, + indicesGen: new GenerateUniqueInt({ minValue: 0, maxValue: Math.pow(lastNames.length, 2) - 1 }), + maxUniqueCompanyNameNumber: Math.pow(lastNames.length, 2), + count: 0, + arraysToChooseFrom: [lastNames, lastNames], + }, + { + template: '#, # and #', + placeholdersCount: 3, + indicesGen: new GenerateUniqueInt({ minValue: 0, maxValue: Math.pow(lastNames.length, 3) - 1 }), + maxUniqueCompanyNameNumber: Math.pow(lastNames.length, 3), + count: 0, + arraysToChooseFrom: [lastNames, lastNames, lastNames], + }, + ]; + + for (const templateObj of templates) { + templateObj.indicesGen.skipCheck = true; + templateObj.indicesGen.init({ count, seed }); + } + + this.state = { rng, templates }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let templateIdx; + [templateIdx, this.state.rng] = prand.uniformIntDistribution(0, this.state.templates.length - 1, this.state.rng); + const templateObj = this.state.templates[templateIdx]!; + + const idx = templateObj.indicesGen.generate() as number; + const values = fastCartesianProduct(templateObj.arraysToChooseFrom, idx) as string[]; + + templateObj.count += 1; + if (templateObj.count === templateObj.maxUniqueCompanyNameNumber) { + this.state.templates[templateIdx] = this.state.templates.at(-1)!; + this.state.templates.pop(); + } + + const companyName = fillTemplate({ + template: templateObj.template, + values, + placeholdersCount: templateObj.placeholdersCount, + }); + return companyName; + } +} + +export class GenerateLoremIpsum extends AbstractGenerator<{ + sentencesCount?: number; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateLoremIpsum'; + + private state: { + rng: prand.RandomGenerator; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + if (this.params.sentencesCount === undefined) this.params.sentencesCount = 1; + + const maxLoremIpsumSentencesLength = maxLoremIpsumLength * this.params.sentencesCount + this.params.sentencesCount + - 1; + if (this.stringLength !== undefined && this.stringLength < maxLoremIpsumSentencesLength) { + throw new Error( + `You can't use lorem ipsum generator with a db column length restriction of ${this.stringLength}. Set the maximum string length to at least ${maxLoremIpsumSentencesLength}.`, + ); + } + + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx, resultText: string = ''; + for (let i = 0; i < this.params.sentencesCount!; i++) { + [idx, this.state.rng] = prand.uniformIntDistribution(0, loremIpsumSentences.length - 1, this.state.rng); + resultText += loremIpsumSentences[idx] + ' '; + } + + return resultText; + } +} + +export class WeightedRandomGenerator extends AbstractGenerator<{ weight: number; value: AbstractGenerator }[]> { + static override readonly entityKind: string = 'WeightedRandomGenerator'; + + private state: { + rng: prand.RandomGenerator; + weightedIndices: number[]; + } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + const weights = this.params.map((weightedGen) => weightedGen.weight); + const weightedIndices = getWeightedIndices(weights); + + let idx: number, valueIdx: number, tempRng = prand.xoroshiro128plus(seed); + const indicesCounter: { [key: number]: number } = {}; + for (let i = 0; i < count; i++) { + [idx, tempRng] = prand.uniformIntDistribution(0, weightedIndices.length - 1, tempRng); + valueIdx = weightedIndices[idx]!; + if (!Object.hasOwn(indicesCounter, valueIdx)) indicesCounter[valueIdx] = 0; + indicesCounter[valueIdx]! += 1; + } + + for (const [idx, weightedGen] of this.params.entries()) { + weightedGen.value.isUnique = this.isUnique; + weightedGen.value.dataType = this.dataType; + weightedGen.value.init({ count: indicesCounter[idx]!, seed }); + + if ( + weightedGen.value.uniqueVersionOfGen !== undefined + && weightedGen.value.isUnique === true + ) { + const uniqueGen = new weightedGen.value.uniqueVersionOfGen({ + ...weightedGen.value.params, + }); + uniqueGen.init({ + count: indicesCounter[idx]!, + seed, + }); + uniqueGen.isUnique = weightedGen.value.isUnique; + uniqueGen.dataType = weightedGen.value.dataType; + + weightedGen.value = uniqueGen; + } + } + + const rng = prand.xoroshiro128plus(seed); + + this.state = { weightedIndices, rng }; + } + + generate({ i }: { i: number }) { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let idx: number; + [idx, this.state.rng] = prand.uniformIntDistribution(0, this.state.weightedIndices.length - 1, this.state.rng); + const generatorIdx = this.state.weightedIndices[idx] as number; + const value = this.params[generatorIdx]!.value.generate({ i }); + + return value; + } +} + +export class GeneratePoint extends AbstractGenerator<{ + isUnique?: boolean; + minXValue?: number; + maxXValue?: number; + minYValue?: number; + maxYValue?: number; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GeneratePoint'; + + private state: { + xCoordinateGen: GenerateNumber; + yCoordinateGen: GenerateNumber; + } | undefined; + override uniqueVersionOfGen = GenerateUniquePoint; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const xCoordinateGen = new GenerateNumber({ + minValue: this.params.minXValue, + maxValue: this.params.maxXValue, + precision: 10, + }); + xCoordinateGen.init({ count, seed }); + + const yCoordinateGen = new GenerateNumber({ + minValue: this.params.minYValue, + maxValue: this.params.maxYValue, + precision: 10, + }); + yCoordinateGen.init({ count, seed }); + + this.state = { xCoordinateGen, yCoordinateGen }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const x = this.state.xCoordinateGen.generate(); + const y = this.state.yCoordinateGen.generate(); + + if (this.dataType === 'json') { + return { x, y }; + } else if (this.dataType === 'string') { + return `[${x}, ${y}]`; + } else { + // if (this.dataType === "array") + return [x, y]; + } + } +} + +export class GenerateUniquePoint extends AbstractGenerator<{ + minXValue?: number; + maxXValue?: number; + minYValue?: number; + maxYValue?: number; + isUnique?: boolean; +}> { + static override readonly entityKind: string = 'GenerateUniquePoint'; + + private state: { + xCoordinateGen: GenerateUniqueNumber; + yCoordinateGen: GenerateUniqueNumber; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + const xCoordinateGen = new GenerateUniqueNumber({ + minValue: this.params.minXValue, + maxValue: this.params.maxXValue, + precision: 10, + }); + xCoordinateGen.init({ count, seed }); + + const yCoordinateGen = new GenerateUniqueNumber({ + minValue: this.params.minYValue, + maxValue: this.params.maxYValue, + precision: 10, + }); + yCoordinateGen.init({ count, seed }); + + this.state = { xCoordinateGen, yCoordinateGen }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const x = this.state.xCoordinateGen.generate(); + const y = this.state.yCoordinateGen.generate(); + + if (this.dataType === 'json') { + return { x, y }; + } else if (this.dataType === 'string') { + return `[${x}, ${y}]`; + } else { + // if (this.dataType === "array") + return [x, y]; + } + } +} + +export class GenerateLine extends AbstractGenerator<{ + isUnique?: boolean; + minAValue?: number; + maxAValue?: number; + minBValue?: number; + maxBValue?: number; + minCValue?: number; + maxCValue?: number; + arraySize?: number; +}> { + static override readonly entityKind: string = 'GenerateLine'; + + private state: { + aCoefficientGen: GenerateNumber; + bCoefficientGen: GenerateNumber; + cCoefficientGen: GenerateNumber; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueLine; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const aCoefficientGen = new GenerateNumber({ + minValue: this.params.minAValue, + maxValue: this.params.maxAValue, + precision: 10, + }); + aCoefficientGen.init({ count, seed }); + + const bCoefficientGen = new GenerateNumber({ + minValue: this.params.minBValue, + maxValue: this.params.maxBValue, + precision: 10, + }); + bCoefficientGen.init({ count, seed }); + + const cCoefficientGen = new GenerateNumber({ + minValue: this.params.minCValue, + maxValue: this.params.maxCValue, + precision: 10, + }); + cCoefficientGen.init({ count, seed }); + + this.state = { aCoefficientGen, bCoefficientGen, cCoefficientGen }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let b: number; + const a = this.state.aCoefficientGen.generate(); + + b = this.state.bCoefficientGen.generate(); + while (a === 0 && b === 0) { + b = this.state.bCoefficientGen.generate(); + } + + const c = this.state.cCoefficientGen.generate(); + + if (this.dataType === 'json') { + return { a, b, c }; + } else if (this.dataType === 'string') { + return `[${a}, ${b}, ${c}]`; + } else { + // if (this.dataType === "array") + return [a, b, c]; + } + } +} + +export class GenerateUniqueLine extends AbstractGenerator<{ + minAValue?: number; + maxAValue?: number; + minBValue?: number; + maxBValue?: number; + minCValue?: number; + maxCValue?: number; + isUnique?: boolean; +}> { + static override readonly entityKind: string = 'GenerateUniqueLine'; + + private state: { + aCoefficientGen: GenerateUniqueNumber; + bCoefficientGen: GenerateUniqueNumber; + cCoefficientGen: GenerateUniqueNumber; + } | undefined; + public override isUnique = true; + + override init({ count, seed }: { count: number; seed: number }) { + const aCoefficientGen = new GenerateUniqueNumber({ + minValue: this.params.minAValue, + maxValue: this.params.maxAValue, + precision: 10, + }); + aCoefficientGen.init({ count, seed }); + + const bCoefficientGen = new GenerateUniqueNumber({ + minValue: this.params.minBValue, + maxValue: this.params.maxBValue, + precision: 10, + }); + bCoefficientGen.init({ count, seed }); + + const cCoefficientGen = new GenerateUniqueNumber({ + minValue: this.params.minCValue, + maxValue: this.params.maxCValue, + precision: 10, + }); + cCoefficientGen.init({ count, seed }); + + this.state = { aCoefficientGen, bCoefficientGen, cCoefficientGen }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let b: number; + const a = this.state.aCoefficientGen.generate(); + + b = this.state.bCoefficientGen.generate(); + while (a === 0 && b === 0) { + b = this.state.bCoefficientGen.generate(); + } + + const c = this.state.cCoefficientGen.generate(); + + if (this.dataType === 'json') { + return { a, b, c }; + } else if (this.dataType === 'string') { + return `[${a}, ${b}, ${c}]`; + } else { + // if (this.dataType === "array") + return [a, b, c]; + } + } +} diff --git a/drizzle-seed/src/services/SeedService.ts b/drizzle-seed/src/services/SeedService.ts new file mode 100644 index 000000000..e68a939e0 --- /dev/null +++ b/drizzle-seed/src/services/SeedService.ts @@ -0,0 +1,1611 @@ +/* eslint-disable drizzle-internal/require-entity-kind */ +import { entityKind, eq, is } from 'drizzle-orm'; +import type { MySqlTable, MySqlTableWithColumns } from 'drizzle-orm/mysql-core'; +import { MySqlDatabase } from 'drizzle-orm/mysql-core'; +import type { PgTable, PgTableWithColumns } from 'drizzle-orm/pg-core'; +import { PgDatabase } from 'drizzle-orm/pg-core'; +import type { SQLiteTable, SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core'; +import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'; +import type { + GeneratePossibleGeneratorsColumnType, + GeneratePossibleGeneratorsTableType, + RefinementsType, + TableGeneratorsType, +} from '../types/seedService.ts'; +import type { Column, Prettify, Relation, Table } from '../types/tables.ts'; +import { generatorsMap } from './GeneratorFuncs.ts'; +import type { AbstractGenerator, GenerateArray, GenerateInterval, GenerateWeightedCount } from './Generators.ts'; + +import { latestVersion } from './apiVersion.ts'; +import { equalSets, generateHashFromString } from './utils.ts'; + +export class SeedService { + static readonly entityKind: string = 'SeedService'; + + private defaultCountForTable = 10; + private postgresPgLiteMaxParametersNumber = 32740; + private postgresMaxParametersNumber = 65535; + // there is no max parameters number in mysql, so you can increase mysqlMaxParametersNumber if it's needed. + private mysqlMaxParametersNumber = 100000; + // SQLITE_MAX_VARIABLE_NUMBER, which by default equals to 999 for SQLite versions prior to 3.32.0 (2020-05-22) or 32766 for SQLite versions after 3.32.0. + private sqliteMaxParametersNumber = 32766; + private version?: number; + + generatePossibleGenerators = ( + connectionType: 'postgresql' | 'mysql' | 'sqlite', + tables: Table[], + relations: (Relation & { isCyclic: boolean })[], + refinements?: RefinementsType, + options?: { count?: number; seed?: number; version?: number }, + ) => { + let columnPossibleGenerator: Prettify; + let tablePossibleGenerators: Prettify; + const customSeed = options?.seed === undefined ? 0 : options.seed; + this.version = options?.version === undefined ? latestVersion : options.version; + if (Number.isNaN(this.version) || this.version < 1 || this.version > latestVersion) { + throw new Error(`Version should be in range [1, ${latestVersion}].`); + } + + // sorting table in order which they will be filled up (tables with foreign keys case) + const { tablesInOutRelations } = this.getInfoFromRelations(relations); + const orderedTablesNames = this.getOrderedTablesList(tablesInOutRelations); + tables = tables.sort((table1, table2) => { + const rel = relations.find((rel) => rel.table === table1.name && rel.refTable === table2.name); + + if (rel?.isCyclic === true) { + const reverseRel = relations.find((rel) => rel.table === table2.name && rel.refTable === table1.name); + return this.cyclicTablesCompare(table1, table2, rel, reverseRel); + } + + const table1Order = orderedTablesNames.indexOf( + table1.name, + ), + table2Order = orderedTablesNames.indexOf( + table2.name, + ); + return table1Order - table2Order; + }); + + const tableNamesSet = new Set(tables.map((table) => table.name)); + const tablesPossibleGenerators: Prettify< + (typeof tablePossibleGenerators)[] + > = tables.map((table) => ({ + tableName: table.name, + columnsPossibleGenerators: [], + withFromTable: {}, + })); + + for (const [i, table] of tables.entries()) { + // get foreignKey columns relations + const foreignKeyColumns: { + [columnName: string]: { table: string; column: string }; + } = {}; + + for ( + const rel of relations + .filter((rel) => rel.table === table.name) + ) { + for (const [idx, col] of rel.columns.entries()) { + foreignKeyColumns[col] = { + table: rel.refTable, + column: rel.refColumns[idx] as string, + }; + } + } + + if (refinements !== undefined && refinements[table.name] !== undefined) { + if (refinements[table.name]!.count !== undefined) { + tablesPossibleGenerators[i]!.count = refinements[table.name]!.count; + } + + if (refinements[table.name]!.with !== undefined) { + tablesPossibleGenerators[i]!.count = refinements[table.name]!.count + || options?.count + || this.defaultCountForTable; + let idx: number; + for ( + const fkTableName of Object.keys( + refinements[table.name]!.with as {}, + ) + ) { + if (!tablesInOutRelations[table.name]?.dependantTableNames.has(fkTableName)) { + const reason = tablesInOutRelations[table.name]?.selfRelation === true + ? `"${table.name}" table has self reference` + : `"${fkTableName}" table doesn't have a reference to "${table.name}" table or` + + `\nyou didn't include your one-to-many relation in the seed function schema`; + throw new Error( + `${reason}.` + `\nYou can't specify "${fkTableName}" as parameter in ${table.name}.with object.` + + `\n\nFor more details, check this: https://orm.drizzle.team/docs/guides/seeding-using-with-option`, + ); + } + + idx = tablesPossibleGenerators.findIndex( + (table) => table.tableName === fkTableName, + ); + if (idx !== -1) { + let newTableWithCount: number, + weightedCountSeed: number | undefined; + if ( + typeof refinements![table.name]!.with![fkTableName] === 'number' + ) { + newTableWithCount = (tablesPossibleGenerators[i]!.withCount + || tablesPossibleGenerators[i]!.count)! + * (refinements[table.name]!.with![fkTableName] as number); + } else { + const weightedRepeatedValuesCount = refinements[table.name]! + .with![fkTableName] as { + weight: number; + count: number | number[]; + }[]; + + weightedCountSeed = customSeed + + generateHashFromString(`${table.name}.${fkTableName}`); + + newTableWithCount = this.getWeightedWithCount( + weightedRepeatedValuesCount, + (tablesPossibleGenerators[i]!.withCount + || tablesPossibleGenerators[i]!.count)!, + weightedCountSeed, + ); + } + + if ( + tablesPossibleGenerators[idx]!.withCount === undefined + || newTableWithCount > tablesPossibleGenerators[idx]!.withCount! + ) { + tablesPossibleGenerators[idx]!.withCount = newTableWithCount; + } + + tablesPossibleGenerators[idx]!.withFromTable[table.name] = { + repeatedValuesCount: refinements[table.name]!.with![fkTableName]!, + weightedCountSeed, + }; + } + } + } + } + tablePossibleGenerators = tablesPossibleGenerators[i]!; + for (const col of table.columns) { + // col.myType = typeMap[col._type as keyof typeof typeMap]; + columnPossibleGenerator = { + columnName: col.name, + isUnique: col.isUnique, + notNull: col.notNull, + primary: col.primary, + generatedIdentityType: col.generatedIdentityType, + generator: undefined, + isCyclic: false, + wasDefinedBefore: false, + wasRefined: false, + }; + + if ( + refinements !== undefined + && refinements[table.name] !== undefined + && refinements[table.name]!.columns !== undefined + && refinements[table.name]!.columns[col.name] !== undefined + ) { + const genObj = refinements[table.name]!.columns[col.name]!; + + if (col.columnType.match(/\[\w*]/g) !== null) { + if ( + (col.baseColumn?.dataType === 'array' && col.baseColumn.columnType.match(/\[\w*]/g) !== null) + // studio case + || (col.typeParams.dimensions !== undefined && col.typeParams.dimensions > 1) + ) { + throw new Error("for now you can't specify generators for columns of dimension greater than 1."); + } + + genObj.baseColumnDataType = col.baseColumn?.dataType; + } + + columnPossibleGenerator.generator = genObj; + columnPossibleGenerator.wasRefined = true; + } else if (Object.hasOwn(foreignKeyColumns, col.name)) { + // TODO: I might need to assign repeatedValuesCount to column there instead of doing so in generateTablesValues + const cyclicRelation = relations.find((rel) => + rel.table === table.name + && rel.isCyclic === true + && rel.columns.includes(col.name) + ); + + if (cyclicRelation !== undefined) { + columnPossibleGenerator.isCyclic = true; + } + + if ( + (foreignKeyColumns[col.name]?.table === undefined || !tableNamesSet.has(foreignKeyColumns[col.name]!.table)) + && col.notNull === true + ) { + throw new Error( + `Column '${col.name}' has not null contraint,` + + `\nand you didn't specify a table for foreign key on column '${col.name}' in '${table.name}' table.` + + `\n\nFor more details, check this: https://orm.drizzle.team/docs/guides/seeding-with-partially-exposed-tables#example-1`, + ); + } + + const predicate = ( + cyclicRelation !== undefined + || ( + foreignKeyColumns[col.name]?.table === undefined + || !tableNamesSet.has(foreignKeyColumns[col.name]!.table) + ) + ) + && col.notNull === false; + + if (predicate === true) { + if ( + (foreignKeyColumns[col.name]?.table === undefined + || !tableNamesSet.has(foreignKeyColumns[col.name]!.table)) && col.notNull === false + ) { + console.warn( + `Column '${col.name}' in '${table.name}' table will be filled with Null values` + + `\nbecause you specified neither a table for foreign key on column '${col.name}'` + + `\nnor a function for '${col.name}' column in refinements.` + + `\n\nFor more details, check this: https://orm.drizzle.team/docs/guides/seeding-with-partially-exposed-tables#example-2`, + ); + } + columnPossibleGenerator.generator = new generatorsMap.GenerateDefault[0]({ defaultValue: null }); + columnPossibleGenerator.wasDefinedBefore = true; + } else { + columnPossibleGenerator.generator = new generatorsMap.HollowGenerator[0](); + } + } // TODO: rewrite pickGeneratorFor... using new col properties: isUnique and notNull + else if (connectionType === 'postgresql') { + columnPossibleGenerator.generator = this.selectGeneratorForPostgresColumn( + table, + col, + ); + } else if (connectionType === 'mysql') { + columnPossibleGenerator.generator = this.selectGeneratorForMysqlColumn( + table, + col, + ); + } else if (connectionType === 'sqlite') { + columnPossibleGenerator.generator = this.selectGeneratorForSqlite( + table, + col, + ); + } + + if (columnPossibleGenerator.generator === undefined) { + throw new Error( + `column with type ${col.columnType} is not supported for now.`, + ); + } + + const arrayGen = columnPossibleGenerator.generator.replaceIfArray(); + if (arrayGen !== undefined) { + columnPossibleGenerator.generator = arrayGen; + } + + columnPossibleGenerator.generator.isUnique = col.isUnique; + const uniqueGen = columnPossibleGenerator.generator.replaceIfUnique(); + if (uniqueGen !== undefined) { + columnPossibleGenerator.generator = uniqueGen; + } + + // selecting version of generator + columnPossibleGenerator.generator = this.selectVersionOfGenerator(columnPossibleGenerator.generator); + + // TODO: for now only GenerateValuesFromArray support notNull property + columnPossibleGenerator.generator.notNull = col.notNull; + columnPossibleGenerator.generator.dataType = col.dataType; + columnPossibleGenerator.generator.stringLength = col.typeParams.length; + + tablePossibleGenerators.columnsPossibleGenerators.push( + columnPossibleGenerator, + ); + } + } + + return tablesPossibleGenerators; + }; + + selectVersionOfGenerator = (generator: AbstractGenerator) => { + const entityKind = generator.getEntityKind(); + if (entityKind === 'GenerateArray') { + const oldBaseColumnGen = (generator as GenerateArray).params.baseColumnGen; + + const newBaseColumnGen = this.selectVersionOfGenerator(oldBaseColumnGen); + // newGenerator.baseColumnDataType = oldGenerator.baseColumnDataType; + + (generator as GenerateArray).params.baseColumnGen = newBaseColumnGen; + } + + const possibleGeneratorConstructors = generatorsMap[entityKind as keyof typeof generatorsMap]; + + const possibleGeneratorConstructorsFiltered = possibleGeneratorConstructors?.filter((possGenCon) => + possGenCon.version <= this.version! // sorting in ascending order by version + ).sort((a, b) => a.version - b.version); + const generatorConstructor = possibleGeneratorConstructorsFiltered?.at(-1) as + | (new(params: any) => AbstractGenerator) + | undefined; + if (generatorConstructor === undefined) { + throw new Error(`Can't select ${entityKind} generator for ${this.version} version.`); + } + + const newGenerator = new generatorConstructor(generator.params); + newGenerator.baseColumnDataType = generator.baseColumnDataType; + newGenerator.isUnique = generator.isUnique; + // TODO: for now only GenerateValuesFromArray support notNull property + newGenerator.notNull = generator.notNull; + newGenerator.dataType = generator.dataType; + newGenerator.stringLength = generator.stringLength; + + return newGenerator; + }; + + cyclicTablesCompare = ( + table1: Table, + table2: Table, + relation: Relation & { isCyclic: boolean }, + reverseRelation: Relation & { isCyclic: boolean } | undefined, + ) => { + // TODO: revise + const hasTable1NotNullColumns = relation.columns.some((colIName) => + table1.columns.find((colJ) => colJ.name === colIName)?.notNull === true + ); + + if (reverseRelation !== undefined) { + const hasTable2NotNullColumns = reverseRelation.columns.some((colIName) => + table2.columns.find((colJ) => colJ.name === colIName)?.notNull === true + ); + + if (hasTable1NotNullColumns && hasTable2NotNullColumns) { + throw new Error( + `The '${table1.name}' and '${table2.name}' tables have not null foreign keys. You can't seed cyclic tables with not null foreign key columns.`, + ); + } + + if (hasTable1NotNullColumns) return 1; + else if (hasTable2NotNullColumns) return -1; + return 0; + } + + if (hasTable1NotNullColumns) { + return 1; + } + return 0; + + // if (hasTable1NotNullColumns) return 1; + // else if (hasTable2NotNullColumns) return -1; + }; + + getOrderedTablesList = ( + tablesInOutRelations: ReturnType['tablesInOutRelations'], + ): string[] => { + const leafTablesNames = Object.entries(tablesInOutRelations) + .filter( + (tableRel) => + tableRel[1].out === 0 + || (tableRel[1].out !== 0 + && tableRel[1].selfRelCount === tableRel[1].out), + ) + .map((tableRel) => tableRel[0]); + + const orderedTablesNames: string[] = []; + let parent: string, children: string[]; + for (let i = 0; leafTablesNames.length !== 0; i++) { + parent = leafTablesNames.shift() as string; + + if (orderedTablesNames.includes(parent)) { + continue; + } + + if (tablesInOutRelations[parent] === undefined) { + orderedTablesNames.push(parent); + continue; + } + + for (const orderedTableName of orderedTablesNames) { + tablesInOutRelations[parent]!.requiredTableNames.delete(orderedTableName); + } + + if ( + tablesInOutRelations[parent]!.requiredTableNames.size === 0 + || equalSets( + tablesInOutRelations[parent]!.requiredTableNames, + tablesInOutRelations[parent]!.dependantTableNames, + ) + ) { + orderedTablesNames.push(parent); + } else { + leafTablesNames.push(parent); + continue; + } + + children = [...tablesInOutRelations[parent]!.dependantTableNames]; + leafTablesNames.push(...children); + } + return orderedTablesNames; + }; + + getInfoFromRelations = (relations: (Relation & { isCyclic: boolean })[]) => { + const tablesInOutRelations: { + [tableName: string]: { + out: number; + in: number; + selfRelation: boolean; + selfRelCount: number; + requiredTableNames: Set; + dependantTableNames: Set; + }; + } = {}; + + // const cyclicRelations: { [cyclicTableName: string]: Relation & { isCyclic: boolean } } = {}; + + for (const rel of relations) { + // if (rel.isCyclic) { + // cyclicRelations[rel.table] = rel; + // } + + if (tablesInOutRelations[rel.table] === undefined) { + tablesInOutRelations[rel.table] = { + out: 0, + in: 0, + selfRelation: false, + selfRelCount: 0, + requiredTableNames: new Set(), + dependantTableNames: new Set(), + }; + } + + if ( + rel.refTable !== undefined + && tablesInOutRelations[rel.refTable] === undefined + ) { + tablesInOutRelations[rel.refTable] = { + out: 0, + in: 0, + selfRelation: false, + selfRelCount: 0, + requiredTableNames: new Set(), + dependantTableNames: new Set(), + }; + } + + if (rel.refTable !== undefined) { + tablesInOutRelations[rel.table]!.out += 1; + tablesInOutRelations[rel.refTable]!.in += 1; + } + + if (rel.refTable === rel.table) { + tablesInOutRelations[rel.table]!.selfRelation = true; + tablesInOutRelations[rel.table]!.selfRelCount = rel.columns.length; + } else if (rel.refTable !== undefined) { + tablesInOutRelations[rel.table]!.requiredTableNames.add(rel.refTable); + tablesInOutRelations[rel.refTable]!.dependantTableNames.add(rel.table); + } + } + + return { tablesInOutRelations }; + }; + + getWeightedWithCount = ( + weightedCount: { weight: number; count: number | number[] }[], + count: number, + seed: number, + ) => { + let gen = new generatorsMap.GenerateWeightedCount[0](); + gen = this.selectVersionOfGenerator(gen) as GenerateWeightedCount; + // const gen = new GenerateWeightedCount({}); + gen.init({ count: weightedCount, seed }); + let weightedWithCount = 0; + for (let i = 0; i < count; i++) { + weightedWithCount += gen.generate(); + } + + return weightedWithCount; + }; + + // TODO: revise serial part generators + selectGeneratorForPostgresColumn = ( + table: Table, + col: Column, + ) => { + const pickGenerator = (table: Table, col: Column) => { + // ARRAY + if (col.columnType.match(/\[\w*]/g) !== null && col.baseColumn !== undefined) { + const baseColumnGen = this.selectGeneratorForPostgresColumn( + table, + col.baseColumn!, + ) as AbstractGenerator; + if (baseColumnGen === undefined) { + throw new Error(`column with type ${col.baseColumn!.columnType} is not supported for now.`); + } + + // const getBaseColumnDataType = (baseColumn: Column) => { + // if (baseColumn.baseColumn !== undefined) { + // return getBaseColumnDataType(baseColumn.baseColumn); + // } + + // return baseColumn.dataType; + // }; + // const baseColumnDataType = getBaseColumnDataType(col.baseColumn); + + const generator = new generatorsMap.GenerateArray[0]({ baseColumnGen, size: col.size }); + // generator.baseColumnDataType = baseColumnDataType; + + return generator; + } + + // ARRAY for studio + if (col.columnType.match(/\[\w*]/g) !== null) { + // remove dimensions from type + const baseColumnType = col.columnType.replace(/\[\w*]/g, ''); + const baseColumn: Column = { + ...col, + }; + baseColumn.columnType = baseColumnType; + + const baseColumnGen = this.selectGeneratorForPostgresColumn(table, baseColumn) as AbstractGenerator; + if (baseColumnGen === undefined) { + throw new Error(`column with type ${col.baseColumn!.columnType} is not supported for now.`); + } + + let generator = new generatorsMap.GenerateArray[0]({ baseColumnGen }); + + for (let i = 0; i < col.typeParams.dimensions! - 1; i++) { + generator = new generatorsMap.GenerateArray[0]({ baseColumnGen: generator }); + } + + return generator; + } + + // INT ------------------------------------------------------------------------------------------------------------ + if ( + (col.columnType.includes('serial') + || col.columnType === 'integer' + || col.columnType === 'smallint' + || col.columnType.includes('bigint')) + && table.primaryKeys.includes(col.name) + ) { + const generator = new generatorsMap.GenerateIntPrimaryKey[0](); + + return generator; + } + + let minValue: number | bigint | undefined; + let maxValue: number | bigint | undefined; + if (col.columnType.includes('serial')) { + minValue = 1; + if (col.columnType === 'smallserial') { + // 2^16 / 2 - 1, 2 bytes + maxValue = 32767; + } else if (col.columnType === 'serial') { + // 2^32 / 2 - 1, 4 bytes + maxValue = 2147483647; + } else if (col.columnType === 'bigserial') { + // 2^64 / 2 - 1, 8 bytes + minValue = BigInt(1); + maxValue = BigInt('9223372036854775807'); + } + } else if (col.columnType.includes('int')) { + if (col.columnType === 'smallint') { + // 2^16 / 2 - 1, 2 bytes + minValue = -32768; + maxValue = 32767; + } else if (col.columnType === 'integer') { + // 2^32 / 2 - 1, 4 bytes + minValue = -2147483648; + maxValue = 2147483647; + } else if (col.columnType.includes('bigint')) { + if (col.dataType === 'bigint') { + // 2^64 / 2 - 1, 8 bytes + minValue = BigInt('-9223372036854775808'); + maxValue = BigInt('9223372036854775807'); + } else { + // if (col.dataType === 'number') + // if you’re expecting values above 2^31 but below 2^53 + minValue = -9007199254740991; + maxValue = 9007199254740991; + } + } + } + + if ( + col.columnType.includes('int') + && !col.columnType.includes('interval') + && !col.columnType.includes('point') + ) { + const generator = new generatorsMap.GenerateInt[0]({ + minValue, + maxValue, + }); + + return generator; + } + + if (col.columnType.includes('serial')) { + const generator = new generatorsMap.GenerateIntPrimaryKey[0](); + + generator.maxValue = maxValue; + + return generator; + } + + // NUMBER(real, double, decimal, numeric) + if ( + col.columnType.startsWith('real') + || col.columnType.startsWith('double precision') + || col.columnType.startsWith('decimal') + || col.columnType.startsWith('numeric') + ) { + if (col.typeParams.precision !== undefined) { + const precision = col.typeParams.precision; + const scale = col.typeParams.scale === undefined ? 0 : col.typeParams.scale; + + const maxAbsoluteValue = Math.pow(10, precision - scale) - Math.pow(10, -scale); + const generator = new generatorsMap.GenerateNumber[0]({ + minValue: -maxAbsoluteValue, + maxValue: maxAbsoluteValue, + precision: Math.pow(10, scale), + }); + return generator; + } + const generator = new generatorsMap.GenerateNumber[0](); + + return generator; + } + + // STRING + if ( + (col.columnType === 'text' + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('char')) + && table.primaryKeys.includes(col.name) + ) { + const generator = new generatorsMap.GenerateUniqueString[0](); + + return generator; + } + + if ( + (col.columnType === 'text' + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('char')) + && col.name.toLowerCase().includes('name') + ) { + const generator = new generatorsMap.GenerateFirstName[0](); + + return generator; + } + + if ( + (col.columnType === 'text' + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('char')) + && col.name.toLowerCase().includes('email') + ) { + const generator = new generatorsMap.GenerateEmail[0](); + + return generator; + } + + if ( + col.columnType === 'text' + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('char') + ) { + const generator = new generatorsMap.GenerateString[0](); + + return generator; + } + + // UUID + if (col.columnType === 'uuid') { + const generator = new generatorsMap.GenerateUUID[0](); + + return generator; + } + + // BOOLEAN + if (col.columnType === 'boolean') { + const generator = new generatorsMap.GenerateBoolean[0](); + + return generator; + } + + // DATE, TIME, TIMESTAMP + if (col.columnType.includes('date')) { + const generator = new generatorsMap.GenerateDate[0](); + + return generator; + } + + if (col.columnType === 'time') { + const generator = new generatorsMap.GenerateTime[0](); + + return generator; + } + + if (col.columnType.includes('timestamp')) { + const generator = new generatorsMap.GenerateTimestamp[0](); + + return generator; + } + + // JSON, JSONB + if (col.columnType === 'json' || col.columnType === 'jsonb') { + const generator = new generatorsMap.GenerateJson[0](); + + return generator; + } + + // if (col.columnType === "jsonb") { + // const generator = new GenerateJsonb({}); + // return generator; + // } + + // ENUM + if (col.enumValues !== undefined) { + const generator = new generatorsMap.GenerateEnum[0]({ + enumValues: col.enumValues, + }); + + return generator; + } + + // INTERVAL + if (col.columnType.startsWith('interval')) { + if (col.columnType === 'interval') { + const generator = new generatorsMap.GenerateInterval[0](); + + return generator; + } + + const fields = col.columnType.replace('interval ', '') as GenerateInterval['params']['fields']; + const generator = new generatorsMap.GenerateInterval[0]({ fields }); + + return generator; + } + + // POINT, LINE + if (col.columnType.includes('point')) { + const generator = new generatorsMap.GeneratePoint[0](); + + return generator; + } + + if (col.columnType.includes('line')) { + const generator = new generatorsMap.GenerateLine[0](); + + return generator; + } + + if (col.hasDefault && col.default !== undefined) { + const generator = new generatorsMap.GenerateDefault[0]({ + defaultValue: col.default, + }); + return generator; + } + + return; + }; + + const generator = pickGenerator(table, col); + if (generator !== undefined) { + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + generator.stringLength = col.typeParams.length; + } + + return generator; + }; + + selectGeneratorForMysqlColumn = ( + table: Table, + col: Column, + ) => { + const pickGenerator = (table: Table, col: Column) => { + // INT ------------------------------------------------------------------------------------------------------------ + if ( + (col.columnType.includes('serial') || col.columnType.includes('int')) + && table.primaryKeys.includes(col.name) + ) { + const generator = new generatorsMap.GenerateIntPrimaryKey[0](); + return generator; + } + + let minValue: number | bigint | undefined; + let maxValue: number | bigint | undefined; + if (col.columnType === 'serial') { + // 2^64 % 2 - 1, 8 bytes + minValue = BigInt(0); + maxValue = BigInt('9223372036854775807'); + } else if (col.columnType.includes('int')) { + if (col.columnType === 'tinyint') { + // 2^8 / 2 - 1, 1 bytes + minValue = -128; + maxValue = 127; + } else if (col.columnType === 'smallint') { + // 2^16 / 2 - 1, 2 bytes + minValue = -32768; + maxValue = 32767; + } else if (col.columnType === 'mediumint') { + // 2^16 / 2 - 1, 2 bytes + minValue = -8388608; + maxValue = 8388607; + } else if (col.columnType === 'int') { + // 2^32 / 2 - 1, 4 bytes + minValue = -2147483648; + maxValue = 2147483647; + } else if (col.columnType === 'bigint') { + // 2^64 / 2 - 1, 8 bytes + minValue = BigInt('-9223372036854775808'); + maxValue = BigInt('9223372036854775807'); + } + } + + if (col.columnType.includes('int')) { + const generator = new generatorsMap.GenerateInt[0]({ + minValue, + maxValue, + }); + return generator; + } + + if (col.columnType.includes('serial')) { + const generator = new generatorsMap.GenerateIntPrimaryKey[0](); + generator.maxValue = maxValue; + return generator; + } + + // NUMBER(real, double, decimal, float) + if ( + col.columnType.startsWith('real') + || col.columnType.startsWith('double') + || col.columnType.startsWith('decimal') + || col.columnType.startsWith('float') + || col.columnType.startsWith('numeric') + ) { + if (col.typeParams.precision !== undefined) { + const precision = col.typeParams.precision; + const scale = col.typeParams.scale === undefined ? 0 : col.typeParams.scale; + + const maxAbsoluteValue = Math.pow(10, precision - scale) - Math.pow(10, -scale); + const generator = new generatorsMap.GenerateNumber[0]({ + minValue: -maxAbsoluteValue, + maxValue: maxAbsoluteValue, + precision: Math.pow(10, scale), + }); + return generator; + } + + const generator = new generatorsMap.GenerateNumber[0](); + return generator; + } + + // STRING + if ( + (col.columnType === 'text' + || col.columnType === 'blob' + || col.columnType.startsWith('char') + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('binary') + || col.columnType.startsWith('varbinary')) + && table.primaryKeys.includes(col.name) + ) { + const generator = new generatorsMap.GenerateUniqueString[0](); + return generator; + } + + if ( + (col.columnType === 'text' + || col.columnType === 'blob' + || col.columnType.startsWith('char') + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('binary') + || col.columnType.startsWith('varbinary')) + && col.name.toLowerCase().includes('name') + ) { + const generator = new generatorsMap.GenerateFirstName[0](); + return generator; + } + + if ( + (col.columnType === 'text' + || col.columnType === 'blob' + || col.columnType.startsWith('char') + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('binary') + || col.columnType.startsWith('varbinary')) + && col.name.toLowerCase().includes('email') + ) { + const generator = new generatorsMap.GenerateEmail[0](); + return generator; + } + + if ( + col.columnType === 'text' + || col.columnType === 'blob' + || col.columnType.startsWith('char') + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('binary') + || col.columnType.startsWith('varbinary') + ) { + const generator = new generatorsMap.GenerateString[0](); + return generator; + } + + // BOOLEAN + if (col.columnType === 'boolean') { + const generator = new generatorsMap.GenerateBoolean[0](); + return generator; + } + + // DATE, TIME, TIMESTAMP, DATETIME, YEAR + if (col.columnType.includes('datetime')) { + const generator = new generatorsMap.GenerateDatetime[0](); + return generator; + } + + if (col.columnType.includes('date')) { + const generator = new generatorsMap.GenerateDate[0](); + return generator; + } + + if (col.columnType === 'time') { + const generator = new generatorsMap.GenerateTime[0](); + return generator; + } + + if (col.columnType.includes('timestamp')) { + const generator = new generatorsMap.GenerateTimestamp[0](); + return generator; + } + + if (col.columnType === 'year') { + const generator = new generatorsMap.GenerateYear[0](); + return generator; + } + + // JSON + if (col.columnType === 'json') { + const generator = new generatorsMap.GenerateJson[0](); + return generator; + } + + // ENUM + if (col.enumValues !== undefined) { + const generator = new generatorsMap.GenerateEnum[0]({ + enumValues: col.enumValues, + }); + return generator; + } + + if (col.hasDefault && col.default !== undefined) { + const generator = new generatorsMap.GenerateDefault[0]({ + defaultValue: col.default, + }); + return generator; + } + + return; + }; + + const generator = pickGenerator(table, col); + + return generator; + }; + + selectGeneratorForSqlite = ( + table: Table, + col: Column, + ) => { + const pickGenerator = (table: Table, col: Column) => { + // int section --------------------------------------------------------------------------------------- + if ( + (col.columnType === 'integer' || col.columnType === 'numeric') + && table.primaryKeys.includes(col.name) + ) { + const generator = new generatorsMap.GenerateIntPrimaryKey[0](); + return generator; + } + + if (col.columnType === 'integer' && col.dataType === 'boolean') { + const generator = new generatorsMap.GenerateBoolean[0](); + return generator; + } + + if ((col.columnType === 'integer' && col.dataType === 'date')) { + const generator = new generatorsMap.GenerateTimestamp[0](); + return generator; + } + + if ( + col.columnType === 'integer' + || (col.dataType === 'bigint' && col.columnType === 'blob') + ) { + const generator = new generatorsMap.GenerateInt[0](); + return generator; + } + + // number section ------------------------------------------------------------------------------------ + if (col.columnType.startsWith('real') || col.columnType.startsWith('numeric')) { + if (col.typeParams.precision !== undefined) { + const precision = col.typeParams.precision; + const scale = col.typeParams.scale === undefined ? 0 : col.typeParams.scale; + + const maxAbsoluteValue = Math.pow(10, precision - scale) - Math.pow(10, -scale); + const generator = new generatorsMap.GenerateNumber[0]({ + minValue: -maxAbsoluteValue, + maxValue: maxAbsoluteValue, + precision: Math.pow(10, scale), + }); + return generator; + } + + const generator = new generatorsMap.GenerateNumber[0](); + return generator; + } + + // string section ------------------------------------------------------------------------------------ + if ( + (col.columnType.startsWith('text') + || col.columnType.startsWith('numeric') + || col.columnType.startsWith('blob')) + && table.primaryKeys.includes(col.name) + ) { + const generator = new generatorsMap.GenerateUniqueString[0](); + return generator; + } + + if ( + (col.columnType.startsWith('text') + || col.columnType.startsWith('numeric') + || col.columnType.startsWith('blob')) + && col.name.toLowerCase().includes('name') + ) { + const generator = new generatorsMap.GenerateFirstName[0](); + return generator; + } + + if ( + (col.columnType.startsWith('text') + || col.columnType.startsWith('numeric') + || col.columnType.startsWith('blob')) + && col.name.toLowerCase().includes('email') + ) { + const generator = new generatorsMap.GenerateEmail[0](); + return generator; + } + + if ( + col.columnType.startsWith('text') + || col.columnType.startsWith('numeric') + || col.columnType.startsWith('blob') + || col.columnType.startsWith('blobbuffer') + ) { + const generator = new generatorsMap.GenerateString[0](); + return generator; + } + + if ( + (col.columnType.startsWith('text') && col.dataType === 'json') + || (col.columnType.startsWith('blob') && col.dataType === 'json') + ) { + const generator = new generatorsMap.GenerateJson[0](); + return generator; + } + + if (col.hasDefault && col.default !== undefined) { + const generator = new generatorsMap.GenerateDefault[0]({ + defaultValue: col.default, + }); + return generator; + } + + return; + }; + + const generator = pickGenerator(table, col); + + return generator; + }; + + filterCyclicTables = (tablesGenerators: ReturnType) => { + const filteredTablesGenerators = tablesGenerators.filter((tableGen) => + tableGen.columnsPossibleGenerators.some((columnGen) => + columnGen.isCyclic === true && columnGen.wasDefinedBefore === true + ) + ); + + const tablesUniqueNotNullColumn: { [tableName: string]: { uniqueNotNullColName: string } } = {}; + + for (const [idx, tableGen] of filteredTablesGenerators.entries()) { + const uniqueNotNullColName = filteredTablesGenerators[idx]!.columnsPossibleGenerators.find((colGen) => + colGen.primary === true + || (colGen.isUnique === true + && colGen.notNull === true) + )?.columnName; + if (uniqueNotNullColName === undefined) { + throw new Error( + `Table '${tableGen.tableName}' does not have primary or (unique and notNull) column. Can't seed table with cyclic relation.`, + ); + } + tablesUniqueNotNullColumn[tableGen.tableName] = { uniqueNotNullColName }; + + filteredTablesGenerators[idx]!.columnsPossibleGenerators = tableGen.columnsPossibleGenerators.filter((colGen) => + (colGen.isCyclic === true && colGen.wasDefinedBefore === true) || colGen.columnName === uniqueNotNullColName + ).map((colGen) => { + const newColGen = { ...colGen }; + newColGen.wasDefinedBefore = false; + return newColGen; + }); + } + + return { filteredTablesGenerators, tablesUniqueNotNullColumn }; + }; + + generateTablesValues = async ( + relations: (Relation & { isCyclic: boolean })[], + tablesGenerators: ReturnType, + db?: + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema?: { [key: string]: PgTable | MySqlTable | SQLiteTable }, + options?: { + count?: number; + seed?: number; + preserveData?: boolean; + preserveCyclicTablesData?: boolean; + insertDataInDb?: boolean; + updateDataInDb?: boolean; + tablesValues?: { + tableName: string; + rows: { + [columnName: string]: string | number | boolean | undefined; + }[]; + }[]; + tablesUniqueNotNullColumn?: { [tableName: string]: { uniqueNotNullColName: string } }; + }, + ) => { + const customSeed = options?.seed === undefined ? 0 : options.seed; + let tableCount: number | undefined; + let columnsGenerators: Prettify[]; + let tableGenerators: Prettify; + + let tableValues: { + [columnName: string]: string | number | boolean | undefined; + }[]; + + let tablesValues: { + tableName: string; + rows: typeof tableValues; + }[] = options?.tablesValues === undefined ? [] : options.tablesValues; + + let pRNGSeed: number; + let filteredRelations: typeof relations; + + let preserveData: boolean, insertDataInDb: boolean = true, updateDataInDb: boolean = false; + if (options?.preserveData !== undefined) preserveData = options.preserveData; + if (options?.insertDataInDb !== undefined) insertDataInDb = options.insertDataInDb; + if (options?.updateDataInDb !== undefined) updateDataInDb = options.updateDataInDb; + if (updateDataInDb === true) insertDataInDb = false; + + // TODO: now I'm generating tablesInOutRelations twice, first time in generatePossibleGenerators and second time here. maybe should generate it once instead. + const { tablesInOutRelations } = this.getInfoFromRelations(relations); + for (const table of tablesGenerators) { + tableCount = table.count === undefined ? options?.count || this.defaultCountForTable : table.count; + + tableGenerators = {}; + columnsGenerators = table.columnsPossibleGenerators; + + filteredRelations = relations.filter( + (rel) => rel.table === table.tableName, + ); + + // adding pRNG seed to column + for (const col of columnsGenerators) { + const columnRelations = filteredRelations.filter((rel) => rel.columns.includes(col.columnName)); + pRNGSeed = (columnRelations.length !== 0 + && columnRelations[0]!.columns.length >= 2) + ? (customSeed + generateHashFromString( + `${columnRelations[0]!.table}.${columnRelations[0]!.columns.join('_')}`, + )) + : (customSeed + generateHashFromString(`${table.tableName}.${col.columnName}`)); + + tableGenerators[col.columnName] = { + pRNGSeed, + ...col, + }; + } + + // get values to generate columns with foreign key + + // if table posts contains foreign key to table users, then rel.table === 'posts' and rel.refTable === 'users', because table posts has reference to table users. + if (filteredRelations.length !== 0) { + for (const rel of filteredRelations) { + if ( + table.withFromTable[rel.refTable] !== undefined + && table.withCount !== undefined + ) { + tableCount = table.withCount; + } + + for (let colIdx = 0; colIdx < rel.columns.length; colIdx++) { + let refColumnValues: (string | number | boolean)[]; + let hasSelfRelation: boolean = false; + let repeatedValuesCount: + | number + | { weight: number; count: number | number[] }[] + | undefined, + weightedCountSeed: number | undefined; + let genObj: AbstractGenerator | undefined; + + if ( + rel.table === rel.refTable + && tableGenerators[rel.columns[colIdx]!]?.wasRefined === false + ) { + const refColName = rel.refColumns[colIdx] as string; + pRNGSeed = generateHashFromString( + `${table.tableName}.${refColName}`, + ); + + const refColumnGenerator: typeof tableGenerators = {}; + refColumnGenerator[refColName] = { + ...tableGenerators[refColName]!, + pRNGSeed, + }; + + refColumnValues = (await this.generateColumnsValuesByGenerators({ + tableGenerators: refColumnGenerator, + count: tableCount, + preserveData: true, + insertDataInDb: false, + }))!.map((rows) => rows[refColName]) as (string | number | boolean)[]; + + hasSelfRelation = true; + genObj = new generatorsMap.GenerateSelfRelationsValuesFromArray[0]({ + values: refColumnValues, + }); + genObj = this.selectVersionOfGenerator(genObj); + // genObj = new GenerateSelfRelationsValuesFromArray({ + // values: refColumnValues, + // }); + } else if ( + tableGenerators[rel.columns[colIdx]!]?.wasDefinedBefore === false + && tableGenerators[rel.columns[colIdx]!]?.wasRefined === false + ) { + refColumnValues = tablesValues + .find((val) => val.tableName === rel.refTable)! + .rows!.map((row) => row[rel.refColumns[colIdx]!]!); + + if ( + table.withFromTable[rel.refTable] !== undefined + && table.withFromTable[rel.refTable]!.repeatedValuesCount + !== undefined + ) { + repeatedValuesCount = table.withFromTable[rel.refTable]!.repeatedValuesCount; + weightedCountSeed = table.withFromTable[rel.refTable]!.weightedCountSeed; + } + + // TODO: revise maybe need to select version of generator here too + genObj = new generatorsMap.GenerateValuesFromArray[0]({ values: refColumnValues }); + genObj.notNull = tableGenerators[rel.columns[colIdx]!]!.notNull; + genObj.weightedCountSeed = weightedCountSeed; + genObj.maxRepeatedValuesCount = repeatedValuesCount; + } + + if (genObj !== undefined) { + tableGenerators[rel.columns[colIdx]!]!.generator = genObj; + } + tableGenerators[rel.columns[colIdx]!] = { + ...tableGenerators[rel.columns[colIdx]!]!, + hasSelfRelation, + hasRelation: true, + }; + } + } + } + + preserveData = ( + options?.preserveData === undefined + && tablesInOutRelations[table.tableName]?.in === 0 + ) + ? false + : true; + + preserveData = preserveData || (options?.preserveCyclicTablesData === true + && table.columnsPossibleGenerators.some((colGen) => colGen.isCyclic === true)); + + tableValues = await this.generateColumnsValuesByGenerators({ + tableGenerators, + db, + schema, + tableName: table.tableName, + count: tableCount, + preserveData, + insertDataInDb, + updateDataInDb, + uniqueNotNullColName: options?.tablesUniqueNotNullColumn === undefined + ? undefined + : options?.tablesUniqueNotNullColumn[table.tableName]?.uniqueNotNullColName, + }); + + if (preserveData === true) { + tablesValues.push({ + tableName: table.tableName, + rows: tableValues, + }); + } + + // removing "link" from table that was required to generate current table + if (tablesInOutRelations[table.tableName] !== undefined) { + for (const tableName of tablesInOutRelations[table.tableName]!.requiredTableNames) { + tablesInOutRelations[tableName]!.in -= 1; + } + } + + if (preserveData === false) { + tablesValues = tablesValues.filter( + (table) => + tablesInOutRelations[table.tableName] !== undefined && tablesInOutRelations[table.tableName]!.in > 0, + ); + } + } + + return tablesValues; + }; + + generateColumnsValuesByGenerators = async ({ + tableGenerators, + db, + schema, + tableName, + count, + preserveData = true, + insertDataInDb = true, + updateDataInDb = false, + uniqueNotNullColName, + batchSize = 10000, + }: { + tableGenerators: Prettify; + db?: + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase; + schema?: { [key: string]: PgTable | MySqlTable | SQLiteTable }; + tableName?: string; + count?: number; + preserveData?: boolean; + insertDataInDb?: boolean; + updateDataInDb?: boolean; + uniqueNotNullColName?: string; + batchSize?: number; + }) => { + if (count === undefined) { + count = this.defaultCountForTable; + } + + if (updateDataInDb === true) { + batchSize = 1; + } + + let columnGenerator: (typeof tableGenerators)[string]; + const columnsGenerators: { + [columnName: string]: AbstractGenerator; + } = {}; + let generatedValues: { [columnName: string]: number | string | boolean | undefined }[] = []; + + let columnsNumber = 0; + let override = false; + for (const columnName of Object.keys(tableGenerators)) { + columnsNumber += 1; + columnGenerator = tableGenerators[columnName]!; + override = tableGenerators[columnName]?.generatedIdentityType === 'always' ? true : override; + + columnsGenerators[columnName] = columnGenerator.generator!; + columnsGenerators[columnName]!.init({ + count, + seed: columnGenerator.pRNGSeed, + }); + + // const arrayGen = columnsGenerators[columnName]!.replaceIfArray({ count, seed: columnGenerator.pRNGSeed }); + // if (arrayGen !== undefined) { + // columnsGenerators[columnName] = arrayGen; + // } + + // const uniqueGen = columnsGenerators[columnName]!.replaceIfUnique({ count, seed: columnGenerator.pRNGSeed }); + // if (uniqueGen !== undefined) { + // columnsGenerators[columnName] = uniqueGen; + // } + } + let maxParametersNumber: number; + if (is(db, PgDatabase)) { + // @ts-ignore + maxParametersNumber = db.constructor[entityKind] === 'PgliteDatabase' + ? this.postgresPgLiteMaxParametersNumber + : this.postgresMaxParametersNumber; + } else if (is(db, MySqlDatabase)) { + maxParametersNumber = this.mysqlMaxParametersNumber; + } else { + // is(db, BaseSQLiteDatabase) + maxParametersNumber = this.sqliteMaxParametersNumber; + } + const maxBatchSize = Math.floor(maxParametersNumber / columnsNumber); + batchSize = batchSize > maxBatchSize ? maxBatchSize : batchSize; + + if ( + (insertDataInDb === true || updateDataInDb === true) + && (db === undefined || schema === undefined || tableName === undefined) + ) { + throw new Error('db or schema or tableName is undefined.'); + } + + let row: { [columnName: string]: string | number | boolean }, + generatedValue, + i: number; + + for (i = 0; i < count; i++) { + row = {}; + generatedValues.push(row); + + for (const columnName of Object.keys(columnsGenerators)) { + // generatedValue = columnsGenerators[columnName].next().value as + // | string + // | number + // | boolean; + generatedValue = columnsGenerators[columnName]!.generate({ i }) as + | string + | number + | boolean; + row[columnName as keyof typeof row] = generatedValue; + } + + if ( + (insertDataInDb === true || updateDataInDb === true) + && ((i + 1) % batchSize === 0 || i === count - 1) + ) { + if (preserveData === false) { + if (insertDataInDb === true) { + await this.insertInDb({ + generatedValues, + db: db as + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema: schema as { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }, + tableName: tableName as string, + override, + }); + } else if (updateDataInDb === true) { + await this.updateDb({ + generatedValues, + db: db as + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema: schema as { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }, + tableName: tableName as string, + uniqueNotNullColName: uniqueNotNullColName as string, + }); + } + + generatedValues = []; + } else { + const batchCount = Math.floor(i / batchSize); + + if (insertDataInDb === true) { + await this.insertInDb({ + generatedValues: generatedValues.slice( + batchSize * batchCount, + batchSize * (batchCount + 1), + ), + db: db as + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema: schema as { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }, + tableName: tableName as string, + override, + }); + } else if (updateDataInDb === true) { + await this.updateDb({ + generatedValues: generatedValues.slice( + batchSize * batchCount, + batchSize * (batchCount + 1), + ), + db: db as + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema: schema as { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }, + tableName: tableName as string, + uniqueNotNullColName: uniqueNotNullColName as string, + }); + } + } + } + } + + return preserveData === true ? generatedValues : []; + }; + + insertInDb = async ({ + generatedValues, + db, + schema, + tableName, + override, + }: { + generatedValues: { + [columnName: string]: number | string | boolean | undefined; + }[]; + db: + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase; + schema: { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }; + tableName: string; + override: boolean; + }) => { + if (is(db, PgDatabase)) { + const query = db.insert((schema as { [key: string]: PgTable })[tableName]!); + if (override === true) { + return await query.overridingSystemValue().values(generatedValues); + } + await query.values(generatedValues); + } else if (is(db, MySqlDatabase)) { + await db + .insert((schema as { [key: string]: MySqlTable })[tableName]!) + .values(generatedValues); + } else if (is(db, BaseSQLiteDatabase)) { + await db + .insert((schema as { [key: string]: SQLiteTable })[tableName]!) + .values(generatedValues); + } + }; + + updateDb = async ({ + generatedValues, + db, + schema, + tableName, + uniqueNotNullColName, + }: { + generatedValues: { + [columnName: string]: number | string | boolean | undefined; + }[]; + db: + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase; + schema: { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }; + tableName: string; + uniqueNotNullColName: string; + }) => { + if (is(db, PgDatabase)) { + const table = (schema as { [key: string]: PgTableWithColumns })[tableName]!; + const uniqueNotNullCol = table[uniqueNotNullColName]; + await db.update(table).set(generatedValues[0]!).where( + eq(uniqueNotNullCol, generatedValues[0]![uniqueNotNullColName]), + ); + } else if (is(db, MySqlDatabase)) { + const table = (schema as { [key: string]: MySqlTableWithColumns })[tableName]!; + await db.update(table).set(generatedValues[0]!).where( + eq(table[uniqueNotNullColName], generatedValues[0]![uniqueNotNullColName]), + ); + } else if (is(db, BaseSQLiteDatabase)) { + const table = (schema as { [key: string]: SQLiteTableWithColumns })[tableName]!; + await db.update(table).set(generatedValues[0]!).where( + eq(table[uniqueNotNullColName], generatedValues[0]![uniqueNotNullColName]), + ); + } + }; +} diff --git a/drizzle-seed/src/services/apiVersion.ts b/drizzle-seed/src/services/apiVersion.ts new file mode 100644 index 000000000..6cda0267e --- /dev/null +++ b/drizzle-seed/src/services/apiVersion.ts @@ -0,0 +1 @@ +export const latestVersion = 2; diff --git a/drizzle-seed/src/services/utils.ts b/drizzle-seed/src/services/utils.ts new file mode 100644 index 000000000..c972e7bd1 --- /dev/null +++ b/drizzle-seed/src/services/utils.ts @@ -0,0 +1,105 @@ +export const fastCartesianProduct = (sets: (number | string | boolean | object)[][], index: number) => { + const resultList = []; + let currSet: (typeof sets)[number]; + let element: (typeof sets)[number][number]; + + for (let i = sets.length - 1; i >= 0; i--) { + currSet = sets[i]!; + element = currSet[index % currSet.length]!; + resultList.unshift(element); + index = Math.floor(index / currSet.length); + } + + return resultList; +}; + +const sumArray = (weights: number[]) => { + const scale = 1e10; + const scaledSum = weights.reduce((acc, currVal) => acc + Math.round(currVal * scale), 0); + return scaledSum / scale; +}; + +/** + * @param weights positive number in range [0, 1], that represents probabilities to choose index of array. Example: weights = [0.2, 0.8] + * @param [accuracy=100] approximate number of elements in returning array + * @returns Example: with weights = [0.2, 0.8] and accuracy = 10 returning array of indices gonna equal this: [0, 0, 1, 1, 1, 1, 1, 1, 1, 1] + */ +export const getWeightedIndices = (weights: number[], accuracy = 100) => { + const weightsSum = sumArray(weights); + if (weightsSum !== 1) { + throw new Error( + `The weights for the Weighted Random feature must add up to exactly 1. Please review your weights to ensure they total 1 before proceeding`, + ); + } + + // const accuracy = 100; + const weightedIndices: number[] = []; + for (const [index, weight] of weights.entries()) { + const ticketsNumb = Math.floor(weight * accuracy); + weightedIndices.push(...Array.from({ length: ticketsNumb }).fill(index)); + } + + return weightedIndices; +}; + +export const generateHashFromString = (s: string) => { + let hash = 0; + // p and m are prime numbers + const p = 53; + const m = 28871271685163; + + for (let i = 0; i < s.length; i++) { + hash += ((s.codePointAt(i) || 0) * Math.pow(p, i)) % m; + } + + return hash; +}; + +/** + * @param param0.template example: "#####" or "#####-####" + * @param param0.values example: ["3", "2", "h"] + * @param param0.defaultValue example: "0" + * @returns + */ +export const fillTemplate = ({ template, placeholdersCount, values, defaultValue = ' ' }: { + template: string; + placeholdersCount?: number; + values: string[]; + defaultValue?: string; +}) => { + if (placeholdersCount === undefined) { + const iterArray = [...template.matchAll(/#/g)]; + placeholdersCount = iterArray.length; + } + + const diff = placeholdersCount - values.length; + if (diff > 0) { + values.unshift(...Array.from({ length: diff }).fill(defaultValue)); + } + + let resultStr = '', valueIdx = 0; + for (const si of template) { + if (si === '#') { + resultStr += values[valueIdx]; + valueIdx += 1; + continue; + } + resultStr += si; + } + + return resultStr; +}; + +// is variable is object-like. +// Example: +// isObject({f: 4}) === true; +// isObject([1,2,3]) === false; +// isObject(new Set()) === false; +export const isObject = (value: any) => { + if (value !== null && value !== undefined && value.constructor === Object) return true; + return false; +}; + +export const equalSets = (set1: Set, set2: Set) => { + return set1.size === set2.size && [...set1].every((si) => set2.has(si)); +}; diff --git a/drizzle-seed/src/services/versioning/v2.ts b/drizzle-seed/src/services/versioning/v2.ts new file mode 100644 index 000000000..f4dbf32f4 --- /dev/null +++ b/drizzle-seed/src/services/versioning/v2.ts @@ -0,0 +1,232 @@ +/* eslint-disable drizzle-internal/require-entity-kind */ +import prand from 'pure-rand'; +import { AbstractGenerator } from '../Generators.ts'; + +export class GenerateUniqueIntervalV2 extends AbstractGenerator<{ + fields?: + | 'year' + | 'month' + | 'day' + | 'hour' + | 'minute' + | 'second' + | 'year to month' + | 'day to hour' + | 'day to minute' + | 'day to second' + | 'hour to minute' + | 'hour to second' + | 'minute to second'; + isUnique?: boolean; +}> { + static override readonly 'entityKind': string = 'GenerateUniqueInterval'; + static override readonly version: number = 2; + + private state: { + rng: prand.RandomGenerator; + fieldsToGenerate: string[]; + intervalSet: Set; + } | undefined; + public override isUnique = true; + private config: { [key: string]: { from: number; to: number } } = { + year: { + from: 0, + to: 5, + }, + month: { + from: 0, + to: 11, + }, + day: { + from: 0, + to: 29, + }, + hour: { + from: 0, + to: 23, + }, + minute: { + from: 0, + to: 59, + }, + second: { + from: 0, + to: 59, + }, + }; + + override init({ count, seed }: { count: number; seed: number }) { + const allFields = ['year', 'month', 'day', 'hour', 'minute', 'second']; + let fieldsToGenerate: string[] = allFields; + + if (this.params.fields !== undefined && this.params.fields?.includes(' to ')) { + const tokens = this.params.fields.split(' to '); + const endIdx = allFields.indexOf(tokens[1]!); + fieldsToGenerate = allFields.slice(0, endIdx + 1); + } else if (this.params.fields !== undefined) { + const endIdx = allFields.indexOf(this.params.fields); + fieldsToGenerate = allFields.slice(0, endIdx + 1); + } + + let maxUniqueIntervalsNumber = 1; + for (const field of fieldsToGenerate) { + const from = this.config[field]!.from, to = this.config[field]!.to; + maxUniqueIntervalsNumber *= from - to + 1; + } + + if (count > maxUniqueIntervalsNumber) { + throw new RangeError(`count exceeds max number of unique intervals(${maxUniqueIntervalsNumber})`); + } + + const rng = prand.xoroshiro128plus(seed); + const intervalSet = new Set(); + this.state = { rng, fieldsToGenerate, intervalSet }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + let interval, numb: number; + + for (;;) { + interval = ''; + + for (const field of this.state.fieldsToGenerate) { + const from = this.config[field]!.from, to = this.config[field]!.to; + [numb, this.state.rng] = prand.uniformIntDistribution(from, to, this.state.rng); + interval += `${numb} ${field} `; + } + + if (!this.state.intervalSet.has(interval)) { + this.state.intervalSet.add(interval); + break; + } + } + + return interval; + } +} + +export class GenerateStringV2 extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { + static override readonly 'entityKind': string = 'GenerateString'; + static override readonly version: number = 2; + + private state: { + rng: prand.RandomGenerator; + minStringLength: number; + maxStringLength: number; + } | undefined; + override uniqueVersionOfGen = GenerateUniqueStringV2; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + let minStringLength = 7; + let maxStringLength = 20; + if (this.stringLength !== undefined) { + maxStringLength = this.stringLength; + if (maxStringLength === 1) minStringLength = maxStringLength; + if (maxStringLength < minStringLength) minStringLength = 1; + } + + const rng = prand.xoroshiro128plus(seed); + this.state = { rng, minStringLength, maxStringLength }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const minStringLength = this.state.minStringLength, + maxStringLength = this.state.maxStringLength; + const stringChars = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let idx: number, + strLength: number, + currStr: string; + + currStr = ''; + [strLength, this.state.rng] = prand.uniformIntDistribution( + minStringLength, + maxStringLength, + this.state.rng, + ); + for (let j = 0; j < strLength; j++) { + [idx, this.state.rng] = prand.uniformIntDistribution( + 0, + stringChars.length - 1, + this.state.rng, + ); + currStr += stringChars[idx]; + } + return currStr; + } +} + +export class GenerateUniqueStringV2 extends AbstractGenerator<{ isUnique?: boolean }> { + static override readonly 'entityKind': string = 'GenerateUniqueString'; + static override readonly version: number = 2; + + private state: { + rng: prand.RandomGenerator; + minStringLength: number; + maxStringLength: number; + } | undefined; + public override isUnique = true; + + override init({ seed, count }: { seed: number; count: number }) { + const rng = prand.xoroshiro128plus(seed); + + let minStringLength = 7; + let maxStringLength = 20; + // TODO: revise later + if (this.stringLength !== undefined) { + maxStringLength = this.stringLength; + if (maxStringLength === 1 || maxStringLength < minStringLength) minStringLength = maxStringLength; + } + + if (maxStringLength < count.toString(16).length) { + throw new Error( + `You can't generate ${count} unique strings, with a maximum string length of ${maxStringLength}.`, + ); + } + + this.state = { rng, minStringLength, maxStringLength }; + } + + generate({ i }: { i: number }) { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + + const minStringLength = this.state.minStringLength, + maxStringLength = this.state.maxStringLength; + const stringChars = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let idx: number, + strLength: number; + let currStr: string; + + currStr = ''; + const uniqueStr = i.toString(16); + [strLength, this.state.rng] = prand.uniformIntDistribution( + minStringLength, + maxStringLength - uniqueStr.length, + this.state.rng, + ); + for (let j = 0; j < strLength - uniqueStr.length; j++) { + [idx, this.state.rng] = prand.uniformIntDistribution( + 0, + stringChars.length - 1, + this.state.rng, + ); + currStr += stringChars[idx]; + } + + return uniqueStr + currStr; + } +} diff --git a/drizzle-seed/src/types/drizzleStudio.ts b/drizzle-seed/src/types/drizzleStudio.ts new file mode 100644 index 000000000..c3b635b97 --- /dev/null +++ b/drizzle-seed/src/types/drizzleStudio.ts @@ -0,0 +1,65 @@ +export type DbPrimaryKey = { + name: string; + columns: string[]; +}; + +export type DbForeignKey = { + name: string; + tableFrom: string; + columnsFrom: string[]; + schemaFrom: string; + schemaTo: string; + tableTo: string; + columnsTo: string[]; + onUpdate?: string; + onDelete?: string; +}; + +export type DbColumn = { + name: string; + type: string; + primaryKey: boolean; + notNull: boolean; + default?: any; + isUnique?: any; + autoIncrement?: boolean; + uniqueName?: string; + nullsNotDistinct?: boolean; + onUpdate?: boolean; +}; + +export type DbTable = { + name: string; + type: 'table'; + database?: string; + schema: string; + columns: Record; + indexes: Record; + foreignKeys: Record; + compositePrimaryKeys: Record; + uniqueConstraints: Record; +}; + +export type DbView = Omit & { + type: 'view' | 'mat_view'; +}; + +export type DbSchema = { + database?: string; + tables: Record; + views: Record; + enums: Record; +}; + +export type DrizzleStudioObjectType = { [schemaName: string]: DbSchema }; + +export type DrizzleStudioRelationType = { + name: string; + type: 'one' | 'many'; + table: string; + schema: string; + columns: string[]; + refTable: string; + refSchema: string; + refColumns: string[]; +}; diff --git a/drizzle-seed/src/types/seedService.ts b/drizzle-seed/src/types/seedService.ts new file mode 100644 index 000000000..1ae06f44c --- /dev/null +++ b/drizzle-seed/src/types/seedService.ts @@ -0,0 +1,49 @@ +import type { AbstractGenerator } from '../services/Generators.ts'; +import type { Prettify } from './tables.ts'; + +export type TableGeneratorsType = { + [columnName: string]: Prettify< + { + hasSelfRelation?: boolean | undefined; + hasRelation?: boolean | undefined; + pRNGSeed: number; + } & GeneratePossibleGeneratorsColumnType + >; +}; + +export type GeneratePossibleGeneratorsColumnType = { + columnName: string; + generator: AbstractGenerator | undefined; + isUnique: boolean; + notNull: boolean; + primary: boolean; + generatedIdentityType?: 'always' | 'byDefault' | undefined; + wasRefined: boolean; + wasDefinedBefore: boolean; + isCyclic: boolean; +}; + +export type GeneratePossibleGeneratorsTableType = Prettify<{ + tableName: string; + count?: number; + withCount?: number; + withFromTable: { + [withFromTableName: string]: { + repeatedValuesCount: + | number + | { weight: number; count: number | number[] }[]; + weightedCountSeed?: number; + }; + }; + // repeatedValuesCount?: number, + // withFromTableName?: string, + columnsPossibleGenerators: GeneratePossibleGeneratorsColumnType[]; +}>; + +export type RefinementsType = Prettify<{ + [tableName: string]: { + count?: number; + columns: { [columnName: string]: AbstractGenerator<{}> }; + with?: { [tableName: string]: number | { weight: number; count: number | number[] }[] }; + }; +}>; diff --git a/drizzle-seed/src/types/tables.ts b/drizzle-seed/src/types/tables.ts new file mode 100644 index 000000000..2fadd23f0 --- /dev/null +++ b/drizzle-seed/src/types/tables.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export type Column = { + name: string; + dataType: string; + columnType: string; + typeParams: { + precision?: number; + scale?: number; + length?: number; + dimensions?: number; + }; + size?: number; + default?: any; + hasDefault: boolean; + enumValues?: string[]; + isUnique: boolean; + notNull: boolean; + primary: boolean; + generatedIdentityType?: 'always' | 'byDefault' | undefined; + baseColumn?: Omit; +}; + +export type Table = { + name: string; + columns: Column[]; + primaryKeys: string[]; +}; + +export type Relation = { + // name: string; + type?: 'one' | 'many'; + table: string; + // schema: string; + columns: string[]; + refTable: string; + // refSchema: string; + refColumns: string[]; +}; + +export type RelationWithReferences = Relation & { isCyclic?: boolean; refTableRels: RelationWithReferences[] }; + +export type Prettify = + & { + [K in keyof T]: T[K]; + } + & {}; diff --git a/drizzle-seed/tests/benchmarks/generatorsBenchmark.ts b/drizzle-seed/tests/benchmarks/generatorsBenchmark.ts new file mode 100644 index 000000000..23fca0c6c --- /dev/null +++ b/drizzle-seed/tests/benchmarks/generatorsBenchmark.ts @@ -0,0 +1,131 @@ +import lastNames from '../../src/datasets/lastNames.ts'; +import { + GenerateBoolean, + GenerateCity, + GenerateCompanyName, + GenerateCountry, + GenerateDate, + GenerateDatetime, + GenerateDefault, + GenerateEmail, + GenerateFirstName, + GenerateFullName, + GenerateInt, + GenerateInterval, + GenerateIntPrimaryKey, + GenerateJobTitle, + GenerateJson, + GenerateLastName, + GenerateLine, + GenerateLoremIpsum, + GenerateNumber, + GeneratePhoneNumber, + GeneratePoint, + GeneratePostcode, + GenerateState, + GenerateStreetAddress, + GenerateString, + GenerateTime, + GenerateTimestamp, + GenerateUniqueCompanyName, + GenerateUniqueFullName, + GenerateUniqueInt, + GenerateUniqueInterval, + GenerateUniqueLine, + GenerateUniqueNumber, + GenerateUniquePoint, + GenerateUniquePostcode, + GenerateUniqueStreetAddress, + GenerateUniqueString, + GenerateValuesFromArray, + GenerateYear, + WeightedRandomGenerator, +} from '../../src/services/Generators.ts'; + +const benchmark = ({ generatorName, generator, count = 100000, seed = 1 }: { + generatorName: string; + generator: (typeof generatorsFuncs)[keyof typeof generatorsFuncs]; + count?: number; + seed?: number; +}) => { + generator.init({ count, seed }); + + let timeSpentToInit = 0, timeSpent = 0; + const t0 = new Date(); + + generator.init({ count, seed }); + timeSpentToInit += (Date.now() - t0.getTime()) / 1000; + + for (let i = 0; i < count; i++) { + const val = generator.generate({ i }); + if (val === undefined) { + console.log(val, `in ${generatorName} generator.`); + } + } + + timeSpent += (Date.now() - t0.getTime()) / 1000; + console.log(`${generatorName} spent ${timeSpentToInit} to init and spent ${timeSpent} to generate ${count} rows.`); + console.log( + 'time spent in particular code part:', + generator.timeSpent, + ';', + generator.timeSpent === undefined ? generator.timeSpent : (generator.timeSpent / timeSpent), + 'percent of all time', + ); + console.log('\n'); +}; + +const generatorsFuncs = { + default: new GenerateDefault({ defaultValue: 'defaultValue' }), + valuesFromArray: new GenerateValuesFromArray({ values: lastNames }), + intPrimaryKey: new GenerateIntPrimaryKey({}), + number: new GenerateNumber({}), + uniqueNumber: new GenerateUniqueNumber({}), + int: new GenerateInt({}), + uniqueInt: new GenerateUniqueInt({}), + boolean: new GenerateBoolean({}), + date: new GenerateDate({}), + time: new GenerateTime({}), + timestamp: new GenerateTimestamp({}), + datetime: new GenerateDatetime({}), + year: new GenerateYear({}), + json: new GenerateJson({}), + jsonb: new GenerateJson({}), + interval: new GenerateInterval({}), + uniqueInterval: new GenerateUniqueInterval({}), + string: new GenerateString({}), + uniqueString: new GenerateUniqueString({}), + firstName: new GenerateFirstName({}), + // uniqueFirstName: new GenerateUniqueName({}), + lastName: new GenerateLastName({}), + // uniqueLastName: new GenerateUniqueSurname({}), + fullName: new GenerateFullName({}), + uniqueFullName: new GenerateUniqueFullName({}), + email: new GenerateEmail({}), + phoneNumber: new GeneratePhoneNumber({ template: '+380 ## ## ### ##' }), + country: new GenerateCountry({}), + // uniqueCountry: new GenerateUniqueCountry({}), + city: new GenerateCity({}), + // uniqueCity: new GenerateUniqueCity({}), + streetAddress: new GenerateStreetAddress({}), + uniqueStreetAddress: new GenerateUniqueStreetAddress({}), + jobTitle: new GenerateJobTitle({}), + postcode: new GeneratePostcode({}), + uniquePostcode: new GenerateUniquePostcode({}), + state: new GenerateState({}), + companyName: new GenerateCompanyName({}), + uniqueCompanyName: new GenerateUniqueCompanyName({}), + loremIpsum: new GenerateLoremIpsum({}), + point: new GeneratePoint({}), + uniquePoint: new GenerateUniquePoint({}), + line: new GenerateLine({}), + uniqueLine: new GenerateUniqueLine({}), + weightedRandom: new WeightedRandomGenerator([ + { weight: 0.8, value: new GenerateUniqueInt({ minValue: 0, maxValue: 90000 }) }, + { weight: 0.2, value: new GenerateDefault({ defaultValue: Number.NaN }) }, + ]), +}; + +for (const [generatorName, generator] of Object.entries(generatorsFuncs)) { + benchmark({ generatorName, generator, count: 100000, seed: 1 }); +} diff --git a/drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts b/drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts new file mode 100644 index 000000000..78ff7a54b --- /dev/null +++ b/drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + schema: './src/tests/mysql/allDataTypesTest/mysqlSchema.ts', + out: './src/tests/mysql/allDataTypesTest/mysqlMigrations', + dialect: 'mysql', +}); diff --git a/drizzle-seed/tests/mysql/allDataTypesTest/mysqlSchema.ts b/drizzle-seed/tests/mysql/allDataTypesTest/mysqlSchema.ts new file mode 100644 index 000000000..217fe74e7 --- /dev/null +++ b/drizzle-seed/tests/mysql/allDataTypesTest/mysqlSchema.ts @@ -0,0 +1,56 @@ +import { + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + mysqlEnum, + mysqlTable, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varbinary, + varchar, + year, +} from 'drizzle-orm/mysql-core'; + +export const allDataTypes = mysqlTable('all_data_types', { + int: int('integer'), + tinyint: tinyint('tinyint'), + smallint: smallint('smallint'), + mediumint: mediumint('mediumint'), + biginteger: bigint('bigint', { mode: 'bigint' }), + bigintNumber: bigint('bigint_number', { mode: 'number' }), + real: real('real'), + decimal: decimal('decimal'), + double: double('double'), + float: float('float'), + serial: serial('serial'), + binary: binary('binary', { length: 255 }), + varbinary: varbinary('varbinary', { length: 256 }), + char: char('char', { length: 255 }), + varchar: varchar('varchar', { length: 256 }), + text: text('text'), + boolean: boolean('boolean'), + dateString: date('date_string', { mode: 'string' }), + date: date('date', { mode: 'date' }), + datetime: datetime('datetime', { mode: 'date' }), + datetimeString: datetime('datetimeString', { mode: 'string' }), + time: time('time'), + year: year('year'), + timestampDate: timestamp('timestamp_date', { mode: 'date' }), + timestampString: timestamp('timestamp_string', { mode: 'string' }), + json: json('json'), + mysqlEnum: mysqlEnum('popularity', ['unknown', 'known', 'popular']), +}); diff --git a/drizzle-seed/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts b/drizzle-seed/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts new file mode 100644 index 000000000..f39a55fef --- /dev/null +++ b/drizzle-seed/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts @@ -0,0 +1,121 @@ +import Docker from 'dockerode'; +import { sql } from 'drizzle-orm'; +import type { MySql2Database } from 'drizzle-orm/mysql2'; +import { drizzle } from 'drizzle-orm/mysql2'; +import getPort from 'get-port'; +import type { Connection } from 'mysql2/promise'; +import { createConnection } from 'mysql2/promise'; +import { v4 as uuid } from 'uuid'; +import { afterAll, beforeAll, expect, test } from 'vitest'; +import { seed } from '../../../src/index.ts'; +import * as schema from './mysqlSchema.ts'; + +let mysqlContainer: Docker.Container; +let client: Connection | undefined; +let db: MySql2Database; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'mysql:8'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + mysqlContainer = await docker.createContainer({ + Image: image, + Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await mysqlContainer.start(); + + return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; +} + +beforeAll(async () => { + const connectionString = await createDockerDB(); + + const sleep = 1000; + let timeLeft = 40000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + const client = await createConnection(connectionString); + await client.connect(); + db = drizzle(client); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); + throw lastError; + } + + await db.execute( + sql` + CREATE TABLE \`all_data_types\` ( + \`integer\` int, + \`tinyint\` tinyint, + \`smallint\` smallint, + \`mediumint\` mediumint, + \`bigint\` bigint, + \`bigint_number\` bigint, + \`real\` real, + \`decimal\` decimal, + \`double\` double, + \`float\` float, + \`serial\` serial AUTO_INCREMENT, + \`binary\` binary(255), + \`varbinary\` varbinary(256), + \`char\` char(255), + \`varchar\` varchar(256), + \`text\` text, + \`boolean\` boolean, + \`date_string\` date, + \`date\` date, + \`datetime\` datetime, + \`datetimeString\` datetime, + \`time\` time, + \`year\` year, + \`timestamp_date\` timestamp, + \`timestamp_string\` timestamp, + \`json\` json, + \`popularity\` enum('unknown','known','popular') + ); + `, + ); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +test('basic seed test', async () => { + await seed(db, schema, { count: 10000 }); + + const allDataTypes = await db.select().from(schema.allDataTypes); + + // every value in each 10 rows does not equal undefined. + const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/mysql/cyclicTables/cyclicTables.test.ts b/drizzle-seed/tests/mysql/cyclicTables/cyclicTables.test.ts new file mode 100644 index 000000000..08fb7a0fe --- /dev/null +++ b/drizzle-seed/tests/mysql/cyclicTables/cyclicTables.test.ts @@ -0,0 +1,211 @@ +import Docker from 'dockerode'; +import { sql } from 'drizzle-orm'; +import type { MySql2Database } from 'drizzle-orm/mysql2'; +import { drizzle } from 'drizzle-orm/mysql2'; +import getPort from 'get-port'; +import type { Connection } from 'mysql2/promise'; +import { createConnection } from 'mysql2/promise'; +import { v4 as uuid } from 'uuid'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './mysqlSchema.ts'; + +let mysqlContainer: Docker.Container; +let client: Connection; +let db: MySql2Database; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'mysql:8'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + mysqlContainer = await docker.createContainer({ + Image: image, + Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await mysqlContainer.start(); + + return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; +} + +beforeAll(async () => { + const connectionString = await createDockerDB(); + + const sleep = 1000; + let timeLeft = 40000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + db = drizzle(client); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); + throw lastError; + } + + await db.execute( + sql` + create table model + ( + id int not null + primary key, + name varchar(256) not null, + defaultImageId int null + ); + `, + ); + + await db.execute( + sql` + create table model_image + ( + id int not null + primary key, + url varchar(256) not null, + caption varchar(256) null, + modelId int not null, + constraint model_image_modelId_model_id_fk + foreign key (modelId) references model (id) + ); + `, + ); + + await db.execute( + sql` + alter table model + add constraint model_defaultImageId_model_image_id_fk + foreign key (defaultImageId) references model_image (id); + `, + ); + + // 3 tables case + await db.execute( + sql` + create table model1 + ( + id int not null + primary key, + name varchar(256) not null, + userId int null, + defaultImageId int null + ); + `, + ); + + await db.execute( + sql` + create table model_image1 + ( + id int not null + primary key, + url varchar(256) not null, + caption varchar(256) null, + modelId int not null, + constraint model_image1_modelId_model1_id_fk + foreign key (modelId) references model1 (id) + ); + `, + ); + + await db.execute( + sql` + create table user + ( + id int not null + primary key, + name text null, + invitedBy int null, + imageId int not null, + constraint user_imageId_model_image1_id_fk + foreign key (imageId) references model_image1 (id), + constraint user_invitedBy_user_id_fk + foreign key (invitedBy) references user (id) + ); + `, + ); + + await db.execute( + sql` + alter table model1 + add constraint model1_userId_user_id_fk + foreign key (userId) references user (id); + `, + ); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +test('2 cyclic tables test', async () => { + await seed(db, { + modelTable: schema.modelTable, + modelImageTable: schema.modelImageTable, + }); + + const modelTable = await db.select().from(schema.modelTable); + const modelImageTable = await db.select().from(schema.modelImageTable); + + expect(modelTable.length).toBe(10); + let predicate = modelTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable.length).toBe(10); + predicate = modelImageTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('3 cyclic tables test', async () => { + await seed(db, { + modelTable1: schema.modelTable1, + modelImageTable1: schema.modelImageTable1, + user: schema.user, + }); + + const modelTable1 = await db.select().from(schema.modelTable1); + const modelImageTable1 = await db.select().from(schema.modelImageTable1); + const user = await db.select().from(schema.user); + + expect(modelTable1.length).toBe(10); + let predicate = modelTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable1.length).toBe(10); + predicate = modelImageTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(user.length).toBe(10); + predicate = user.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/mysql/cyclicTables/mysqlSchema.ts b/drizzle-seed/tests/mysql/cyclicTables/mysqlSchema.ts new file mode 100644 index 000000000..ff8149a81 --- /dev/null +++ b/drizzle-seed/tests/mysql/cyclicTables/mysqlSchema.ts @@ -0,0 +1,76 @@ +import { relations } from 'drizzle-orm'; +import type { AnyMySqlColumn } from 'drizzle-orm/mysql-core'; +import { int, mysqlTable, serial, text, varchar } from 'drizzle-orm/mysql-core'; + +// MODEL +export const modelTable = mysqlTable( + 'model', + { + id: serial().primaryKey(), + name: varchar({ length: 256 }).notNull(), + defaultImageId: int().references(() => modelImageTable.id), + }, +); + +export const modelRelations = relations(modelTable, ({ one, many }) => ({ + images: many(modelImageTable), + defaultImage: one(modelImageTable, { + fields: [modelTable.defaultImageId], + references: [modelImageTable.id], + }), +})); + +// MODEL IMAGE +export const modelImageTable = mysqlTable( + 'model_image', + { + id: serial().primaryKey(), + url: varchar({ length: 256 }).notNull(), + caption: varchar({ length: 256 }), + modelId: int() + .notNull() + .references((): AnyMySqlColumn => modelTable.id), + }, +); + +export const modelImageRelations = relations(modelImageTable, ({ one }) => ({ + model: one(modelTable, { + fields: [modelImageTable.modelId], + references: [modelTable.id], + }), +})); + +// 3 tables case +export const modelTable1 = mysqlTable( + 'model1', + { + id: serial().primaryKey(), + name: varchar({ length: 256 }).notNull(), + userId: int() + .references(() => user.id), + defaultImageId: int(), + }, +); + +export const modelImageTable1 = mysqlTable( + 'model_image1', + { + id: serial().primaryKey(), + url: varchar({ length: 256 }).notNull(), + caption: varchar({ length: 256 }), + modelId: int().notNull() + .references((): AnyMySqlColumn => modelTable1.id), + }, +); + +export const user = mysqlTable( + 'user', + { + id: serial().primaryKey(), + name: text(), + invitedBy: int().references((): AnyMySqlColumn => user.id), + imageId: int() + .notNull() + .references((): AnyMySqlColumn => modelImageTable1.id), + }, +); diff --git a/drizzle-seed/tests/mysql/drizzle.config.ts b/drizzle-seed/tests/mysql/drizzle.config.ts new file mode 100644 index 000000000..9a84354e3 --- /dev/null +++ b/drizzle-seed/tests/mysql/drizzle.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + schema: './src/tests/mysql/mysqlSchema.ts', + out: './src/tests/mysql/mysqlMigrations', + dialect: 'mysql', +}); diff --git a/drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts b/drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts new file mode 100644 index 000000000..621d8acc1 --- /dev/null +++ b/drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + schema: './src/tests/mysql/generatorsTest/mysqlSchema.ts', + out: './src/tests/mysql/generatorsTest/mysqlMigrations', + dialect: 'mysql', +}); diff --git a/drizzle-seed/tests/mysql/generatorsTest/generators.test.ts b/drizzle-seed/tests/mysql/generatorsTest/generators.test.ts new file mode 100644 index 000000000..2bef885da --- /dev/null +++ b/drizzle-seed/tests/mysql/generatorsTest/generators.test.ts @@ -0,0 +1,128 @@ +import Docker from 'dockerode'; +import { sql } from 'drizzle-orm'; +import type { MySql2Database } from 'drizzle-orm/mysql2'; +import { drizzle } from 'drizzle-orm/mysql2'; +import getPort from 'get-port'; +import type { Connection } from 'mysql2/promise'; +import { createConnection } from 'mysql2/promise'; +import { v4 as uuid } from 'uuid'; +import { afterAll, beforeAll, expect, test } from 'vitest'; +import { seed } from '../../../src/index.ts'; +import * as schema from './mysqlSchema.ts'; + +let mysqlContainer: Docker.Container; +let client: Connection; +let db: MySql2Database; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'mysql:8'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + mysqlContainer = await docker.createContainer({ + Image: image, + Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await mysqlContainer.start(); + + return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; +} + +beforeAll(async () => { + const connectionString = await createDockerDB(); + + const sleep = 1000; + let timeLeft = 40000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + db = drizzle(client); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); + throw lastError; + } + + await db.execute( + sql` + CREATE TABLE \`datetime_table\` ( + \`datetime\` datetime + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`year_table\` ( + \`year\` year + ); + `, + ); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +const count = 10000; + +test('datetime generator test', async () => { + await seed(db, { datetimeTable: schema.datetimeTable }).refine((funcs) => ({ + datetimeTable: { + count, + columns: { + datetime: funcs.datetime(), + }, + }, + })); + + const data = await db.select().from(schema.datetimeTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('year generator test', async () => { + await seed(db, { yearTable: schema.yearTable }).refine((funcs) => ({ + yearTable: { + count, + columns: { + year: funcs.year(), + }, + }, + })); + + const data = await db.select().from(schema.yearTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/mysql/generatorsTest/mysqlSchema.ts b/drizzle-seed/tests/mysql/generatorsTest/mysqlSchema.ts new file mode 100644 index 000000000..c9a7edc62 --- /dev/null +++ b/drizzle-seed/tests/mysql/generatorsTest/mysqlSchema.ts @@ -0,0 +1,9 @@ +import { datetime, mysqlTable, year } from 'drizzle-orm/mysql-core'; + +export const datetimeTable = mysqlTable('datetime_table', { + datetime: datetime('datetime'), +}); + +export const yearTable = mysqlTable('year_table', { + year: year('year'), +}); diff --git a/drizzle-seed/tests/mysql/mysql.test.ts b/drizzle-seed/tests/mysql/mysql.test.ts new file mode 100644 index 000000000..7d6bfd48e --- /dev/null +++ b/drizzle-seed/tests/mysql/mysql.test.ts @@ -0,0 +1,381 @@ +import Docker from 'dockerode'; +import { sql } from 'drizzle-orm'; +import type { MySql2Database } from 'drizzle-orm/mysql2'; +import { drizzle } from 'drizzle-orm/mysql2'; +import getPort from 'get-port'; +import type { Connection } from 'mysql2/promise'; +import { createConnection } from 'mysql2/promise'; +import { v4 as uuid } from 'uuid'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../src/index.ts'; +import * as schema from './mysqlSchema.ts'; + +let mysqlContainer: Docker.Container; +let client: Connection; +let db: MySql2Database; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'mysql:8'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + mysqlContainer = await docker.createContainer({ + Image: image, + Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await mysqlContainer.start(); + + return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; +} + +beforeAll(async () => { + const connectionString = await createDockerDB(); + + const sleep = 1000; + let timeLeft = 40000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + db = drizzle(client); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); + throw lastError; + } + + await db.execute( + sql` + CREATE TABLE \`customer\` ( + \`id\` varchar(256) NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text, + \`region\` text, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + \`fax\` text, + CONSTRAINT \`customer_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`order_detail\` ( + \`unit_price\` float NOT NULL, + \`quantity\` int NOT NULL, + \`discount\` float NOT NULL, + \`order_id\` int NOT NULL, + \`product_id\` int NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`employee\` ( + \`id\` int NOT NULL, + \`last_name\` text NOT NULL, + \`first_name\` text, + \`title\` text NOT NULL, + \`title_of_courtesy\` text NOT NULL, + \`birth_date\` timestamp NOT NULL, + \`hire_date\` timestamp NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`home_phone\` text NOT NULL, + \`extension\` int NOT NULL, + \`notes\` text NOT NULL, + \`reports_to\` int, + \`photo_path\` text, + CONSTRAINT \`employee_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`order\` ( + \`id\` int NOT NULL, + \`order_date\` timestamp NOT NULL, + \`required_date\` timestamp NOT NULL, + \`shipped_date\` timestamp, + \`ship_via\` int NOT NULL, + \`freight\` float NOT NULL, + \`ship_name\` text NOT NULL, + \`ship_city\` text NOT NULL, + \`ship_region\` text, + \`ship_postal_code\` text, + \`ship_country\` text NOT NULL, + \`customer_id\` varchar(256) NOT NULL, + \`employee_id\` int NOT NULL, + CONSTRAINT \`order_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`product\` ( + \`id\` int NOT NULL, + \`name\` text NOT NULL, + \`quantity_per_unit\` text NOT NULL, + \`unit_price\` float NOT NULL, + \`units_in_stock\` int NOT NULL, + \`units_on_order\` int NOT NULL, + \`reorder_level\` int NOT NULL, + \`discontinued\` int NOT NULL, + \`supplier_id\` int NOT NULL, + CONSTRAINT \`product_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`supplier\` ( + \`id\` int NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`region\` text, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + CONSTRAINT \`supplier_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + ALTER TABLE \`order_detail\` ADD CONSTRAINT \`order_detail_order_id_order_id_fk\` FOREIGN KEY (\`order_id\`) REFERENCES \`order\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`order_detail\` ADD CONSTRAINT \`order_detail_product_id_product_id_fk\` FOREIGN KEY (\`product_id\`) REFERENCES \`product\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`employee\` ADD CONSTRAINT \`employee_reports_to_employee_id_fk\` FOREIGN KEY (\`reports_to\`) REFERENCES \`employee\`(\`id\`) ON DELETE no action ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`order\` ADD CONSTRAINT \`order_customer_id_customer_id_fk\` FOREIGN KEY (\`customer_id\`) REFERENCES \`customer\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`order\` ADD CONSTRAINT \`order_employee_id_employee_id_fk\` FOREIGN KEY (\`employee_id\`) REFERENCES \`employee\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`product\` ADD CONSTRAINT \`product_supplier_id_supplier_id_fk\` FOREIGN KEY (\`supplier_id\`) REFERENCES \`supplier\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +test('basic seed test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); +}); + +test('seed with options.count:11 test', async () => { + await seed(db, schema, { count: 11 }); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(11); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) customers count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) all tables count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + details: { + count: 13, + }, + employees: { + count: 14, + }, + orders: { + count: 15, + }, + products: { + count: 16, + }, + suppliers: { + count: 17, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(13); + expect(employees.length).toBe(14); + expect(orders.length).toBe(15); + expect(products.length).toBe(16); + expect(suppliers.length).toBe(17); +}); + +test("redefine(refine) orders count using 'with' in customers", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test("sequential using of 'with'", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); diff --git a/drizzle-seed/tests/mysql/mysqlSchema.ts b/drizzle-seed/tests/mysql/mysqlSchema.ts new file mode 100644 index 000000000..624d45d3e --- /dev/null +++ b/drizzle-seed/tests/mysql/mysqlSchema.ts @@ -0,0 +1,102 @@ +import type { AnyMySqlColumn } from 'drizzle-orm/mysql-core'; +import { float, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'; + +export const customers = mysqlTable('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = mysqlTable( + 'employee', + { + id: int('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: int('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: int('reports_to').references((): AnyMySqlColumn => employees.id), + photoPath: text('photo_path'), + }, +); + +export const orders = mysqlTable('order', { + id: int('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: int('ship_via').notNull(), + freight: float('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: varchar('customer_id', { length: 256 }) + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: int('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = mysqlTable('supplier', { + id: int('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = mysqlTable('product', { + id: int('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: float('unit_price').notNull(), + unitsInStock: int('units_in_stock').notNull(), + unitsOnOrder: int('units_on_order').notNull(), + reorderLevel: int('reorder_level').notNull(), + discontinued: int('discontinued').notNull(), + + supplierId: int('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = mysqlTable('order_detail', { + unitPrice: float('unit_price').notNull(), + quantity: int('quantity').notNull(), + discount: float('discount').notNull(), + + orderId: int('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: int('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); diff --git a/drizzle-seed/tests/mysql/softRelationsTest/mysqlSchema.ts b/drizzle-seed/tests/mysql/softRelationsTest/mysqlSchema.ts new file mode 100644 index 000000000..7f0fc17df --- /dev/null +++ b/drizzle-seed/tests/mysql/softRelationsTest/mysqlSchema.ts @@ -0,0 +1,128 @@ +import { relations } from 'drizzle-orm'; +import { float, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'; + +export const customers = mysqlTable('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = mysqlTable( + 'employee', + { + id: int('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: int('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: int('reports_to'), + photoPath: text('photo_path'), + }, +); + +export const employeesRelations = relations(employees, ({ one }) => ({ + employee: one(employees, { + fields: [employees.reportsTo], + references: [employees.id], + }), +})); + +export const orders = mysqlTable('order', { + id: int('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: int('ship_via').notNull(), + freight: float('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: varchar('customer_id', { length: 256 }).notNull(), + + employeeId: int('employee_id').notNull(), +}); + +export const ordersRelations = relations(orders, ({ one }) => ({ + customer: one(customers, { + fields: [orders.customerId], + references: [customers.id], + }), + employee: one(employees, { + fields: [orders.employeeId], + references: [employees.id], + }), +})); + +export const suppliers = mysqlTable('supplier', { + id: int('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = mysqlTable('product', { + id: int('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: float('unit_price').notNull(), + unitsInStock: int('units_in_stock').notNull(), + unitsOnOrder: int('units_on_order').notNull(), + reorderLevel: int('reorder_level').notNull(), + discontinued: int('discontinued').notNull(), + + supplierId: int('supplier_id').notNull(), +}); + +export const productsRelations = relations(products, ({ one }) => ({ + supplier: one(suppliers, { + fields: [products.supplierId], + references: [suppliers.id], + }), +})); + +export const details = mysqlTable('order_detail', { + unitPrice: float('unit_price').notNull(), + quantity: int('quantity').notNull(), + discount: float('discount').notNull(), + + orderId: int('order_id').notNull(), + + productId: int('product_id').notNull(), +}); + +export const detailsRelations = relations(details, ({ one }) => ({ + order: one(orders, { + fields: [details.orderId], + references: [orders.id], + }), + product: one(products, { + fields: [details.productId], + references: [products.id], + }), +})); diff --git a/drizzle-seed/tests/mysql/softRelationsTest/softRelations.test.ts b/drizzle-seed/tests/mysql/softRelationsTest/softRelations.test.ts new file mode 100644 index 000000000..7f61b80eb --- /dev/null +++ b/drizzle-seed/tests/mysql/softRelationsTest/softRelations.test.ts @@ -0,0 +1,314 @@ +import Docker from 'dockerode'; +import { sql } from 'drizzle-orm'; +import type { MySql2Database } from 'drizzle-orm/mysql2'; +import { drizzle } from 'drizzle-orm/mysql2'; +import getPort from 'get-port'; +import type { Connection } from 'mysql2/promise'; +import { createConnection } from 'mysql2/promise'; +import { v4 as uuid } from 'uuid'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './mysqlSchema.ts'; + +let mysqlContainer: Docker.Container; +let client: Connection; +let db: MySql2Database; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'mysql:8'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + mysqlContainer = await docker.createContainer({ + Image: image, + Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await mysqlContainer.start(); + + return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; +} + +beforeAll(async () => { + const connectionString = await createDockerDB(); + + const sleep = 1000; + let timeLeft = 40000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + db = drizzle(client); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); + throw lastError; + } + + await db.execute( + sql` + CREATE TABLE \`customer\` ( + \`id\` varchar(256) NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text, + \`region\` text, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + \`fax\` text, + CONSTRAINT \`customer_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`order_detail\` ( + \`unit_price\` float NOT NULL, + \`quantity\` int NOT NULL, + \`discount\` float NOT NULL, + \`order_id\` int NOT NULL, + \`product_id\` int NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`employee\` ( + \`id\` int NOT NULL, + \`last_name\` text NOT NULL, + \`first_name\` text, + \`title\` text NOT NULL, + \`title_of_courtesy\` text NOT NULL, + \`birth_date\` timestamp NOT NULL, + \`hire_date\` timestamp NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`home_phone\` text NOT NULL, + \`extension\` int NOT NULL, + \`notes\` text NOT NULL, + \`reports_to\` int, + \`photo_path\` text, + CONSTRAINT \`employee_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`order\` ( + \`id\` int NOT NULL, + \`order_date\` timestamp NOT NULL, + \`required_date\` timestamp NOT NULL, + \`shipped_date\` timestamp, + \`ship_via\` int NOT NULL, + \`freight\` float NOT NULL, + \`ship_name\` text NOT NULL, + \`ship_city\` text NOT NULL, + \`ship_region\` text, + \`ship_postal_code\` text, + \`ship_country\` text NOT NULL, + \`customer_id\` varchar(256) NOT NULL, + \`employee_id\` int NOT NULL, + CONSTRAINT \`order_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`product\` ( + \`id\` int NOT NULL, + \`name\` text NOT NULL, + \`quantity_per_unit\` text NOT NULL, + \`unit_price\` float NOT NULL, + \`units_in_stock\` int NOT NULL, + \`units_on_order\` int NOT NULL, + \`reorder_level\` int NOT NULL, + \`discontinued\` int NOT NULL, + \`supplier_id\` int NOT NULL, + CONSTRAINT \`product_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`supplier\` ( + \`id\` int NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`region\` text, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + CONSTRAINT \`supplier_id\` PRIMARY KEY(\`id\`) + ); + `, + ); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +const checkSoftRelations = ( + customers: (typeof schema.customers.$inferSelect)[], + details: (typeof schema.details.$inferSelect)[], + employees: (typeof schema.employees.$inferSelect)[], + orders: (typeof schema.orders.$inferSelect)[], + products: (typeof schema.products.$inferSelect)[], + suppliers: (typeof schema.suppliers.$inferSelect)[], +) => { + // employees soft relations check + const employeeIds = new Set(employees.map((employee) => employee.id)); + const employeesPredicate = employees.every((employee) => + employee.reportsTo !== null && employeeIds.has(employee.reportsTo) + ); + expect(employeesPredicate).toBe(true); + + // orders soft relations check + const customerIds = new Set(customers.map((customer) => customer.id)); + const ordersPredicate1 = orders.every((order) => order.customerId !== null && customerIds.has(order.customerId)); + expect(ordersPredicate1).toBe(true); + + const ordersPredicate2 = orders.every((order) => order.employeeId !== null && employeeIds.has(order.employeeId)); + expect(ordersPredicate2).toBe(true); + + // product soft relations check + const supplierIds = new Set(suppliers.map((supplier) => supplier.id)); + const productsPredicate = products.every((product) => + product.supplierId !== null && supplierIds.has(product.supplierId) + ); + expect(productsPredicate).toBe(true); + + // details soft relations check + const orderIds = new Set(orders.map((order) => order.id)); + const detailsPredicate1 = details.every((detail) => detail.orderId !== null && orderIds.has(detail.orderId)); + expect(detailsPredicate1).toBe(true); + + const productIds = new Set(products.map((product) => product.id)); + const detailsPredicate2 = details.every((detail) => detail.productId !== null && productIds.has(detail.productId)); + expect(detailsPredicate2).toBe(true); +}; + +test('basic seed, soft relations test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("redefine(refine) orders count using 'with' in customers, soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("sequential using of 'with', soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); diff --git a/drizzle-seed/tests/northwind/mysqlSchema.ts b/drizzle-seed/tests/northwind/mysqlSchema.ts new file mode 100644 index 000000000..624d45d3e --- /dev/null +++ b/drizzle-seed/tests/northwind/mysqlSchema.ts @@ -0,0 +1,102 @@ +import type { AnyMySqlColumn } from 'drizzle-orm/mysql-core'; +import { float, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'; + +export const customers = mysqlTable('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = mysqlTable( + 'employee', + { + id: int('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: int('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: int('reports_to').references((): AnyMySqlColumn => employees.id), + photoPath: text('photo_path'), + }, +); + +export const orders = mysqlTable('order', { + id: int('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: int('ship_via').notNull(), + freight: float('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: varchar('customer_id', { length: 256 }) + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: int('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = mysqlTable('supplier', { + id: int('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = mysqlTable('product', { + id: int('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: float('unit_price').notNull(), + unitsInStock: int('units_in_stock').notNull(), + unitsOnOrder: int('units_on_order').notNull(), + reorderLevel: int('reorder_level').notNull(), + discontinued: int('discontinued').notNull(), + + supplierId: int('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = mysqlTable('order_detail', { + unitPrice: float('unit_price').notNull(), + quantity: int('quantity').notNull(), + discount: float('discount').notNull(), + + orderId: int('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: int('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); diff --git a/drizzle-seed/tests/northwind/mysqlTest.ts b/drizzle-seed/tests/northwind/mysqlTest.ts new file mode 100644 index 000000000..1cbdb7704 --- /dev/null +++ b/drizzle-seed/tests/northwind/mysqlTest.ts @@ -0,0 +1,176 @@ +import 'dotenv/config'; +import path from 'path'; + +import { drizzle } from 'drizzle-orm/mysql2'; +import { migrate } from 'drizzle-orm/mysql2/migrator'; +import mysql from 'mysql2/promise'; + +import * as schema from './mysqlSchema.ts'; + +import { seed } from '../../src/index.ts'; + +const { Mysql_HOST, Mysql_PORT, Mysql_DATABASE, Mysql_USER, Mysql_PASSWORD } = process.env; + +const mysqlPool = mysql.createPool({ + host: Mysql_HOST, + port: Number(Mysql_PORT) || 3306, + database: Mysql_DATABASE, + user: Mysql_USER, + password: Mysql_PASSWORD, + // ssl: { rejectUnauthorized: false } +}); + +const db = drizzle(mysqlPool); + +console.log('database connection was established successfully.'); + +(async () => { + await migrate(db, { migrationsFolder: path.join(__dirname, '../../../mysqlMigrations') }); + console.log('database was migrated.'); + + // await seed(db, schema, { count: 100000, seed: 1 }); + + const titlesOfCourtesy = ['Ms.', 'Mrs.', 'Dr.']; + const unitsOnOrders = [0, 10, 20, 30, 50, 60, 70, 80, 100]; + const reorderLevels = [0, 5, 10, 15, 20, 25, 30]; + const quantityPerUnit = [ + '100 - 100 g pieces', + '100 - 250 g bags', + '10 - 200 g glasses', + '10 - 4 oz boxes', + '10 - 500 g pkgs.', + '10 - 500 g pkgs.', + '10 boxes x 12 pieces', + '10 boxes x 20 bags', + '10 boxes x 8 pieces', + '10 kg pkg.', + '10 pkgs.', + '12 - 100 g bars', + '12 - 100 g pkgs', + '12 - 12 oz cans', + '12 - 1 lb pkgs.', + '12 - 200 ml jars', + '12 - 250 g pkgs.', + '12 - 355 ml cans', + '12 - 500 g pkgs.', + '750 cc per bottle', + '5 kg pkg.', + '50 bags x 30 sausgs.', + '500 ml', + '500 g', + '48 pieces', + '48 - 6 oz jars', + '4 - 450 g glasses', + '36 boxes', + '32 - 8 oz bottles', + '32 - 500 g boxes', + ]; + const discounts = [0.05, 0.15, 0.2, 0.25]; + + await seed(db, schema).refine((funcs) => ({ + customers: { + count: 10000, + columns: { + companyName: funcs.companyName({}), + contactName: funcs.fullName({}), + contactTitle: funcs.jobTitle({}), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + region: funcs.state({}), + country: funcs.country({}), + phone: funcs.phoneNumber({ template: '(###) ###-####' }), + fax: funcs.phoneNumber({ template: '(###) ###-####' }), + }, + }, + employees: { + count: 200, + columns: { + firstName: funcs.firstName({}), + lastName: funcs.lastName({}), + title: funcs.jobTitle({}), + titleOfCourtesy: funcs.valuesFromArray({ values: titlesOfCourtesy }), + birthDate: funcs.date({ minDate: '1990-01-01', maxDate: '2010-12-31' }), + hireDate: funcs.date({ minDate: '2010-12-31', maxDate: '2024-08-26' }), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + country: funcs.country({}), + homePhone: funcs.phoneNumber({ template: '(###) ###-####' }), + extension: funcs.int({ minValue: 428, maxValue: 5467 }), + notes: funcs.loremIpsum({}), + }, + }, + orders: { + count: 50000, + columns: { + shipVia: funcs.int({ minValue: 1, maxValue: 3 }), + freight: funcs.number({ minValue: 0, maxValue: 1000, precision: 100 }), + shipName: funcs.streetAddress({}), + shipCity: funcs.city({}), + shipRegion: funcs.state({}), + shipPostalCode: funcs.postcode({}), + shipCountry: funcs.country({}), + }, + with: { + details: [ + { weight: 0.6, count: [1, 2, 3, 4] }, + { weight: 0.2, count: [5, 6, 7, 8, 9, 10] }, + { weight: 0.15, count: [11, 12, 13, 14, 15, 16, 17] }, + { weight: 0.05, count: [18, 19, 20, 21, 22, 23, 24, 25] }, + ], + }, + }, + suppliers: { + count: 1000, + columns: { + companyName: funcs.companyName({}), + contactName: funcs.fullName({}), + contactTitle: funcs.jobTitle({}), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + region: funcs.state({}), + country: funcs.country({}), + phone: funcs.phoneNumber({ template: '(###) ###-####' }), + }, + }, + products: { + count: 5000, + columns: { + name: funcs.companyName({}), + quantityPerUnit: funcs.valuesFromArray({ values: quantityPerUnit }), + unitPrice: funcs.weightedRandom( + [ + { + weight: 0.5, + value: funcs.int({ minValue: 3, maxValue: 300 }), + }, + { + weight: 0.5, + value: funcs.number({ minValue: 3, maxValue: 300, precision: 100 }), + }, + ], + ), + unitsInStock: funcs.int({ minValue: 0, maxValue: 125 }), + unitsOnOrder: funcs.valuesFromArray({ values: unitsOnOrders }), + reorderLevel: funcs.valuesFromArray({ values: reorderLevels }), + discontinued: funcs.int({ minValue: 0, maxValue: 1 }), + }, + }, + details: { + columns: { + unitPrice: funcs.number({ minValue: 10, maxValue: 130 }), + quantity: funcs.int({ minValue: 1, maxValue: 130 }), + discount: funcs.weightedRandom( + [ + { weight: 0.5, value: funcs.valuesFromArray({ values: discounts }) }, + { weight: 0.5, value: funcs.default({ defaultValue: 0 }) }, + ], + ), + }, + }, + })); + + await mysqlPool.end(); +})().then(); diff --git a/drizzle-seed/tests/northwind/pgSchema.ts b/drizzle-seed/tests/northwind/pgSchema.ts new file mode 100644 index 000000000..f0f9a05fb --- /dev/null +++ b/drizzle-seed/tests/northwind/pgSchema.ts @@ -0,0 +1,104 @@ +import type { AnyPgColumn } from 'drizzle-orm/pg-core'; +import { integer, numeric, pgSchema, text, timestamp, varchar } from 'drizzle-orm/pg-core'; + +export const schema = pgSchema('seeder_lib_pg'); + +export const customers = schema.table('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = schema.table('employee', { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to').references((): AnyPgColumn => employees.id), + photoPath: text('photo_path'), +}); + +export const orders = schema.table('order', { + id: integer('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id') + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: integer('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = schema.table( + 'supplier', + { + id: integer('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), + }, +); + +export const products = schema.table('product', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = schema.table('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: integer('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); diff --git a/drizzle-seed/tests/northwind/pgTest.ts b/drizzle-seed/tests/northwind/pgTest.ts new file mode 100644 index 000000000..84c366b6c --- /dev/null +++ b/drizzle-seed/tests/northwind/pgTest.ts @@ -0,0 +1,175 @@ +import 'dotenv/config'; +import path from 'path'; + +import { drizzle } from 'drizzle-orm/node-postgres'; +import { migrate } from 'drizzle-orm/node-postgres/migrator'; +import { Pool as PgPool } from 'pg'; + +import { seed } from '../../src/index.ts'; +import * as schema from './pgSchema.ts'; + +const { PG_HOST, PG_PORT, PG_DATABASE, PG_USER, PG_PASSWORD } = process.env; + +const pgPool = new PgPool({ + host: PG_HOST, + port: Number(PG_PORT) || 5432, + database: PG_DATABASE, + user: PG_USER, + password: PG_PASSWORD, + // ssl: true +}); + +const db = drizzle(pgPool); + +console.log('database connection was established successfully.'); + +(async () => { + await migrate(db, { migrationsFolder: path.join(__dirname, '../../../pgMigrations') }); + console.log('database was migrated.'); + + // await seed(db, schema, { count: 100000, seed: 1 }); + + const titlesOfCourtesy = ['Ms.', 'Mrs.', 'Dr.']; + const unitsOnOrders = [0, 10, 20, 30, 50, 60, 70, 80, 100]; + const reorderLevels = [0, 5, 10, 15, 20, 25, 30]; + const quantityPerUnit = [ + '100 - 100 g pieces', + '100 - 250 g bags', + '10 - 200 g glasses', + '10 - 4 oz boxes', + '10 - 500 g pkgs.', + '10 - 500 g pkgs.', + '10 boxes x 12 pieces', + '10 boxes x 20 bags', + '10 boxes x 8 pieces', + '10 kg pkg.', + '10 pkgs.', + '12 - 100 g bars', + '12 - 100 g pkgs', + '12 - 12 oz cans', + '12 - 1 lb pkgs.', + '12 - 200 ml jars', + '12 - 250 g pkgs.', + '12 - 355 ml cans', + '12 - 500 g pkgs.', + '750 cc per bottle', + '5 kg pkg.', + '50 bags x 30 sausgs.', + '500 ml', + '500 g', + '48 pieces', + '48 - 6 oz jars', + '4 - 450 g glasses', + '36 boxes', + '32 - 8 oz bottles', + '32 - 500 g boxes', + ]; + const discounts = [0.05, 0.15, 0.2, 0.25]; + + await seed(db, schema).refine((funcs) => ({ + customers: { + count: 10000, + columns: { + companyName: funcs.companyName({}), + contactName: funcs.fullName({}), + contactTitle: funcs.jobTitle({}), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + region: funcs.state({}), + country: funcs.country({}), + phone: funcs.phoneNumber({ template: '(###) ###-####' }), + fax: funcs.phoneNumber({ template: '(###) ###-####' }), + }, + }, + employees: { + count: 200, + columns: { + firstName: funcs.firstName({}), + lastName: funcs.lastName({}), + title: funcs.jobTitle({}), + titleOfCourtesy: funcs.valuesFromArray({ values: titlesOfCourtesy }), + birthDate: funcs.date({ minDate: '1990-01-01', maxDate: '2010-12-31' }), + hireDate: funcs.date({ minDate: '2010-12-31', maxDate: '2024-08-26' }), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + country: funcs.country({}), + homePhone: funcs.phoneNumber({ template: '(###) ###-####' }), + extension: funcs.int({ minValue: 428, maxValue: 5467 }), + notes: funcs.loremIpsum({}), + }, + }, + orders: { + count: 50000, + columns: { + shipVia: funcs.int({ minValue: 1, maxValue: 3 }), + freight: funcs.number({ minValue: 0, maxValue: 1000, precision: 100 }), + shipName: funcs.streetAddress({}), + shipCity: funcs.city({}), + shipRegion: funcs.state({}), + shipPostalCode: funcs.postcode({}), + shipCountry: funcs.country({}), + }, + with: { + details: [ + { weight: 0.6, count: [1, 2, 3, 4] }, + { weight: 0.2, count: [5, 6, 7, 8, 9, 10] }, + { weight: 0.15, count: [11, 12, 13, 14, 15, 16, 17] }, + { weight: 0.05, count: [18, 19, 20, 21, 22, 23, 24, 25] }, + ], + }, + }, + suppliers: { + count: 1000, + columns: { + companyName: funcs.companyName({}), + contactName: funcs.fullName({}), + contactTitle: funcs.jobTitle({}), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + region: funcs.state({}), + country: funcs.country({}), + phone: funcs.phoneNumber({ template: '(###) ###-####' }), + }, + }, + products: { + count: 5000, + columns: { + name: funcs.companyName({}), + quantityPerUnit: funcs.valuesFromArray({ values: quantityPerUnit }), + unitPrice: funcs.weightedRandom( + [ + { + weight: 0.5, + value: funcs.int({ minValue: 3, maxValue: 300 }), + }, + { + weight: 0.5, + value: funcs.number({ minValue: 3, maxValue: 300, precision: 100 }), + }, + ], + ), + unitsInStock: funcs.int({ minValue: 0, maxValue: 125 }), + unitsOnOrder: funcs.valuesFromArray({ values: unitsOnOrders }), + reorderLevel: funcs.valuesFromArray({ values: reorderLevels }), + discontinued: funcs.int({ minValue: 0, maxValue: 1 }), + }, + }, + details: { + columns: { + unitPrice: funcs.number({ minValue: 10, maxValue: 130 }), + quantity: funcs.int({ minValue: 1, maxValue: 130 }), + discount: funcs.weightedRandom( + [ + { weight: 0.5, value: funcs.valuesFromArray({ values: discounts }) }, + { weight: 0.5, value: funcs.default({ defaultValue: 0 }) }, + ], + ), + }, + }, + })); + + await pgPool.end(); +})().then(); diff --git a/drizzle-seed/tests/northwind/sqliteSchema.ts b/drizzle-seed/tests/northwind/sqliteSchema.ts new file mode 100644 index 000000000..fa00dd365 --- /dev/null +++ b/drizzle-seed/tests/northwind/sqliteSchema.ts @@ -0,0 +1,107 @@ +import { foreignKey, integer, numeric, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const customers = sqliteTable('customer', { + id: text('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = sqliteTable( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: integer('birth_date', { mode: 'timestamp' }).notNull(), + hireDate: integer('hire_date', { mode: 'timestamp' }).notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to'), + photoPath: text('photo_path'), + }, + (table) => ({ + reportsToFk: foreignKey(() => ({ + columns: [table.reportsTo], + foreignColumns: [table.id], + })), + }), +); + +export const orders = sqliteTable('order', { + id: integer('id').primaryKey(), + orderDate: integer('order_date', { mode: 'timestamp' }).notNull(), + requiredDate: integer('required_date', { mode: 'timestamp' }).notNull(), + shippedDate: integer('shipped_date', { mode: 'timestamp' }), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id') + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: integer('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = sqliteTable('supplier', { + id: integer('id').primaryKey({ autoIncrement: true }), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = sqliteTable('product', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = sqliteTable('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: integer('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); diff --git a/drizzle-seed/tests/northwind/sqliteTest.ts b/drizzle-seed/tests/northwind/sqliteTest.ts new file mode 100644 index 000000000..0267bc288 --- /dev/null +++ b/drizzle-seed/tests/northwind/sqliteTest.ts @@ -0,0 +1,162 @@ +import 'dotenv/config'; +import path from 'path'; + +import betterSqlite3 from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; + +import { seed } from '../../src/index.ts'; +import * as schema from './sqliteSchema.ts'; + +const { Sqlite_PATH } = process.env; +const sqliteDb = betterSqlite3(Sqlite_PATH); +const db = drizzle(sqliteDb); + +console.log('database connection was established successfully.'); + +(async () => { + migrate(db, { migrationsFolder: path.join(__dirname, '../../../sqliteMigrations') }); + console.log('database was migrated.'); + + const titlesOfCourtesy = ['Ms.', 'Mrs.', 'Dr.']; + const unitsOnOrders = [0, 10, 20, 30, 50, 60, 70, 80, 100]; + const reorderLevels = [0, 5, 10, 15, 20, 25, 30]; + const quantityPerUnit = [ + '100 - 100 g pieces', + '100 - 250 g bags', + '10 - 200 g glasses', + '10 - 4 oz boxes', + '10 - 500 g pkgs.', + '10 - 500 g pkgs.', + '10 boxes x 12 pieces', + '10 boxes x 20 bags', + '10 boxes x 8 pieces', + '10 kg pkg.', + '10 pkgs.', + '12 - 100 g bars', + '12 - 100 g pkgs', + '12 - 12 oz cans', + '12 - 1 lb pkgs.', + '12 - 200 ml jars', + '12 - 250 g pkgs.', + '12 - 355 ml cans', + '12 - 500 g pkgs.', + '750 cc per bottle', + '5 kg pkg.', + '50 bags x 30 sausgs.', + '500 ml', + '500 g', + '48 pieces', + '48 - 6 oz jars', + '4 - 450 g glasses', + '36 boxes', + '32 - 8 oz bottles', + '32 - 500 g boxes', + ]; + const discounts = [0.05, 0.15, 0.2, 0.25]; + + await seed(db, schema).refine((funcs) => ({ + customers: { + count: 10000, + columns: { + companyName: funcs.companyName({}), + contactName: funcs.fullName({}), + contactTitle: funcs.jobTitle({}), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + region: funcs.state({}), + country: funcs.country({}), + phone: funcs.phoneNumber({ template: '(###) ###-####' }), + fax: funcs.phoneNumber({ template: '(###) ###-####' }), + }, + }, + employees: { + count: 200, + columns: { + firstName: funcs.firstName({}), + lastName: funcs.lastName({}), + title: funcs.jobTitle({}), + titleOfCourtesy: funcs.valuesFromArray({ values: titlesOfCourtesy }), + birthDate: funcs.date({ minDate: '1990-01-01', maxDate: '2010-12-31' }), + hireDate: funcs.date({ minDate: '2010-12-31', maxDate: '2024-08-26' }), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + country: funcs.country({}), + homePhone: funcs.phoneNumber({ template: '(###) ###-####' }), + extension: funcs.int({ minValue: 428, maxValue: 5467 }), + notes: funcs.loremIpsum({}), + }, + }, + orders: { + count: 50000, + columns: { + shipVia: funcs.int({ minValue: 1, maxValue: 3 }), + freight: funcs.number({ minValue: 0, maxValue: 1000, precision: 100 }), + shipName: funcs.streetAddress({}), + shipCity: funcs.city({}), + shipRegion: funcs.state({}), + shipPostalCode: funcs.postcode({}), + shipCountry: funcs.country({}), + }, + with: { + details: [ + { weight: 0.6, count: [1, 2, 3, 4] }, + { weight: 0.2, count: [5, 6, 7, 8, 9, 10] }, + { weight: 0.15, count: [11, 12, 13, 14, 15, 16, 17] }, + { weight: 0.05, count: [18, 19, 20, 21, 22, 23, 24, 25] }, + ], + }, + }, + suppliers: { + count: 1000, + columns: { + companyName: funcs.companyName({}), + contactName: funcs.fullName({}), + contactTitle: funcs.jobTitle({}), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + region: funcs.state({}), + country: funcs.country({}), + phone: funcs.phoneNumber({ template: '(###) ###-####' }), + }, + }, + products: { + count: 5000, + columns: { + name: funcs.companyName({}), + quantityPerUnit: funcs.valuesFromArray({ values: quantityPerUnit }), + unitPrice: funcs.weightedRandom( + [ + { + weight: 0.5, + value: funcs.int({ minValue: 3, maxValue: 300 }), + }, + { + weight: 0.5, + value: funcs.number({ minValue: 3, maxValue: 300, precision: 100 }), + }, + ], + ), + unitsInStock: funcs.int({ minValue: 0, maxValue: 125 }), + unitsOnOrder: funcs.valuesFromArray({ values: unitsOnOrders }), + reorderLevel: funcs.valuesFromArray({ values: reorderLevels }), + discontinued: funcs.int({ minValue: 0, maxValue: 1 }), + }, + }, + details: { + columns: { + unitPrice: funcs.number({ minValue: 10, maxValue: 130 }), + quantity: funcs.int({ minValue: 1, maxValue: 130 }), + discount: funcs.weightedRandom( + [ + { weight: 0.5, value: funcs.valuesFromArray({ values: discounts }) }, + { weight: 0.5, value: funcs.default({ defaultValue: 0 }) }, + ], + ), + }, + }, + })); +})().then(); diff --git a/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts b/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts new file mode 100644 index 000000000..d4f45de22 --- /dev/null +++ b/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts @@ -0,0 +1,115 @@ +import { + bigint, + bigserial, + boolean, + char, + date, + decimal, + doublePrecision, + integer, + interval, + json, + jsonb, + line, + numeric, + pgEnum, + pgSchema, + point, + real, + serial, + smallint, + smallserial, + text, + time, + timestamp, + uuid, + varchar, +} from 'drizzle-orm/pg-core'; + +export const schema = pgSchema('seeder_lib_pg'); + +export const moodEnum = pgEnum('mood_enum', ['sad', 'ok', 'happy']); + +export const allDataTypes = schema.table('all_data_types', { + integer: integer('integer'), + smallint: smallint('smallint'), + biginteger: bigint('bigint', { mode: 'bigint' }), + bigintNumber: bigint('bigint_number', { mode: 'number' }), + serial: serial('serial'), + smallserial: smallserial('smallserial'), + bigserial: bigserial('bigserial', { mode: 'bigint' }), + bigserialNumber: bigserial('bigserial_number', { mode: 'number' }), + boolean: boolean('boolean'), + text: text('text'), + varchar: varchar('varchar', { length: 256 }), + char: char('char', { length: 256 }), + numeric: numeric('numeric'), + decimal: decimal('decimal'), + real: real('real'), + doublePrecision: doublePrecision('double_precision'), + json: json('json'), + jsonb: jsonb('jsonb'), + time: time('time'), + timestampDate: timestamp('timestamp_date', { mode: 'date' }), + timestampString: timestamp('timestamp_string', { mode: 'string' }), + dateString: date('date_string', { mode: 'string' }), + date: date('date', { mode: 'date' }), + interval: interval('interval'), + point: point('point', { mode: 'xy' }), + pointTuple: point('point_tuple', { mode: 'tuple' }), + line: line('line', { mode: 'abc' }), + lineTuple: line('line_tuple', { mode: 'tuple' }), + moodEnum: moodEnum('mood_enum'), + uuid: uuid('uuid'), +}); + +export const allArrayDataTypes = schema.table('all_array_data_types', { + // integerArray: integer('integer_array').array(), + // smallintArray: smallint('smallint_array').array(), + // bigintegerArray: bigint('bigint_array', { mode: 'bigint' }).array(), + // bigintNumberArray: bigint('bigint_number_array', { mode: 'number' }).array(), + // booleanArray: boolean('boolean_array').array(), + // textArray: text('text_array').array(), + // varcharArray: varchar('varchar_array', { length: 256 }).array(), + // charArray: char('char_array', { length: 256 }).array(), + // numericArray: numeric('numeric_array').array(), + // decimalArray: decimal('decimal_array').array(), + // realArray: real('real_array').array(), + // doublePrecisionArray: doublePrecision('double_precision_array').array(), + // jsonArray: json('json_array').array(), + // jsonbArray: jsonb('jsonb_array').array(), + // timeArray: time('time_array').array(), + // timestampDateArray: timestamp('timestamp_date_array', { mode: 'date' }).array(), + // timestampStringArray: timestamp('timestamp_string_array', { mode: 'string' }).array(), + dateStringArray: date('date_string_array', { mode: 'string' }).array(), + dateArray: date('date_array', { mode: 'date' }).array(), + // intervalArray: interval('interval_array').array(), + // pointArray: point('point_array', { mode: 'xy' }).array(), + // pointTupleArray: point('point_tuple_array', { mode: 'tuple' }).array(), + // lineArray: line('line_array', { mode: 'abc' }).array(), + // lineTupleArray: line('line_tuple_array', { mode: 'tuple' }).array(), + // moodEnumArray: moodEnum('mood_enum_array').array(), +}); + +export const ndArrays = schema.table('nd_arrays', { + integer1DArray: integer('integer_1d_array').array(3), + integer2DArray: integer('integer_2d_array').array(3).array(4), + integer3DArray: integer('integer_3d_array').array(3).array(4).array(5), + integer4DArray: integer('integer_4d_array').array(3).array(4).array(5).array(6), +}); + +export const intervals = schema.table('intervals', { + intervalYear: interval({ fields: 'year' }), + intervalYearToMonth: interval({ fields: 'year to month' }), + intervalMonth: interval({ fields: 'month' }), + intervalDay: interval({ fields: 'day' }), + intervalDayToHour: interval({ fields: 'day to hour' }), + intervalDayToMinute: interval({ fields: 'day to minute' }), + intervalDayToSecond: interval({ fields: 'day to second' }), + intervalHour: interval({ fields: 'hour' }), + intervalHourToMinute: interval({ fields: 'hour to minute' }), + intervalHourToSecond: interval({ fields: 'hour to second' }), + intervalMinute: interval({ fields: 'minute' }), + intervalMinuteToSecond: interval({ fields: 'minute to second' }), + intervalSecond: interval({ fields: 'second' }), +}); diff --git a/drizzle-seed/tests/pg/allDataTypesTest/pg_all_data_types.test.ts b/drizzle-seed/tests/pg/allDataTypesTest/pg_all_data_types.test.ts new file mode 100644 index 000000000..62d0895c0 --- /dev/null +++ b/drizzle-seed/tests/pg/allDataTypesTest/pg_all_data_types.test.ts @@ -0,0 +1,189 @@ +import { PGlite } from '@electric-sql/pglite'; +import { sql } from 'drizzle-orm'; +import type { PgliteDatabase } from 'drizzle-orm/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; +import { afterAll, beforeAll, expect, test } from 'vitest'; +import { seed } from '../../../src/index.ts'; +import * as schema from './pgSchema.ts'; + +let client: PGlite; +let db: PgliteDatabase; + +beforeAll(async () => { + client = new PGlite(); + + db = drizzle(client); + + await db.execute(sql`CREATE SCHEMA if not exists "seeder_lib_pg";`); + + await db.execute( + sql` + DO $$ BEGIN + CREATE TYPE "seeder_lib_pg"."mood_enum" AS ENUM('sad', 'ok', 'happy'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."all_data_types" ( + "integer" integer, + "smallint" smallint, + "bigint" bigint, + "bigint_number" bigint, + "serial" serial, + "smallserial" smallserial, + "bigserial" bigserial, + "bigserial_number" bigserial, + "boolean" boolean, + "text" text, + "varchar" varchar(256), + "char" char(256), + "numeric" numeric, + "decimal" numeric, + "real" real, + "double_precision" double precision, + "json" json, + "jsonb" jsonb, + "time" time, + "timestamp_date" timestamp, + "timestamp_string" timestamp, + "date_string" date, + "date" date, + "interval" interval, + "point" "point", + "point_tuple" "point", + "line" "line", + "line_tuple" "line", + "mood_enum" "seeder_lib_pg"."mood_enum", + "uuid" "uuid" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."all_array_data_types" ( + "integer_array" integer[], + "smallint_array" smallint[], + "bigint_array" bigint[], + "bigint_number_array" bigint[], + "boolean_array" boolean[], + "text_array" text[], + "varchar_array" varchar(256)[], + "char_array" char(256)[], + "numeric_array" numeric[], + "decimal_array" numeric[], + "real_array" real[], + "double_precision_array" double precision[], + "json_array" json[], + "jsonb_array" jsonb[], + "time_array" time[], + "timestamp_date_array" timestamp[], + "timestamp_string_array" timestamp[], + "date_string_array" date[], + "date_array" date[], + "interval_array" interval[], + "point_array" "point"[], + "point_tuple_array" "point"[], + "line_array" "line"[], + "line_tuple_array" "line"[], + "mood_enum_array" "seeder_lib_pg"."mood_enum"[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."nd_arrays" ( + "integer_1d_array" integer[3], + "integer_2d_array" integer[3][4], + "integer_3d_array" integer[3][4][5], + "integer_4d_array" integer[3][4][5][6] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."intervals" ( + "intervalYear" interval year, + "intervalYearToMonth" interval year to month, + "intervalMonth" interval month, + "intervalDay" interval day, + "intervalDayToHour" interval day to hour, + "intervalDayToMinute" interval day to minute, + "intervalDayToSecond" interval day to second, + "intervalHour" interval hour, + "intervalHourToMinute" interval hour to minute, + "intervalHourToSecond" interval hour to second, + "intervalMinute" interval minute, + "intervalMinuteToSecond" interval minute to second, + "intervalSecond" interval second + ); + `, + ); +}); + +afterAll(async () => { + await client.close(); +}); + +test('all data types test', async () => { + await seed(db, { allDataTypes: schema.allDataTypes }, { count: 10000 }); + + const allDataTypes = await db.select().from(schema.allDataTypes); + // every value in each rows does not equal undefined. + const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + + expect(predicate).toBe(true); +}); + +test('all array data types test', async () => { + await seed(db, { allArrayDataTypes: schema.allArrayDataTypes }, { count: 1000 }); + + const allArrayDataTypes = await db.select().from(schema.allArrayDataTypes); + // every value in each rows does not equal undefined. + const predicate = allArrayDataTypes.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val.length === 10) + ); + + expect(predicate).toBe(true); +}); + +test('nd arrays', async () => { + await seed(db, { ndArrays: schema.ndArrays }, { count: 1000 }); + + const ndArrays = await db.select().from(schema.ndArrays); + // every value in each rows does not equal undefined. + const predicate0 = ndArrays.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val.length !== 0) + ); + let predicate1 = true, predicate2 = true, predicate3 = true, predicate4 = true; + + for (const row of ndArrays) { + predicate1 = predicate1 && (row.integer1DArray?.length === 3); + + predicate2 = predicate2 && (row.integer2DArray?.length === 4) && (row.integer2DArray[0]?.length === 3); + + predicate3 = predicate3 && (row.integer3DArray?.length === 5) && (row.integer3DArray[0]?.length === 4) + && (row.integer3DArray[0][0]?.length === 3); + + predicate4 = predicate4 && (row.integer4DArray?.length === 6) && (row.integer4DArray[0]?.length === 5) + && (row.integer4DArray[0][0]?.length === 4) && (row.integer4DArray[0][0][0]?.length === 3); + } + + expect(predicate0 && predicate1 && predicate2 && predicate3 && predicate4).toBe(true); +}); + +test('intervals test', async () => { + await seed(db, { intervals: schema.intervals }, { count: 1000 }); + + const intervals = await db.select().from(schema.intervals); + // every value in each rows does not equal undefined. + const predicate = intervals.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/pg/cyclicTables/cyclicTables.test.ts b/drizzle-seed/tests/pg/cyclicTables/cyclicTables.test.ts new file mode 100644 index 000000000..c4be3509e --- /dev/null +++ b/drizzle-seed/tests/pg/cyclicTables/cyclicTables.test.ts @@ -0,0 +1,157 @@ +import { PGlite } from '@electric-sql/pglite'; +import { sql } from 'drizzle-orm'; +import type { PgliteDatabase } from 'drizzle-orm/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './pgSchema.ts'; + +let client: PGlite; +let db: PgliteDatabase; + +beforeAll(async () => { + client = new PGlite(); + + db = drizzle(client); + + await db.execute( + sql` + create table model_image + ( + id serial + primary key, + url varchar not null, + caption varchar, + "modelId" integer not null + ); + `, + ); + + await db.execute( + sql` + create table model + ( + id serial + primary key, + name varchar not null, + "defaultImageId" integer + constraint "model_defaultImageId_model_image_id_fk" + references model_image + ); + `, + ); + + await db.execute( + sql` + alter table model_image + add constraint "model_image_modelId_model_id_fk" + foreign key ("modelId") references model; + `, + ); + + // 3 tables case + await db.execute( + sql` + create table model_image1 + ( + id serial + primary key, + url varchar not null, + caption varchar, + "modelId" integer not null + ); + `, + ); + + await db.execute( + sql` + create table "user" + ( + id serial + primary key, + name text, + "invitedBy" integer + constraint "user_invitedBy_user_id_fk" + references "user", + "imageId" integer not null + constraint "user_imageId_model_image1_id_fk" + references model_image1 + ); + `, + ); + + await db.execute( + sql` + create table model1 + ( + id serial + primary key, + name varchar not null, + "userId" integer + constraint "model1_userId_user_id_fk" + references "user", + "defaultImageId" integer + constraint "model1_defaultImageId_model_image1_id_fk" + references model_image1 + ); + `, + ); + + await db.execute( + sql` + alter table model_image1 + add constraint "model_image1_modelId_model1_id_fk" + foreign key ("modelId") references model1; + `, + ); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +afterAll(async () => { + await client.close(); +}); + +test('2 cyclic tables test', async () => { + await seed(db, { + modelTable: schema.modelTable, + modelImageTable: schema.modelImageTable, + }); + + const modelTable = await db.select().from(schema.modelTable); + const modelImageTable = await db.select().from(schema.modelImageTable); + + expect(modelTable.length).toBe(10); + let predicate = modelTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable.length).toBe(10); + predicate = modelImageTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('3 cyclic tables test', async () => { + await seed(db, { + modelTable1: schema.modelTable1, + modelImageTable1: schema.modelImageTable1, + user: schema.user, + }); + + const modelTable1 = await db.select().from(schema.modelTable1); + const modelImageTable1 = await db.select().from(schema.modelImageTable1); + const user = await db.select().from(schema.user); + + expect(modelTable1.length).toBe(10); + let predicate = modelTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable1.length).toBe(10); + predicate = modelImageTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(user.length).toBe(10); + predicate = user.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/pg/cyclicTables/pgSchema.ts b/drizzle-seed/tests/pg/cyclicTables/pgSchema.ts new file mode 100644 index 000000000..7615470b0 --- /dev/null +++ b/drizzle-seed/tests/pg/cyclicTables/pgSchema.ts @@ -0,0 +1,88 @@ +import { relations } from 'drizzle-orm'; +import type { AnyPgColumn } from 'drizzle-orm/pg-core'; +import { foreignKey, integer, pgTable, serial, text, varchar } from 'drizzle-orm/pg-core'; + +// MODEL +export const modelTable = pgTable( + 'model', + { + id: serial().primaryKey(), + name: varchar().notNull(), + defaultImageId: integer(), + }, + (t) => [ + foreignKey({ + columns: [t.defaultImageId], + foreignColumns: [modelImageTable.id], + }), + ], +); + +export const modelRelations = relations(modelTable, ({ one, many }) => ({ + images: many(modelImageTable), + defaultImage: one(modelImageTable, { + fields: [modelTable.defaultImageId], + references: [modelImageTable.id], + }), +})); + +// MODEL IMAGE +export const modelImageTable = pgTable( + 'model_image', + { + id: serial().primaryKey(), + url: varchar().notNull(), + caption: varchar(), + modelId: integer() + .notNull() + .references((): AnyPgColumn => modelTable.id), + }, +); + +export const modelImageRelations = relations(modelImageTable, ({ one }) => ({ + model: one(modelTable, { + fields: [modelImageTable.modelId], + references: [modelTable.id], + }), +})); + +// 3 tables case +export const modelTable1 = pgTable( + 'model1', + { + id: serial().primaryKey(), + name: varchar().notNull(), + userId: integer() + .references(() => user.id), + defaultImageId: integer(), + }, + (t) => [ + foreignKey({ + columns: [t.defaultImageId], + foreignColumns: [modelImageTable1.id], + }), + ], +); + +export const modelImageTable1 = pgTable( + 'model_image1', + { + id: serial().primaryKey(), + url: varchar().notNull(), + caption: varchar(), + modelId: integer().notNull() + .references((): AnyPgColumn => modelTable1.id), + }, +); + +export const user = pgTable( + 'user', + { + id: serial().primaryKey(), + name: text(), + invitedBy: integer().references((): AnyPgColumn => user.id), + imageId: integer() + .notNull() + .references((): AnyPgColumn => modelImageTable1.id), + }, +); diff --git a/drizzle-seed/tests/pg/generatorsTest/generators.test.ts b/drizzle-seed/tests/pg/generatorsTest/generators.test.ts new file mode 100644 index 000000000..3de2ce99e --- /dev/null +++ b/drizzle-seed/tests/pg/generatorsTest/generators.test.ts @@ -0,0 +1,2097 @@ +import { afterAll, beforeAll, expect, test } from 'vitest'; + +import { PGlite } from '@electric-sql/pglite'; +import type { PgliteDatabase } from 'drizzle-orm/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; + +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './pgSchema.ts'; + +import { sql } from 'drizzle-orm'; +import cities from '../../../src/datasets/cityNames.ts'; +import countries from '../../../src/datasets/countries.ts'; +import firstNames from '../../../src/datasets/firstNames.ts'; +import lastNames from '../../../src/datasets/lastNames.ts'; + +let client: PGlite; +let db: PgliteDatabase; + +beforeAll(async () => { + client = new PGlite(); + + db = drizzle(client); + + await db.execute(sql`CREATE SCHEMA "seeder_lib_pg";`); + + await db.execute( + sql` + DO $$ BEGIN + CREATE TYPE "seeder_lib_pg"."enum" AS ENUM('sad', 'ok', 'happy'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_table" ( + "default_string" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_array_table" ( + "default_string" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."boolean_table" ( + "boolean" boolean + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."boolean_array_table" ( + "boolean" boolean[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_table" ( + "city" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_unique_table" ( + "city_unique" varchar(256), + CONSTRAINT "city_unique_table_city_unique_unique" UNIQUE("city_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_array_table" ( + "city" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_table" ( + "company_name" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_unique_table" ( + "company_name_unique" varchar(256), + CONSTRAINT "company_name_unique_table_company_name_unique_unique" UNIQUE("company_name_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_array_table" ( + "company_name" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_table" ( + "country" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_unique_table" ( + "country_unique" varchar(256), + CONSTRAINT "country_unique_table_country_unique_unique" UNIQUE("country_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_array_table" ( + "country" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."date_table" ( + "date" date + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."date_array_table" ( + "date" date[], + "date_string" date[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."email_table" ( + "email" varchar(256), + CONSTRAINT "email_table_email_unique" UNIQUE("email") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."email_array_table" ( + "email" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."enum_table" ( + "mood_enum" "seeder_lib_pg"."enum" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."first_name_table" ( + "first_name" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."first_name_unique_table" ( + "first_name_unique" varchar(256), + CONSTRAINT "first_name_unique_table_first_name_unique_unique" UNIQUE("first_name_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."first_name_array_table" ( + "first_name" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name__table" ( + "full_name_" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name_unique_table" ( + "full_name_unique" varchar(256), + CONSTRAINT "full_name_unique_table_full_name_unique_unique" UNIQUE("full_name_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name_array_table" ( + "full_name" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_primary_key_table" ( + "int_primary_key" integer, + CONSTRAINT "int_primary_key_table_int_primary_key_unique" UNIQUE("int_primary_key") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_table" ( + "int" integer + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_unique_table" ( + "int_unique" integer, + CONSTRAINT "int_unique_table_int_unique_unique" UNIQUE("int_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_array_table" ( + "int" integer[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_table" ( + "interval" interval + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_unique_table" ( + "interval_unique" interval, + CONSTRAINT "interval_unique_table_interval_unique_unique" UNIQUE("interval_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_array_table" ( + "interval" interval[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."job_Title_table" ( + "job_title" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."job_title_array_table" ( + "job_title" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."json_table" ( + "json" json + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."json_array_table" ( + "json" json[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_table" ( + "last_name" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_unique_table" ( + "last_name_unique" varchar(256), + CONSTRAINT "last_name_unique_table_last_name_unique_unique" UNIQUE("last_name_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_array_table" ( + "last_name" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."line_table" ( + "line" "line" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."line_array_table" ( + "line" "line"[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."lorem_ipsum_table" ( + "lorem_ipsum" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."lorem_ipsum_array_table" ( + "lorem_ipsum" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_table" ( + "number" real + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_unique_table" ( + "number_unique" real, + CONSTRAINT "number_unique_table_number_unique_unique" UNIQUE("number_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_array_table" ( + "number" real[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."phone_number_table" ( + "phoneNumber" varchar(256), + "phone_number_template" varchar(256), + "phone_number_prefixes" varchar(256), + CONSTRAINT "phone_number_table_phoneNumber_unique" UNIQUE("phoneNumber"), + CONSTRAINT "phone_number_table_phone_number_template_unique" UNIQUE("phone_number_template"), + CONSTRAINT "phone_number_table_phone_number_prefixes_unique" UNIQUE("phone_number_prefixes") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."phone_number_array_table" ( + "phoneNumber" varchar(256)[], + "phone_number_template" varchar(256)[], + "phone_number_prefixes" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."point_table" ( + "point" "point" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."point_array_table" ( + "point" "point"[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_table" ( + "postcode" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_unique_table" ( + "postcode_unique" varchar(256), + CONSTRAINT "postcode_unique_table_postcode_unique_unique" UNIQUE("postcode_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_array_table" ( + "postcode" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."state_table" ( + "state" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."state_array_table" ( + "state" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_table" ( + "street_address" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_unique_table" ( + "street_address_unique" varchar(256), + CONSTRAINT "street_address_unique_table_street_address_unique_unique" UNIQUE("street_address_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_array_table" ( + "street_address" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_table" ( + "string" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_unique_table" ( + "string_unique" varchar(256), + CONSTRAINT "string_unique_table_string_unique_unique" UNIQUE("string_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_array_table" ( + "string" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."time_table" ( + "time" time + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."time_array_table" ( + "time" time[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."timestamp_table" ( + "timestamp" timestamp + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."timestamp_array_table" ( + "timestamp" timestamp[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_table" ( + "values_from_array_not_null" varchar(256) NOT NULL, + "values_from_array_weighted_not_null" varchar(256) NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_unique_table" ( + "values_from_array" varchar(256), + "values_from_array_not_null" varchar(256) NOT NULL, + "values_from_array_weighted" varchar(256), + "values_from_array_weighted_not_null" varchar(256) NOT NULL, + CONSTRAINT "values_from_array_unique_table_values_from_array_unique" UNIQUE("values_from_array"), + CONSTRAINT "values_from_array_unique_table_values_from_array_not_null_unique" UNIQUE("values_from_array_not_null"), + CONSTRAINT "values_from_array_unique_table_values_from_array_weighted_unique" UNIQUE("values_from_array_weighted"), + CONSTRAINT "values_from_array_unique_table_values_from_array_weighted_not_null_unique" UNIQUE("values_from_array_weighted_not_null") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_array_table" ( + "values_from_array" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."weighted_random_table" ( + "weighted_random" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."weighted_random_with_unique_gens_table" ( + "weighted_random_with_unique_gens" varchar(256), + CONSTRAINT "weighted_random_with_unique_gens_table_weighted_random_with_unique_gens_unique" UNIQUE("weighted_random_with_unique_gens") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."uuid_table" ( + "uuid" uuid + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."uuid_array_table" ( + "uuid" uuid[] + ); + `, + ); +}); + +afterAll(async () => { + await client.close(); +}); + +const count = 1000; + +test('enum generator test', async () => { + await seed(db, { enumTable: schema.enumTable }).refine(() => ({ + enumTable: { + count, + }, + })); + + const data = await db.select().from(schema.enumTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('default generator test', async () => { + await seed(db, { defaultTable: schema.defaultTable }).refine((funcs) => ({ + defaultTable: { + count, + columns: { + defaultString: funcs.default({ defaultValue: 'default string' }), + }, + }, + })); + + const data = await db.select().from(schema.defaultTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('default array generator test', async () => { + await seed(db, { defaultTable: schema.defaultArrayTable }).refine((funcs) => ({ + defaultTable: { + count, + columns: { + defaultString: funcs.default({ defaultValue: 'default string', arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.defaultArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('valuesFromArray generator test', async () => { + await seed(db, { valuesFromArrayTable: schema.valuesFromArrayTable }).refine((funcs) => ({ + valuesFromArrayTable: { + count, + columns: { + valuesFromArrayNotNull: funcs.valuesFromArray({ values: lastNames }), + valuesFromArrayWeightedNotNull: funcs.valuesFromArray({ + values: [ + { values: lastNames, weight: 0.3 }, + { values: firstNames, weight: 0.7 }, + ], + }), + }, + }, + })); + + const data = await db.select().from(schema.valuesFromArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('valuesFromArray unique generator test', async () => { + // valuesFromArrayUniqueTable----------------------------------------------------------------------------------- + await seed(db, { valuesFromArrayUniqueTable: schema.valuesFromArrayUniqueTable }, { seed: 1 }).refine((funcs) => ({ + valuesFromArrayUniqueTable: { + count: 49998, + columns: { + valuesFromArray: funcs.valuesFromArray({ values: lastNames.slice(0, 20), isUnique: true }), + valuesFromArrayNotNull: funcs.valuesFromArray({ values: lastNames, isUnique: true }), + valuesFromArrayWeighted: funcs.valuesFromArray({ + values: [ + { values: lastNames.slice(0, 20000), weight: 0.3 }, + { values: lastNames.slice(20000), weight: 0.7 }, + ], + isUnique: true, + }), + valuesFromArrayWeightedNotNull: funcs.valuesFromArray({ + values: [ + { values: lastNames.slice(0, 14920), weight: 0.3 }, + { values: lastNames.slice(14920), weight: 0.7 }, + ], + isUnique: true, + }), + }, + }, + })); + + const data = await db.select().from(schema.valuesFromArrayUniqueTable); + // console.log(valuesFromArrayUniqueTableData); + const predicate = data.length !== 0 && data.every((row) => + row['valuesFromArrayWeightedNotNull'] !== null + && row['valuesFromArrayNotNull'] !== null + ); + expect(predicate).toBe(true); + + await expect( + seed(db, { valuesFromArrayUniqueTable: schema.valuesFromArrayUniqueTable }).refine((funcs) => ({ + valuesFromArrayUniqueTable: { + count: 49998, + columns: { + valuesFromArrayWeightedNotNull: funcs.valuesFromArray({ + values: [ + { values: lastNames.slice(0, 20000), weight: 0.3 }, + { values: lastNames.slice(20000), weight: 0.7 }, + ], + isUnique: true, + }), + }, + }, + })), + ).rejects.toThrow( + /^weighted values arrays is too small to generate values with specified probability for unique not null column\..+/, + ); + + await expect( + seed(db, { valuesFromArrayUniqueTable: schema.valuesFromArrayUniqueTable }).refine((funcs) => ({ + valuesFromArrayUniqueTable: { + count: 49998, + columns: { + valuesFromArrayNotNull: funcs.valuesFromArray({ + values: lastNames.slice(20), + isUnique: true, + }), + }, + }, + })), + ).rejects.toThrow('There are no enough values to fill unique column.'); + + await expect( + seed(db, { valuesFromArrayUniqueTable: schema.valuesFromArrayUniqueTable }, { seed: 1 }).refine((funcs) => ({ + valuesFromArrayUniqueTable: { + count: 49999, + columns: { + valuesFromArrayNotNull: funcs.valuesFromArray({ + values: lastNames, + isUnique: true, + }), + valuesFromArrayWeightedNotNull: funcs.valuesFromArray({ + values: [ + { values: lastNames.slice(0, 14854), weight: 0.3 }, + { values: lastNames.slice(14854), weight: 0.7 }, + ], + isUnique: true, + }), + }, + }, + })), + ).rejects.toThrow('There are no enough values to fill unique column.'); +}); + +test('valuesFromArray array generator test', async () => { + await seed(db, { valuesFromArrayTable: schema.valuesFromArrayArrayTable }).refine((funcs) => ({ + valuesFromArrayTable: { + count, + columns: { + valuesFromArray: funcs.valuesFromArray({ values: lastNames, arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.valuesFromArrayArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('intPrimaryKey generator test', async () => { + await seed(db, { intPrimaryKeyTable: schema.intPrimaryKeyTable }).refine((funcs) => ({ + intPrimaryKeyTable: { + count, + columns: { + intPrimaryKey: funcs.intPrimaryKey(), + }, + }, + })); + + const data = await db.select().from(schema.intPrimaryKeyTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('number generator test', async () => { + await seed(db, { numberTable: schema.numberTable }).refine((funcs) => ({ + numberTable: { + count, + columns: { + number: funcs.number(), + }, + }, + })); + + const data = await db.select().from(schema.numberTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('number unique generator test', async () => { + // numberUniqueTable----------------------------------------------------------------------------------- + await seed(db, { numberUniqueTable: schema.numberUniqueTable }).refine((funcs) => ({ + numberUniqueTable: { + count: 20070, + columns: { + numberUnique: funcs.number({ isUnique: true, minValue: -100.23, maxValue: 100.46 }), + }, + }, + })); + + const data = await db.select().from(schema.numberUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val >= -100.23 && val <= 100.46) + ); + expect(predicate).toBe(true); + + await expect( + seed(db, { numberUniqueTable: schema.numberUniqueTable }).refine((funcs) => ({ + numberUniqueTable: { + count: 20071, + columns: { + numberUnique: funcs.number({ isUnique: true, minValue: -100.23, maxValue: 100.46 }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique integers in given range(min, max), try to make range wider.'); +}); + +test('number array generator test', async () => { + await seed(db, { numberTable: schema.numberArrayTable }).refine((funcs) => ({ + numberTable: { + count, + columns: { + number: funcs.number({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.numberArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('int generator test', async () => { + await seed(db, { intTable: schema.intTable }).refine((funcs) => ({ + intTable: { + count, + columns: { + int: funcs.int(), + }, + }, + })); + + const data = await db.select().from(schema.intTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('int unique generator test', async () => { + // intUniqueTable----------------------------------------------------------------------------------- + await seed(db, { intUniqueTable: schema.intUniqueTable }).refine((funcs) => ({ + intUniqueTable: { + count: 201, + columns: { + intUnique: funcs.int({ isUnique: true, minValue: -100, maxValue: 100 }), + }, + }, + })); + + const data = await db.select().from(schema.intUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { intUniqueTable: schema.intUniqueTable }).refine((funcs) => ({ + intUniqueTable: { + count: 202, + columns: { + intUnique: funcs.int({ isUnique: true, minValue: -100, maxValue: 100 }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique integers in given range(min, max), try to make range wider.'); +}); + +test('int array generator test', async () => { + await seed(db, { intTable: schema.intArrayTable }).refine((funcs) => ({ + intTable: { + count, + columns: { + int: funcs.int({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.intArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('boolean generator test', async () => { + await seed(db, { booleanTable: schema.booleanTable }).refine((funcs) => ({ + booleanTable: { + count, + columns: { + boolean: funcs.boolean(), + }, + }, + })); + + const data = await db.select().from(schema.booleanTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('boolean array generator test', async () => { + await seed(db, { booleanTable: schema.booleanArrayTable }).refine((funcs) => ({ + booleanTable: { + count, + columns: { + boolean: funcs.boolean({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.booleanArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('date generator test', async () => { + await seed(db, { dateTable: schema.dateTable }).refine((funcs) => ({ + dateTable: { + count, + columns: { + date: funcs.date(), + }, + }, + })); + + const data = await db.select().from(schema.dateTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('date array generator test', async () => { + await seed(db, { dateTable: schema.dateArrayTable }).refine((funcs) => ({ + dateTable: { + count, + columns: { + date: funcs.date({ arraySize: 3 }), + dateString: funcs.date({ arraySize: 4 }), + }, + }, + })); + + const data = await db.select().from(schema.dateArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && [3, 4].includes(val.length)) + ); + expect(predicate).toBe(true); +}); + +test('time generator test', async () => { + await seed(db, { timeTable: schema.timeTable }).refine((funcs) => ({ + timeTable: { + count, + columns: { + time: funcs.time(), + }, + }, + })); + + const data = await db.select().from(schema.timeTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('time array generator test', async () => { + await seed(db, { timeTable: schema.timeArrayTable }).refine((funcs) => ({ + timeTable: { + count, + columns: { + time: funcs.time({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.timeArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('timestamp generator test', async () => { + await seed(db, { timestampTable: schema.timestampTable }).refine((funcs) => ({ + timestampTable: { + count, + columns: { + timestamp: funcs.timestamp(), + }, + }, + })); + + const data = await db.select().from(schema.timestampTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('timestamp array generator test', async () => { + await seed(db, { timestampTable: schema.timestampArrayTable }).refine((funcs) => ({ + timestampTable: { + count, + columns: { + timestamp: funcs.timestamp({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.timestampArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('json generator test', async () => { + await seed(db, { jsonTable: schema.jsonTable }).refine((funcs) => ({ + jsonTable: { + count, + columns: { + json: funcs.json(), + }, + }, + })); + + const data = await db.select().from(schema.jsonTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('json array generator test', async () => { + await seed(db, { jsonTable: schema.jsonArrayTable }).refine((funcs) => ({ + jsonTable: { + count, + columns: { + json: funcs.json({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.jsonArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('interval generator test', async () => { + await seed(db, { intervalTable: schema.intervalTable }).refine((funcs) => ({ + intervalTable: { + count, + columns: { + interval: funcs.interval(), + }, + }, + })); + + const data = await db.select().from(schema.intervalTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('interval unique generator test', async () => { + // intervalUniqueTable----------------------------------------------------------------------------------- + await seed(db, { intervalUniqueTable: schema.intervalUniqueTable }).refine((funcs) => ({ + intervalUniqueTable: { + count, + columns: { + intervalUnique: funcs.interval({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.intervalUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('interval array generator test', async () => { + await seed(db, { intervalTable: schema.intervalArrayTable }).refine((funcs) => ({ + intervalTable: { + count, + columns: { + interval: funcs.interval({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.intervalArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('string generator test', async () => { + await seed(db, { stringTable: schema.stringTable }).refine((funcs) => ({ + stringTable: { + count, + columns: { + string: funcs.string(), + }, + }, + })); + + const data = await db.select().from(schema.stringTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('string unique generator test', async () => { + await seed(db, { stringUniqueTable: schema.stringUniqueTable }).refine((funcs) => ({ + stringUniqueTable: { + count, + columns: { + stringUnique: funcs.string({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.stringUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('string array generator test', async () => { + await seed(db, { stringTable: schema.stringArrayTable }).refine((funcs) => ({ + stringTable: { + count, + columns: { + string: funcs.string({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.stringArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('email generator test', async () => { + await seed(db, { emailTable: schema.emailTable }).refine((funcs) => ({ + emailTable: { + count, + columns: { + email: funcs.email(), + }, + }, + })); + + const data = await db.select().from(schema.emailTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('email array generator test', async () => { + await seed(db, { emailTable: schema.emailArrayTable }).refine((funcs) => ({ + emailTable: { + count, + columns: { + email: funcs.email({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.emailArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('firstName generator test', async () => { + await seed(db, { firstNameTable: schema.firstNameTable }).refine((funcs) => ({ + firstNameTable: { + count, + columns: { + firstName: funcs.firstName(), + }, + }, + })); + + const data = await db.select().from(schema.firstNameTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('firstName unique generator test', async () => { + // firstNameUniqueTable----------------------------------------------------------------------------------- + await seed(db, { firstNameUniqueTable: schema.firstNameUniqueTable }).refine((funcs) => ({ + firstNameUniqueTable: { + count: 30274, + columns: { + firstNameUnique: funcs.firstName({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.firstNameUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { firstNameUniqueTable: schema.firstNameUniqueTable }, { count: 30275 }).refine((funcs) => ({ + firstNameUniqueTable: { + count: 30275, + columns: { + firstNameUnique: funcs.firstName({ isUnique: true }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique first names.'); +}); + +test('firstName array generator test', async () => { + await seed(db, { firstNameTable: schema.firstNameArrayTable }).refine((funcs) => ({ + firstNameTable: { + count, + columns: { + firstName: funcs.firstName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.firstNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('lastName generator test', async () => { + await seed(db, { lastNameTable: schema.lastNameTable }).refine((funcs) => ({ + lastNameTable: { + count, + columns: { + lastName: funcs.lastName(), + }, + }, + })); + + const data = await db.select().from(schema.lastNameTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('lastName unique generator test', async () => { + // lastNameUniqueTable----------------------------------------------------------------------------------- + await seed(db, { lastNameUniqueTable: schema.lastNameUniqueTable }).refine((funcs) => ({ + lastNameUniqueTable: { + count: 49998, + columns: { + lastNameUnique: funcs.lastName({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.lastNameUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { lastNameUniqueTable: schema.lastNameUniqueTable }).refine((funcs) => ({ + lastNameUniqueTable: { + count: 49999, + columns: { + lastNameUnique: funcs.lastName({ isUnique: true }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique last names.'); +}); + +test('lastName array generator test', async () => { + await seed(db, { lastNameTable: schema.lastNameArrayTable }).refine((funcs) => ({ + lastNameTable: { + count, + columns: { + lastName: funcs.lastName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.lastNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('fullName generator test', async () => { + await seed(db, { fullNameTable: schema.fullNameTable }).refine((funcs) => ({ + fullNameTable: { + count, + columns: { + fullName: funcs.fullName(), + }, + }, + })); + + const data = await db.select().from(schema.fullNameTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('fullName unique generator test', async () => { + // fullNameUniqueTable----------------------------------------------------------------------------------- + await seed(db, { fullNameUniqueTable: schema.fullNameUniqueTable }).refine((funcs) => ({ + fullNameUniqueTable: { + count, + columns: { + fullNameUnique: funcs.fullName({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.fullNameUniqueTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('fullName array generator test', async () => { + await seed(db, { fullNameTable: schema.fullNameArrayTable }).refine((funcs) => ({ + fullNameTable: { + count, + columns: { + fullName: funcs.fullName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.fullNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('country generator test', async () => { + await seed(db, { countryTable: schema.countryTable }).refine((funcs) => ({ + countryTable: { + count, + columns: { + country: funcs.country(), + }, + }, + })); + + const data = await db.select().from(schema.countryTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('country unique generator test', async () => { + // countryUniqueTable----------------------------------------------------------------------------------- + await seed(db, { countryUniqueTable: schema.countryUniqueTable }).refine((funcs) => ({ + countryUniqueTable: { + count: countries.length, + columns: { + countryUnique: funcs.country({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.countryUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { countryUniqueTable: schema.countryUniqueTable }).refine((funcs) => ({ + countryUniqueTable: { + count: countries.length + 1, + columns: { + countryUnique: funcs.country({ isUnique: true }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique countries.'); +}); + +test('country array generator test', async () => { + await seed(db, { countryTable: schema.countryArrayTable }).refine((funcs) => ({ + countryTable: { + count, + columns: { + country: funcs.country({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.countryArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('city generator test', async () => { + await seed(db, { cityTable: schema.cityTable }).refine((funcs) => ({ + cityTable: { + count, + columns: { + city: funcs.city(), + }, + }, + })); + + const data = await db.select().from(schema.cityTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('city unique generator test', async () => { + // cityUniqueTable----------------------------------------------------------------------------------- + await reset(db, { cityUniqueTable: schema.cityUniqueTable }); + await seed(db, { cityUniqueTable: schema.cityUniqueTable }).refine((funcs) => ({ + cityUniqueTable: { + count: cities.length, + columns: { + cityUnique: funcs.city({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.cityUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { cityUniqueTable: schema.cityUniqueTable }).refine((funcs) => ({ + cityUniqueTable: { + count: cities.length + 1, + columns: { + cityUnique: funcs.city({ isUnique: true }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique cities.'); +}); + +test('city array generator test', async () => { + await seed(db, { cityTable: schema.cityArrayTable }).refine((funcs) => ({ + cityTable: { + count, + columns: { + city: funcs.city({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.cityArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('streetAddress generator test', async () => { + await seed(db, { streetAddressTable: schema.streetAddressTable }).refine((funcs) => ({ + streetAddressTable: { + count, + columns: { + streetAddress: funcs.streetAddress(), + }, + }, + })); + + const data = await db.select().from(schema.streetAddressTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('streetAddress unique generator test', async () => { + await seed(db, { streetAddressUniqueTable: schema.streetAddressUniqueTable }).refine((funcs) => ({ + streetAddressUniqueTable: { + count, + columns: { + streetAddressUnique: funcs.streetAddress({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.streetAddressUniqueTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('streetAddress array generator test', async () => { + await seed(db, { streetAddressTable: schema.streetAddressArrayTable }).refine((funcs) => ({ + streetAddressTable: { + count, + columns: { + streetAddress: funcs.streetAddress({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.streetAddressArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('jobTitle generator test', async () => { + await seed(db, { jobTitleTable: schema.jobTitleTable }).refine((funcs) => ({ + jobTitleTable: { + count, + columns: { + jobTitle: funcs.jobTitle(), + }, + }, + })); + + const data = await db.select().from(schema.jobTitleTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('jobTitle array generator test', async () => { + await seed(db, { jobTitleTable: schema.jobTitleArrayTable }).refine((funcs) => ({ + jobTitleTable: { + count, + columns: { + jobTitle: funcs.jobTitle({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.jobTitleArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('postcode generator test', async () => { + await seed(db, { postcodeTable: schema.postcodeTable }).refine((funcs) => ({ + postcodeTable: { + count, + columns: { + postcode: funcs.postcode(), + }, + }, + })); + + const data = await db.select().from(schema.postcodeTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('postcode unique generator test', async () => { + await seed(db, { postcodeUniqueTable: schema.postcodeUniqueTable }).refine((funcs) => ({ + postcodeUniqueTable: { + count, + columns: { + postcodeUnique: funcs.postcode({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.postcodeUniqueTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('postcode array generator test', async () => { + await seed(db, { postcodeTable: schema.postcodeArrayTable }).refine((funcs) => ({ + postcodeTable: { + count, + columns: { + postcode: funcs.postcode({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.postcodeArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('state generator test', async () => { + await seed(db, { stateTable: schema.stateTable }).refine((funcs) => ({ + stateTable: { + count, + columns: { + state: funcs.state(), + }, + }, + })); + + const data = await db.select().from(schema.stateTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('state array generator test', async () => { + await seed(db, { stateTable: schema.stateArrayTable }).refine((funcs) => ({ + stateTable: { + count, + columns: { + state: funcs.state({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.stateArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('companyName generator test', async () => { + await seed(db, { companyNameTable: schema.companyNameTable }).refine((funcs) => ({ + companyNameTable: { + count, + columns: { + companyName: funcs.companyName(), + }, + }, + })); + + const data = await db.select().from(schema.companyNameTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('companyName unique generator test', async () => { + await seed(db, { companyNameUniqueTable: schema.companyNameUniqueTable }).refine((funcs) => ({ + companyNameUniqueTable: { + count, + columns: { + companyNameUnique: funcs.companyName({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.companyNameUniqueTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('companyName array generator test', async () => { + await seed(db, { companyNameTable: schema.companyNameArrayTable }).refine((funcs) => ({ + companyNameTable: { + count, + columns: { + companyName: funcs.companyName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.companyNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('loremIpsum generator test', async () => { + await seed(db, { loremIpsumTable: schema.loremIpsumTable }).refine((funcs) => ({ + loremIpsumTable: { + count, + columns: { + loremIpsum: funcs.loremIpsum(), + }, + }, + })); + + const data = await db.select().from(schema.loremIpsumTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('loremIpsum array generator test', async () => { + await seed(db, { loremIpsumTable: schema.loremIpsumArrayTable }).refine((funcs) => ({ + loremIpsumTable: { + count, + columns: { + loremIpsum: funcs.loremIpsum({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.loremIpsumArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('point generator test', async () => { + await seed(db, { pointTable: schema.pointTable }).refine((funcs) => ({ + pointTable: { + count, + columns: { + point: funcs.point(), + }, + }, + })); + + const data = await db.select().from(schema.pointTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('point unique generator test', async () => { + await reset(db, { pointTable: schema.pointTable }); + await seed(db, { pointTable: schema.pointTable }).refine((funcs) => ({ + pointTable: { + count, + columns: { + point: funcs.point({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.pointTable); + // every value in each row does not equal undefined. + let predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + // using Set because PGlite does not support unique point + const pointStrsSet = new Set(data.map((row) => row.point!.map(String).join(','))); + predicate = pointStrsSet.size === data.length; + expect(predicate).toBe(true); +}); + +test('point array generator test', async () => { + await seed(db, { pointTable: schema.pointArrayTable }).refine((funcs) => ({ + pointTable: { + count, + columns: { + point: funcs.point({ arraySize: 2 }), + }, + }, + })); + + const data = await db.select().from(schema.pointArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 2)); + expect(predicate).toBe(true); +}); + +test('line generator test', async () => { + await seed(db, { lineTable: schema.lineTable }).refine((funcs) => ({ + lineTable: { + count, + columns: { + line: funcs.line(), + }, + }, + })); + + const data = await db.select().from(schema.lineTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('line unique generator test', async () => { + await reset(db, { lineTable: schema.lineTable }); + await seed(db, { lineTable: schema.lineTable }).refine((funcs) => ({ + lineTable: { + count, + columns: { + line: funcs.line({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.lineTable); + // every value in each row does not equal undefined. + let predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + // using Set because PGlite does not support unique point + const lineStrsSet = new Set(data.map((row) => row.line!.map(String).join(','))); + predicate = lineStrsSet.size === data.length; + expect(predicate).toBe(true); +}); + +test('line array generator test', async () => { + await seed(db, { lineTable: schema.lineArrayTable }).refine((funcs) => ({ + lineTable: { + count, + columns: { + line: funcs.line({ arraySize: 2 }), + }, + }, + })); + + const data = await db.select().from(schema.lineArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 2)); + expect(predicate).toBe(true); +}); + +test('phoneNumber generator test', async () => { + await seed(db, { phoneNumberTable: schema.phoneNumberTable }).refine((funcs) => ({ + phoneNumberTable: { + count, + columns: { + phoneNumber: funcs.phoneNumber(), + phoneNumberPrefixes: funcs.phoneNumber({ + prefixes: ['+380 99', '+380 67', '+1'], + generatedDigitsNumbers: [7, 7, 10], + }), + phoneNumberTemplate: funcs.phoneNumber({ template: '+380 ## ## ### ##' }), + }, + }, + })); + + const data = await db.select().from(schema.phoneNumberTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('phoneNumber array generator test', async () => { + await seed(db, { phoneNumberTable: schema.phoneNumberArrayTable }).refine((funcs) => ({ + phoneNumberTable: { + count, + columns: { + phoneNumber: funcs.phoneNumber({ arraySize: 3 }), + phoneNumberPrefixes: funcs.phoneNumber({ + prefixes: ['+380 99', '+380 67', '+1'], + generatedDigitsNumbers: [7, 7, 10], + arraySize: 4, + }), + phoneNumberTemplate: funcs.phoneNumber({ + template: '+380 ## ## ### ##', + arraySize: 5, + }), + }, + }, + })); + + const data = await db.select().from(schema.phoneNumberArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && [3, 4, 5].includes(val.length)) + ); + expect(predicate).toBe(true); +}); + +test('weightedRandom generator test', async () => { + await seed(db, { weightedRandomTable: schema.weightedRandomTable }).refine((funcs) => ({ + weightedRandomTable: { + count, + columns: { + weightedRandom: funcs.weightedRandom([ + { value: funcs.default({ defaultValue: 'default value' }), weight: 0.3 }, + { value: funcs.loremIpsum(), weight: 0.7 }, + ]), + }, + }, + })); + + const data = await db.select().from(schema.weightedRandomTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('weightedRandom with unique gens generator test', async () => { + await seed(db, { weightedRandomWithUniqueGensTable: schema.weightedRandomWithUniqueGensTable }).refine((funcs) => ({ + weightedRandomWithUniqueGensTable: { + count: 10000, + columns: { + weightedRandomWithUniqueGens: funcs.weightedRandom([ + { weight: 0.3, value: funcs.email() }, + { weight: 0.7, value: funcs.firstName({ isUnique: true }) }, + ]), + }, + }, + })); + + const data = await db.select().from(schema.weightedRandomWithUniqueGensTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { weightedRandomWithUniqueGensTable: schema.weightedRandomWithUniqueGensTable }).refine((funcs) => ({ + weightedRandomWithUniqueGensTable: { + count: 40000, + columns: { + weightedRandomWithUniqueGens: funcs.weightedRandom([ + { weight: 0.1, value: funcs.email() }, + { weight: 0.9, value: funcs.firstName({ isUnique: true }) }, + ]), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique first names.'); + + await expect( + seed(db, { weightedRandomWithUniqueGensTable: schema.weightedRandomWithUniqueGensTable }).refine((funcs) => ({ + weightedRandomWithUniqueGensTable: { + count: 10000, + columns: { + weightedRandomWithUniqueGens: funcs.weightedRandom([ + { weight: 0.2, value: funcs.email() }, + { weight: 0.9, value: funcs.firstName({ isUnique: true }) }, + ]), + }, + }, + })), + ).rejects.toThrow( + 'The weights for the Weighted Random feature must add up to exactly 1. Please review your weights to ensure they total 1 before proceeding', + ); +}); + +test('uuid generator test', async () => { + await reset(db, { uuidTable: schema.uuidTable }); + await seed(db, { uuidTable: schema.uuidTable }).refine((funcs) => ({ + uuidTable: { + count, + columns: { + uuid: funcs.uuid(), + }, + }, + })); + + const data = await db.select().from(schema.uuidTable); + // every value in each row does not equal undefined. + let predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + const uuidStrsSet = new Set(data.map((row) => row.uuid!)); + predicate = uuidStrsSet.size === data.length; + expect(predicate).toBe(true); +}); + +test('uuid array generator test', async () => { + await reset(db, { uuidArrayTable: schema.uuidArrayTable }); + await seed(db, { uuidArrayTable: schema.uuidArrayTable }).refine((funcs) => ({ + uuidArrayTable: { + count, + columns: { + uuid: funcs.uuid({ arraySize: 4 }), + }, + }, + })); + + const data = await db.select().from(schema.uuidArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/pg/generatorsTest/pgSchema.ts b/drizzle-seed/tests/pg/generatorsTest/pgSchema.ts new file mode 100644 index 000000000..48902ac6e --- /dev/null +++ b/drizzle-seed/tests/pg/generatorsTest/pgSchema.ts @@ -0,0 +1,322 @@ +import { + boolean, + date, + integer, + interval, + json, + line, + pgSchema, + point, + real, + text, + time, + timestamp, + uuid, + varchar, +} from 'drizzle-orm/pg-core'; + +export const schema = pgSchema('seeder_lib_pg'); + +export const moodEnum = schema.enum('enum', ['sad', 'ok', 'happy']); + +export const enumTable = schema.table('enum_table', { + mood: moodEnum('mood_enum'), +}); + +export const defaultTable = schema.table('default_table', { + defaultString: text('default_string'), +}); + +export const defaultArrayTable = schema.table('default_array_table', { + defaultString: text('default_string').array(), +}); + +export const valuesFromArrayTable = schema.table('values_from_array_table', { + valuesFromArrayNotNull: varchar('values_from_array_not_null', { length: 256 }).notNull(), + valuesFromArrayWeightedNotNull: varchar('values_from_array_weighted_not_null', { length: 256 }).notNull(), +}); + +export const valuesFromArrayUniqueTable = schema.table('values_from_array_unique_table', { + valuesFromArray: varchar('values_from_array', { length: 256 }).unique(), + valuesFromArrayNotNull: varchar('values_from_array_not_null', { length: 256 }).unique().notNull(), + valuesFromArrayWeighted: varchar('values_from_array_weighted', { length: 256 }).unique(), + valuesFromArrayWeightedNotNull: varchar('values_from_array_weighted_not_null', { length: 256 }).unique().notNull(), +}); + +export const valuesFromArrayArrayTable = schema.table('values_from_array_array_table', { + valuesFromArray: varchar('values_from_array', { length: 256 }).array(), +}); + +export const intPrimaryKeyTable = schema.table('int_primary_key_table', { + intPrimaryKey: integer('int_primary_key').unique(), +}); + +export const numberTable = schema.table('number_table', { + number: real('number'), +}); + +export const numberUniqueTable = schema.table('number_unique_table', { + numberUnique: real('number_unique').unique(), +}); + +export const numberArrayTable = schema.table('number_array_table', { + number: real('number').array(), +}); + +export const intTable = schema.table('int_table', { + int: integer('int'), +}); + +export const intUniqueTable = schema.table('int_unique_table', { + intUnique: integer('int_unique').unique(), +}); + +export const intArrayTable = schema.table('int_array_table', { + int: integer('int').array(), +}); + +export const booleanTable = schema.table('boolean_table', { + boolean: boolean('boolean'), +}); + +export const booleanArrayTable = schema.table('boolean_array_table', { + boolean: boolean('boolean').array(), +}); + +export const dateTable = schema.table('date_table', { + date: date('date'), +}); + +// TODO: add tests for data type with different modes +export const dateArrayTable = schema.table('date_array_table', { + date: date('date', { mode: 'date' }).array(), + dateString: date('date_string', { mode: 'string' }).array(), +}); + +export const timeTable = schema.table('time_table', { + time: time('time'), +}); + +export const timeArrayTable = schema.table('time_array_table', { + time: time('time').array(), +}); + +export const timestampTable = schema.table('timestamp_table', { + timestamp: timestamp('timestamp'), +}); + +export const timestampArrayTable = schema.table('timestamp_array_table', { + timestamp: timestamp('timestamp').array(), +}); + +export const jsonTable = schema.table('json_table', { + json: json('json'), +}); + +export const jsonArrayTable = schema.table('json_array_table', { + json: json('json').array(), +}); + +export const intervalTable = schema.table('interval_table', { + interval: interval('interval'), +}); + +export const intervalUniqueTable = schema.table('interval_unique_table', { + intervalUnique: interval('interval_unique').unique(), +}); + +export const intervalArrayTable = schema.table('interval_array_table', { + interval: interval('interval').array(), +}); + +export const stringTable = schema.table('string_table', { + string: text('string'), +}); + +export const stringUniqueTable = schema.table('string_unique_table', { + stringUnique: varchar('string_unique', { length: 256 }).unique(), +}); + +export const stringArrayTable = schema.table('string_array_table', { + string: text('string').array(), +}); + +export const emailTable = schema.table('email_table', { + email: varchar('email', { length: 256 }).unique(), +}); + +export const emailArrayTable = schema.table('email_array_table', { + email: varchar('email', { length: 256 }).array(), +}); + +export const firstNameTable = schema.table('first_name_table', { + firstName: varchar('first_name', { length: 256 }), +}); + +export const firstNameUniqueTable = schema.table('first_name_unique_table', { + firstNameUnique: varchar('first_name_unique', { length: 256 }).unique(), +}); + +export const firstNameArrayTable = schema.table('first_name_array_table', { + firstName: varchar('first_name', { length: 256 }).array(), +}); + +export const lastNameTable = schema.table('last_name_table', { + lastName: varchar('last_name', { length: 256 }), +}); + +export const lastNameUniqueTable = schema.table('last_name_unique_table', { + lastNameUnique: varchar('last_name_unique', { length: 256 }).unique(), +}); + +export const lastNameArrayTable = schema.table('last_name_array_table', { + lastName: varchar('last_name', { length: 256 }).array(), +}); + +export const fullNameTable = schema.table('full_name__table', { + fullName: varchar('full_name_', { length: 256 }), +}); + +export const fullNameUniqueTable = schema.table('full_name_unique_table', { + fullNameUnique: varchar('full_name_unique', { length: 256 }).unique(), +}); + +export const fullNameArrayTable = schema.table('full_name_array_table', { + fullName: varchar('full_name', { length: 256 }).array(), +}); + +export const countryTable = schema.table('country_table', { + country: varchar('country', { length: 256 }), +}); + +export const countryUniqueTable = schema.table('country_unique_table', { + countryUnique: varchar('country_unique', { length: 256 }).unique(), +}); + +export const countryArrayTable = schema.table('country_array_table', { + country: varchar('country', { length: 256 }).array(), +}); + +export const cityTable = schema.table('city_table', { + city: varchar('city', { length: 256 }), +}); + +export const cityUniqueTable = schema.table('city_unique_table', { + cityUnique: varchar('city_unique', { length: 256 }).unique(), +}); + +export const cityArrayTable = schema.table('city_array_table', { + city: varchar('city', { length: 256 }).array(), +}); + +export const streetAddressTable = schema.table('street_address_table', { + streetAddress: varchar('street_address', { length: 256 }), +}); + +export const streetAddressUniqueTable = schema.table('street_address_unique_table', { + streetAddressUnique: varchar('street_address_unique', { length: 256 }).unique(), +}); + +export const streetAddressArrayTable = schema.table('street_address_array_table', { + streetAddress: varchar('street_address', { length: 256 }).array(), +}); + +export const jobTitleTable = schema.table('job_Title_table', { + jobTitle: text('job_title'), +}); + +export const jobTitleArrayTable = schema.table('job_title_array_table', { + jobTitle: text('job_title').array(), +}); + +export const postcodeTable = schema.table('postcode_table', { + postcode: varchar('postcode', { length: 256 }), +}); + +export const postcodeUniqueTable = schema.table('postcode_unique_table', { + postcodeUnique: varchar('postcode_unique', { length: 256 }).unique(), +}); + +export const postcodeArrayTable = schema.table('postcode_array_table', { + postcode: varchar('postcode', { length: 256 }).array(), +}); + +export const stateTable = schema.table('state_table', { + state: text('state'), +}); + +export const stateArrayTable = schema.table('state_array_table', { + state: text('state').array(), +}); + +export const companyNameTable = schema.table('company_name_table', { + companyName: text('company_name'), +}); + +export const companyNameUniqueTable = schema.table('company_name_unique_table', { + companyNameUnique: varchar('company_name_unique', { length: 256 }).unique(), +}); + +export const companyNameArrayTable = schema.table('company_name_array_table', { + companyName: text('company_name').array(), +}); + +export const loremIpsumTable = schema.table('lorem_ipsum_table', { + loremIpsum: text('lorem_ipsum'), +}); + +export const loremIpsumArrayTable = schema.table('lorem_ipsum_array_table', { + loremIpsum: text('lorem_ipsum').array(), +}); + +export const pointTable = schema.table('point_table', { + point: point('point'), +}); + +export const pointArrayTable = schema.table('point_array_table', { + point: point('point').array(), +}); + +export const lineTable = schema.table('line_table', { + line: line('line'), +}); + +export const lineArrayTable = schema.table('line_array_table', { + line: line('line').array(), +}); + +// export const pointUniqueTable = schema.table("point_unique_table", { +// pointUnique: point("point_unique").unique(), +// }); + +// export const lineUniqueTable = schema.table("line_unique_table", { +// lineUnique: line("line_unique").unique(), +// }); + +export const phoneNumberTable = schema.table('phone_number_table', { + phoneNumber: varchar('phoneNumber', { length: 256 }).unique(), + phoneNumberTemplate: varchar('phone_number_template', { length: 256 }).unique(), + phoneNumberPrefixes: varchar('phone_number_prefixes', { length: 256 }).unique(), +}); + +export const phoneNumberArrayTable = schema.table('phone_number_array_table', { + phoneNumber: varchar('phoneNumber', { length: 256 }).array(), + phoneNumberTemplate: varchar('phone_number_template', { length: 256 }).array(), + phoneNumberPrefixes: varchar('phone_number_prefixes', { length: 256 }).array(), +}); + +export const weightedRandomTable = schema.table('weighted_random_table', { + weightedRandom: varchar('weighted_random', { length: 256 }), +}); + +export const weightedRandomWithUniqueGensTable = schema.table('weighted_random_with_unique_gens_table', { + weightedRandomWithUniqueGens: varchar('weighted_random_with_unique_gens', { length: 256 }).unique(), +}); + +export const uuidTable = schema.table('uuid_table', { + uuid: uuid('uuid'), +}); + +export const uuidArrayTable = schema.table('uuid_array_table', { + uuid: uuid('uuid').array(), +}); diff --git a/drizzle-seed/tests/pg/pg.test.ts b/drizzle-seed/tests/pg/pg.test.ts new file mode 100644 index 000000000..90d6b4fc2 --- /dev/null +++ b/drizzle-seed/tests/pg/pg.test.ts @@ -0,0 +1,395 @@ +import { PGlite } from '@electric-sql/pglite'; +import { sql } from 'drizzle-orm'; +import type { PgliteDatabase } from 'drizzle-orm/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../src/index.ts'; +import * as schema from './pgSchema.ts'; + +let client: PGlite; +let db: PgliteDatabase; + +beforeAll(async () => { + client = new PGlite(); + + db = drizzle(client); + + await db.execute(sql`CREATE SCHEMA "seeder_lib_pg";`); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."customer" ( + "id" varchar(256) PRIMARY KEY NOT NULL, + "company_name" text NOT NULL, + "contact_name" text NOT NULL, + "contact_title" text NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "postal_code" text, + "region" text, + "country" text NOT NULL, + "phone" text NOT NULL, + "fax" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."order_detail" ( + "unit_price" numeric NOT NULL, + "quantity" integer NOT NULL, + "discount" numeric NOT NULL, + "order_id" integer NOT NULL, + "product_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."employee" ( + "id" integer PRIMARY KEY NOT NULL, + "last_name" text NOT NULL, + "first_name" text, + "title" text NOT NULL, + "title_of_courtesy" text NOT NULL, + "birth_date" timestamp NOT NULL, + "hire_date" timestamp NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "postal_code" text NOT NULL, + "country" text NOT NULL, + "home_phone" text NOT NULL, + "extension" integer NOT NULL, + "notes" text NOT NULL, + "reports_to" integer, + "photo_path" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."order" ( + "id" integer PRIMARY KEY NOT NULL, + "order_date" timestamp NOT NULL, + "required_date" timestamp NOT NULL, + "shipped_date" timestamp, + "ship_via" integer NOT NULL, + "freight" numeric NOT NULL, + "ship_name" text NOT NULL, + "ship_city" text NOT NULL, + "ship_region" text, + "ship_postal_code" text, + "ship_country" text NOT NULL, + "customer_id" text NOT NULL, + "employee_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."product" ( + "id" integer PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "quantity_per_unit" text NOT NULL, + "unit_price" numeric NOT NULL, + "units_in_stock" integer NOT NULL, + "units_on_order" integer NOT NULL, + "reorder_level" integer NOT NULL, + "discontinued" integer NOT NULL, + "supplier_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."supplier" ( + "id" integer PRIMARY KEY NOT NULL, + "company_name" text NOT NULL, + "contact_name" text NOT NULL, + "contact_title" text NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "region" text, + "postal_code" text NOT NULL, + "country" text NOT NULL, + "phone" text NOT NULL + ); + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."order_detail" ADD CONSTRAINT "order_detail_order_id_order_id_fk" FOREIGN KEY ("order_id") REFERENCES "seeder_lib_pg"."order"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."order_detail" ADD CONSTRAINT "order_detail_product_id_product_id_fk" FOREIGN KEY ("product_id") REFERENCES "seeder_lib_pg"."product"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."employee" ADD CONSTRAINT "employee_reports_to_employee_id_fk" FOREIGN KEY ("reports_to") REFERENCES "seeder_lib_pg"."employee"("id") ON DELETE no action ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."order" ADD CONSTRAINT "order_customer_id_customer_id_fk" FOREIGN KEY ("customer_id") REFERENCES "seeder_lib_pg"."customer"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."order" ADD CONSTRAINT "order_employee_id_employee_id_fk" FOREIGN KEY ("employee_id") REFERENCES "seeder_lib_pg"."employee"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."product" ADD CONSTRAINT "product_supplier_id_supplier_id_fk" FOREIGN KEY ("supplier_id") REFERENCES "seeder_lib_pg"."supplier"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."identity_columns_table" ( + "id" integer generated always as identity, + "id1" integer generated by default as identity, + "name" text + ); + `, + ); + + await db.execute( + sql` + create table "seeder_lib_pg"."user" + ( + id serial + primary key, + name text, + "invitedBy" integer + constraint "user_invitedBy_user_id_fk" + references "seeder_lib_pg"."user" + ); + `, + ); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +afterAll(async () => { + await client.close(); +}); + +test('basic seed test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); +}); + +test('seed with options.count:11 test', async () => { + await seed(db, schema, { count: 11 }); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(11); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) customers count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) all tables count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + details: { + count: 13, + }, + employees: { + count: 14, + }, + orders: { + count: 15, + }, + products: { + count: 16, + }, + suppliers: { + count: 17, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(13); + expect(employees.length).toBe(14); + expect(orders.length).toBe(15); + expect(products.length).toBe(16); + expect(suppliers.length).toBe(17); +}); + +test("redefine(refine) orders count using 'with' in customers", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test("sequential using of 'with'", async () => { + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('seeding with identity columns', async () => { + await seed(db, { identityColumnsTable: schema.identityColumnsTable }); + + const result = await db.select().from(schema.identityColumnsTable); + + expect(result.length).toBe(10); +}); + +test('seeding with self relation', async () => { + await seed(db, { user: schema.user }); + + const result = await db.select().from(schema.user); + + expect(result.length).toBe(10); + const predicate = result.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/pg/pgSchema.ts b/drizzle-seed/tests/pg/pgSchema.ts new file mode 100644 index 000000000..1a9af755e --- /dev/null +++ b/drizzle-seed/tests/pg/pgSchema.ts @@ -0,0 +1,119 @@ +import type { AnyPgColumn } from 'drizzle-orm/pg-core'; +import { integer, numeric, pgSchema, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core'; + +export const schema = pgSchema('seeder_lib_pg'); + +export const customers = schema.table('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = schema.table( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to').references((): AnyPgColumn => employees.id), + photoPath: text('photo_path'), + }, +); + +export const orders = schema.table('order', { + id: integer('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id') + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: integer('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = schema.table('supplier', { + id: integer('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = schema.table('product', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = schema.table('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: integer('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); + +export const identityColumnsTable = schema.table('identity_columns_table', { + id: integer().generatedAlwaysAsIdentity(), + id1: integer().generatedByDefaultAsIdentity(), + name: text(), +}); + +export const user = schema.table( + 'user', + { + id: serial().primaryKey(), + name: text(), + invitedBy: integer().references((): AnyPgColumn => user.id), + }, +); diff --git a/drizzle-seed/tests/pg/softRelationsTest/pgSchema.ts b/drizzle-seed/tests/pg/softRelationsTest/pgSchema.ts new file mode 100644 index 000000000..357ea23cf --- /dev/null +++ b/drizzle-seed/tests/pg/softRelationsTest/pgSchema.ts @@ -0,0 +1,130 @@ +import { relations } from 'drizzle-orm'; +import { integer, numeric, pgSchema, text, timestamp, varchar } from 'drizzle-orm/pg-core'; + +export const schema = pgSchema('seeder_lib_pg'); + +export const customers = schema.table('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = schema.table( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to'), + photoPath: text('photo_path'), + }, +); + +export const employeesRelations = relations(employees, ({ one }) => ({ + employee: one(employees, { + fields: [employees.reportsTo], + references: [employees.id], + }), +})); + +export const orders = schema.table('order', { + id: integer('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id').notNull(), + + employeeId: integer('employee_id').notNull(), +}); + +export const ordersRelations = relations(orders, ({ one }) => ({ + customer: one(customers, { + fields: [orders.customerId], + references: [customers.id], + }), + employee: one(employees, { + fields: [orders.employeeId], + references: [employees.id], + }), +})); + +export const suppliers = schema.table('supplier', { + id: integer('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = schema.table('product', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id').notNull(), +}); + +export const productsRelations = relations(products, ({ one }) => ({ + supplier: one(suppliers, { + fields: [products.supplierId], + references: [suppliers.id], + }), +})); + +export const details = schema.table('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id').notNull(), + + productId: integer('product_id').notNull(), +}); + +export const detailsRelations = relations(details, ({ one }) => ({ + order: one(orders, { + fields: [details.orderId], + references: [orders.id], + }), + product: one(products, { + fields: [details.productId], + references: [products.id], + }), +})); diff --git a/drizzle-seed/tests/pg/softRelationsTest/softRelations.test.ts b/drizzle-seed/tests/pg/softRelationsTest/softRelations.test.ts new file mode 100644 index 000000000..205647812 --- /dev/null +++ b/drizzle-seed/tests/pg/softRelationsTest/softRelations.test.ts @@ -0,0 +1,254 @@ +import { PGlite } from '@electric-sql/pglite'; +import { sql } from 'drizzle-orm'; +import type { PgliteDatabase } from 'drizzle-orm/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './pgSchema.ts'; + +let client: PGlite; +let db: PgliteDatabase; + +beforeAll(async () => { + client = new PGlite(); + + db = drizzle(client); + + await db.execute(sql`CREATE SCHEMA "seeder_lib_pg";`); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."customer" ( + "id" varchar(256) PRIMARY KEY NOT NULL, + "company_name" text NOT NULL, + "contact_name" text NOT NULL, + "contact_title" text NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "postal_code" text, + "region" text, + "country" text NOT NULL, + "phone" text NOT NULL, + "fax" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."order_detail" ( + "unit_price" numeric NOT NULL, + "quantity" integer NOT NULL, + "discount" numeric NOT NULL, + "order_id" integer NOT NULL, + "product_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."employee" ( + "id" integer PRIMARY KEY NOT NULL, + "last_name" text NOT NULL, + "first_name" text, + "title" text NOT NULL, + "title_of_courtesy" text NOT NULL, + "birth_date" timestamp NOT NULL, + "hire_date" timestamp NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "postal_code" text NOT NULL, + "country" text NOT NULL, + "home_phone" text NOT NULL, + "extension" integer NOT NULL, + "notes" text NOT NULL, + "reports_to" integer, + "photo_path" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."order" ( + "id" integer PRIMARY KEY NOT NULL, + "order_date" timestamp NOT NULL, + "required_date" timestamp NOT NULL, + "shipped_date" timestamp, + "ship_via" integer NOT NULL, + "freight" numeric NOT NULL, + "ship_name" text NOT NULL, + "ship_city" text NOT NULL, + "ship_region" text, + "ship_postal_code" text, + "ship_country" text NOT NULL, + "customer_id" text NOT NULL, + "employee_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."product" ( + "id" integer PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "quantity_per_unit" text NOT NULL, + "unit_price" numeric NOT NULL, + "units_in_stock" integer NOT NULL, + "units_on_order" integer NOT NULL, + "reorder_level" integer NOT NULL, + "discontinued" integer NOT NULL, + "supplier_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."supplier" ( + "id" integer PRIMARY KEY NOT NULL, + "company_name" text NOT NULL, + "contact_name" text NOT NULL, + "contact_title" text NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "region" text, + "postal_code" text NOT NULL, + "country" text NOT NULL, + "phone" text NOT NULL + ); + `, + ); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +afterAll(async () => { + await client.close(); +}); + +const checkSoftRelations = ( + customers: (typeof schema.customers.$inferSelect)[], + details: (typeof schema.details.$inferSelect)[], + employees: (typeof schema.employees.$inferSelect)[], + orders: (typeof schema.orders.$inferSelect)[], + products: (typeof schema.products.$inferSelect)[], + suppliers: (typeof schema.suppliers.$inferSelect)[], +) => { + // employees soft relations check + const employeeIds = new Set(employees.map((employee) => employee.id)); + const employeesPredicate = employees.every((employee) => + employee.reportsTo !== null && employeeIds.has(employee.reportsTo) + ); + expect(employeesPredicate).toBe(true); + + // orders soft relations check + const customerIds = new Set(customers.map((customer) => customer.id)); + const ordersPredicate1 = orders.every((order) => order.customerId !== null && customerIds.has(order.customerId)); + expect(ordersPredicate1).toBe(true); + + const ordersPredicate2 = orders.every((order) => order.employeeId !== null && employeeIds.has(order.employeeId)); + expect(ordersPredicate2).toBe(true); + + // product soft relations check + const supplierIds = new Set(suppliers.map((supplier) => supplier.id)); + const productsPredicate = products.every((product) => + product.supplierId !== null && supplierIds.has(product.supplierId) + ); + expect(productsPredicate).toBe(true); + + // details soft relations check + const orderIds = new Set(orders.map((order) => order.id)); + const detailsPredicate1 = details.every((detail) => detail.orderId !== null && orderIds.has(detail.orderId)); + expect(detailsPredicate1).toBe(true); + + const productIds = new Set(products.map((product) => product.id)); + const detailsPredicate2 = details.every((detail) => detail.productId !== null && productIds.has(detail.productId)); + expect(detailsPredicate2).toBe(true); +}; + +test('basic seed, soft relations test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("redefine(refine) orders count using 'with' in customers, soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("sequential using of 'with', soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); diff --git a/drizzle-seed/tests/sqlite/allDataTypesTest/sqliteSchema.ts b/drizzle-seed/tests/sqlite/allDataTypesTest/sqliteSchema.ts new file mode 100644 index 000000000..f9737344e --- /dev/null +++ b/drizzle-seed/tests/sqlite/allDataTypesTest/sqliteSchema.ts @@ -0,0 +1,15 @@ +import { blob, integer, numeric, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const allDataTypes = sqliteTable('all_data_types', { + integerNumber: integer('integer_number', { mode: 'number' }), + integerBoolean: integer('integer_boolean', { mode: 'boolean' }), + integerTimestamp: integer('integer_timestamp', { mode: 'timestamp' }), + integerTimestampms: integer('integer_timestampms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { mode: 'text' }), + textJson: text('text_json', { mode: 'json' }), + blobBigint: blob('blob_bigint', { mode: 'bigint' }), + blobBuffer: blob('blob_buffer', { mode: 'buffer' }), + blobJson: blob('blob_json', { mode: 'json' }), + numeric: numeric('numeric'), +}); diff --git a/drizzle-seed/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts b/drizzle-seed/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts new file mode 100644 index 000000000..8282f921d --- /dev/null +++ b/drizzle-seed/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts @@ -0,0 +1,53 @@ +import BetterSqlite3 from 'better-sqlite3'; +import { sql } from 'drizzle-orm'; +import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { afterAll, beforeAll, expect, test } from 'vitest'; +import { seed } from '../../../src/index.ts'; +import * as schema from './sqliteSchema.ts'; + +let client: BetterSqlite3.Database; +let db: BetterSQLite3Database; + +beforeAll(async () => { + client = new BetterSqlite3(':memory:'); + + db = drizzle(client); + + db.run( + sql.raw(` + CREATE TABLE \`all_data_types\` ( + \`integer_number\` integer, + \`integer_boolean\` integer, + \`integer_timestamp\` integer, + \`integer_timestampms\` integer, + \`real\` real, + \`text\` text, + \`text_json\` text, + \`blob_bigint\` blob, + \`blob_buffer\` blob, + \`blob_json\` blob, + \`numeric\` numeric +); + + `), + ); +}); + +afterAll(async () => { + client.close(); +}); + +test('basic seed test', async () => { + // migrate(db, { migrationsFolder: path.join(__dirname, "sqliteMigrations") }); + + await seed(db, schema, { count: 10000 }); + + const allDataTypes = await db.select().from(schema.allDataTypes); + // every value in each 10 rows does not equal undefined. + const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + + expect(predicate).toBe(true); + + client.close(); +}); diff --git a/drizzle-seed/tests/sqlite/cyclicTables/cyclicTables.test.ts b/drizzle-seed/tests/sqlite/cyclicTables/cyclicTables.test.ts new file mode 100644 index 000000000..d404072eb --- /dev/null +++ b/drizzle-seed/tests/sqlite/cyclicTables/cyclicTables.test.ts @@ -0,0 +1,138 @@ +import BetterSqlite3 from 'better-sqlite3'; +import { sql } from 'drizzle-orm'; +import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './sqliteSchema.ts'; + +let client: BetterSqlite3.Database; +let db: BetterSQLite3Database; + +beforeAll(async () => { + client = new BetterSqlite3(':memory:'); + + db = drizzle(client); + + db.run( + sql` + create table model + ( + id integer not null + primary key, + name text not null, + defaultImageId integer, + foreign key (defaultImageId) references model_image + ); + `, + ); + + db.run( + sql` + create table model_image + ( + id integer not null + primary key, + url text not null, + caption text, + modelId integer not null + references model + ); + `, + ); + + // 3 tables case + db.run( + sql` + create table model1 + ( + id integer not null + primary key, + name text not null, + userId integer, + defaultImageId integer, + foreign key (defaultImageId) references model_image1, + foreign key (userId) references user + ); + `, + ); + + db.run( + sql` + create table model_image1 + ( + id integer not null + primary key, + url text not null, + caption text, + modelId integer not null + references model1 + ); + `, + ); + + db.run( + sql` + create table user + ( + id integer not null + primary key, + name text, + invitedBy integer + references user, + imageId integer not null + references model_image1 + ); + `, + ); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +afterAll(async () => { + client.close(); +}); + +test('2 cyclic tables test', async () => { + await seed(db, { + modelTable: schema.modelTable, + modelImageTable: schema.modelImageTable, + }); + + const modelTable = await db.select().from(schema.modelTable); + const modelImageTable = await db.select().from(schema.modelImageTable); + + expect(modelTable.length).toBe(10); + let predicate = modelTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable.length).toBe(10); + predicate = modelImageTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('3 cyclic tables test', async () => { + await seed(db, { + modelTable1: schema.modelTable1, + modelImageTable1: schema.modelImageTable1, + user: schema.user, + }); + + const modelTable1 = await db.select().from(schema.modelTable1); + const modelImageTable1 = await db.select().from(schema.modelImageTable1); + const user = await db.select().from(schema.user); + + expect(modelTable1.length).toBe(10); + let predicate = modelTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable1.length).toBe(10); + predicate = modelImageTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(user.length).toBe(10); + predicate = user.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/sqlite/cyclicTables/sqliteSchema.ts b/drizzle-seed/tests/sqlite/cyclicTables/sqliteSchema.ts new file mode 100644 index 000000000..c9babadde --- /dev/null +++ b/drizzle-seed/tests/sqlite/cyclicTables/sqliteSchema.ts @@ -0,0 +1,76 @@ +import { relations } from 'drizzle-orm'; +import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'; + +// MODEL +export const modelTable = sqliteTable( + 'model', + { + id: integer().primaryKey(), + name: text().notNull(), + defaultImageId: integer().references(() => modelImageTable.id), + }, +); + +export const modelRelations = relations(modelTable, ({ one, many }) => ({ + images: many(modelImageTable), + defaultImage: one(modelImageTable, { + fields: [modelTable.defaultImageId], + references: [modelImageTable.id], + }), +})); + +// MODEL IMAGE +export const modelImageTable = sqliteTable( + 'model_image', + { + id: integer().primaryKey(), + url: text().notNull(), + caption: text(), + modelId: integer() + .notNull() + .references((): AnySQLiteColumn => modelTable.id), + }, +); + +export const modelImageRelations = relations(modelImageTable, ({ one }) => ({ + model: one(modelTable, { + fields: [modelImageTable.modelId], + references: [modelTable.id], + }), +})); + +// 3 tables case +export const modelTable1 = sqliteTable( + 'model1', + { + id: integer().primaryKey(), + name: text().notNull(), + userId: integer() + .references(() => user.id), + defaultImageId: integer().references(() => modelImageTable1.id), + }, +); + +export const modelImageTable1 = sqliteTable( + 'model_image1', + { + id: integer().primaryKey(), + url: text().notNull(), + caption: text(), + modelId: integer().notNull() + .references((): AnySQLiteColumn => modelTable1.id), + }, +); + +export const user = sqliteTable( + 'user', + { + id: integer().primaryKey(), + name: text(), + invitedBy: integer().references((): AnySQLiteColumn => user.id), + imageId: integer() + .notNull() + .references((): AnySQLiteColumn => modelImageTable1.id), + }, +); diff --git a/drizzle-seed/tests/sqlite/softRelationsTest/softRelations.test.ts b/drizzle-seed/tests/sqlite/softRelationsTest/softRelations.test.ts new file mode 100644 index 000000000..124ac8ee1 --- /dev/null +++ b/drizzle-seed/tests/sqlite/softRelationsTest/softRelations.test.ts @@ -0,0 +1,253 @@ +import BetterSqlite3 from 'better-sqlite3'; +import { sql } from 'drizzle-orm'; +import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './sqliteSchema.ts'; + +let client: BetterSqlite3.Database; +let db: BetterSQLite3Database; + +beforeAll(async () => { + client = new BetterSqlite3(':memory:'); + + db = drizzle(client); + + db.run( + sql.raw(` + CREATE TABLE \`customer\` ( + \`id\` text PRIMARY KEY NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text, + \`region\` text, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + \`fax\` text +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`order_detail\` ( + \`unit_price\` numeric NOT NULL, + \`quantity\` integer NOT NULL, + \`discount\` numeric NOT NULL, + \`order_id\` integer NOT NULL, + \`product_id\` integer NOT NULL +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`employee\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`last_name\` text NOT NULL, + \`first_name\` text, + \`title\` text NOT NULL, + \`title_of_courtesy\` text NOT NULL, + \`birth_date\` integer NOT NULL, + \`hire_date\` integer NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`home_phone\` text NOT NULL, + \`extension\` integer NOT NULL, + \`notes\` text NOT NULL, + \`reports_to\` integer, + \`photo_path\` text +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`order\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`order_date\` integer NOT NULL, + \`required_date\` integer NOT NULL, + \`shipped_date\` integer, + \`ship_via\` integer NOT NULL, + \`freight\` numeric NOT NULL, + \`ship_name\` text NOT NULL, + \`ship_city\` text NOT NULL, + \`ship_region\` text, + \`ship_postal_code\` text, + \`ship_country\` text NOT NULL, + \`customer_id\` text NOT NULL, + \`employee_id\` integer NOT NULL +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`product\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`name\` text NOT NULL, + \`quantity_per_unit\` text NOT NULL, + \`unit_price\` numeric NOT NULL, + \`units_in_stock\` integer NOT NULL, + \`units_on_order\` integer NOT NULL, + \`reorder_level\` integer NOT NULL, + \`discontinued\` integer NOT NULL, + \`supplier_id\` integer NOT NULL +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`supplier\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`region\` text, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`phone\` text NOT NULL +); + `), + ); +}); + +afterAll(async () => { + client.close(); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +const checkSoftRelations = ( + customers: (typeof schema.customers.$inferSelect)[], + details: (typeof schema.details.$inferSelect)[], + employees: (typeof schema.employees.$inferSelect)[], + orders: (typeof schema.orders.$inferSelect)[], + products: (typeof schema.products.$inferSelect)[], + suppliers: (typeof schema.suppliers.$inferSelect)[], +) => { + // employees soft relations check + const employeeIds = new Set(employees.map((employee) => employee.id)); + const employeesPredicate = employees.every((employee) => + employee.reportsTo !== null && employeeIds.has(employee.reportsTo) + ); + expect(employeesPredicate).toBe(true); + + // orders soft relations check + const customerIds = new Set(customers.map((customer) => customer.id)); + const ordersPredicate1 = orders.every((order) => order.customerId !== null && customerIds.has(order.customerId)); + expect(ordersPredicate1).toBe(true); + + const ordersPredicate2 = orders.every((order) => order.employeeId !== null && employeeIds.has(order.employeeId)); + expect(ordersPredicate2).toBe(true); + + // product soft relations check + const supplierIds = new Set(suppliers.map((supplier) => supplier.id)); + const productsPredicate = products.every((product) => + product.supplierId !== null && supplierIds.has(product.supplierId) + ); + expect(productsPredicate).toBe(true); + + // details soft relations check + const orderIds = new Set(orders.map((order) => order.id)); + const detailsPredicate1 = details.every((detail) => detail.orderId !== null && orderIds.has(detail.orderId)); + expect(detailsPredicate1).toBe(true); + + const productIds = new Set(products.map((product) => product.id)); + const detailsPredicate2 = details.every((detail) => detail.productId !== null && productIds.has(detail.productId)); + expect(detailsPredicate2).toBe(true); +}; + +test('basic seed, soft relations test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("redefine(refine) orders count using 'with' in customers, soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("sequential using of 'with', soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); diff --git a/drizzle-seed/tests/sqlite/softRelationsTest/sqliteSchema.ts b/drizzle-seed/tests/sqlite/softRelationsTest/sqliteSchema.ts new file mode 100644 index 000000000..75572c63a --- /dev/null +++ b/drizzle-seed/tests/sqlite/softRelationsTest/sqliteSchema.ts @@ -0,0 +1,128 @@ +import { relations } from 'drizzle-orm'; +import { integer, numeric, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const customers = sqliteTable('customer', { + id: text('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = sqliteTable( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: integer('birth_date', { mode: 'timestamp' }).notNull(), + hireDate: integer('hire_date', { mode: 'timestamp' }).notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to'), + photoPath: text('photo_path'), + }, +); + +export const employeesRelations = relations(employees, ({ one }) => ({ + employee: one(employees, { + fields: [employees.reportsTo], + references: [employees.id], + }), +})); + +export const orders = sqliteTable('order', { + id: integer('id').primaryKey(), + orderDate: integer('order_date', { mode: 'timestamp' }).notNull(), + requiredDate: integer('required_date', { mode: 'timestamp' }).notNull(), + shippedDate: integer('shipped_date', { mode: 'timestamp' }), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id').notNull(), + + employeeId: integer('employee_id').notNull(), +}); + +export const ordersRelations = relations(orders, ({ one }) => ({ + customer: one(customers, { + fields: [orders.customerId], + references: [customers.id], + }), + employee: one(employees, { + fields: [orders.employeeId], + references: [employees.id], + }), +})); + +export const suppliers = sqliteTable('supplier', { + id: integer('id').primaryKey({ autoIncrement: true }), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = sqliteTable('product', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id').notNull(), +}); + +export const productsRelations = relations(products, ({ one }) => ({ + supplier: one(suppliers, { + fields: [products.supplierId], + references: [suppliers.id], + }), +})); + +export const details = sqliteTable('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id').notNull(), + + productId: integer('product_id').notNull(), +}); + +export const detailsRelations = relations(details, ({ one }) => ({ + order: one(orders, { + fields: [details.orderId], + references: [orders.id], + }), + product: one(products, { + fields: [details.productId], + references: [products.id], + }), +})); diff --git a/drizzle-seed/tests/sqlite/sqlite.test.ts b/drizzle-seed/tests/sqlite/sqlite.test.ts new file mode 100644 index 000000000..550648d49 --- /dev/null +++ b/drizzle-seed/tests/sqlite/sqlite.test.ts @@ -0,0 +1,290 @@ +import BetterSqlite3 from 'better-sqlite3'; +import { sql } from 'drizzle-orm'; +import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../src/index.ts'; +import * as schema from './sqliteSchema.ts'; + +let client: BetterSqlite3.Database; +let db: BetterSQLite3Database; + +beforeAll(async () => { + client = new BetterSqlite3(':memory:'); + + db = drizzle(client); + + db.run( + sql.raw(` + CREATE TABLE \`customer\` ( + \`id\` text PRIMARY KEY NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text, + \`region\` text, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + \`fax\` text +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`order_detail\` ( + \`unit_price\` numeric NOT NULL, + \`quantity\` integer NOT NULL, + \`discount\` numeric NOT NULL, + \`order_id\` integer NOT NULL, + \`product_id\` integer NOT NULL, + FOREIGN KEY (\`order_id\`) REFERENCES \`order\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`product_id\`) REFERENCES \`product\`(\`id\`) ON UPDATE no action ON DELETE cascade +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`employee\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`last_name\` text NOT NULL, + \`first_name\` text, + \`title\` text NOT NULL, + \`title_of_courtesy\` text NOT NULL, + \`birth_date\` integer NOT NULL, + \`hire_date\` integer NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`home_phone\` text NOT NULL, + \`extension\` integer NOT NULL, + \`notes\` text NOT NULL, + \`reports_to\` integer, + \`photo_path\` text, + FOREIGN KEY (\`reports_to\`) REFERENCES \`employee\`(\`id\`) ON UPDATE no action ON DELETE no action +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`order\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`order_date\` integer NOT NULL, + \`required_date\` integer NOT NULL, + \`shipped_date\` integer, + \`ship_via\` integer NOT NULL, + \`freight\` numeric NOT NULL, + \`ship_name\` text NOT NULL, + \`ship_city\` text NOT NULL, + \`ship_region\` text, + \`ship_postal_code\` text, + \`ship_country\` text NOT NULL, + \`customer_id\` text NOT NULL, + \`employee_id\` integer NOT NULL, + FOREIGN KEY (\`customer_id\`) REFERENCES \`customer\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`employee_id\`) REFERENCES \`employee\`(\`id\`) ON UPDATE no action ON DELETE cascade +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`product\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`name\` text NOT NULL, + \`quantity_per_unit\` text NOT NULL, + \`unit_price\` numeric NOT NULL, + \`units_in_stock\` integer NOT NULL, + \`units_on_order\` integer NOT NULL, + \`reorder_level\` integer NOT NULL, + \`discontinued\` integer NOT NULL, + \`supplier_id\` integer NOT NULL, + FOREIGN KEY (\`supplier_id\`) REFERENCES \`supplier\`(\`id\`) ON UPDATE no action ON DELETE cascade +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`supplier\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`region\` text, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`phone\` text NOT NULL +); + `), + ); +}); + +afterAll(async () => { + client.close(); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +test('basic seed test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); +}); + +test('seed with options.count:11 test', async () => { + await seed(db, schema, { count: 11 }); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(11); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) customers count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) all tables count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + details: { + count: 13, + }, + employees: { + count: 14, + }, + orders: { + count: 15, + }, + products: { + count: 16, + }, + suppliers: { + count: 17, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(13); + expect(employees.length).toBe(14); + expect(orders.length).toBe(15); + expect(products.length).toBe(16); + expect(suppliers.length).toBe(17); +}); + +test("redefine(refine) orders count using 'with' in customers", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test("sequential using of 'with'", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); diff --git a/drizzle-seed/tests/sqlite/sqliteSchema.ts b/drizzle-seed/tests/sqlite/sqliteSchema.ts new file mode 100644 index 000000000..fa00dd365 --- /dev/null +++ b/drizzle-seed/tests/sqlite/sqliteSchema.ts @@ -0,0 +1,107 @@ +import { foreignKey, integer, numeric, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const customers = sqliteTable('customer', { + id: text('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = sqliteTable( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: integer('birth_date', { mode: 'timestamp' }).notNull(), + hireDate: integer('hire_date', { mode: 'timestamp' }).notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to'), + photoPath: text('photo_path'), + }, + (table) => ({ + reportsToFk: foreignKey(() => ({ + columns: [table.reportsTo], + foreignColumns: [table.id], + })), + }), +); + +export const orders = sqliteTable('order', { + id: integer('id').primaryKey(), + orderDate: integer('order_date', { mode: 'timestamp' }).notNull(), + requiredDate: integer('required_date', { mode: 'timestamp' }).notNull(), + shippedDate: integer('shipped_date', { mode: 'timestamp' }), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id') + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: integer('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = sqliteTable('supplier', { + id: integer('id').primaryKey({ autoIncrement: true }), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = sqliteTable('product', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = sqliteTable('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: integer('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); diff --git a/drizzle-seed/tsconfig.build.json b/drizzle-seed/tsconfig.build.json new file mode 100644 index 000000000..3377281ba --- /dev/null +++ b/drizzle-seed/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/drizzle-seed/tsconfig.json b/drizzle-seed/tsconfig.json new file mode 100644 index 000000000..f32902e10 --- /dev/null +++ b/drizzle-seed/tsconfig.json @@ -0,0 +1,48 @@ +{ + "compilerOptions": { + "isolatedModules": true, + "composite": false, + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["es2020", "es2018", "es2017", "es7", "es6", "es5", "es2022"], + "declarationMap": false, + "sourceMap": true, + "allowJs": true, + "incremental": false, + "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + "exactOptionalPropertyTypes": false, /* Interpret optional property types as written, rather than adding 'undefined'. */ + "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + "allowUnusedLabels": false, /* Disable error reporting for unused labels. */ + "allowUnreachableCode": false, /* Disable error reporting for unreachable code. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "noErrorTruncation": true, /* Disable truncating types in error messages. */ + "checkJs": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "outDir": "dist", + "baseUrl": ".", + "declaration": true, + "paths": { + "~/*": ["src/*"] + } + }, + "exclude": ["**/dist", "src/dev"], + "include": ["src", "*.ts", "tests"] +} diff --git a/drizzle-seed/type-tests/mysql.ts b/drizzle-seed/type-tests/mysql.ts new file mode 100644 index 000000000..ffd42726d --- /dev/null +++ b/drizzle-seed/type-tests/mysql.ts @@ -0,0 +1,17 @@ +import type { MySqlColumn } from 'drizzle-orm/mysql-core'; +import { int, mysqlTable, text } from 'drizzle-orm/mysql-core'; +import { drizzle as mysql2Drizzle } from 'drizzle-orm/mysql2'; +import { reset, seed } from '../src/index.ts'; + +const mysqlUsers = mysqlTable('users', { + id: int().primaryKey().autoincrement(), + name: text(), + inviteId: int('invite_id').references((): MySqlColumn => mysqlUsers.id), +}); + +{ + const db = mysql2Drizzle(''); + + await seed(db, { users: mysqlUsers }); + await reset(db, { users: mysqlUsers }); +} diff --git a/drizzle-seed/type-tests/pg.ts b/drizzle-seed/type-tests/pg.ts new file mode 100644 index 000000000..3bec9989f --- /dev/null +++ b/drizzle-seed/type-tests/pg.ts @@ -0,0 +1,48 @@ +import { drizzle as nodePostgresDrizzle } from 'drizzle-orm/node-postgres'; +import type { PgColumn } from 'drizzle-orm/pg-core'; +import { integer, pgTable, text } from 'drizzle-orm/pg-core'; +import { drizzle as pgliteDrizzle } from 'drizzle-orm/pglite'; +import { drizzle as postgresJsDrizzle } from 'drizzle-orm/postgres-js'; +import { reset, seed } from '../src/index.ts'; + +const pgUsers = pgTable('users', { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + name: text(), + inviteId: integer('invite_id').references((): PgColumn => pgUsers.id), +}); + +{ + const db0 = nodePostgresDrizzle('', { schema: { users: pgUsers } }); + + await seed(db0, { users: pgUsers }); + await reset(db0, { users: pgUsers }); + + const db1 = nodePostgresDrizzle(''); + + await seed(db1, { users: pgUsers }); + await reset(db1, { users: pgUsers }); +} + +{ + const db0 = pgliteDrizzle('', { schema: { users: pgUsers } }); + + await seed(db0, { users: pgUsers }); + await reset(db0, { users: pgUsers }); + + const db1 = pgliteDrizzle(''); + + await seed(db1, { users: pgUsers }); + await reset(db1, { users: pgUsers }); +} + +{ + const db0 = postgresJsDrizzle('', { schema: { users: pgUsers } }); + + await seed(db0, { users: pgUsers }); + await reset(db0, { users: pgUsers }); + + const db1 = postgresJsDrizzle(''); + + await seed(db1, { users: pgUsers }); + await reset(db1, { users: pgUsers }); +} diff --git a/drizzle-seed/type-tests/sqlite.ts b/drizzle-seed/type-tests/sqlite.ts new file mode 100644 index 000000000..c9fa3d23b --- /dev/null +++ b/drizzle-seed/type-tests/sqlite.ts @@ -0,0 +1,17 @@ +import { drizzle as betterSqlite3Drizzle } from 'drizzle-orm/better-sqlite3'; +import type { SQLiteColumn } from 'drizzle-orm/sqlite-core'; +import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { reset, seed } from '../src/index.ts'; + +const mysqlUsers = sqliteTable('users', { + id: int().primaryKey(), + name: text(), + inviteId: int('invite_id').references((): SQLiteColumn => mysqlUsers.id), +}); + +{ + const db = betterSqlite3Drizzle(''); + + await seed(db, { users: mysqlUsers }); + await reset(db, { users: mysqlUsers }); +} diff --git a/drizzle-seed/type-tests/tsconfig.json b/drizzle-seed/type-tests/tsconfig.json new file mode 100644 index 000000000..b4e6c8007 --- /dev/null +++ b/drizzle-seed/type-tests/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.build.json", + "compilerOptions": { + "composite": false, + "noEmit": true, + "rootDir": "..", + "outDir": "./.cache" + }, + "include": [".", "../src"], + "exclude": ["**/playground"] +} diff --git a/drizzle-seed/vitest.config.ts b/drizzle-seed/vitest.config.ts new file mode 100644 index 000000000..5489010bd --- /dev/null +++ b/drizzle-seed/vitest.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: [ + './tests/pg/**/*.test.ts', + './tests/mysql/**/*.test.ts', + './tests/sqlite/**/*.test.ts', + ], + exclude: [], + typecheck: { + tsconfig: 'tsconfig.json', + }, + testTimeout: 100000, + hookTimeout: 100000, + isolate: true, + poolOptions: { + threads: { + singleThread: true, + }, + }, + maxWorkers: 1, + fileParallelism: false, + }, +}); diff --git a/drizzle-typebox/README.md b/drizzle-typebox/README.md index 912a67f7f..72d85e684 100644 --- a/drizzle-typebox/README.md +++ b/drizzle-typebox/README.md @@ -1,10 +1,10 @@ `drizzle-typebox` is a plugin for [Drizzle ORM](https://github.com/drizzle-team/drizzle-orm) that allows you to generate [@sinclair/typebox](https://github.com/sinclairzx81/typebox) schemas from Drizzle ORM schemas. -| Database | Insert schema | Select schema | -| :--------- | :-----------: | :-----------: | -| PostgreSQL | ✅ | ✅ | -| MySQL | ✅ | ✅ | -| SQLite | ✅ | ✅ | +**Features** + +- Create a select schema for tables, views and enums. +- Create insert and update schemas for tables. +- Supports all dialects: PostgreSQL, MySQL and SQLite. # Usage @@ -25,6 +25,9 @@ const users = pgTable('users', { // Schema for inserting a user - can be used to validate API requests const insertUserSchema = createInsertSchema(users); +// Schema for updating a user - can be used to validate API requests +const updateUserSchema = createUpdateSchema(users); + // Schema for selecting a user - can be used to validate API responses const selectUserSchema = createSelectSchema(users); @@ -35,7 +38,7 @@ const insertUserSchema = createInsertSchema(users, { // Refining the fields - useful if you want to change the fields before they become nullable/optional in the final schema const insertUserSchema = createInsertSchema(users, { - id: (schema) => Type.Number({ minimum: 0 }), + id: (schema) => Type.Number({ ...schema, minimum: 0 }), role: Type.String(), }); diff --git a/drizzle-typebox/package.json b/drizzle-typebox/package.json index 5e812f4fe..c03d64105 100644 --- a/drizzle-typebox/package.json +++ b/drizzle-typebox/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-typebox", - "version": "0.1.1", + "version": "0.2.1", "description": "Generate Typebox schemas from Drizzle ORM schemas", "type": "module", "scripts": { @@ -55,13 +55,12 @@ "author": "Drizzle Team", "license": "Apache-2.0", "peerDependencies": { - "@sinclair/typebox": ">=0.17.6", - "drizzle-orm": ">=0.23.13" + "@sinclair/typebox": ">=0.34.8", + "drizzle-orm": ">=0.36.0" }, "devDependencies": { - "@rollup/plugin-terser": "^0.4.1", "@rollup/plugin-typescript": "^11.1.0", - "@sinclair/typebox": "^0.29.6", + "@sinclair/typebox": "^0.34.8", "@types/node": "^18.15.10", "cpy": "^10.1.0", "drizzle-orm": "link:../drizzle-orm/dist", diff --git a/drizzle-typebox/rollup.config.ts b/drizzle-typebox/rollup.config.ts index 2ed2d33d3..a29fdd38a 100644 --- a/drizzle-typebox/rollup.config.ts +++ b/drizzle-typebox/rollup.config.ts @@ -1,4 +1,3 @@ -import terser from '@rollup/plugin-terser'; import typescript from '@rollup/plugin-typescript'; import { defineConfig } from 'rollup'; @@ -23,13 +22,12 @@ export default defineConfig([ ], external: [ /^drizzle-orm\/?/, - 'zod', + '@sinclair/typebox', ], plugins: [ typescript({ tsconfig: 'tsconfig.build.json', }), - terser(), ], }, ]); diff --git a/drizzle-typebox/scripts/build.ts b/drizzle-typebox/scripts/build.ts index 1910feac6..07330ffd0 100644 --- a/drizzle-typebox/scripts/build.ts +++ b/drizzle-typebox/scripts/build.ts @@ -13,3 +13,4 @@ await cpy('dist/**/*.d.ts', 'dist', { rename: (basename) => basename.replace(/\.d\.ts$/, '.d.cts'), }); await fs.copy('package.json', 'dist/package.json'); +await $`scripts/fix-imports.ts`; diff --git a/drizzle-typebox/scripts/fix-imports.ts b/drizzle-typebox/scripts/fix-imports.ts new file mode 100755 index 000000000..a90057c5b --- /dev/null +++ b/drizzle-typebox/scripts/fix-imports.ts @@ -0,0 +1,136 @@ +#!/usr/bin/env -S pnpm tsx +import 'zx/globals'; + +import path from 'node:path'; +import { parse, print, visit } from 'recast'; +import parser from 'recast/parsers/typescript'; + +function resolvePathAlias(importPath: string, file: string) { + if (importPath.startsWith('~/')) { + const relativePath = path.relative(path.dirname(file), path.resolve('dist.new', importPath.slice(2))); + importPath = relativePath.startsWith('.') ? relativePath : './' + relativePath; + } + + return importPath; +} + +function fixImportPath(importPath: string, file: string, ext: string) { + importPath = resolvePathAlias(importPath, file); + + if (!/\..*\.(js|ts)$/.test(importPath)) { + return importPath; + } + + return importPath.replace(/\.(js|ts)$/, ext); +} + +const cjsFiles = await glob('dist/**/*.{cjs,d.cts}'); + +await Promise.all(cjsFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + } + this.traverse(path); + }, + visitCallExpression(path) { + if (path.value.callee.type === 'Identifier' && path.value.callee.name === 'require') { + path.value.arguments[0].value = fixImportPath(path.value.arguments[0].value, file, '.cjs'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = resolvePathAlias(path.value.argument.value, file); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.cjs'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); + +let esmFiles = await glob('dist/**/*.{js,d.ts}'); + +await Promise.all(esmFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = fixImportPath(path.value.argument.value, file, '.js'); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.js'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); + +esmFiles = await glob('dist/**/*.{mjs,d.mts}'); + +await Promise.all(esmFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = fixImportPath(path.value.argument.value, file, '.mjs'); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.mjs'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); diff --git a/drizzle-typebox/src/column.ts b/drizzle-typebox/src/column.ts new file mode 100644 index 000000000..9bef765bf --- /dev/null +++ b/drizzle-typebox/src/column.ts @@ -0,0 +1,316 @@ +import { Kind, Type as t, TypeRegistry } from '@sinclair/typebox'; +import type { StringOptions, TSchema, Type as typebox } from '@sinclair/typebox'; +import type { Column, ColumnBaseConfig } from 'drizzle-orm'; +import type { + MySqlBigInt53, + MySqlChar, + MySqlDouble, + MySqlFloat, + MySqlInt, + MySqlMediumInt, + MySqlReal, + MySqlSerial, + MySqlSmallInt, + MySqlText, + MySqlTinyInt, + MySqlVarChar, + MySqlYear, +} from 'drizzle-orm/mysql-core'; +import type { + PgArray, + PgBigInt53, + PgBigSerial53, + PgBinaryVector, + PgChar, + PgDoublePrecision, + PgGeometry, + PgGeometryObject, + PgHalfVector, + PgInteger, + PgLineABC, + PgLineTuple, + PgPointObject, + PgPointTuple, + PgReal, + PgSerial, + PgSmallInt, + PgSmallSerial, + PgUUID, + PgVarchar, + PgVector, +} from 'drizzle-orm/pg-core'; +import { + type SingleStoreBigInt53, + SingleStoreChar, + type SingleStoreDouble, + type SingleStoreFloat, + type SingleStoreInt, + type SingleStoreMediumInt, + type SingleStoreReal, + type SingleStoreSerial, + type SingleStoreSmallInt, + SingleStoreText, + type SingleStoreTinyInt, + SingleStoreVarChar, + SingleStoreYear, +} from 'drizzle-orm/singlestore-core'; +import type { SQLiteInteger, SQLiteReal, SQLiteText } from 'drizzle-orm/sqlite-core'; +import { CONSTANTS } from './constants.ts'; +import { isColumnType, isWithEnum } from './utils.ts'; +import type { BufferSchema, JsonSchema } from './utils.ts'; + +export const literalSchema = t.Union([t.String(), t.Number(), t.Boolean(), t.Null()]); +export const jsonSchema: JsonSchema = t.Recursive((self) => + t.Union([literalSchema, t.Array(self), t.Record(t.String(), self)]) +) as any; +TypeRegistry.Set('Buffer', (_, value) => value instanceof Buffer); // eslint-disable-line no-instanceof/no-instanceof +export const bufferSchema: BufferSchema = { [Kind]: 'Buffer', type: 'buffer' } as any; + +export function mapEnumValues(values: string[]) { + return Object.fromEntries(values.map((value) => [value, value])); +} + +export function columnToSchema(column: Column, t: typeof typebox): TSchema { + let schema!: TSchema; + + if (isWithEnum(column)) { + schema = column.enumValues.length ? t.Enum(mapEnumValues(column.enumValues)) : t.String(); + } + + if (!schema) { + // Handle specific types + if (isColumnType | PgPointTuple>(column, ['PgGeometry', 'PgPointTuple'])) { + schema = t.Tuple([t.Number(), t.Number()]); + } else if ( + isColumnType | PgGeometryObject>(column, ['PgGeometryObject', 'PgPointObject']) + ) { + schema = t.Object({ x: t.Number(), y: t.Number() }); + } else if (isColumnType | PgVector>(column, ['PgHalfVector', 'PgVector'])) { + schema = t.Array( + t.Number(), + column.dimensions + ? { + minItems: column.dimensions, + maxItems: column.dimensions, + } + : undefined, + ); + } else if (isColumnType>(column, ['PgLine'])) { + schema = t.Tuple([t.Number(), t.Number(), t.Number()]); + } else if (isColumnType>(column, ['PgLineABC'])) { + schema = t.Object({ + a: t.Number(), + b: t.Number(), + c: t.Number(), + }); + } // Handle other types + else if (isColumnType>(column, ['PgArray'])) { + schema = t.Array( + columnToSchema(column.baseColumn, t), + column.size + ? { + minItems: column.size, + maxItems: column.size, + } + : undefined, + ); + } else if (column.dataType === 'array') { + schema = t.Array(t.Any()); + } else if (column.dataType === 'number') { + schema = numberColumnToSchema(column, t); + } else if (column.dataType === 'bigint') { + schema = bigintColumnToSchema(column, t); + } else if (column.dataType === 'boolean') { + schema = t.Boolean(); + } else if (column.dataType === 'date') { + schema = t.Date(); + } else if (column.dataType === 'string') { + schema = stringColumnToSchema(column, t); + } else if (column.dataType === 'json') { + schema = jsonSchema; + } else if (column.dataType === 'custom') { + schema = t.Any(); + } else if (column.dataType === 'buffer') { + schema = bufferSchema; + } + } + + if (!schema) { + schema = t.Any(); + } + + return schema; +} + +function numberColumnToSchema(column: Column, t: typeof typebox): TSchema { + let unsigned = column.getSQLType().includes('unsigned'); + let min!: number; + let max!: number; + let integer = false; + + if (isColumnType | SingleStoreTinyInt>(column, ['MySqlTinyInt', 'SingleStoreTinyInt'])) { + min = unsigned ? 0 : CONSTANTS.INT8_MIN; + max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX; + integer = true; + } else if ( + isColumnType | PgSmallSerial | MySqlSmallInt | SingleStoreSmallInt>(column, [ + 'PgSmallInt', + 'PgSmallSerial', + 'MySqlSmallInt', + 'SingleStoreSmallInt', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT16_MIN; + max = unsigned ? CONSTANTS.INT16_UNSIGNED_MAX : CONSTANTS.INT16_MAX; + integer = true; + } else if ( + isColumnType< + PgReal | MySqlFloat | MySqlMediumInt | SingleStoreFloat | SingleStoreMediumInt + >(column, [ + 'PgReal', + 'MySqlFloat', + 'MySqlMediumInt', + 'SingleStoreFloat', + 'SingleStoreMediumInt', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT24_MIN; + max = unsigned ? CONSTANTS.INT24_UNSIGNED_MAX : CONSTANTS.INT24_MAX; + integer = isColumnType(column, ['MySqlMediumInt', 'SingleStoreMediumInt']); + } else if ( + isColumnType | PgSerial | MySqlInt | SingleStoreInt>(column, [ + 'PgInteger', + 'PgSerial', + 'MySqlInt', + 'SingleStoreInt', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT32_MIN; + max = unsigned ? CONSTANTS.INT32_UNSIGNED_MAX : CONSTANTS.INT32_MAX; + integer = true; + } else if ( + isColumnType< + | PgDoublePrecision + | MySqlReal + | MySqlDouble + | SingleStoreReal + | SingleStoreDouble + | SQLiteReal + >(column, [ + 'PgDoublePrecision', + 'MySqlReal', + 'MySqlDouble', + 'SingleStoreReal', + 'SingleStoreDouble', + 'SQLiteReal', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT48_MIN; + max = unsigned ? CONSTANTS.INT48_UNSIGNED_MAX : CONSTANTS.INT48_MAX; + } else if ( + isColumnType< + | PgBigInt53 + | PgBigSerial53 + | MySqlBigInt53 + | MySqlSerial + | SingleStoreBigInt53 + | SingleStoreSerial + | SQLiteInteger + >( + column, + [ + 'PgBigInt53', + 'PgBigSerial53', + 'MySqlBigInt53', + 'MySqlSerial', + 'SingleStoreBigInt53', + 'SingleStoreSerial', + 'SQLiteInteger', + ], + ) + ) { + unsigned = unsigned || isColumnType(column, ['MySqlSerial', 'SingleStoreSerial']); + min = unsigned ? 0 : Number.MIN_SAFE_INTEGER; + max = Number.MAX_SAFE_INTEGER; + integer = true; + } else if (isColumnType | SingleStoreYear>(column, ['MySqlYear', 'SingleStoreYear'])) { + min = 1901; + max = 2155; + integer = true; + } else { + min = Number.MIN_SAFE_INTEGER; + max = Number.MAX_SAFE_INTEGER; + } + + const key = integer ? 'Integer' : 'Number'; + return t[key]({ + minimum: min, + maximum: max, + }); +} + +function bigintColumnToSchema(column: Column, t: typeof typebox): TSchema { + const unsigned = column.getSQLType().includes('unsigned'); + const min = unsigned ? 0n : CONSTANTS.INT64_MIN; + const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX; + + return t.BigInt({ + minimum: min, + maximum: max, + }); +} + +function stringColumnToSchema(column: Column, t: typeof typebox): TSchema { + if (isColumnType>>(column, ['PgUUID'])) { + return t.String({ format: 'uuid' }); + } else if ( + isColumnType & { dimensions: number }>>(column, [ + 'PgBinaryVector', + ]) + ) { + return t.RegExp(/^[01]+$/, column.dimensions ? { maxLength: column.dimensions } : undefined); + } + + let max: number | undefined; + let fixed = false; + + if (isColumnType | SQLiteText>(column, ['PgVarchar', 'SQLiteText'])) { + max = column.length; + } else if ( + isColumnType | SingleStoreVarChar>(column, ['MySqlVarChar', 'SingleStoreVarChar']) + ) { + max = column.length ?? CONSTANTS.INT16_UNSIGNED_MAX; + } else if (isColumnType | SingleStoreText>(column, ['MySqlText', 'SingleStoreText'])) { + if (column.textType === 'longtext') { + max = CONSTANTS.INT32_UNSIGNED_MAX; + } else if (column.textType === 'mediumtext') { + max = CONSTANTS.INT24_UNSIGNED_MAX; + } else if (column.textType === 'text') { + max = CONSTANTS.INT16_UNSIGNED_MAX; + } else { + max = CONSTANTS.INT8_UNSIGNED_MAX; + } + } + + if ( + isColumnType | MySqlChar | SingleStoreChar>(column, [ + 'PgChar', + 'MySqlChar', + 'SingleStoreChar', + ]) + ) { + max = column.length; + fixed = true; + } + + const options: Partial = {}; + + if (max !== undefined && fixed) { + options.minLength = max; + options.maxLength = max; + } else if (max !== undefined) { + options.maxLength = max; + } + + return t.String(Object.keys(options).length > 0 ? options : undefined); +} diff --git a/drizzle-typebox/src/column.types.ts b/drizzle-typebox/src/column.types.ts new file mode 100644 index 000000000..2644946c1 --- /dev/null +++ b/drizzle-typebox/src/column.types.ts @@ -0,0 +1,107 @@ +import type * as t from '@sinclair/typebox'; +import type { Assume, Column } from 'drizzle-orm'; +import type { ArrayHasAtLeastOneValue, BufferSchema, ColumnIsGeneratedAlwaysAs, IsNever, JsonSchema } from './utils.ts'; + +export type GetEnumValuesFromColumn = TColumn['_'] extends { enumValues: [string, ...string[]] } + ? TColumn['_']['enumValues'] + : undefined; + +export type GetBaseColumn = TColumn['_'] extends { baseColumn: Column | never | undefined } + ? IsNever extends false ? TColumn['_']['baseColumn'] + : undefined + : undefined; + +export type EnumValuesToEnum = { [K in TEnumValues[number]]: K }; + +export type GetTypeboxType< + TData, + TDataType extends string, + TColumnType extends string, + TEnumValues extends [string, ...string[]] | undefined, + TBaseColumn extends Column | undefined, +> = TColumnType extends + | 'MySqlTinyInt' + | 'SingleStoreTinyInt' + | 'PgSmallInt' + | 'PgSmallSerial' + | 'MySqlSmallInt' + | 'MySqlMediumInt' + | 'SingleStoreSmallInt' + | 'SingleStoreMediumInt' + | 'PgInteger' + | 'PgSerial' + | 'MySqlInt' + | 'SingleStoreInt' + | 'PgBigInt53' + | 'PgBigSerial53' + | 'MySqlBigInt53' + | 'MySqlSerial' + | 'SingleStoreBigInt53' + | 'SingleStoreSerial' + | 'SQLiteInteger' + | 'MySqlYear' + | 'SingleStoreYear' ? t.TInteger + : TColumnType extends 'PgBinaryVector' ? t.TRegExp + : TBaseColumn extends Column ? t.TArray< + GetTypeboxType< + TBaseColumn['_']['data'], + TBaseColumn['_']['dataType'], + TBaseColumn['_']['columnType'], + GetEnumValuesFromColumn, + GetBaseColumn + > + > + : ArrayHasAtLeastOneValue extends true + ? t.TEnum>> + : TData extends infer TTuple extends [any, ...any[]] ? t.TTuple< + Assume<{ [K in keyof TTuple]: GetTypeboxType }, [any, ...any[]]> + > + : TData extends Date ? t.TDate + : TData extends Buffer ? BufferSchema + : TDataType extends 'array' + ? t.TArray[number], string, string, undefined, undefined>> + : TData extends infer TDict extends Record + ? t.TObject<{ [K in keyof TDict]: GetTypeboxType }> + : TDataType extends 'json' ? JsonSchema + : TData extends number ? t.TNumber + : TData extends bigint ? t.TBigInt + : TData extends boolean ? t.TBoolean + : TData extends string ? t.TString + : t.TAny; + +type HandleSelectColumn< + TSchema extends t.TSchema, + TColumn extends Column, +> = TColumn['_']['notNull'] extends true ? TSchema + : t.Union<[TSchema, t.TNull]>; + +type HandleInsertColumn< + TSchema extends t.TSchema, + TColumn extends Column, +> = ColumnIsGeneratedAlwaysAs extends true ? never + : TColumn['_']['notNull'] extends true ? TColumn['_']['hasDefault'] extends true ? t.TOptional + : TSchema + : t.TOptional>; + +type HandleUpdateColumn< + TSchema extends t.TSchema, + TColumn extends Column, +> = ColumnIsGeneratedAlwaysAs extends true ? never + : TColumn['_']['notNull'] extends true ? t.TOptional + : t.TOptional>; + +export type HandleColumn< + TType extends 'select' | 'insert' | 'update', + TColumn extends Column, +> = GetTypeboxType< + TColumn['_']['data'], + TColumn['_']['dataType'], + TColumn['_']['columnType'], + GetEnumValuesFromColumn, + GetBaseColumn +> extends infer TSchema extends t.TSchema ? TSchema extends t.TAny ? t.TAny + : TType extends 'select' ? HandleSelectColumn + : TType extends 'insert' ? HandleInsertColumn + : TType extends 'update' ? HandleUpdateColumn + : TSchema + : t.TAny; diff --git a/drizzle-typebox/src/constants.ts b/drizzle-typebox/src/constants.ts new file mode 100644 index 000000000..99f5d7a42 --- /dev/null +++ b/drizzle-typebox/src/constants.ts @@ -0,0 +1,20 @@ +export const CONSTANTS = { + INT8_MIN: -128, + INT8_MAX: 127, + INT8_UNSIGNED_MAX: 255, + INT16_MIN: -32768, + INT16_MAX: 32767, + INT16_UNSIGNED_MAX: 65535, + INT24_MIN: -8388608, + INT24_MAX: 8388607, + INT24_UNSIGNED_MAX: 16777215, + INT32_MIN: -2147483648, + INT32_MAX: 2147483647, + INT32_UNSIGNED_MAX: 4294967295, + INT48_MIN: -140737488355328, + INT48_MAX: 140737488355327, + INT48_UNSIGNED_MAX: 281474976710655, + INT64_MIN: -9223372036854775808n, + INT64_MAX: 9223372036854775807n, + INT64_UNSIGNED_MAX: 18446744073709551615n, +}; diff --git a/drizzle-typebox/src/index.ts b/drizzle-typebox/src/index.ts index e70d72c43..0a6499e5b 100644 --- a/drizzle-typebox/src/index.ts +++ b/drizzle-typebox/src/index.ts @@ -1,344 +1,2 @@ -import type { - TAny, - TArray, - TBigInt, - TBoolean, - TDate, - TLiteral, - TNull, - TNumber, - TObject, - TOptional, - TSchema, - TString, - TUnion, -} from '@sinclair/typebox'; -import { Type } from '@sinclair/typebox'; -import { - type AnyColumn, - type Assume, - type Column, - type DrizzleTypeError, - type Equal, - getTableColumns, - is, - type Simplify, - type Table, -} from 'drizzle-orm'; -import { MySqlChar, MySqlVarBinary, MySqlVarChar } from 'drizzle-orm/mysql-core'; -import { type PgArray, PgChar, PgUUID, PgVarchar } from 'drizzle-orm/pg-core'; -import { SQLiteText } from 'drizzle-orm/sqlite-core'; - -type TUnionLiterals = T extends readonly [ - infer U extends string, - ...infer Rest extends string[], -] ? [TLiteral, ...TUnionLiterals] - : []; - -const literalSchema = Type.Union([ - Type.String(), - Type.Number(), - Type.Boolean(), - Type.Null(), -]); - -type Json = typeof jsonSchema; - -export const jsonSchema = Type.Union([ - literalSchema, - Type.Array(Type.Any()), - Type.Record(Type.String(), Type.Any()), -]); - -type TNullable = TUnion<[TType, TNull]>; - -type MapInsertColumnToTypebox< - TColumn extends Column, - TType extends TSchema, -> = TColumn['_']['notNull'] extends false ? TOptional> - : TColumn['_']['hasDefault'] extends true ? TOptional - : TType; - -type MapSelectColumnToTypebox< - TColumn extends Column, - TType extends TSchema, -> = TColumn['_']['notNull'] extends false ? TNullable : TType; - -type MapColumnToTypebox< - TColumn extends Column, - TType extends TSchema, - TMode extends 'insert' | 'select', -> = TMode extends 'insert' ? MapInsertColumnToTypebox - : MapSelectColumnToTypebox; - -type MaybeOptional< - TColumn extends Column, - TType extends TSchema, - TMode extends 'insert' | 'select', - TNoOptional extends boolean, -> = TNoOptional extends true ? TType - : MapColumnToTypebox; - -type GetTypeboxType = TColumn['_']['dataType'] extends infer TDataType - ? TDataType extends 'custom' ? TAny - : TDataType extends 'json' ? Json - : TColumn extends { enumValues: [string, ...string[]] } - ? Equal extends true ? TString - : TUnion> - : TDataType extends 'array' ? TArray< - GetTypeboxType< - Assume< - TColumn['_'], - { baseColumn: Column } - >['baseColumn'] - > - > - : TDataType extends 'bigint' ? TBigInt - : TDataType extends 'number' ? TNumber - : TDataType extends 'string' ? TString - : TDataType extends 'boolean' ? TBoolean - : TDataType extends 'date' ? TDate - : TAny - : never; - -type ValueOrUpdater = T | ((arg: TUpdaterArg) => T); - -type UnwrapValueOrUpdater = T extends ValueOrUpdater ? U - : never; - -export type Refine = { - [K in keyof TTable['_']['columns']]?: ValueOrUpdater< - TSchema, - TMode extends 'select' ? BuildSelectSchema - : BuildInsertSchema - >; -}; - -export type BuildInsertSchema< - TTable extends Table, - TRefine extends Refine | {}, - TNoOptional extends boolean = false, -> = TTable['_']['columns'] extends infer TColumns extends Record< - string, - Column -> ? { - [K in keyof TColumns & string]: MaybeOptional< - TColumns[K], - K extends keyof TRefine ? Assume, TSchema> - : GetTypeboxType, - 'insert', - TNoOptional - >; - } - : never; - -export type BuildSelectSchema< - TTable extends Table, - TRefine extends Refine, - TNoOptional extends boolean = false, -> = Simplify< - { - [K in keyof TTable['_']['columns']]: MaybeOptional< - TTable['_']['columns'][K], - K extends keyof TRefine ? Assume, TSchema> - : GetTypeboxType, - 'select', - TNoOptional - >; - } ->; - -export const Nullable = (schema: T) => Type.Union([schema, Type.Null()]); - -export function createInsertSchema< - TTable extends Table, - TRefine extends Refine = Refine, ->( - table: TTable, - /** - * @param refine Refine schema fields - */ - refine?: { - [K in keyof TRefine]: K extends keyof TTable['_']['columns'] ? TRefine[K] - : DrizzleTypeError< - `Column '${ - & K - & string}' does not exist in table '${TTable['_']['name']}'` - >; - }, - // -): TObject< - BuildInsertSchema< - TTable, - Equal> extends true ? {} : TRefine - > -> { - const columns = getTableColumns(table); - const columnEntries = Object.entries(columns); - - let schemaEntries = Object.fromEntries( - columnEntries.map(([name, column]) => { - return [name, mapColumnToSchema(column)]; - }), - ); - - if (refine) { - schemaEntries = Object.assign( - schemaEntries, - Object.fromEntries( - Object.entries(refine).map(([name, refineColumn]) => { - return [ - name, - typeof refineColumn === 'function' - ? refineColumn( - schemaEntries as BuildInsertSchema< - TTable, - {}, - true - >, - ) - : refineColumn, - ]; - }), - ), - ); - } - - for (const [name, column] of columnEntries) { - if (!column.notNull) { - schemaEntries[name] = Type.Optional(Nullable(schemaEntries[name]!)); - } else if (column.hasDefault) { - schemaEntries[name] = Type.Optional(schemaEntries[name]!); - } - } - - return Type.Object(schemaEntries) as any; -} - -export function createSelectSchema< - TTable extends Table, - TRefine extends Refine = Refine, ->( - table: TTable, - /** - * @param refine Refine schema fields - */ - refine?: { - [K in keyof TRefine]: K extends keyof TTable['_']['columns'] ? TRefine[K] - : DrizzleTypeError< - `Column '${ - & K - & string}' does not exist in table '${TTable['_']['name']}'` - >; - }, -): TObject< - BuildSelectSchema< - TTable, - Equal> extends true ? {} : TRefine - > -> { - const columns = getTableColumns(table); - const columnEntries = Object.entries(columns); - - let schemaEntries = Object.fromEntries( - columnEntries.map(([name, column]) => { - return [name, mapColumnToSchema(column)]; - }), - ); - - if (refine) { - schemaEntries = Object.assign( - schemaEntries, - Object.fromEntries( - Object.entries(refine).map(([name, refineColumn]) => { - return [ - name, - typeof refineColumn === 'function' - ? refineColumn( - schemaEntries as BuildSelectSchema< - TTable, - {}, - true - >, - ) - : refineColumn, - ]; - }), - ), - ); - } - - for (const [name, column] of columnEntries) { - if (!column.notNull) { - schemaEntries[name] = Nullable(schemaEntries[name]!); - } - } - - return Type.Object(schemaEntries) as any; -} - -function isWithEnum( - column: AnyColumn, -): column is typeof column & { enumValues: [string, ...string[]] } { - return ( - 'enumValues' in column - && Array.isArray(column.enumValues) - && column.enumValues.length > 0 - ); -} - -const uuidPattern = /^[\dA-Fa-f]{8}(?:-[\dA-Fa-f]{4}){3}-[\dA-Fa-f]{12}$/; - -function mapColumnToSchema(column: Column): TSchema { - let type: TSchema | undefined; - - if (isWithEnum(column)) { - type = column.enumValues?.length - ? Type.Union(column.enumValues.map((value) => Type.Literal(value))) - : Type.String(); - } - - if (!type) { - if (column.dataType === 'custom') { - type = Type.Any(); - } else if (column.dataType === 'json') { - type = jsonSchema; - } else if (column.dataType === 'array') { - type = Type.Array( - mapColumnToSchema((column as PgArray).baseColumn), - ); - } else if (column.dataType === 'number') { - type = Type.Number(); - } else if (column.dataType === 'bigint') { - type = Type.BigInt(); - } else if (column.dataType === 'boolean') { - type = Type.Boolean(); - } else if (column.dataType === 'date') { - type = Type.Date(); - } else if (column.dataType === 'string') { - const sType = Type.String(); - - if ( - (is(column, PgChar) - || is(column, PgVarchar) - || is(column, MySqlVarChar) - || is(column, MySqlVarBinary) - || is(column, MySqlChar) - || is(column, SQLiteText)) - && typeof column.length === 'number' - ) { - sType.maxLength = column.length; - } - - type = sType; - } else if (is(column, PgUUID)) { - type = Type.RegEx(uuidPattern); - } - } - - if (!type) { - type = Type.Any(); - } - - return type; -} +export * from './schema.ts'; +export * from './schema.types.ts'; diff --git a/drizzle-typebox/src/schema.ts b/drizzle-typebox/src/schema.ts new file mode 100644 index 000000000..b0291723e --- /dev/null +++ b/drizzle-typebox/src/schema.ts @@ -0,0 +1,144 @@ +import { Type as t } from '@sinclair/typebox'; +import type { TSchema } from '@sinclair/typebox'; +import { Column, getTableColumns, getViewSelectedFields, is, isTable, isView, SQL } from 'drizzle-orm'; +import type { Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import { columnToSchema, mapEnumValues } from './column.ts'; +import type { Conditions } from './schema.types.internal.ts'; +import type { + CreateInsertSchema, + CreateSchemaFactoryOptions, + CreateSelectSchema, + CreateUpdateSchema, +} from './schema.types.ts'; +import { isPgEnum } from './utils.ts'; + +function getColumns(tableLike: Table | View) { + return isTable(tableLike) ? getTableColumns(tableLike) : getViewSelectedFields(tableLike); +} + +function handleColumns( + columns: Record, + refinements: Record, + conditions: Conditions, + factory?: CreateSchemaFactoryOptions, +): TSchema { + const columnSchemas: Record = {}; + + for (const [key, selected] of Object.entries(columns)) { + if (!is(selected, Column) && !is(selected, SQL) && !is(selected, SQL.Aliased) && typeof selected === 'object') { + const columns = isTable(selected) || isView(selected) ? getColumns(selected) : selected; + columnSchemas[key] = handleColumns(columns, refinements[key] ?? {}, conditions, factory); + continue; + } + + const refinement = refinements[key]; + if (refinement !== undefined && typeof refinement !== 'function') { + columnSchemas[key] = refinement; + continue; + } + + const column = is(selected, Column) ? selected : undefined; + const schema = column ? columnToSchema(column, factory?.typeboxInstance ?? t) : t.Any(); + const refined = typeof refinement === 'function' ? refinement(schema) : schema; + + if (conditions.never(column)) { + continue; + } else { + columnSchemas[key] = refined; + } + + if (column) { + if (conditions.nullable(column)) { + columnSchemas[key] = t.Union([columnSchemas[key]!, t.Null()]); + } + + if (conditions.optional(column)) { + columnSchemas[key] = t.Optional(columnSchemas[key]!); + } + } + } + + return t.Object(columnSchemas) as any; +} + +function handleEnum(enum_: PgEnum, factory?: CreateSchemaFactoryOptions) { + const typebox: typeof t = factory?.typeboxInstance ?? t; + return typebox.Enum(mapEnumValues(enum_.enumValues)); +} + +const selectConditions: Conditions = { + never: () => false, + optional: () => false, + nullable: (column) => !column.notNull, +}; + +const insertConditions: Conditions = { + never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always', + optional: (column) => !column.notNull || (column.notNull && column.hasDefault), + nullable: (column) => !column.notNull, +}; + +const updateConditions: Conditions = { + never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always', + optional: () => true, + nullable: (column) => !column.notNull, +}; + +export const createSelectSchema: CreateSelectSchema = ( + entity: Table | View | PgEnum<[string, ...string[]]>, + refine?: Record, +) => { + if (isPgEnum(entity)) { + return handleEnum(entity); + } + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, selectConditions) as any; +}; + +export const createInsertSchema: CreateInsertSchema = ( + entity: Table, + refine?: Record, +) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, insertConditions) as any; +}; + +export const createUpdateSchema: CreateUpdateSchema = ( + entity: Table, + refine?: Record, +) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, updateConditions) as any; +}; + +export function createSchemaFactory(options?: CreateSchemaFactoryOptions) { + const createSelectSchema: CreateSelectSchema = ( + entity: Table | View | PgEnum<[string, ...string[]]>, + refine?: Record, + ) => { + if (isPgEnum(entity)) { + return handleEnum(entity, options); + } + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, selectConditions, options) as any; + }; + + const createInsertSchema: CreateInsertSchema = ( + entity: Table, + refine?: Record, + ) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, insertConditions, options) as any; + }; + + const createUpdateSchema: CreateUpdateSchema = ( + entity: Table, + refine?: Record, + ) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, updateConditions, options) as any; + }; + + return { createSelectSchema, createInsertSchema, createUpdateSchema }; +} diff --git a/drizzle-typebox/src/schema.types.internal.ts b/drizzle-typebox/src/schema.types.internal.ts new file mode 100644 index 000000000..e48b764f4 --- /dev/null +++ b/drizzle-typebox/src/schema.types.internal.ts @@ -0,0 +1,94 @@ +import type * as t from '@sinclair/typebox'; +import type { Assume, Column, DrizzleTypeError, SelectedFieldsFlat, Simplify, Table, View } from 'drizzle-orm'; +import type { GetBaseColumn, GetEnumValuesFromColumn, GetTypeboxType, HandleColumn } from './column.types.ts'; +import type { GetSelection, RemoveNever } from './utils.ts'; + +export interface Conditions { + never: (column?: Column) => boolean; + optional: (column: Column) => boolean; + nullable: (column: Column) => boolean; +} + +export type BuildRefineColumns< + TColumns extends Record, +> = Simplify< + RemoveNever< + { + [K in keyof TColumns]: TColumns[K] extends infer TColumn extends Column ? GetTypeboxType< + TColumn['_']['data'], + TColumn['_']['dataType'], + TColumn['_']['columnType'], + GetEnumValuesFromColumn, + GetBaseColumn + > extends infer TSchema extends t.TSchema ? TSchema + : t.TAny + : TColumns[K] extends infer TObject extends SelectedFieldsFlat | Table | View + ? BuildRefineColumns> + : TColumns[K]; + } + > +>; + +export type BuildRefine< + TColumns extends Record, +> = BuildRefineColumns extends infer TBuildColumns ? { + [K in keyof TBuildColumns]?: TBuildColumns[K] extends t.TSchema + ? ((schema: TBuildColumns[K]) => t.TSchema) | t.TSchema + : TBuildColumns[K] extends Record ? Simplify> + : never; + } + : never; + +type HandleRefinement< + TType extends 'select' | 'insert' | 'update', + TRefinement extends t.TSchema | ((schema: t.TSchema) => t.TSchema), + TColumn extends Column, +> = TRefinement extends (schema: any) => t.TSchema ? (TColumn['_']['notNull'] extends true ? ReturnType + : t.TUnion<[ReturnType, t.TNull]>) extends infer TSchema + ? TType extends 'update' ? t.TOptional> : TSchema + : t.TSchema + : TRefinement; + +type IsRefinementDefined = TKey extends keyof TRefinements + ? TRefinements[TKey] extends t.TSchema | ((schema: any) => any) ? true + : false + : false; + +export type BuildSchema< + TType extends 'select' | 'insert' | 'update', + TColumns extends Record, + TRefinements extends Record | undefined, +> = t.TObject< + Simplify< + RemoveNever< + { + [K in keyof TColumns]: TColumns[K] extends infer TColumn extends Column + ? TRefinements extends object + ? IsRefinementDefined> extends true + ? HandleRefinement], TColumn> + : HandleColumn + : HandleColumn + : TColumns[K] extends infer TObject extends SelectedFieldsFlat | Table | View ? BuildSchema< + TType, + GetSelection, + TRefinements extends object + ? TRefinements[Assume] extends infer TNestedRefinements extends object + ? TNestedRefinements + : undefined + : undefined + > + : t.TAny; + } + > + > +>; + +export type NoUnknownKeys< + TRefinement extends Record, + TCompare extends Record, +> = { + [K in keyof TRefinement]: K extends keyof TCompare ? TRefinement[K] extends t.TSchema ? TRefinement[K] + : TRefinement[K] extends Record ? NoUnknownKeys + : TRefinement[K] + : DrizzleTypeError<`Found unknown key in refinement: "${K & string}"`>; +}; diff --git a/drizzle-typebox/src/schema.types.ts b/drizzle-typebox/src/schema.types.ts new file mode 100644 index 000000000..abbb4f8eb --- /dev/null +++ b/drizzle-typebox/src/schema.types.ts @@ -0,0 +1,53 @@ +import type * as t from '@sinclair/typebox'; +import type { Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import type { EnumValuesToEnum } from './column.types.ts'; +import type { BuildRefine, BuildSchema, NoUnknownKeys } from './schema.types.internal.ts'; + +export interface CreateSelectSchema { + (table: TTable): BuildSchema<'select', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine, + >( + table: TTable, + refine?: NoUnknownKeys, + ): BuildSchema<'select', TTable['_']['columns'], TRefine>; + + (view: TView): BuildSchema<'select', TView['_']['selectedFields'], undefined>; + < + TView extends View, + TRefine extends BuildRefine, + >( + view: TView, + refine: NoUnknownKeys, + ): BuildSchema<'select', TView['_']['selectedFields'], TRefine>; + + >(enum_: TEnum): t.TEnum>; +} + +export interface CreateInsertSchema { + (table: TTable): BuildSchema<'insert', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine>, + >( + table: TTable, + refine?: NoUnknownKeys, + ): BuildSchema<'insert', TTable['_']['columns'], TRefine>; +} + +export interface CreateUpdateSchema { + (table: TTable): BuildSchema<'update', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine>, + >( + table: TTable, + refine?: TRefine, + ): BuildSchema<'update', TTable['_']['columns'], TRefine>; +} + +export interface CreateSchemaFactoryOptions { + typeboxInstance?: any; +} diff --git a/drizzle-typebox/src/utils.ts b/drizzle-typebox/src/utils.ts new file mode 100644 index 000000000..686bf01b8 --- /dev/null +++ b/drizzle-typebox/src/utils.ts @@ -0,0 +1,50 @@ +import type { Kind, Static, TSchema } from '@sinclair/typebox'; +import type { Column, SelectedFieldsFlat, Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import type { literalSchema } from './column.ts'; + +export function isColumnType(column: Column, columnTypes: string[]): column is T { + return columnTypes.includes(column.columnType); +} + +export function isWithEnum(column: Column): column is typeof column & { enumValues: [string, ...string[]] } { + return 'enumValues' in column && Array.isArray(column.enumValues) && column.enumValues.length > 0; +} + +export const isPgEnum: (entity: any) => entity is PgEnum<[string, ...string[]]> = isWithEnum as any; + +type Literal = Static; +export type Json = Literal | { [key: string]: Json } | Json[]; +export interface JsonSchema extends TSchema { + [Kind]: 'Union'; + static: Json; + anyOf: Json; +} +export interface BufferSchema extends TSchema { + [Kind]: 'Buffer'; + static: Buffer; + type: 'buffer'; +} + +export type IsNever = [T] extends [never] ? true : false; + +export type ArrayHasAtLeastOneValue = TEnum extends [infer TString, ...any[]] + ? TString extends `${infer TLiteral}` ? TLiteral extends any ? true + : false + : false + : false; + +export type ColumnIsGeneratedAlwaysAs = TColumn['_']['identity'] extends 'always' ? true + : TColumn['_']['generated'] extends undefined ? false + : TColumn['_']['generated'] extends infer TGenerated extends { type: string } + ? TGenerated['type'] extends 'byDefault' ? false + : true + : true; + +export type RemoveNever = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +}; + +export type GetSelection | Table | View> = T extends Table ? T['_']['columns'] + : T extends View ? T['_']['selectedFields'] + : T; diff --git a/drizzle-typebox/tests/mysql.test.ts b/drizzle-typebox/tests/mysql.test.ts index d6942a529..8863f0fb9 100644 --- a/drizzle-typebox/tests/mysql.test.ts +++ b/drizzle-typebox/tests/mysql.test.ts @@ -1,378 +1,495 @@ -import { Type } from '@sinclair/typebox'; -import { Value } from '@sinclair/typebox/value'; -import { - bigint, - binary, - boolean, - char, - customType, - date, - datetime, - decimal, - double, - float, - int, - json, - longtext, - mediumint, - mediumtext, - mysqlEnum, - mysqlTable, - real, - serial, - smallint, - text, - time, - timestamp, - tinyint, - tinytext, - varbinary, - varchar, - year, -} from 'drizzle-orm/mysql-core'; -import { expect, test } from 'vitest'; -import { createInsertSchema, createSelectSchema, jsonSchema } from '../src'; -import { expectSchemaShape } from './utils.ts'; - -const customInt = customType<{ data: number }>({ - dataType() { - return 'int'; - }, +import { Type as t } from '@sinclair/typebox'; +import { type Equal, sql } from 'drizzle-orm'; +import { customType, int, mysqlSchema, mysqlTable, mysqlView, serial, text } from 'drizzle-orm/mysql-core'; +import { test } from 'vitest'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; + +const intSchema = t.Integer({ + minimum: CONSTANTS.INT32_MIN, + maximum: CONSTANTS.INT32_MAX, +}); +const serialNumberModeSchema = t.Integer({ + minimum: 0, + maximum: Number.MAX_SAFE_INTEGER, }); +const textSchema = t.String({ maxLength: CONSTANTS.INT16_UNSIGNED_MAX }); -const testTable = mysqlTable('test', { - bigint: bigint('bigint', { mode: 'bigint' }).notNull(), - bigintNumber: bigint('bigintNumber', { mode: 'number' }).notNull(), - binary: binary('binary').notNull(), - boolean: boolean('boolean').notNull(), - char: char('char', { length: 4 }).notNull(), - charEnum: char('char', { enum: ['a', 'b', 'c'] }).notNull(), - customInt: customInt('customInt').notNull(), - date: date('date').notNull(), - dateString: date('dateString', { mode: 'string' }).notNull(), - datetime: datetime('datetime').notNull(), - datetimeString: datetime('datetimeString', { mode: 'string' }).notNull(), - decimal: decimal('decimal').notNull(), - double: double('double').notNull(), - enum: mysqlEnum('enum', ['a', 'b', 'c']).notNull(), - float: float('float').notNull(), - int: int('int').notNull(), - json: json('json').notNull(), - mediumint: mediumint('mediumint').notNull(), - real: real('real').notNull(), - serial: serial('serial').notNull(), - smallint: smallint('smallint').notNull(), - text: text('text').notNull(), - textEnum: text('textEnum', { enum: ['a', 'b', 'c'] }).notNull(), - tinytext: tinytext('tinytext').notNull(), - tinytextEnum: tinytext('tinytextEnum', { enum: ['a', 'b', 'c'] }).notNull(), - mediumtext: mediumtext('mediumtext').notNull(), - mediumtextEnum: mediumtext('mediumtextEnum', { - enum: ['a', 'b', 'c'], - }).notNull(), - longtext: longtext('longtext').notNull(), - longtextEnum: longtext('longtextEnum', { enum: ['a', 'b', 'c'] }).notNull(), - time: time('time').notNull(), - timestamp: timestamp('timestamp').notNull(), - timestampString: timestamp('timestampString', { mode: 'string' }).notNull(), - tinyint: tinyint('tinyint').notNull(), - varbinary: varbinary('varbinary', { length: 200 }).notNull(), - varchar: varchar('varchar', { length: 200 }).notNull(), - varcharEnum: varchar('varcharEnum', { - length: 1, - enum: ['a', 'b', 'c'], - }).notNull(), - year: year('year').notNull(), - autoIncrement: int('autoIncrement').notNull().autoincrement(), +test('table - select', (tc) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -const testTableRow = { - bigint: BigInt(1), - bigintNumber: 1, - binary: 'binary', - boolean: true, - char: 'char', - charEnum: 'a', - customInt: { data: 1 }, - date: new Date(), - dateString: new Date().toISOString(), - datetime: new Date(), - datetimeString: new Date().toISOString(), - decimal: '1.1', - double: 1.1, - enum: 'a', - float: 1.1, - int: 1, - json: { data: 1 }, - mediumint: 1, - real: 1.1, - serial: 1, - smallint: 1, - text: 'text', - textEnum: 'a', - tinytext: 'tinytext', - tinytextEnum: 'a', - mediumtext: 'mediumtext', - mediumtextEnum: 'a', - longtext: 'longtext', - longtextEnum: 'a', - time: '00:00:00', - timestamp: new Date(), - timestampString: new Date().toISOString(), - tinyint: 1, - varbinary: 'A'.repeat(200), - varchar: 'A'.repeat(200), - varcharEnum: 'a', - year: 2021, - autoIncrement: 1, -}; - -test('insert valid row', () => { - const schema = createInsertSchema(testTable); - - expect(Value.Check( - schema, - testTableRow, - )).toBeTruthy(); +test('table in schema - select', (tc) => { + const schema = mysqlSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('insert invalid varchar length', () => { - const schema = createInsertSchema(testTable); +test('table - insert', (tc) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); - expect(Value.Check(schema, { - ...testTableRow, - varchar: 'A'.repeat(201), - })).toBeFalsy(); + const result = createInsertSchema(table); + const expected = t.Object({ + id: t.Optional(serialNumberModeSchema), + name: textSchema, + age: t.Optional(t.Union([intSchema, t.Null()])), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('insert smaller char length should work', () => { - const schema = createInsertSchema(testTable); +test('table - update', (tc) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); - expect(Value.Check(schema, { ...testTableRow, char: 'abc' })).toBeTruthy(); + const result = createUpdateSchema(table); + const expected = t.Object({ + id: t.Optional(serialNumberModeSchema), + name: t.Optional(textSchema), + age: t.Optional(t.Union([intSchema, t.Null()])), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('insert larger char length should fail', () => { - const schema = createInsertSchema(testTable); +test('view qb - select', (tc) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); - expect(Value.Check(schema, { ...testTableRow, char: 'abcde' })).toBeFalsy(); + const result = createSelectSchema(view); + const expected = t.Object({ id: serialNumberModeSchema, age: t.Any() }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('insert schema', (t) => { - const actual = createInsertSchema(testTable); - - const expected = Type.Object({ - bigint: Type.BigInt(), - bigintNumber: Type.Number(), - binary: Type.String(), - boolean: Type.Boolean(), - char: Type.String({ minLength: 4, maxLength: 4 }), - charEnum: Type.Union([Type.Literal('a'), Type.Literal('b'), Type.Literal('c')]), - customInt: Type.Any(), - date: Type.Date(), - dateString: Type.String(), - datetime: Type.Date(), - datetimeString: Type.String(), - decimal: Type.String(), - double: Type.Number(), - enum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - float: Type.Number(), - int: Type.Number(), - json: jsonSchema, - mediumint: Type.Number(), - real: Type.Number(), - serial: Type.Optional(Type.Number()), - smallint: Type.Number(), - text: Type.String(), - textEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - tinytext: Type.String(), - tinytextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - mediumtext: Type.String(), - mediumtextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - longtext: Type.String(), - longtextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - time: Type.String(), - timestamp: Type.Date(), - timestampString: Type.String(), - tinyint: Type.Number(), - varbinary: Type.String({ maxLength: 200 }), - varchar: Type.String({ maxLength: 200 }), - varcharEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - year: Type.Number(), - autoIncrement: Type.Optional(Type.Number()), +test('view columns - select', (tc) => { + const view = mysqlView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = t.Object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('view with nested fields - select', (tc) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), }); + const view = mysqlView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) + ); - expectSchemaShape(t, expected).from(actual); + const result = createSelectSchema(view); + const expected = t.Object({ + id: serialNumberModeSchema, + nested: t.Object({ name: textSchema, age: t.Any() }), + table: t.Object({ id: serialNumberModeSchema, name: textSchema }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('select schema', (t) => { - const actual = createSelectSchema(testTable); - - const expected = Type.Object({ - bigint: Type.BigInt(), - bigintNumber: Type.Number(), - binary: Type.String(), - boolean: Type.Boolean(), - char: Type.String({ minLength: 4, maxLength: 4 }), - charEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - customInt: Type.Any(), - date: Type.Date(), - dateString: Type.String(), - datetime: Type.Date(), - datetimeString: Type.String(), - decimal: Type.String(), - double: Type.Number(), - enum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - float: Type.Number(), - int: Type.Number(), - // - json: jsonSchema, - mediumint: Type.Number(), - real: Type.Number(), - serial: Type.Number(), - smallint: Type.Number(), - text: Type.String(), - textEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - tinytext: Type.String(), - tinytextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - mediumtext: Type.String(), - mediumtextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - longtext: Type.String(), - longtextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - time: Type.String(), - timestamp: Type.Date(), - timestampString: Type.String(), - tinyint: Type.Number(), - varbinary: Type.String({ maxLength: 200 }), - varchar: Type.String({ maxLength: 200 }), - varcharEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - year: Type.Number(), - autoIncrement: Type.Number(), +test('nullability - select', (tc) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), }); - expectSchemaShape(t, expected).from(actual); + const result = createSelectSchema(table); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: intSchema, + c3: t.Union([intSchema, t.Null()]), + c4: intSchema, + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('select schema w/ refine', (t) => { - const actual = createSelectSchema(testTable, { - bigint: (_) => Type.BigInt({ minimum: 0n }), +test('nullability - insert', (tc) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), }); - const expected = Type.Object({ - bigint: Type.BigInt({ minimum: 0n }), - bigintNumber: Type.Number(), - binary: Type.String(), - boolean: Type.Boolean(), - char: Type.String({ minLength: 5, maxLength: 5 }), - charEnum: Type.Union([Type.Literal('a'), Type.Literal('b'), Type.Literal('c')]), - customInt: Type.Any(), - date: Type.Date(), - dateString: Type.String(), - datetime: Type.Date(), - datetimeString: Type.String(), - decimal: Type.String(), - double: Type.Number(), - enum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - float: Type.Number(), - int: Type.Number(), - json: jsonSchema, - mediumint: Type.Number(), - real: Type.Number(), - serial: Type.Number(), - smallint: Type.Number(), - text: Type.String(), - textEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - tinytext: Type.String(), - tinytextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - mediumtext: Type.String(), - mediumtextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - longtext: Type.String(), - longtextEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - time: Type.String(), - timestamp: Type.Date(), - timestampString: Type.String(), - tinyint: Type.Number(), - varbinary: Type.String({ maxLength: 200 }), - varchar: Type.String({ maxLength: 200 }), - varcharEnum: Type.Union([ - Type.Literal('a'), - Type.Literal('b'), - Type.Literal('c'), - ]), - year: Type.Number(), - autoIncrement: Type.Number(), + const result = createInsertSchema(table); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: intSchema, + c3: t.Optional(t.Union([intSchema, t.Null()])), + c4: t.Optional(intSchema), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('nullability - update', (tc) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Optional(intSchema), + c3: t.Optional(t.Union([intSchema, t.Null()])), + c4: t.Optional(intSchema), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - select', (tc) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), }); - expectSchemaShape(t, expected).from(actual); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); + +test('refine table - select with custom data type', (tc) => { + const customText = customType({ dataType: () => 'text' }); + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); + + const customTextSchema = t.String({ minLength: 1, maxLength: 100 }); + const result = createSelectSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: customTextSchema, + }); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: customTextSchema, + }); + + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (tc) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - update', (tc) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Optional(t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 })), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine view - select', (tc) => { + const table = mysqlTable('test', { + c1: int(), + c2: int(), + c3: int(), + c4: int(), + c5: int(), + c6: int(), + }); + const view = mysqlView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + nested: { + c5: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c6: t.Integer({ minimum: 1, maximum: 10 }), + }, + table: { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }, + }); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), + c3: t.Integer({ minimum: 1, maximum: 10 }), + nested: t.Object({ + c4: t.Union([intSchema, t.Null()]), + c5: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), + c6: t.Integer({ minimum: 1, maximum: 10 }), + }), + table: t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: t.Union([intSchema, t.Null()]), + c5: t.Union([intSchema, t.Null()]), + c6: t.Union([intSchema, t.Null()]), + }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('all data types', (tc) => { + const table = mysqlTable('test', ({ + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + mysqlEnum, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varchar, + varbinary, + year, + longtext, + mediumtext, + tinytext, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(), + bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(), + binary: binary({ length: 10 }).notNull(), + boolean: boolean().notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + datetime1: datetime({ mode: 'date' }).notNull(), + datetime2: datetime({ mode: 'string' }).notNull(), + decimal1: decimal().notNull(), + decimal2: decimal({ unsigned: true }).notNull(), + double1: double().notNull(), + double2: double({ unsigned: true }).notNull(), + float1: float().notNull(), + float2: float({ unsigned: true }).notNull(), + int1: int().notNull(), + int2: int({ unsigned: true }).notNull(), + json: json().notNull(), + mediumint1: mediumint().notNull(), + mediumint2: mediumint({ unsigned: true }).notNull(), + enum: mysqlEnum('enum', ['a', 'b', 'c']).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint1: smallint().notNull(), + smallint2: smallint({ unsigned: true }).notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + tinyint1: tinyint().notNull(), + tinyint2: tinyint({ unsigned: true }).notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + varbinary: varbinary({ length: 10 }).notNull(), + year: year().notNull(), + longtext1: longtext().notNull(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(), + mediumtext1: mediumtext().notNull(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(), + tinytext1: tinytext().notNull(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(), + })); + + const result = createSelectSchema(table); + const expected = t.Object({ + bigint1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }), + bigint2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }), + bigint3: t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }), + bigint4: t.BigInt({ minimum: 0n, maximum: CONSTANTS.INT64_UNSIGNED_MAX }), + binary: t.String(), + boolean: t.Boolean(), + char1: t.String({ minLength: 10, maxLength: 10 }), + char2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + date1: t.Date(), + date2: t.String(), + datetime1: t.Date(), + datetime2: t.String(), + decimal1: t.String(), + decimal2: t.String(), + double1: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }), + double2: t.Number({ minimum: 0, maximum: CONSTANTS.INT48_UNSIGNED_MAX }), + float1: t.Number({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }), + float2: t.Number({ minimum: 0, maximum: CONSTANTS.INT24_UNSIGNED_MAX }), + int1: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }), + int2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT32_UNSIGNED_MAX }), + json: jsonSchema, + mediumint1: t.Integer({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }), + mediumint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT24_UNSIGNED_MAX }), + enum: t.Enum({ a: 'a', b: 'b', c: 'c' }), + real: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }), + serial: t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }), + smallint1: t.Integer({ minimum: CONSTANTS.INT16_MIN, maximum: CONSTANTS.INT16_MAX }), + smallint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT16_UNSIGNED_MAX }), + text1: t.String({ maxLength: CONSTANTS.INT16_UNSIGNED_MAX }), + text2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + time: t.String(), + timestamp1: t.Date(), + timestamp2: t.String(), + tinyint1: t.Integer({ minimum: CONSTANTS.INT8_MIN, maximum: CONSTANTS.INT8_MAX }), + tinyint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT8_UNSIGNED_MAX }), + varchar1: t.String({ maxLength: 10 }), + varchar2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + varbinary: t.String(), + year: t.Integer({ minimum: 1901, maximum: 2155 }), + longtext1: t.String({ maxLength: CONSTANTS.INT32_UNSIGNED_MAX }), + longtext2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + mediumtext1: t.String({ maxLength: CONSTANTS.INT24_UNSIGNED_MAX }), + mediumtext2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + tinytext1: t.String({ maxLength: CONSTANTS.INT8_UNSIGNED_MAX }), + tinytext2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +/* Disallow unknown keys in table refinement - select */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = mysqlTable('test', { id: int() }); + const view = mysqlView('test').as((qb) => qb.select().from(table)); + const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: t.String() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: t.String() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = mysqlView('test', { id: int() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: t.String() }); +} diff --git a/drizzle-typebox/tests/pg.test.ts b/drizzle-typebox/tests/pg.test.ts index 355dee531..8fd8148d8 100644 --- a/drizzle-typebox/tests/pg.test.ts +++ b/drizzle-typebox/tests/pg.test.ts @@ -1,189 +1,540 @@ -import { Type } from '@sinclair/typebox'; -import { Value } from '@sinclair/typebox/value'; -import { char, date, integer, pgEnum, pgTable, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core'; -import { expect, test } from 'vitest'; -import { createInsertSchema, createSelectSchema, Nullable } from '../src'; -import { expectSchemaShape } from './utils.ts'; - -export const roleEnum = pgEnum('role', ['admin', 'user']); - -const users = pgTable('users', { - a: integer('a').array(), - id: serial('id').primaryKey(), - name: text('name'), - email: text('email').notNull(), - birthdayString: date('birthday_string').notNull(), - birthdayDate: date('birthday_date', { mode: 'date' }).notNull(), - createdAt: timestamp('created_at').notNull().defaultNow(), - role: roleEnum('role').notNull(), - roleText: text('role1', { enum: ['admin', 'user'] }).notNull(), - roleText2: text('role2', { enum: ['admin', 'user'] }) - .notNull() - .default('user'), - profession: varchar('profession', { length: 20 }).notNull(), - initials: char('initials', { length: 2 }).notNull(), -}); - -const testUser = { - a: [1, 2, 3], - id: 1, - name: 'John Doe', - email: 'john.doe@example.com', - birthdayString: '1990-01-01', - birthdayDate: new Date('1990-01-01'), - createdAt: new Date(), - role: 'admin', - roleText: 'admin', - roleText2: 'admin', - profession: 'Software Engineer', - initials: 'JD', -}; - -test('users insert valid user', () => { - const schema = createInsertSchema(users); - - expect(Value.Check(schema, testUser)).toBeTruthy(); -}); - -test('users insert invalid varchar', () => { - const schema = createInsertSchema(users); - - expect(Value.Check(schema, { - ...testUser, - profession: 'Chief Executive Officer', - })).toBeFalsy(); -}); - -test('users insert invalid char', () => { - const schema = createInsertSchema(users); - - expect(Value.Check(schema, { ...testUser, initials: 'JoDo' })).toBeFalsy(); -}); - -test('users insert schema', (t) => { - const actual = createInsertSchema(users, { - id: () => Type.Number({ minimum: 0 }), - email: () => Type.String(), - roleText: Type.Union([ - Type.Literal('user'), - Type.Literal('manager'), - Type.Literal('admin'), - ]), - }); - - (() => { - { - createInsertSchema(users, { - // @ts-expect-error (missing property) - foobar: Type.Number(), - }); - } - - { - createInsertSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } - }); - - const expected = Type.Object({ - a: Type.Optional(Nullable(Type.Array(Type.Number()))), - id: Type.Optional(Type.Number({ minimum: 0 })), - name: Type.Optional(Nullable(Type.String())), - email: Type.String(), - birthdayString: Type.String(), - birthdayDate: Type.Date(), - createdAt: Type.Optional(Type.Date()), - role: Type.Union([Type.Literal('admin'), Type.Literal('user')]), - roleText: Type.Union([ - Type.Literal('user'), - Type.Literal('manager'), - Type.Literal('admin'), - ]), - roleText2: Type.Optional( - Type.Union([Type.Literal('admin'), Type.Literal('user')]), - ), - profession: Type.String({ maxLength: 20, minLength: 1 }), - initials: Type.String({ maxLength: 2, minLength: 1 }), - }); - - expectSchemaShape(t, expected).from(actual); -}); - -test('users insert schema w/ defaults', (t) => { - const actual = createInsertSchema(users); - - const expected = Type.Object({ - a: Type.Optional(Nullable(Type.Array(Type.Number()))), - id: Type.Optional(Type.Number()), - name: Type.Optional(Nullable(Type.String())), - email: Type.String(), - birthdayString: Type.String(), - birthdayDate: Type.Date(), - createdAt: Type.Optional(Type.Date()), - role: Type.Union([Type.Literal('admin'), Type.Literal('user')]), - roleText: Type.Union([Type.Literal('admin'), Type.Literal('user')]), - roleText2: Type.Optional( - Type.Union([Type.Literal('admin'), Type.Literal('user')]), - ), - profession: Type.String({ maxLength: 20, minLength: 1 }), - initials: Type.String({ maxLength: 2, minLength: 1 }), - }); - - expectSchemaShape(t, expected).from(actual); -}); - -test('users select schema', (t) => { - const actual = createSelectSchema(users, { - id: () => Type.Number({ minimum: 0 }), - email: () => Type.String(), - roleText: Type.Union([ - Type.Literal('admin'), - Type.Literal('user'), - Type.Literal('manager'), - ]), - }); - - const expected = Type.Object({ - a: Nullable(Type.Array(Type.Number())), - id: Type.Number({ minimum: 0 }), - name: Nullable(Type.String()), - email: Type.String(), - birthdayString: Type.String(), - birthdayDate: Type.Date(), - createdAt: Type.Date(), - role: Type.Union([Type.Literal('admin'), Type.Literal('user')]), - roleText: Type.Union([ - Type.Literal('admin'), - Type.Literal('user'), - Type.Literal('manager'), - ]), - roleText2: Type.Union([Type.Literal('admin'), Type.Literal('user')]), - profession: Type.String({ maxLength: 20, minLength: 1 }), - initials: Type.String({ maxLength: 2, minLength: 1 }), - }); - - expectSchemaShape(t, expected).from(actual); -}); - -test('users select schema w/ defaults', (t) => { - const actual = createSelectSchema(users); - - const expected = Type.Object({ - a: Nullable(Type.Array(Type.Number())), - id: Type.Number(), - name: Nullable(Type.String()), - email: Type.String(), - birthdayString: Type.String(), - birthdayDate: Type.Date(), - createdAt: Type.Date(), - role: Type.Union([Type.Literal('admin'), Type.Literal('user')]), - roleText: Type.Union([Type.Literal('admin'), Type.Literal('user')]), - roleText2: Type.Union([Type.Literal('admin'), Type.Literal('user')]), - profession: Type.String({ maxLength: 20, minLength: 1 }), - initials: Type.String({ maxLength: 2, minLength: 1 }), - }); - - expectSchemaShape(t, expected).from(actual); +import { Type as t } from '@sinclair/typebox'; +import { type Equal, sql } from 'drizzle-orm'; +import { + customType, + integer, + pgEnum, + pgMaterializedView, + pgSchema, + pgTable, + pgView, + serial, + text, +} from 'drizzle-orm/pg-core'; +import { test } from 'vitest'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectEnumValues, expectSchemaShape } from './utils.ts'; + +const integerSchema = t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }); +const textSchema = t.String(); + +test('table - select', (tc) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ id: integerSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table in schema - select', (tc) => { + const schema = pgSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ id: integerSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table - insert', (tc) => { + const table = pgTable('test', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer(), + }); + + const result = createInsertSchema(table); + const expected = t.Object({ name: textSchema, age: t.Optional(t.Union([integerSchema, t.Null()])) }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table - update', (tc) => { + const table = pgTable('test', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer(), + }); + + const result = createUpdateSchema(table); + const expected = t.Object({ + name: t.Optional(textSchema), + age: t.Optional(t.Union([integerSchema, t.Null()])), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('view qb - select', (tc) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = t.Object({ id: integerSchema, age: t.Any() }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('view columns - select', (tc) => { + const view = pgView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = t.Object({ id: integerSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('materialized view qb - select', (tc) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgMaterializedView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = t.Object({ id: integerSchema, age: t.Any() }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('materialized view columns - select', (tc) => { + const view = pgView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = t.Object({ id: integerSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('view with nested fields - select', (tc) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgMaterializedView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view); + const expected = t.Object({ + id: integerSchema, + nested: t.Object({ name: textSchema, age: t.Any() }), + table: t.Object({ id: integerSchema, name: textSchema }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('enum - select', (tc) => { + const enum_ = pgEnum('test', ['a', 'b', 'c']); + + const result = createSelectSchema(enum_); + const expected = t.Enum({ a: 'a', b: 'b', c: 'c' }); + expectEnumValues(tc, expected).from(result); + Expect>(); +}); + +test('nullability - select', (tc) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ + c1: t.Union([integerSchema, t.Null()]), + c2: integerSchema, + c3: t.Union([integerSchema, t.Null()]), + c4: integerSchema, + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('nullability - insert', (tc) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + c5: integer().generatedAlwaysAs(1), + c6: integer().generatedAlwaysAsIdentity(), + c7: integer().generatedByDefaultAsIdentity(), + }); + + const result = createInsertSchema(table); + const expected = t.Object({ + c1: t.Optional(t.Union([integerSchema, t.Null()])), + c2: integerSchema, + c3: t.Optional(t.Union([integerSchema, t.Null()])), + c4: t.Optional(integerSchema), + c7: t.Optional(integerSchema), + }); + expectSchemaShape(tc, expected).from(result); +}); + +test('nullability - update', (tc) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + c5: integer().generatedAlwaysAs(1), + c6: integer().generatedAlwaysAsIdentity(), + c7: integer().generatedByDefaultAsIdentity(), + }); + + const result = createUpdateSchema(table); + const expected = t.Object({ + c1: t.Optional(t.Union([integerSchema, t.Null()])), + c2: t.Optional(integerSchema), + c3: t.Optional(t.Union([integerSchema, t.Null()])), + c4: t.Optional(integerSchema), + c7: t.Optional(integerSchema), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - select', (tc) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Union([integerSchema, t.Null()]), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (tc) => { + const customText = customType({ dataType: () => 'text' }); + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: customText(), + }); + + const customTextSchema = t.String({ minLength: 1, maxLength: 100 }); + const result = createSelectSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: customTextSchema, + }); + const expected = t.Object({ + c1: t.Union([integerSchema, t.Null()]), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: customTextSchema, + }); + + expectSchemaShape(tc, expected).from(result); + Expect>(); }); + +test('refine table - insert', (tc) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: integer().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Optional(t.Union([integerSchema, t.Null()])), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - update', (tc) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: integer().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Optional(t.Union([integerSchema, t.Null()])), + c2: t.Optional(t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 })), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine view - select', (tc) => { + const table = pgTable('test', { + c1: integer(), + c2: integer(), + c3: integer(), + c4: integer(), + c5: integer(), + c6: integer(), + }); + const view = pgView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + nested: { + c5: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c6: t.Integer({ minimum: 1, maximum: 10 }), + }, + table: { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }, + }); + const expected = t.Object({ + c1: t.Union([integerSchema, t.Null()]), + c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), + c3: t.Integer({ minimum: 1, maximum: 10 }), + nested: t.Object({ + c4: t.Union([integerSchema, t.Null()]), + c5: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), + c6: t.Integer({ minimum: 1, maximum: 10 }), + }), + table: t.Object({ + c1: t.Union([integerSchema, t.Null()]), + c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: t.Union([integerSchema, t.Null()]), + c5: t.Union([integerSchema, t.Null()]), + c6: t.Union([integerSchema, t.Null()]), + }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('all data types', (tc) => { + const table = pgTable('test', ({ + bigint, + bigserial, + bit, + boolean, + date, + char, + cidr, + doublePrecision, + geometry, + halfvec, + inet, + integer, + interval, + json, + jsonb, + line, + macaddr, + macaddr8, + numeric, + point, + real, + serial, + smallint, + smallserial, + text, + sparsevec, + time, + timestamp, + uuid, + varchar, + vector, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigserial1: bigserial({ mode: 'number' }).notNull(), + bigserial2: bigserial({ mode: 'bigint' }).notNull(), + bit: bit({ dimensions: 5 }).notNull(), + boolean: boolean().notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + cidr: cidr().notNull(), + doublePrecision: doublePrecision().notNull(), + geometry1: geometry({ type: 'point', mode: 'tuple' }).notNull(), + geometry2: geometry({ type: 'point', mode: 'xy' }).notNull(), + halfvec: halfvec({ dimensions: 3 }).notNull(), + inet: inet().notNull(), + integer: integer().notNull(), + interval: interval().notNull(), + json: json().notNull(), + jsonb: jsonb().notNull(), + line1: line({ mode: 'abc' }).notNull(), + line2: line({ mode: 'tuple' }).notNull(), + macaddr: macaddr().notNull(), + macaddr8: macaddr8().notNull(), + numeric: numeric().notNull(), + point1: point({ mode: 'xy' }).notNull(), + point2: point({ mode: 'tuple' }).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint: smallint().notNull(), + smallserial: smallserial().notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + sparsevec: sparsevec({ dimensions: 3 }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + uuid: uuid().notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + vector: vector({ dimensions: 3 }).notNull(), + array1: integer().array().notNull(), + array2: integer().array().array(2).notNull(), + array3: varchar({ length: 10 }).array().array(2).notNull(), + })); + + const result = createSelectSchema(table); + const expected = t.Object({ + bigint1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }), + bigint2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }), + bigserial1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }), + bigserial2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }), + bit: t.RegExp(/^[01]+$/, { maxLength: 5 }), + boolean: t.Boolean(), + date1: t.Date(), + date2: t.String(), + char1: t.String({ minLength: 10, maxLength: 10 }), + char2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + cidr: t.String(), + doublePrecision: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }), + geometry1: t.Tuple([t.Number(), t.Number()]), + geometry2: t.Object({ x: t.Number(), y: t.Number() }), + halfvec: t.Array(t.Number(), { minItems: 3, maxItems: 3 }), + inet: t.String(), + integer: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }), + interval: t.String(), + json: jsonSchema, + jsonb: jsonSchema, + line1: t.Object({ a: t.Number(), b: t.Number(), c: t.Number() }), + line2: t.Tuple([t.Number(), t.Number(), t.Number()]), + macaddr: t.String(), + macaddr8: t.String(), + numeric: t.String(), + point1: t.Object({ x: t.Number(), y: t.Number() }), + point2: t.Tuple([t.Number(), t.Number()]), + real: t.Number({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }), + serial: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }), + smallint: t.Integer({ minimum: CONSTANTS.INT16_MIN, maximum: CONSTANTS.INT16_MAX }), + smallserial: t.Integer({ minimum: CONSTANTS.INT16_MIN, maximum: CONSTANTS.INT16_MAX }), + text1: t.String(), + text2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + sparsevec: t.String(), + time: t.String(), + timestamp1: t.Date(), + timestamp2: t.String(), + uuid: t.String({ format: 'uuid' }), + varchar1: t.String({ maxLength: 10 }), + varchar2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + vector: t.Array(t.Number(), { minItems: 3, maxItems: 3 }), + array1: t.Array(integerSchema), + array2: t.Array(t.Array(integerSchema), { minItems: 2, maxItems: 2 }), + array3: t.Array(t.Array(t.String({ maxLength: 10 })), { minItems: 2, maxItems: 2 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +/* Disallow unknown keys in table refinement - select */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createSelectSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createInsertSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = pgTable('test', { id: integer() }); + const view = pgView('test').as((qb) => qb.select().from(table)); + const mView = pgMaterializedView('test').as((qb) => qb.select().from(table)); + const nestedSelect = pgView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: t.String() }); + // @ts-expect-error + createSelectSchema(mView, { unknown: t.String() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: t.String() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = pgView('test', { id: integer() }).as(sql``); + const mView = pgView('test', { id: integer() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: t.String() }); + // @ts-expect-error + createSelectSchema(mView, { unknown: t.String() }); +} diff --git a/drizzle-typebox/tests/singlestore.test.ts b/drizzle-typebox/tests/singlestore.test.ts new file mode 100644 index 000000000..f643ab3b7 --- /dev/null +++ b/drizzle-typebox/tests/singlestore.test.ts @@ -0,0 +1,497 @@ +import { Type as t } from '@sinclair/typebox'; +import { type Equal, sql } from 'drizzle-orm'; +import { customType, int, serial, singlestoreSchema, singlestoreTable, text } from 'drizzle-orm/singlestore-core'; +import { test } from 'vitest'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; + +const intSchema = t.Integer({ + minimum: CONSTANTS.INT32_MIN, + maximum: CONSTANTS.INT32_MAX, +}); +const serialNumberModeSchema = t.Integer({ + minimum: 0, + maximum: Number.MAX_SAFE_INTEGER, +}); +const textSchema = t.String({ maxLength: CONSTANTS.INT16_UNSIGNED_MAX }); + +test('table - select', (tc) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table in schema - select', (tc) => { + const schema = singlestoreSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table - insert', (tc) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = t.Object({ + id: t.Optional(serialNumberModeSchema), + name: textSchema, + age: t.Optional(t.Union([intSchema, t.Null()])), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table - update', (tc) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createUpdateSchema(table); + const expected = t.Object({ + id: t.Optional(serialNumberModeSchema), + name: t.Optional(textSchema), + age: t.Optional(t.Union([intSchema, t.Null()])), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +// TODO: SingleStore doesn't support views yet. Add these tests when they're added + +// test('view qb - select', (tc) => { +// const table = singlestoreTable('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }); +// const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + +// const result = createSelectSchema(view); +// const expected = t.Object({ id: serialNumberModeSchema, age: t.Any() }); +// expectSchemaShape(tc, expected).from(result); +// Expect>(); +// }); + +// test('view columns - select', (tc) => { +// const view = mysqlView('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }).as(sql``); + +// const result = createSelectSchema(view); +// const expected = t.Object({ id: serialNumberModeSchema, name: textSchema }); +// expectSchemaShape(tc, expected).from(result); +// Expect>(); +// }); + +// test('view with nested fields - select', (tc) => { +// const table = singlestoreTable('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }); +// const view = mysqlView('test').as((qb) => +// qb.select({ +// id: table.id, +// nested: { +// name: table.name, +// age: sql``.as('age'), +// }, +// table, +// }).from(table) +// ); + +// const result = createSelectSchema(view); +// const expected = t.Object({ +// id: serialNumberModeSchema, +// nested: t.Object({ name: textSchema, age: t.Any() }), +// table: t.Object({ id: serialNumberModeSchema, name: textSchema }), +// }); +// expectSchemaShape(tc, expected).from(result); +// Expect>(); +// }); + +test('nullability - select', (tc) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: intSchema, + c3: t.Union([intSchema, t.Null()]), + c4: intSchema, + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('nullability - insert', (tc) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: intSchema, + c3: t.Optional(t.Union([intSchema, t.Null()])), + c4: t.Optional(intSchema), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('nullability - update', (tc) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Optional(intSchema), + c3: t.Optional(t.Union([intSchema, t.Null()])), + c4: t.Optional(intSchema), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - select', (tc) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (tc) => { + const customText = customType({ dataType: () => 'text' }); + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); + + const customTextSchema = t.String({ minLength: 1, maxLength: 100 }); + const result = createSelectSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: customTextSchema, + }); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: customTextSchema, + }); + + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (tc) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - update', (tc) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Optional(t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 })), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +// test('refine view - select', (tc) => { +// const table = singlestoreTable('test', { +// c1: int(), +// c2: int(), +// c3: int(), +// c4: int(), +// c5: int(), +// c6: int(), +// }); +// const view = mysqlView('test').as((qb) => +// qb.select({ +// c1: table.c1, +// c2: table.c2, +// c3: table.c3, +// nested: { +// c4: table.c4, +// c5: table.c5, +// c6: table.c6, +// }, +// table, +// }).from(table) +// ); + +// const result = createSelectSchema(view, { +// c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), +// c3: t.Integer({ minimum: 1, maximum: 10 }), +// nested: { +// c5: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), +// c6: t.Integer({ minimum: 1, maximum: 10 }), +// }, +// table: { +// c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), +// c3: t.Integer({ minimum: 1, maximum: 10 }), +// }, +// }); +// const expected = t.Object({ +// c1: t.Union([intSchema, t.Null()]), +// c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), +// c3: t.Integer({ minimum: 1, maximum: 10 }), +// nested: t.Object({ +// c4: t.Union([intSchema, t.Null()]), +// c5: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), +// c6: t.Integer({ minimum: 1, maximum: 10 }), +// }), +// table: t.Object({ +// c1: t.Union([intSchema, t.Null()]), +// c2: t.Union([t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: 1000 }), t.Null()]), +// c3: t.Integer({ minimum: 1, maximum: 10 }), +// c4: t.Union([intSchema, t.Null()]), +// c5: t.Union([intSchema, t.Null()]), +// c6: t.Union([intSchema, t.Null()]), +// }), +// }); +// expectSchemaShape(tc, expected).from(result); +// Expect>(); +// }); + +test('all data types', (tc) => { + const table = singlestoreTable('test', ({ + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + singlestoreEnum, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varchar, + varbinary, + year, + longtext, + mediumtext, + tinytext, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(), + bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(), + binary: binary({ length: 10 }).notNull(), + boolean: boolean().notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + datetime1: datetime({ mode: 'date' }).notNull(), + datetime2: datetime({ mode: 'string' }).notNull(), + decimal1: decimal().notNull(), + decimal2: decimal({ unsigned: true }).notNull(), + double1: double().notNull(), + double2: double({ unsigned: true }).notNull(), + float1: float().notNull(), + float2: float({ unsigned: true }).notNull(), + int1: int().notNull(), + int2: int({ unsigned: true }).notNull(), + json: json().notNull(), + mediumint1: mediumint().notNull(), + mediumint2: mediumint({ unsigned: true }).notNull(), + enum: singlestoreEnum('enum', ['a', 'b', 'c']).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint1: smallint().notNull(), + smallint2: smallint({ unsigned: true }).notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + tinyint1: tinyint().notNull(), + tinyint2: tinyint({ unsigned: true }).notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + varbinary: varbinary({ length: 10 }).notNull(), + year: year().notNull(), + longtext1: longtext().notNull(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(), + mediumtext1: mediumtext().notNull(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(), + tinytext1: tinytext().notNull(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(), + })); + + const result = createSelectSchema(table); + const expected = t.Object({ + bigint1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }), + bigint2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }), + bigint3: t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }), + bigint4: t.BigInt({ minimum: 0n, maximum: CONSTANTS.INT64_UNSIGNED_MAX }), + binary: t.String(), + boolean: t.Boolean(), + char1: t.String({ minLength: 10, maxLength: 10 }), + char2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + date1: t.Date(), + date2: t.String(), + datetime1: t.Date(), + datetime2: t.String(), + decimal1: t.String(), + decimal2: t.String(), + double1: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }), + double2: t.Number({ minimum: 0, maximum: CONSTANTS.INT48_UNSIGNED_MAX }), + float1: t.Number({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }), + float2: t.Number({ minimum: 0, maximum: CONSTANTS.INT24_UNSIGNED_MAX }), + int1: t.Integer({ minimum: CONSTANTS.INT32_MIN, maximum: CONSTANTS.INT32_MAX }), + int2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT32_UNSIGNED_MAX }), + json: jsonSchema, + mediumint1: t.Integer({ minimum: CONSTANTS.INT24_MIN, maximum: CONSTANTS.INT24_MAX }), + mediumint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT24_UNSIGNED_MAX }), + enum: t.Enum({ a: 'a', b: 'b', c: 'c' }), + real: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }), + serial: t.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }), + smallint1: t.Integer({ minimum: CONSTANTS.INT16_MIN, maximum: CONSTANTS.INT16_MAX }), + smallint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT16_UNSIGNED_MAX }), + text1: t.String({ maxLength: CONSTANTS.INT16_UNSIGNED_MAX }), + text2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + time: t.String(), + timestamp1: t.Date(), + timestamp2: t.String(), + tinyint1: t.Integer({ minimum: CONSTANTS.INT8_MIN, maximum: CONSTANTS.INT8_MAX }), + tinyint2: t.Integer({ minimum: 0, maximum: CONSTANTS.INT8_UNSIGNED_MAX }), + varchar1: t.String({ maxLength: 10 }), + varchar2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + varbinary: t.String(), + year: t.Integer({ minimum: 1901, maximum: 2155 }), + longtext1: t.String({ maxLength: CONSTANTS.INT32_UNSIGNED_MAX }), + longtext2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + mediumtext1: t.String({ maxLength: CONSTANTS.INT24_UNSIGNED_MAX }), + mediumtext2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + tinytext1: t.String({ maxLength: CONSTANTS.INT8_UNSIGNED_MAX }), + tinytext2: t.Enum({ a: 'a', b: 'b', c: 'c' }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +/* Disallow unknown keys in table refinement - select */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: t.String() }); +} + +// /* Disallow unknown keys in view qb - select */ { +// const table = singlestoreTable('test', { id: int() }); +// const view = mysqlView('test').as((qb) => qb.select().from(table)); +// const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table)); +// // @ts-expect-error +// createSelectSchema(view, { unknown: t.String() }); +// // @ts-expect-error +// createSelectSchema(nestedSelect, { table: { unknown: t.String() } }); +// } + +// /* Disallow unknown keys in view columns - select */ { +// const view = mysqlView('test', { id: int() }).as(sql``); +// // @ts-expect-error +// createSelectSchema(view, { unknown: t.String() }); +// } diff --git a/drizzle-typebox/tests/sqlite.test.ts b/drizzle-typebox/tests/sqlite.test.ts index a8506a269..2b5083b92 100644 --- a/drizzle-typebox/tests/sqlite.test.ts +++ b/drizzle-typebox/tests/sqlite.test.ts @@ -1,189 +1,389 @@ -import { type Static, Type } from '@sinclair/typebox'; -import { Value } from '@sinclair/typebox/value'; -import { blob, integer, numeric, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; -import { expect, test } from 'vitest'; -import { createInsertSchema, createSelectSchema, jsonSchema, Nullable } from '../src'; -import { expectSchemaShape } from './utils.ts'; - -const blobJsonSchema = Type.Object({ - foo: Type.String(), -}); +import { Type as t } from '@sinclair/typebox'; +import { type Equal, sql } from 'drizzle-orm'; +import { customType, int, sqliteTable, sqliteView, text } from 'drizzle-orm/sqlite-core'; +import { test } from 'vitest'; +import { bufferSchema, jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; + +const intSchema = t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }); +const textSchema = t.String(); -const users = sqliteTable('users', { - id: integer('id').primaryKey(), - blobJson: blob('blob', { mode: 'json' }) - .$type>() - .notNull(), - blobBigInt: blob('blob', { mode: 'bigint' }).notNull(), - numeric: numeric('numeric').notNull(), - createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), - createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }).notNull(), - boolean: integer('boolean', { mode: 'boolean' }).notNull(), - real: real('real').notNull(), - text: text('text', { length: 255 }), - role: text('role', { enum: ['admin', 'user'] }) - .notNull() - .default('user'), +test('table - select', (tc) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ id: intSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -const testUser = { - id: 1, - blobJson: { foo: 'bar' }, - blobBigInt: BigInt(123), - numeric: '123.45', - createdAt: new Date(), - createdAtMs: new Date(), - boolean: true, - real: 123.45, - text: 'foobar', - role: 'admin', -}; - -test('users insert valid user', () => { - const schema = createInsertSchema(users); - // - expect(Value.Check(schema, testUser)).toBeTruthy(); +test('table - insert', (tc) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = t.Object({ + id: t.Optional(intSchema), + name: textSchema, + age: t.Optional(t.Union([intSchema, t.Null()])), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('users insert invalid text length', () => { - const schema = createInsertSchema(users); +test('table - update', (tc) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + age: int(), + }); - expect(Value.Check(schema, { ...testUser, text: 'a'.repeat(256) })).toBeFalsy(); + const result = createUpdateSchema(table); + const expected = t.Object({ + id: t.Optional(intSchema), + name: t.Optional(textSchema), + age: t.Optional(t.Union([intSchema, t.Null()])), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('users insert schema', (t) => { - const actual = createInsertSchema(users, { - id: () => Type.Number({ minimum: 0 }), - blobJson: blobJsonSchema, - role: Type.Union([ - Type.Literal('admin'), - Type.Literal('user'), - Type.Literal('manager'), - ]), - }); - - (() => { - { - createInsertSchema(users, { - // @ts-expect-error (missing property) - foobar: Type.Number(), - }); - } - - { - createInsertSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } - }); - - const expected = Type.Object({ - id: Type.Optional(Type.Number({ minimum: 0 })), - blobJson: blobJsonSchema, - blobBigInt: Type.BigInt(), - numeric: Type.String(), - createdAt: Type.Date(), - createdAtMs: Type.Date(), - boolean: Type.Boolean(), - real: Type.Number(), - text: Type.Optional(Nullable(Type.String())), - role: Type.Optional( - Type.Union([ - Type.Literal('admin'), - Type.Literal('user'), - Type.Literal('manager'), - ]), - ), - }); - - expectSchemaShape(t, expected).from(actual); +test('view qb - select', (tc) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }); + const view = sqliteView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = t.Object({ id: intSchema, age: t.Any() }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('users insert schema w/ defaults', (t) => { - const actual = createInsertSchema(users); - - const expected = Type.Object({ - id: Type.Optional(Type.Number()), - blobJson: jsonSchema, - blobBigInt: Type.BigInt(), - numeric: Type.String(), - createdAt: Type.Date(), - createdAtMs: Type.Date(), - boolean: Type.Boolean(), - real: Type.Number(), - text: Type.Optional(Nullable(Type.String())), - role: Type.Optional( - Type.Union([Type.Literal('admin'), Type.Literal('user')]), - ), - }); - - expectSchemaShape(t, expected).from(actual); +test('view columns - select', (tc) => { + const view = sqliteView('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = t.Object({ id: intSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('users select schema', (t) => { - const actual = createSelectSchema(users, { - blobJson: jsonSchema, - role: Type.Union([ - Type.Literal('admin'), - Type.Literal('user'), - Type.Literal('manager'), - ]), - }); - - (() => { - { - createSelectSchema(users, { - // @ts-expect-error (missing property) - foobar: Type.Number(), - }); - } - - { - createSelectSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } - }); - - const expected = Type.Strict( - Type.Object({ - id: Type.Number(), - blobJson: jsonSchema, - blobBigInt: Type.BigInt(), - numeric: Type.String(), - createdAt: Type.Date(), - createdAtMs: Type.Date(), - boolean: Type.Boolean(), - real: Type.Number(), - text: Nullable(Type.String()), - role: Type.Union([ - Type.Literal('admin'), - Type.Literal('user'), - Type.Literal('manager'), - ]), - }), +test('view with nested fields - select', (tc) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }); + const view = sqliteView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) ); - expectSchemaShape(t, expected).from(actual); + const result = createSelectSchema(view); + const expected = t.Object({ + id: intSchema, + nested: t.Object({ name: textSchema, age: t.Any() }), + table: t.Object({ id: intSchema, name: textSchema }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('nullability - select', (tc) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + }); + + const result = createSelectSchema(table); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: intSchema, + c3: t.Union([intSchema, t.Null()]), + c4: intSchema, + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('nullability - insert', (tc) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: intSchema, + c3: t.Optional(t.Union([intSchema, t.Null()])), + c4: t.Optional(intSchema), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('nullability - update', (tc) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Optional(intSchema), + c3: t.Optional(t.Union([intSchema, t.Null()])), + c4: t.Optional(intSchema), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - select', (tc) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (tc) => { + const customText = customType({ dataType: () => 'text' }); + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); + + const customTextSchema = t.String({ minLength: 1, maxLength: 100 }); + const result = createSelectSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: customTextSchema, + }); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: customTextSchema, + }); + + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (tc) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('refine table - update', (tc) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + const expected = t.Object({ + c1: t.Optional(t.Union([intSchema, t.Null()])), + c2: t.Optional(t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 })), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('users select schema w/ defaults', (t) => { - const actual = createSelectSchema(users); +test('refine view - select', (tc) => { + const table = sqliteTable('test', { + c1: int(), + c2: int(), + c3: int(), + c4: int(), + c5: int(), + c6: int(), + }); + const view = sqliteView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); - const expected = Type.Object({ - id: Type.Number(), - blobJson: jsonSchema, - blobBigInt: Type.BigInt(), - numeric: Type.String(), - createdAt: Type.Date(), - createdAtMs: Type.Date(), - boolean: Type.Boolean(), - real: Type.Number(), - text: Nullable(Type.String()), - role: Type.Union([Type.Literal('admin'), Type.Literal('user')]), + const result = createSelectSchema(view, { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + nested: { + c5: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c6: t.Integer({ minimum: 1, maximum: 10 }), + }, + table: { + c2: (schema) => t.Integer({ minimum: schema.minimum, maximum: 1000 }), + c3: t.Integer({ minimum: 1, maximum: 10 }), + }, + }); + const expected = t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Union([t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), t.Null()]), + c3: t.Integer({ minimum: 1, maximum: 10 }), + nested: t.Object({ + c4: t.Union([intSchema, t.Null()]), + c5: t.Union([t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), t.Null()]), + c6: t.Integer({ minimum: 1, maximum: 10 }), + }), + table: t.Object({ + c1: t.Union([intSchema, t.Null()]), + c2: t.Union([t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: 1000 }), t.Null()]), + c3: t.Integer({ minimum: 1, maximum: 10 }), + c4: t.Union([intSchema, t.Null()]), + c5: t.Union([intSchema, t.Null()]), + c6: t.Union([intSchema, t.Null()]), + }), }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('all data types', (tc) => { + const table = sqliteTable('test', ({ + blob, + integer, + numeric, + real, + text, + }) => ({ + blob1: blob({ mode: 'buffer' }).notNull(), + blob2: blob({ mode: 'bigint' }).notNull(), + blob3: blob({ mode: 'json' }).notNull(), + integer1: integer({ mode: 'number' }).notNull(), + integer2: integer({ mode: 'boolean' }).notNull(), + integer3: integer({ mode: 'timestamp' }).notNull(), + integer4: integer({ mode: 'timestamp_ms' }).notNull(), + numeric: numeric().notNull(), + real: real().notNull(), + text1: text({ mode: 'text' }).notNull(), + text2: text({ mode: 'text', length: 10 }).notNull(), + text3: text({ mode: 'text', enum: ['a', 'b', 'c'] }).notNull(), + text4: text({ mode: 'json' }).notNull(), + })); - expectSchemaShape(t, expected).from(actual); + const result = createSelectSchema(table); + const expected = t.Object({ + blob1: bufferSchema, + blob2: t.BigInt({ minimum: CONSTANTS.INT64_MIN, maximum: CONSTANTS.INT64_MAX }), + blob3: jsonSchema, + integer1: t.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER }), + integer2: t.Boolean(), + integer3: t.Date(), + integer4: t.Date(), + numeric: t.String(), + real: t.Number({ minimum: CONSTANTS.INT48_MIN, maximum: CONSTANTS.INT48_MAX }), + text1: t.String(), + text2: t.String({ maxLength: 10 }), + text3: t.Enum({ a: 'a', b: 'b', c: 'c' }), + text4: jsonSchema, + }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); + +/* Disallow unknown keys in table refinement - select */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: t.String() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = sqliteTable('test', { id: int() }); + const view = sqliteView('test').as((qb) => qb.select().from(table)); + const nestedSelect = sqliteView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: t.String() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: t.String() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = sqliteView('test', { id: int() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: t.String() }); +} diff --git a/drizzle-typebox/tests/utils.ts b/drizzle-typebox/tests/utils.ts index e17e5f26d..46cd16a32 100644 --- a/drizzle-typebox/tests/utils.ts +++ b/drizzle-typebox/tests/utils.ts @@ -1,17 +1,34 @@ -import type { TSchema } from '@sinclair/typebox'; +import type * as t from '@sinclair/typebox'; import { expect, type TaskContext } from 'vitest'; -export function expectSchemaShape(t: TaskContext, expected: T) { +function removeKeysFromObject(obj: Record, keys: string[]) { + for (const key of keys) { + delete obj[key]; + } + return obj; +} + +export function expectSchemaShape(t: TaskContext, expected: T) { return { from(actual: T) { - expect(Object.keys(actual)).toStrictEqual(Object.keys(expected)); + expect(Object.keys(actual.properties)).toStrictEqual(Object.keys(expected.properties)); + const keys = ['$id', '$schema', 'title', 'description', 'default', 'examples', 'readOnly', 'writeOnly']; - for (const key of Object.keys(actual)) { - expect(actual[key].type).toStrictEqual(expected[key]?.type); - if (actual[key].optional) { - expect(actual[key].optional).toStrictEqual(expected[key]?.optional); - } + for (const key of Object.keys(actual.properties)) { + expect(removeKeysFromObject(actual.properties[key]!, keys)).toStrictEqual( + removeKeysFromObject(expected.properties[key]!, keys), + ); } }, }; } + +export function expectEnumValues>(t: TaskContext, expected: T) { + return { + from(actual: T) { + expect(actual.anyOf).toStrictEqual(expected.anyOf); + }, + }; +} + +export function Expect<_ extends true>() {} diff --git a/drizzle-typebox/tsconfig.json b/drizzle-typebox/tsconfig.json index 038d79591..c25379c37 100644 --- a/drizzle-typebox/tsconfig.json +++ b/drizzle-typebox/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "dist", "baseUrl": ".", "declaration": true, + "noEmit": true, "paths": { "~/*": ["src/*"] } diff --git a/drizzle-valibot/README.md b/drizzle-valibot/README.md index 7696e0297..735e40b34 100644 --- a/drizzle-valibot/README.md +++ b/drizzle-valibot/README.md @@ -1,11 +1,17 @@ `drizzle-valibot` is a plugin for [Drizzle ORM](https://github.com/drizzle-team/drizzle-orm) that allows you to generate [valibot](https://valibot.dev/) schemas from Drizzle ORM schemas. +**Features** + +- Create a select schema for tables, views and enums. +- Create insert and update schemas for tables. +- Supports all dialects: PostgreSQL, MySQL and SQLite. + # Usage ```ts import { pgEnum, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core'; import { createInsertSchema, createSelectSchema } from 'drizzle-valibot'; -import { string, parse, number } from 'valibot'; +import { string, parse, number, pipe } from 'valibot'; const users = pgTable('users', { id: serial('id').primaryKey(), @@ -18,6 +24,9 @@ const users = pgTable('users', { // Schema for inserting a user - can be used to validate API requests const insertUserSchema = createInsertSchema(users); +// Schema for updating a user - can be used to validate API requests +const updateUserSchema = createUpdateSchema(users); + // Schema for selecting a user - can be used to validate API responses const selectUserSchema = createSelectSchema(users); @@ -28,7 +37,7 @@ const insertUserSchema = createInsertSchema(users, { // Refining the fields - useful if you want to change the fields before they become nullable/optional in the final schema const insertUserSchema = createInsertSchema(users, { - id: (schema) => number([minValue(0)]), + id: (schema) => pipe([schema, minValue(0)]), role: string(), }); diff --git a/drizzle-valibot/package.json b/drizzle-valibot/package.json index 1d88fd26a..621d36782 100644 --- a/drizzle-valibot/package.json +++ b/drizzle-valibot/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-valibot", - "version": "0.2.0", + "version": "0.3.1", "description": "Generate valibot schemas from Drizzle ORM schemas", "type": "module", "scripts": { @@ -55,18 +55,17 @@ "author": "Drizzle Team", "license": "Apache-2.0", "peerDependencies": { - "drizzle-orm": ">=0.23.13", - "valibot": ">=0.20" + "drizzle-orm": ">=0.36.0", + "valibot": ">=1.0.0-beta.7" }, "devDependencies": { - "@rollup/plugin-terser": "^0.4.1", "@rollup/plugin-typescript": "^11.1.0", "@types/node": "^18.15.10", "cpy": "^10.1.0", "drizzle-orm": "link:../drizzle-orm/dist", "rimraf": "^5.0.0", "rollup": "^3.20.7", - "valibot": "^0.30.0", + "valibot": "1.0.0-beta.7", "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0", "zx": "^7.2.2" diff --git a/drizzle-valibot/rollup.config.ts b/drizzle-valibot/rollup.config.ts index 2ed2d33d3..986da4883 100644 --- a/drizzle-valibot/rollup.config.ts +++ b/drizzle-valibot/rollup.config.ts @@ -1,4 +1,3 @@ -import terser from '@rollup/plugin-terser'; import typescript from '@rollup/plugin-typescript'; import { defineConfig } from 'rollup'; @@ -23,13 +22,12 @@ export default defineConfig([ ], external: [ /^drizzle-orm\/?/, - 'zod', + 'valibot', ], plugins: [ typescript({ tsconfig: 'tsconfig.build.json', }), - terser(), ], }, ]); diff --git a/drizzle-valibot/scripts/build.ts b/drizzle-valibot/scripts/build.ts index 1910feac6..07330ffd0 100755 --- a/drizzle-valibot/scripts/build.ts +++ b/drizzle-valibot/scripts/build.ts @@ -13,3 +13,4 @@ await cpy('dist/**/*.d.ts', 'dist', { rename: (basename) => basename.replace(/\.d\.ts$/, '.d.cts'), }); await fs.copy('package.json', 'dist/package.json'); +await $`scripts/fix-imports.ts`; diff --git a/drizzle-valibot/scripts/fix-imports.ts b/drizzle-valibot/scripts/fix-imports.ts new file mode 100755 index 000000000..a90057c5b --- /dev/null +++ b/drizzle-valibot/scripts/fix-imports.ts @@ -0,0 +1,136 @@ +#!/usr/bin/env -S pnpm tsx +import 'zx/globals'; + +import path from 'node:path'; +import { parse, print, visit } from 'recast'; +import parser from 'recast/parsers/typescript'; + +function resolvePathAlias(importPath: string, file: string) { + if (importPath.startsWith('~/')) { + const relativePath = path.relative(path.dirname(file), path.resolve('dist.new', importPath.slice(2))); + importPath = relativePath.startsWith('.') ? relativePath : './' + relativePath; + } + + return importPath; +} + +function fixImportPath(importPath: string, file: string, ext: string) { + importPath = resolvePathAlias(importPath, file); + + if (!/\..*\.(js|ts)$/.test(importPath)) { + return importPath; + } + + return importPath.replace(/\.(js|ts)$/, ext); +} + +const cjsFiles = await glob('dist/**/*.{cjs,d.cts}'); + +await Promise.all(cjsFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + } + this.traverse(path); + }, + visitCallExpression(path) { + if (path.value.callee.type === 'Identifier' && path.value.callee.name === 'require') { + path.value.arguments[0].value = fixImportPath(path.value.arguments[0].value, file, '.cjs'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = resolvePathAlias(path.value.argument.value, file); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.cjs'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); + +let esmFiles = await glob('dist/**/*.{js,d.ts}'); + +await Promise.all(esmFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = fixImportPath(path.value.argument.value, file, '.js'); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.js'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); + +esmFiles = await glob('dist/**/*.{mjs,d.mts}'); + +await Promise.all(esmFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = fixImportPath(path.value.argument.value, file, '.mjs'); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.mjs'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); diff --git a/drizzle-valibot/src/column.ts b/drizzle-valibot/src/column.ts new file mode 100644 index 000000000..040dbac21 --- /dev/null +++ b/drizzle-valibot/src/column.ts @@ -0,0 +1,296 @@ +import type { Column, ColumnBaseConfig } from 'drizzle-orm'; +import type { + MySqlBigInt53, + MySqlChar, + MySqlDouble, + MySqlFloat, + MySqlInt, + MySqlMediumInt, + MySqlReal, + MySqlSerial, + MySqlSmallInt, + MySqlText, + MySqlTinyInt, + MySqlVarChar, + MySqlYear, +} from 'drizzle-orm/mysql-core'; +import type { + PgArray, + PgBigInt53, + PgBigSerial53, + PgBinaryVector, + PgChar, + PgDoublePrecision, + PgGeometry, + PgGeometryObject, + PgHalfVector, + PgInteger, + PgLineABC, + PgLineTuple, + PgPointObject, + PgPointTuple, + PgReal, + PgSerial, + PgSmallInt, + PgSmallSerial, + PgUUID, + PgVarchar, + PgVector, +} from 'drizzle-orm/pg-core'; +import type { + SingleStoreBigInt53, + SingleStoreChar, + SingleStoreDouble, + SingleStoreFloat, + SingleStoreInt, + SingleStoreMediumInt, + SingleStoreReal, + SingleStoreSerial, + SingleStoreSmallInt, + SingleStoreText, + SingleStoreTinyInt, + SingleStoreVarChar, + SingleStoreYear, +} from 'drizzle-orm/singlestore-core'; +import type { SQLiteInteger, SQLiteReal, SQLiteText } from 'drizzle-orm/sqlite-core'; +import * as v from 'valibot'; +import { CONSTANTS } from './constants.ts'; +import { isColumnType, isWithEnum } from './utils.ts'; +import type { Json } from './utils.ts'; + +export const literalSchema = v.union([v.string(), v.number(), v.boolean(), v.null()]); +export const jsonSchema: v.GenericSchema = v.union([ + literalSchema, + v.array(v.lazy(() => jsonSchema)), + v.record(v.string(), v.lazy(() => jsonSchema)), +]); +export const bufferSchema: v.GenericSchema = v.custom((v) => v instanceof Buffer); // eslint-disable-line no-instanceof/no-instanceof + +export function mapEnumValues(values: string[]) { + return Object.fromEntries(values.map((value) => [value, value])); +} + +export function columnToSchema(column: Column): v.GenericSchema { + let schema!: v.GenericSchema; + + if (isWithEnum(column)) { + schema = column.enumValues.length ? v.enum(mapEnumValues(column.enumValues)) : v.string(); + } + + if (!schema) { + // Handle specific types + if (isColumnType | PgPointTuple>(column, ['PgGeometry', 'PgPointTuple'])) { + schema = v.tuple([v.number(), v.number()]); + } else if ( + isColumnType | PgGeometryObject>(column, ['PgGeometryObject', 'PgPointObject']) + ) { + schema = v.object({ x: v.number(), y: v.number() }); + } else if (isColumnType | PgVector>(column, ['PgHalfVector', 'PgVector'])) { + schema = v.array(v.number()); + schema = column.dimensions ? v.pipe(schema as v.ArraySchema, v.length(column.dimensions)) : schema; + } else if (isColumnType>(column, ['PgLine'])) { + schema = v.tuple([v.number(), v.number(), v.number()]); + v.array(v.array(v.number())); + } else if (isColumnType>(column, ['PgLineABC'])) { + schema = v.object({ a: v.number(), b: v.number(), c: v.number() }); + } // Handle other types + else if (isColumnType>(column, ['PgArray'])) { + schema = v.array(columnToSchema(column.baseColumn)); + schema = column.size ? v.pipe(schema as v.ArraySchema, v.length(column.size)) : schema; + } else if (column.dataType === 'array') { + schema = v.array(v.any()); + } else if (column.dataType === 'number') { + schema = numberColumnToSchema(column); + } else if (column.dataType === 'bigint') { + schema = bigintColumnToSchema(column); + } else if (column.dataType === 'boolean') { + schema = v.boolean(); + } else if (column.dataType === 'date') { + schema = v.date(); + } else if (column.dataType === 'string') { + schema = stringColumnToSchema(column); + } else if (column.dataType === 'json') { + schema = jsonSchema; + } else if (column.dataType === 'custom') { + schema = v.any(); + } else if (column.dataType === 'buffer') { + schema = bufferSchema; + } + } + + if (!schema) { + schema = v.any(); + } + + return schema; +} + +function numberColumnToSchema(column: Column): v.GenericSchema { + let unsigned = column.getSQLType().includes('unsigned'); + let min!: number; + let max!: number; + let integer = false; + + if (isColumnType | SingleStoreTinyInt>(column, ['MySqlTinyInt', 'SingleStoreTinyInt'])) { + min = unsigned ? 0 : CONSTANTS.INT8_MIN; + max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX; + integer = true; + } else if ( + isColumnType | PgSmallSerial | MySqlSmallInt | SingleStoreSmallInt>(column, [ + 'PgSmallInt', + 'PgSmallSerial', + 'MySqlSmallInt', + 'SingleStoreSmallInt', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT16_MIN; + max = unsigned ? CONSTANTS.INT16_UNSIGNED_MAX : CONSTANTS.INT16_MAX; + integer = true; + } else if ( + isColumnType< + PgReal | MySqlFloat | MySqlMediumInt | SingleStoreFloat | SingleStoreMediumInt + >(column, [ + 'PgReal', + 'MySqlFloat', + 'MySqlMediumInt', + 'SingleStoreFloat', + 'SingleStoreMediumInt', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT24_MIN; + max = unsigned ? CONSTANTS.INT24_UNSIGNED_MAX : CONSTANTS.INT24_MAX; + integer = isColumnType(column, ['MySqlMediumInt', 'SingleStoreMediumInt']); + } else if ( + isColumnType | PgSerial | MySqlInt | SingleStoreInt>(column, [ + 'PgInteger', + 'PgSerial', + 'MySqlInt', + 'SingleStoreInt', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT32_MIN; + max = unsigned ? CONSTANTS.INT32_UNSIGNED_MAX : CONSTANTS.INT32_MAX; + integer = true; + } else if ( + isColumnType< + | PgDoublePrecision + | MySqlReal + | MySqlDouble + | SingleStoreReal + | SingleStoreDouble + | SQLiteReal + >(column, [ + 'PgDoublePrecision', + 'MySqlReal', + 'MySqlDouble', + 'SingleStoreReal', + 'SingleStoreDouble', + 'SQLiteReal', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT48_MIN; + max = unsigned ? CONSTANTS.INT48_UNSIGNED_MAX : CONSTANTS.INT48_MAX; + } else if ( + isColumnType< + | PgBigInt53 + | PgBigSerial53 + | MySqlBigInt53 + | MySqlSerial + | SingleStoreBigInt53 + | SingleStoreSerial + | SQLiteInteger + >( + column, + [ + 'PgBigInt53', + 'PgBigSerial53', + 'MySqlBigInt53', + 'MySqlSerial', + 'SingleStoreBigInt53', + 'SingleStoreSerial', + 'SQLiteInteger', + ], + ) + ) { + unsigned = unsigned || isColumnType(column, ['MySqlSerial', 'SingleStoreSerial']); + min = unsigned ? 0 : Number.MIN_SAFE_INTEGER; + max = Number.MAX_SAFE_INTEGER; + integer = true; + } else if (isColumnType | SingleStoreYear>(column, ['MySqlYear', 'SingleStoreYear'])) { + min = 1901; + max = 2155; + integer = true; + } else { + min = Number.MIN_SAFE_INTEGER; + max = Number.MAX_SAFE_INTEGER; + } + + const actions: any[] = [v.minValue(min), v.maxValue(max)]; + if (integer) { + actions.push(v.integer()); + } + return v.pipe(v.number(), ...actions); +} + +function bigintColumnToSchema(column: Column): v.GenericSchema { + const unsigned = column.getSQLType().includes('unsigned'); + const min = unsigned ? 0n : CONSTANTS.INT64_MIN; + const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX; + + return v.pipe(v.bigint(), v.minValue(min), v.maxValue(max)); +} + +function stringColumnToSchema(column: Column): v.GenericSchema { + if (isColumnType>>(column, ['PgUUID'])) { + return v.pipe(v.string(), v.uuid()); + } + + let max: number | undefined; + let regex: RegExp | undefined; + let fixed = false; + + if (isColumnType | SQLiteText>(column, ['PgVarchar', 'SQLiteText'])) { + max = column.length; + } else if ( + isColumnType | SingleStoreVarChar>(column, ['MySqlVarChar', 'SingleStoreVarChar']) + ) { + max = column.length ?? CONSTANTS.INT16_UNSIGNED_MAX; + } else if (isColumnType | SingleStoreText>(column, ['MySqlText', 'SingleStoreText'])) { + if (column.textType === 'longtext') { + max = CONSTANTS.INT32_UNSIGNED_MAX; + } else if (column.textType === 'mediumtext') { + max = CONSTANTS.INT24_UNSIGNED_MAX; + } else if (column.textType === 'text') { + max = CONSTANTS.INT16_UNSIGNED_MAX; + } else { + max = CONSTANTS.INT8_UNSIGNED_MAX; + } + } + + if ( + isColumnType | MySqlChar | SingleStoreChar>(column, [ + 'PgChar', + 'MySqlChar', + 'SingleStoreChar', + ]) + ) { + max = column.length; + fixed = true; + } + + if (isColumnType>(column, ['PgBinaryVector'])) { + regex = /^[01]+$/; + max = column.dimensions; + } + + const actions: any[] = []; + if (regex) { + actions.push(v.regex(regex)); + } + if (max && fixed) { + actions.push(v.length(max)); + } else if (max) { + actions.push(v.maxLength(max)); + } + return actions.length > 0 ? v.pipe(v.string(), ...actions) : v.string(); +} diff --git a/drizzle-valibot/src/column.types.ts b/drizzle-valibot/src/column.types.ts new file mode 100644 index 000000000..2b30cb60a --- /dev/null +++ b/drizzle-valibot/src/column.types.ts @@ -0,0 +1,174 @@ +import type { Assume, Column } from 'drizzle-orm'; +import type * as v from 'valibot'; +import type { + ArrayHasAtLeastOneValue, + ColumnIsGeneratedAlwaysAs, + IsNever, + Json, + RemoveNeverElements, +} from './utils.ts'; + +export type GetEnumValuesFromColumn = TColumn['_'] extends { enumValues: [string, ...string[]] } + ? TColumn['_']['enumValues'] + : undefined; + +export type GetBaseColumn = TColumn['_'] extends { baseColumn: Column | never | undefined } + ? IsNever extends false ? TColumn['_']['baseColumn'] + : undefined + : undefined; + +export type EnumValuesToEnum = { readonly [K in TEnumValues[number]]: K }; + +export type ExtractAdditionalProperties = { + max: TColumn['_']['columnType'] extends 'PgVarchar' | 'SQLiteText' | 'PgChar' | 'MySqlChar' | 'SingleStoreChar' + ? Assume['length'] + : TColumn['_']['columnType'] extends 'MySqlText' | 'MySqlVarChar' | 'SingleStoreText' | 'SingleStoreVarChar' + ? number + : TColumn['_']['columnType'] extends 'PgBinaryVector' | 'PgHalfVector' | 'PgVector' + ? Assume['dimensions'] + : TColumn['_']['columnType'] extends 'PgArray' ? Assume['size'] + : undefined; + fixedLength: TColumn['_']['columnType'] extends + 'PgChar' | 'PgHalfVector' | 'PgVector' | 'PgArray' | 'MySqlChar' | 'SingleStoreChar' ? true + : false; +}; + +type GetLengthAction, TType extends string | ArrayLike> = + T['fixedLength'] extends true ? v.LengthAction + : v.MaxLengthAction; + +type GetArraySchema = v.ArraySchema< + GetValibotType< + T['_']['data'], + T['_']['dataType'], + T['_']['columnType'], + GetEnumValuesFromColumn, + GetBaseColumn, + ExtractAdditionalProperties + >, + undefined +>; + +export type GetValibotType< + TData, + TDataType extends string, + TColumnType extends string, + TEnumValues extends [string, ...string[]] | undefined, + TBaseColumn extends Column | undefined, + TAdditionalProperties extends Record, +> = TColumnType extends 'PgHalfVector' | 'PgVector' ? TAdditionalProperties['max'] extends number ? v.SchemaWithPipe< + [v.ArraySchema, undefined>, GetLengthAction] + > + : v.ArraySchema, undefined> + : TColumnType extends 'PgUUID' ? v.SchemaWithPipe<[v.StringSchema, v.UuidAction]> + : TColumnType extends 'PgBinaryVector' ? v.SchemaWithPipe< + RemoveNeverElements<[ + v.StringSchema, + v.RegexAction, + TAdditionalProperties['max'] extends number ? GetLengthAction : never, + ]> + > + : TBaseColumn extends Column ? TAdditionalProperties['max'] extends number ? v.SchemaWithPipe< + [GetArraySchema, GetLengthAction] + > + : GetArraySchema + : ArrayHasAtLeastOneValue extends true + ? v.EnumSchema>, undefined> + : TData extends infer TTuple extends [any, ...any[]] ? v.TupleSchema< + Assume< + { [K in keyof TTuple]: GetValibotType }, + [any, ...any[]] + >, + undefined + > + : TData extends Date ? v.DateSchema + : TData extends Buffer ? v.GenericSchema + : TDataType extends 'array' ? v.ArraySchema< + GetValibotType[number], string, string, undefined, undefined, { noPipe: true }>, + undefined + > + : TData extends infer TDict extends Record ? v.ObjectSchema< + { readonly [K in keyof TDict]: GetValibotType }, + undefined + > + : TDataType extends 'json' ? v.GenericSchema + : TData extends number ? TAdditionalProperties['noPipe'] extends true ? v.NumberSchema : v.SchemaWithPipe< + RemoveNeverElements<[ + v.NumberSchema, + v.MinValueAction, + v.MaxValueAction, + TColumnType extends + | 'MySqlTinyInt' + | 'SingleStoreTinyInt' + | 'PgSmallInt' + | 'PgSmallSerial' + | 'MySqlSmallInt' + | 'MySqlMediumInt' + | 'SingleStoreSmallInt' + | 'SingleStoreMediumInt' + | 'PgInteger' + | 'PgSerial' + | 'MySqlInt' + | 'SingleStoreInt' + | 'PgBigInt53' + | 'PgBigSerial53' + | 'MySqlBigInt53' + | 'MySqlSerial' + | 'SingleStoreBigInt53' + | 'SingleStoreSerial' + | 'SQLiteInteger' + | 'MySqlYear' + | 'SingleStoreYear' ? v.IntegerAction + : never, + ]> + > + : TData extends bigint ? TAdditionalProperties['noPipe'] extends true ? v.BigintSchema : v.SchemaWithPipe<[ + v.BigintSchema, + v.MinValueAction, + v.MaxValueAction, + ]> + : TData extends boolean ? v.BooleanSchema + : TData extends string + ? TAdditionalProperties['max'] extends number + ? v.SchemaWithPipe<[v.StringSchema, GetLengthAction]> + : v.StringSchema + : v.AnySchema; + +type HandleSelectColumn< + TSchema extends v.GenericSchema, + TColumn extends Column, +> = TColumn['_']['notNull'] extends true ? TSchema + : v.NullableSchema; + +type HandleInsertColumn< + TSchema extends v.GenericSchema, + TColumn extends Column, +> = ColumnIsGeneratedAlwaysAs extends true ? never + : TColumn['_']['notNull'] extends true + ? TColumn['_']['hasDefault'] extends true ? v.OptionalSchema + : TSchema + : v.OptionalSchema, undefined>; + +type HandleUpdateColumn< + TSchema extends v.GenericSchema, + TColumn extends Column, +> = ColumnIsGeneratedAlwaysAs extends true ? never + : TColumn['_']['notNull'] extends true ? v.OptionalSchema + : v.OptionalSchema, undefined>; + +export type HandleColumn< + TType extends 'select' | 'insert' | 'update', + TColumn extends Column, +> = GetValibotType< + TColumn['_']['data'], + TColumn['_']['dataType'], + TColumn['_']['columnType'], + GetEnumValuesFromColumn, + GetBaseColumn, + ExtractAdditionalProperties +> extends infer TSchema extends v.GenericSchema ? TSchema extends v.AnySchema ? v.AnySchema + : TType extends 'select' ? HandleSelectColumn + : TType extends 'insert' ? HandleInsertColumn + : TType extends 'update' ? HandleUpdateColumn + : TSchema + : v.AnySchema; diff --git a/drizzle-valibot/src/constants.ts b/drizzle-valibot/src/constants.ts new file mode 100644 index 000000000..99f5d7a42 --- /dev/null +++ b/drizzle-valibot/src/constants.ts @@ -0,0 +1,20 @@ +export const CONSTANTS = { + INT8_MIN: -128, + INT8_MAX: 127, + INT8_UNSIGNED_MAX: 255, + INT16_MIN: -32768, + INT16_MAX: 32767, + INT16_UNSIGNED_MAX: 65535, + INT24_MIN: -8388608, + INT24_MAX: 8388607, + INT24_UNSIGNED_MAX: 16777215, + INT32_MIN: -2147483648, + INT32_MAX: 2147483647, + INT32_UNSIGNED_MAX: 4294967295, + INT48_MIN: -140737488355328, + INT48_MAX: 140737488355327, + INT48_UNSIGNED_MAX: 281474976710655, + INT64_MIN: -9223372036854775808n, + INT64_MAX: 9223372036854775807n, + INT64_UNSIGNED_MAX: 18446744073709551615n, +}; diff --git a/drizzle-valibot/src/index.ts b/drizzle-valibot/src/index.ts index 0c84c5052..0a6499e5b 100644 --- a/drizzle-valibot/src/index.ts +++ b/drizzle-valibot/src/index.ts @@ -1,332 +1,2 @@ -import { - type AnyColumn, - type Assume, - type Column, - type DrizzleTypeError, - type Equal, - getTableColumns, - is, - type Simplify, - type Table, -} from 'drizzle-orm'; -import { MySqlChar, MySqlVarBinary, MySqlVarChar } from 'drizzle-orm/mysql-core'; -import { type PgArray, PgChar, PgUUID, PgVarchar } from 'drizzle-orm/pg-core'; -import { SQLiteText } from 'drizzle-orm/sqlite-core'; - -import { - any, - type AnySchema, - array, - type ArraySchema, - type BaseSchema, - bigint, - type BigintSchema, - boolean, - type BooleanSchema, - date, - type DateSchema, - maxLength, - null_, - nullable, - type NullableSchema, - number, - type NumberSchema, - object, - type ObjectSchema, - optional, - type OptionalSchema, - picklist, - type PicklistSchema, - record, - string, - type StringSchema, - union, - uuid, -} from 'valibot'; - -const literalSchema = union([string(), number(), boolean(), null_()]); - -type Json = typeof jsonSchema; - -export const jsonSchema = union([literalSchema, array(any()), record(any())]); - -type MapInsertColumnToValibot< - TColumn extends Column, - TType extends BaseSchema, -> = TColumn['_']['notNull'] extends false ? OptionalSchema> - : TColumn['_']['hasDefault'] extends true ? OptionalSchema - : TType; - -type MapSelectColumnToValibot< - TColumn extends Column, - TType extends BaseSchema, -> = TColumn['_']['notNull'] extends false ? NullableSchema : TType; - -type MapColumnToValibot< - TColumn extends Column, - TType extends BaseSchema, - TMode extends 'insert' | 'select', -> = TMode extends 'insert' ? MapInsertColumnToValibot - : MapSelectColumnToValibot; - -type MaybeOptional< - TColumn extends Column, - TType extends BaseSchema, - TMode extends 'insert' | 'select', - TNoOptional extends boolean, -> = TNoOptional extends true ? TType - : MapColumnToValibot; - -type GetValibotType = TColumn['_']['dataType'] extends infer TDataType - ? TDataType extends 'custom' ? AnySchema - : TDataType extends 'json' ? Json - : TColumn extends { enumValues: [string, ...string[]] } - ? Equal extends true ? StringSchema - : PicklistSchema - : TDataType extends 'array' - ? TColumn['_']['baseColumn'] extends Column ? ArraySchema> : never - : TDataType extends 'bigint' ? BigintSchema - : TDataType extends 'number' ? NumberSchema - : TDataType extends 'string' ? StringSchema - : TDataType extends 'boolean' ? BooleanSchema - : TDataType extends 'date' ? DateSchema - : AnySchema - : never; - -type ValueOrUpdater = T | ((arg: TUpdaterArg) => T); - -type UnwrapValueOrUpdater = T extends ValueOrUpdater ? U - : never; - -export type Refine = { - [K in keyof TTable['_']['columns']]?: ValueOrUpdater< - BaseSchema, - TMode extends 'select' ? BuildSelectSchema - : BuildInsertSchema - >; -}; - -export type BuildInsertSchema< - TTable extends Table, - TRefine extends Refine | {}, - TNoOptional extends boolean = false, -> = TTable['_']['columns'] extends infer TColumns extends Record< - string, - Column -> ? { - [K in keyof TColumns & string]: MaybeOptional< - TColumns[K], - K extends keyof TRefine ? Assume, BaseSchema> - : GetValibotType, - 'insert', - TNoOptional - >; - } - : never; - -export type BuildSelectSchema< - TTable extends Table, - TRefine extends Refine, - TNoOptional extends boolean = false, -> = Simplify< - { - [K in keyof TTable['_']['columns']]: MaybeOptional< - TTable['_']['columns'][K], - K extends keyof TRefine ? Assume, BaseSchema> - : GetValibotType, - 'select', - TNoOptional - >; - } ->; - -export function createInsertSchema< - TTable extends Table, - TRefine extends Refine = Refine, ->( - table: TTable, - /** - * @param refine Refine schema fields - */ - refine?: { - [K in keyof TRefine]: K extends keyof TTable['_']['columns'] ? TRefine[K] - : DrizzleTypeError< - `Column '${ - & K - & string}' does not exist in table '${TTable['_']['name']}'` - >; - }, - // -): ObjectSchema< - BuildInsertSchema< - TTable, - Equal> extends true ? {} : TRefine - > -> { - const columns = getTableColumns(table); - const columnEntries = Object.entries(columns); - - let schemaEntries = Object.fromEntries( - columnEntries.map(([name, column]) => { - return [name, mapColumnToSchema(column)]; - }), - ); - - if (refine) { - schemaEntries = Object.assign( - schemaEntries, - Object.fromEntries( - Object.entries(refine).map(([name, refineColumn]) => { - return [ - name, - typeof refineColumn === 'function' - ? refineColumn( - schemaEntries as BuildInsertSchema< - TTable, - {}, - true - >, - ) - : refineColumn, - ]; - }), - ), - ); - } - - for (const [name, column] of columnEntries) { - if (!column.notNull) { - schemaEntries[name] = optional(nullable(schemaEntries[name]!)); - } else if (column.hasDefault) { - schemaEntries[name] = optional(schemaEntries[name]!); - } - } - - return object(schemaEntries) as any; -} - -export function createSelectSchema< - TTable extends Table, - TRefine extends Refine = Refine, ->( - table: TTable, - /** - * @param refine Refine schema fields - */ - refine?: { - [K in keyof TRefine]: K extends keyof TTable['_']['columns'] ? TRefine[K] - : DrizzleTypeError< - `Column '${ - & K - & string}' does not exist in table '${TTable['_']['name']}'` - >; - }, -): ObjectSchema< - BuildSelectSchema< - TTable, - Equal> extends true ? {} : TRefine - > -> { - const columns = getTableColumns(table); - const columnEntries = Object.entries(columns); - - let schemaEntries = Object.fromEntries( - columnEntries.map(([name, column]) => { - return [name, mapColumnToSchema(column)]; - }), - ); - - if (refine) { - schemaEntries = Object.assign( - schemaEntries, - Object.fromEntries( - Object.entries(refine).map(([name, refineColumn]) => { - return [ - name, - typeof refineColumn === 'function' - ? refineColumn( - schemaEntries as BuildSelectSchema< - TTable, - {}, - true - >, - ) - : refineColumn, - ]; - }), - ), - ); - } - - for (const [name, column] of columnEntries) { - if (!column.notNull) { - schemaEntries[name] = nullable(schemaEntries[name]!); - } - } - - return object(schemaEntries) as any; -} - -function isWithEnum( - column: AnyColumn, -): column is typeof column & { enumValues: [string, ...string[]] } { - return ( - 'enumValues' in column - && Array.isArray(column.enumValues) - && column.enumValues.length > 0 - ); -} - -function mapColumnToSchema(column: Column): BaseSchema { - let type: BaseSchema | undefined; - - if (isWithEnum(column)) { - type = column.enumValues?.length - ? picklist(column.enumValues) - : string(); - } - - if (!type) { - if (column.dataType === 'custom') { - type = any(); - } else if (column.dataType === 'json') { - type = jsonSchema; - } else if (column.dataType === 'array') { - type = array( - mapColumnToSchema((column as PgArray).baseColumn), - ); - } else if (column.dataType === 'number') { - type = number(); - } else if (column.dataType === 'bigint') { - type = bigint(); - } else if (column.dataType === 'boolean') { - type = boolean(); - } else if (column.dataType === 'date') { - type = date(); - } else if (column.dataType === 'string') { - let sType = string(); - - if ( - (is(column, PgChar) - || is(column, PgVarchar) - || is(column, MySqlVarChar) - || is(column, MySqlVarBinary) - || is(column, MySqlChar) - || is(column, SQLiteText)) - && typeof column.length === 'number' - ) { - sType = string([maxLength(column.length)]); - } - - type = sType; - } else if (is(column, PgUUID)) { - type = string([uuid()]); - } - } - - if (!type) { - type = any(); - } - - return type; -} +export * from './schema.ts'; +export * from './schema.types.ts'; diff --git a/drizzle-valibot/src/schema.ts b/drizzle-valibot/src/schema.ts new file mode 100644 index 000000000..30a6f77ec --- /dev/null +++ b/drizzle-valibot/src/schema.ts @@ -0,0 +1,95 @@ +import { Column, getTableColumns, getViewSelectedFields, is, isTable, isView, SQL } from 'drizzle-orm'; +import type { Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import * as v from 'valibot'; +import { columnToSchema, mapEnumValues } from './column.ts'; +import type { Conditions } from './schema.types.internal.ts'; +import type { CreateInsertSchema, CreateSelectSchema, CreateUpdateSchema } from './schema.types.ts'; +import { isPgEnum } from './utils.ts'; + +function getColumns(tableLike: Table | View) { + return isTable(tableLike) ? getTableColumns(tableLike) : getViewSelectedFields(tableLike); +} + +function handleColumns( + columns: Record, + refinements: Record, + conditions: Conditions, +): v.GenericSchema { + const columnSchemas: Record = {}; + + for (const [key, selected] of Object.entries(columns)) { + if (!is(selected, Column) && !is(selected, SQL) && !is(selected, SQL.Aliased) && typeof selected === 'object') { + const columns = isTable(selected) || isView(selected) ? getColumns(selected) : selected; + columnSchemas[key] = handleColumns(columns, refinements[key] ?? {}, conditions); + continue; + } + + const refinement = refinements[key]; + if (refinement !== undefined && typeof refinement !== 'function') { + columnSchemas[key] = refinement; + continue; + } + + const column = is(selected, Column) ? selected : undefined; + const schema = column ? columnToSchema(column) : v.any(); + const refined = typeof refinement === 'function' ? refinement(schema) : schema; + + if (conditions.never(column)) { + continue; + } else { + columnSchemas[key] = refined; + } + + if (column) { + if (conditions.nullable(column)) { + columnSchemas[key] = v.nullable(columnSchemas[key]!); + } + + if (conditions.optional(column)) { + columnSchemas[key] = v.optional(columnSchemas[key]!); + } + } + } + + return v.object(columnSchemas) as any; +} + +export const createSelectSchema: CreateSelectSchema = ( + entity: Table | View | PgEnum<[string, ...string[]]>, + refine?: Record, +) => { + if (isPgEnum(entity)) { + return v.enum(mapEnumValues(entity.enumValues)); + } + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, { + never: () => false, + optional: () => false, + nullable: (column) => !column.notNull, + }) as any; +}; + +export const createInsertSchema: CreateInsertSchema = ( + entity: Table, + refine?: Record, +) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, { + never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always', + optional: (column) => !column.notNull || (column.notNull && column.hasDefault), + nullable: (column) => !column.notNull, + }) as any; +}; + +export const createUpdateSchema: CreateUpdateSchema = ( + entity: Table, + refine?: Record, +) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, { + never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always', + optional: () => true, + nullable: (column) => !column.notNull, + }) as any; +}; diff --git a/drizzle-valibot/src/schema.types.internal.ts b/drizzle-valibot/src/schema.types.internal.ts new file mode 100644 index 000000000..e39c34d8a --- /dev/null +++ b/drizzle-valibot/src/schema.types.internal.ts @@ -0,0 +1,104 @@ +import type { Assume, Column, DrizzleTypeError, SelectedFieldsFlat, Simplify, Table, View } from 'drizzle-orm'; +import type * as v from 'valibot'; +import type { + ExtractAdditionalProperties, + GetBaseColumn, + GetEnumValuesFromColumn, + GetValibotType, + HandleColumn, +} from './column.types.ts'; +import type { GetSelection, RemoveNever } from './utils.ts'; + +export interface Conditions { + never: (column?: Column) => boolean; + optional: (column: Column) => boolean; + nullable: (column: Column) => boolean; +} + +export type BuildRefineColumns< + TColumns extends Record, +> = Simplify< + RemoveNever< + { + [K in keyof TColumns]: TColumns[K] extends infer TColumn extends Column ? GetValibotType< + TColumn['_']['data'], + TColumn['_']['dataType'], + TColumn['_']['columnType'], + GetEnumValuesFromColumn, + GetBaseColumn, + ExtractAdditionalProperties + > extends infer TSchema extends v.GenericSchema ? TSchema + : v.AnySchema + : TColumns[K] extends infer TObject extends SelectedFieldsFlat | Table | View + ? BuildRefineColumns> + : TColumns[K]; + } + > +>; + +export type BuildRefine< + TColumns extends Record, +> = BuildRefineColumns extends infer TBuildColumns ? { + [K in keyof TBuildColumns]?: TBuildColumns[K] extends v.GenericSchema + ? ((schema: TBuildColumns[K]) => v.GenericSchema) | v.GenericSchema + : TBuildColumns[K] extends Record ? Simplify> + : never; + } + : never; + +type HandleRefinement< + TType extends 'select' | 'insert' | 'update', + TRefinement extends v.GenericSchema | ((schema: v.GenericSchema) => v.GenericSchema), + TColumn extends Column, +> = TRefinement extends (schema: any) => v.GenericSchema ? ( + TColumn['_']['notNull'] extends true ? ReturnType + : v.NullableSchema, undefined> + ) extends infer TSchema ? TType extends 'update' ? v.OptionalSchema, undefined> + : TSchema + : v.AnySchema + : TRefinement; + +type IsRefinementDefined = TKey extends keyof TRefinements + ? TRefinements[TKey] extends v.GenericSchema | ((schema: any) => any) ? true + : false + : false; + +export type BuildSchema< + TType extends 'select' | 'insert' | 'update', + TColumns extends Record, + TRefinements extends Record | undefined, +> = v.ObjectSchema< + Simplify< + RemoveNever< + { + readonly [K in keyof TColumns]: TColumns[K] extends infer TColumn extends Column + ? TRefinements extends object + ? IsRefinementDefined> extends true + ? HandleRefinement], TColumn> + : HandleColumn + : HandleColumn + : TColumns[K] extends infer TObject extends SelectedFieldsFlat | Table | View ? BuildSchema< + TType, + GetSelection, + TRefinements extends object + ? TRefinements[Assume] extends infer TNestedRefinements extends object + ? TNestedRefinements + : undefined + : undefined + > + : v.AnySchema; + } + > + >, + undefined +>; + +export type NoUnknownKeys< + TRefinement extends Record, + TCompare extends Record, +> = { + [K in keyof TRefinement]: K extends keyof TCompare + ? TRefinement[K] extends Record ? NoUnknownKeys + : TRefinement[K] + : DrizzleTypeError<`Found unknown key in refinement: "${K & string}"`>; +}; diff --git a/drizzle-valibot/src/schema.types.ts b/drizzle-valibot/src/schema.types.ts new file mode 100644 index 000000000..c0b2ef82c --- /dev/null +++ b/drizzle-valibot/src/schema.types.ts @@ -0,0 +1,49 @@ +import type { Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import type * as v from 'valibot'; +import type { EnumValuesToEnum } from './column.types.ts'; +import type { BuildRefine, BuildSchema, NoUnknownKeys } from './schema.types.internal.ts'; + +export interface CreateSelectSchema { + (table: TTable): BuildSchema<'select', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine, + >( + table: TTable, + refine?: NoUnknownKeys, + ): BuildSchema<'select', TTable['_']['columns'], TRefine>; + + (view: TView): BuildSchema<'select', TView['_']['selectedFields'], undefined>; + < + TView extends View, + TRefine extends BuildRefine, + >( + view: TView, + refine: NoUnknownKeys, + ): BuildSchema<'select', TView['_']['selectedFields'], TRefine>; + + >(enum_: TEnum): v.EnumSchema, undefined>; +} + +export interface CreateInsertSchema { + (table: TTable): BuildSchema<'insert', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine>, + >( + table: TTable, + refine?: NoUnknownKeys, + ): BuildSchema<'insert', TTable['_']['columns'], TRefine>; +} + +export interface CreateUpdateSchema { + (table: TTable): BuildSchema<'update', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine>, + >( + table: TTable, + refine?: TRefine, + ): BuildSchema<'update', TTable['_']['columns'], TRefine>; +} diff --git a/drizzle-valibot/src/utils.ts b/drizzle-valibot/src/utils.ts new file mode 100644 index 000000000..eb5034d6f --- /dev/null +++ b/drizzle-valibot/src/utils.ts @@ -0,0 +1,45 @@ +import type { Column, SelectedFieldsFlat, Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import type * as v from 'valibot'; +import type { literalSchema } from './column.ts'; + +export function isColumnType(column: Column, columnTypes: string[]): column is T { + return columnTypes.includes(column.columnType); +} + +export function isWithEnum(column: Column): column is typeof column & { enumValues: [string, ...string[]] } { + return 'enumValues' in column && Array.isArray(column.enumValues) && column.enumValues.length > 0; +} + +export const isPgEnum: (entity: any) => entity is PgEnum<[string, ...string[]]> = isWithEnum as any; + +type Literal = v.InferOutput; +export type Json = Literal | { [key: string]: Json } | Json[]; + +export type IsNever = [T] extends [never] ? true : false; + +export type ArrayHasAtLeastOneValue = TEnum extends [infer TString, ...any[]] + ? TString extends `${infer TLiteral}` ? TLiteral extends any ? true + : false + : false + : false; + +export type ColumnIsGeneratedAlwaysAs = TColumn['_']['identity'] extends 'always' ? true + : TColumn['_']['generated'] extends undefined ? false + : TColumn['_']['generated'] extends infer TGenerated extends { type: string } + ? TGenerated['type'] extends 'byDefault' ? false + : true + : true; + +export type RemoveNever = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +}; + +export type RemoveNeverElements = T extends [infer First, ...infer Rest] + ? IsNever extends true ? RemoveNeverElements + : [First, ...RemoveNeverElements] + : []; + +export type GetSelection | Table | View> = T extends Table ? T['_']['columns'] + : T extends View ? T['_']['selectedFields'] + : T; diff --git a/drizzle-valibot/tests/mysql.test.ts b/drizzle-valibot/tests/mysql.test.ts index 9635ef8fa..6578729a4 100644 --- a/drizzle-valibot/tests/mysql.test.ts +++ b/drizzle-valibot/tests/mysql.test.ts @@ -1,400 +1,498 @@ -import { - bigint, - binary, - boolean, - char, - customType, - date, - datetime, - decimal, - double, - float, - int, - json, - longtext, - mediumint, - mediumtext, - mysqlEnum, - mysqlTable, - real, - serial, - smallint, - text, - time, - timestamp, - tinyint, - tinytext, - varbinary, - varchar, - year, -} from 'drizzle-orm/mysql-core'; -import { - any, - bigint as valibigint, - boolean as valiboolean, - date as valiDate, - maxLength, - minLength, - minValue, - number, - object, - optional, - parse, - picklist, - string, -} from 'valibot'; -import { expect, test } from 'vitest'; -import { createInsertSchema, createSelectSchema, jsonSchema } from '../src'; -import { expectSchemaShape } from './utils.ts'; - -const customInt = customType<{ data: number }>({ - dataType() { - return 'int'; - }, +import { type Equal, sql } from 'drizzle-orm'; +import { customType, int, mysqlSchema, mysqlTable, mysqlView, serial, text } from 'drizzle-orm/mysql-core'; +import * as v from 'valibot'; +import { test } from 'vitest'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; + +const intSchema = v.pipe( + v.number(), + v.minValue(CONSTANTS.INT32_MIN as number), + v.maxValue(CONSTANTS.INT32_MAX as number), + v.integer(), +); +const serialNumberModeSchema = v.pipe( + v.number(), + v.minValue(0 as number), + v.maxValue(Number.MAX_SAFE_INTEGER as number), + v.integer(), +); +const textSchema = v.pipe(v.string(), v.maxLength(CONSTANTS.INT16_UNSIGNED_MAX as number)); + +test('table - select', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = v.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -const testTable = mysqlTable('test', { - bigint: bigint('bigint', { mode: 'bigint' }).notNull(), - bigintNumber: bigint('bigintNumber', { mode: 'number' }).notNull(), - binary: binary('binary').notNull(), - boolean: boolean('boolean').notNull(), - char: char('char', { length: 4 }).notNull(), - charEnum: char('char', { enum: ['a', 'b', 'c'] }).notNull(), - customInt: customInt('customInt').notNull(), - date: date('date').notNull(), - dateString: date('dateString', { mode: 'string' }).notNull(), - datetime: datetime('datetime').notNull(), - datetimeString: datetime('datetimeString', { mode: 'string' }).notNull(), - decimal: decimal('decimal').notNull(), - double: double('double').notNull(), - enum: mysqlEnum('enum', ['a', 'b', 'c']).notNull(), - float: float('float').notNull(), - int: int('int').notNull(), - json: json('json').notNull(), - mediumint: mediumint('mediumint').notNull(), - real: real('real').notNull(), - serial: serial('serial').notNull(), - smallint: smallint('smallint').notNull(), - text: text('text').notNull(), - textEnum: text('textEnum', { enum: ['a', 'b', 'c'] }).notNull(), - tinytext: tinytext('tinytext').notNull(), - tinytextEnum: tinytext('tinytextEnum', { enum: ['a', 'b', 'c'] }).notNull(), - mediumtext: mediumtext('mediumtext').notNull(), - mediumtextEnum: mediumtext('mediumtextEnum', { - enum: ['a', 'b', 'c'], - }).notNull(), - longtext: longtext('longtext').notNull(), - longtextEnum: longtext('longtextEnum', { enum: ['a', 'b', 'c'] }).notNull(), - time: time('time').notNull(), - timestamp: timestamp('timestamp').notNull(), - timestampString: timestamp('timestampString', { mode: 'string' }).notNull(), - tinyint: tinyint('tinyint').notNull(), - varbinary: varbinary('varbinary', { length: 200 }).notNull(), - varchar: varchar('varchar', { length: 200 }).notNull(), - varcharEnum: varchar('varcharEnum', { - length: 1, - enum: ['a', 'b', 'c'], - }).notNull(), - year: year('year').notNull(), - autoIncrement: int('autoIncrement').notNull().autoincrement(), +test('table in schema - select', (tc) => { + const schema = mysqlSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = v.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -const testTableRow = { - bigint: BigInt(1), - bigintNumber: 1, - binary: 'binary', - boolean: true, - char: 'char', - charEnum: 'a' as const, - customInt: { data: 1 }, - date: new Date(), - dateString: new Date().toISOString(), - datetime: new Date(), - datetimeString: new Date().toISOString(), - decimal: '1.1', - double: 1.1, - enum: 'a' as const, - float: 1.1, - int: 1, - json: { data: 1 }, - mediumint: 1, - real: 1.1, - serial: 1, - smallint: 1, - text: 'text', - textEnum: 'a' as const, - tinytext: 'tinytext', - tinytextEnum: 'a' as const, - mediumtext: 'mediumtext', - mediumtextEnum: 'a' as const, - longtext: 'longtext', - longtextEnum: 'a' as const, - time: '00:00:00', - timestamp: new Date(), - timestampString: new Date().toISOString(), - tinyint: 1, - varbinary: 'A'.repeat(200), - varchar: 'A'.repeat(200), - varcharEnum: 'a' as const, - year: 2021, - autoIncrement: 1, -}; - -test('insert valid row', () => { - const schema = createInsertSchema(testTable); - - expect(parse(schema, testTableRow)).toStrictEqual(testTableRow); +test('table - insert', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = v.object({ + id: v.optional(serialNumberModeSchema), + name: textSchema, + age: v.optional(v.nullable(intSchema)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('insert invalid varchar length', () => { - const schema = createInsertSchema(testTable); +test('table - update', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); - expect(() => - parse(schema, { - ...testTableRow, - varchar: 'A'.repeat(201), - }) - ).toThrow(undefined); + const result = createUpdateSchema(table); + const expected = v.object({ + id: v.optional(serialNumberModeSchema), + name: v.optional(textSchema), + age: v.optional(v.nullable(intSchema)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('insert smaller char length should work', () => { - const schema = createInsertSchema(testTable); +test('view qb - select', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = v.object({ id: serialNumberModeSchema, age: v.any() }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); - const input = { ...testTableRow, char: 'abc' }; +test('view columns - select', (t) => { + const view = mysqlView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); - expect(parse(schema, input)).toStrictEqual(input); + const result = createSelectSchema(view); + const expected = v.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('insert larger char length should fail', () => { - const schema = createInsertSchema(testTable); +test('view with nested fields - select', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = mysqlView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) + ); - expect(() => parse(schema, { ...testTableRow, char: 'abcde' })).toThrow(undefined); + const result = createSelectSchema(view); + const expected = v.object({ + id: serialNumberModeSchema, + nested: v.object({ name: textSchema, age: v.any() }), + table: v.object({ id: serialNumberModeSchema, name: textSchema }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('insert schema', (t) => { - const actual = createInsertSchema(testTable); - - const expected = object({ - bigint: valibigint(), - bigintNumber: number(), - binary: string(), - boolean: valiboolean(), - char: string([minLength(4), maxLength(4)]), - charEnum: picklist([ - 'a', - 'b', - 'c', - ]), - customInt: any(), - date: valiDate(), - dateString: string(), - datetime: valiDate(), - datetimeString: string(), - decimal: string(), - double: number(), - enum: picklist([ - 'a', - 'b', - 'c', - ]), - float: number(), - int: number(), - json: jsonSchema, - mediumint: number(), - real: number(), - serial: optional(number()), - smallint: number(), - text: string(), - textEnum: picklist([ - 'a', - 'b', - 'c', - ]), - tinytext: string(), - tinytextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - mediumtext: string(), - mediumtextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - longtext: string(), - longtextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - time: string(), - timestamp: valiDate(), - timestampString: string(), - tinyint: number(), - varbinary: string([maxLength(200)]), - varchar: string([maxLength(200)]), - varcharEnum: picklist([ - 'a', - 'b', - 'c', - ]), - year: number(), - autoIncrement: optional(number()), +test('nullability - select', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), }); - expectSchemaShape(t, expected).from(actual); + const result = createSelectSchema(table); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: intSchema, + c3: v.nullable(intSchema), + c4: intSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('select schema', (t) => { - const actual = createSelectSchema(testTable); - - const expected = object({ - bigint: valibigint(), - bigintNumber: number(), - binary: string(), - boolean: valiboolean(), - char: string([minLength(4), maxLength(4)]), - charEnum: picklist([ - 'a', - 'b', - 'c', - ]), - customInt: any(), - date: valiDate(), - dateString: string(), - datetime: valiDate(), - datetimeString: string(), - decimal: string(), - double: number(), - enum: picklist([ - 'a', - 'b', - 'c', - ]), - float: number(), - int: number(), - // - json: jsonSchema, - mediumint: number(), - real: number(), - serial: number(), - smallint: number(), - text: string(), - textEnum: picklist([ - 'a', - 'b', - 'c', - ]), - tinytext: string(), - tinytextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - mediumtext: string(), - mediumtextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - longtext: string(), - longtextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - time: string(), - timestamp: valiDate(), - timestampString: string(), - tinyint: number(), - varbinary: string([maxLength(200)]), - varchar: string([maxLength(200)]), - varcharEnum: picklist([ - 'a', - 'b', - 'c', - ]), - year: number(), - autoIncrement: number(), +test('nullability - insert', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), }); - expectSchemaShape(t, expected).from(actual); + const result = createInsertSchema(table); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: intSchema, + c3: v.optional(v.nullable(intSchema)), + c4: v.optional(intSchema), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('select schema w/ refine', (t) => { - const actual = createSelectSchema(testTable, { - bigint: (_) => valibigint([minValue(0n)]), +test('nullability - update', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), }); - const expected = object({ - bigint: valibigint([minValue(0n)]), - bigintNumber: number(), - binary: string(), - boolean: valiboolean(), - char: string([minLength(5), maxLength(5)]), - charEnum: picklist([ - 'a', - 'b', - 'c', - ]), - customInt: any(), - date: valiDate(), - dateString: string(), - datetime: valiDate(), - datetimeString: string(), - decimal: string(), - double: number(), - enum: picklist([ - 'a', - 'b', - 'c', - ]), - float: number(), - int: number(), - json: jsonSchema, - mediumint: number(), - real: number(), - serial: number(), - smallint: number(), - text: string(), - textEnum: picklist([ - 'a', - 'b', - 'c', - ]), - tinytext: string(), - tinytextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - mediumtext: string(), - mediumtextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - longtext: string(), - longtextEnum: picklist([ - 'a', - 'b', - 'c', - ]), - time: string(), - timestamp: valiDate(), - timestampString: string(), - tinyint: number(), - varbinary: string([maxLength(200)]), - varchar: string([maxLength(200)]), - varcharEnum: picklist([ - 'a', - 'b', - 'c', - ]), - year: number(), - autoIncrement: number(), + const result = createUpdateSchema(table); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.optional(intSchema), + c3: v.optional(v.nullable(intSchema)), + c4: v.optional(intSchema), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), }); - expectSchemaShape(t, expected).from(actual); + const result = createSelectSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); + +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); + + const customTextSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(100)); + const result = createSelectSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + c4: customTextSchema, + }); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + c4: customTextSchema, + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - update', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.optional(v.pipe(intSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine view - select', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int(), + c3: int(), + c4: int(), + c5: int(), + c6: int(), + }); + const view = mysqlView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + nested: { + c5: (schema) => v.pipe(schema, v.maxValue(1000)), + c6: v.pipe(v.string(), v.transform(Number)), + }, + table: { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }, + }); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: v.nullable(v.pipe(intSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + nested: v.object({ + c4: v.nullable(intSchema), + c5: v.nullable(v.pipe(intSchema, v.maxValue(1000))), + c6: v.pipe(v.string(), v.transform(Number)), + }), + table: v.object({ + c1: v.nullable(intSchema), + c2: v.nullable(v.pipe(intSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + c4: v.nullable(intSchema), + c5: v.nullable(intSchema), + c6: v.nullable(intSchema), + }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('all data types', (t) => { + const table = mysqlTable('test', ({ + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + mysqlEnum, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varchar, + varbinary, + year, + longtext, + mediumtext, + tinytext, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(), + bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(), + binary: binary({ length: 10 }).notNull(), + boolean: boolean().notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + datetime1: datetime({ mode: 'date' }).notNull(), + datetime2: datetime({ mode: 'string' }).notNull(), + decimal1: decimal().notNull(), + decimal2: decimal({ unsigned: true }).notNull(), + double1: double().notNull(), + double2: double({ unsigned: true }).notNull(), + float1: float().notNull(), + float2: float({ unsigned: true }).notNull(), + int1: int().notNull(), + int2: int({ unsigned: true }).notNull(), + json: json().notNull(), + mediumint1: mediumint().notNull(), + mediumint2: mediumint({ unsigned: true }).notNull(), + enum: mysqlEnum('enum', ['a', 'b', 'c']).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint1: smallint().notNull(), + smallint2: smallint({ unsigned: true }).notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + tinyint1: tinyint().notNull(), + tinyint2: tinyint({ unsigned: true }).notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + varbinary: varbinary({ length: 10 }).notNull(), + year: year().notNull(), + longtext1: longtext().notNull(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(), + mediumtext1: mediumtext().notNull(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(), + tinytext1: tinytext().notNull(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(), + })); + + const result = createSelectSchema(table); + const expected = v.object({ + bigint1: v.pipe(v.number(), v.minValue(Number.MIN_SAFE_INTEGER), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), + bigint2: v.pipe(v.bigint(), v.minValue(CONSTANTS.INT64_MIN), v.maxValue(CONSTANTS.INT64_MAX)), + bigint3: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), + bigint4: v.pipe(v.bigint(), v.minValue(0n as bigint), v.maxValue(CONSTANTS.INT64_UNSIGNED_MAX)), + binary: v.string(), + boolean: v.boolean(), + char1: v.pipe(v.string(), v.length(10 as number)), + char2: v.enum({ a: 'a', b: 'b', c: 'c' }), + date1: v.date(), + date2: v.string(), + datetime1: v.date(), + datetime2: v.string(), + decimal1: v.string(), + decimal2: v.string(), + double1: v.pipe(v.number(), v.minValue(CONSTANTS.INT48_MIN), v.maxValue(CONSTANTS.INT48_MAX)), + double2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT48_UNSIGNED_MAX)), + float1: v.pipe(v.number(), v.minValue(CONSTANTS.INT24_MIN), v.maxValue(CONSTANTS.INT24_MAX)), + float2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT24_UNSIGNED_MAX)), + int1: v.pipe(v.number(), v.minValue(CONSTANTS.INT32_MIN), v.maxValue(CONSTANTS.INT32_MAX), v.integer()), + int2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT32_UNSIGNED_MAX), v.integer()), + json: jsonSchema, + mediumint1: v.pipe(v.number(), v.minValue(CONSTANTS.INT24_MIN), v.maxValue(CONSTANTS.INT24_MAX), v.integer()), + mediumint2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT24_UNSIGNED_MAX), v.integer()), + enum: v.enum({ a: 'a', b: 'b', c: 'c' }), + real: v.pipe(v.number(), v.minValue(CONSTANTS.INT48_MIN), v.maxValue(CONSTANTS.INT48_MAX)), + serial: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), + smallint1: v.pipe(v.number(), v.minValue(CONSTANTS.INT16_MIN), v.maxValue(CONSTANTS.INT16_MAX), v.integer()), + smallint2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT16_UNSIGNED_MAX), v.integer()), + text1: v.pipe(v.string(), v.maxLength(CONSTANTS.INT16_UNSIGNED_MAX)), + text2: v.enum({ a: 'a', b: 'b', c: 'c' }), + time: v.string(), + timestamp1: v.date(), + timestamp2: v.string(), + tinyint1: v.pipe(v.number(), v.minValue(CONSTANTS.INT8_MIN), v.maxValue(CONSTANTS.INT8_MAX), v.integer()), + tinyint2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT8_UNSIGNED_MAX), v.integer()), + varchar1: v.pipe(v.string(), v.maxLength(10 as number)), + varchar2: v.enum({ a: 'a', b: 'b', c: 'c' }), + varbinary: v.string(), + year: v.pipe(v.number(), v.minValue(1901 as number), v.maxValue(2155 as number), v.integer()), + longtext1: v.pipe(v.string(), v.maxLength(CONSTANTS.INT32_UNSIGNED_MAX)), + longtext2: v.enum({ a: 'a', b: 'b', c: 'c' }), + mediumtext1: v.pipe(v.string(), v.maxLength(CONSTANTS.INT24_UNSIGNED_MAX)), + mediumtext2: v.enum({ a: 'a', b: 'b', c: 'c' }), + tinytext1: v.pipe(v.string(), v.maxLength(CONSTANTS.INT8_UNSIGNED_MAX)), + tinytext2: v.enum({ a: 'a', b: 'b', c: 'c' }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +/* Disallow unknown keys in table refinement - select */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = mysqlTable('test', { id: int() }); + const view = mysqlView('test').as((qb) => qb.select().from(table)); + const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: v.string() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: v.string() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = mysqlView('test', { id: int() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: v.string() }); +} diff --git a/drizzle-valibot/tests/pg.test.ts b/drizzle-valibot/tests/pg.test.ts index 659845fa1..ea2bf2dd0 100644 --- a/drizzle-valibot/tests/pg.test.ts +++ b/drizzle-valibot/tests/pg.test.ts @@ -1,184 +1,546 @@ -import { char, date, integer, pgEnum, pgTable, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core'; +import { type Equal, sql } from 'drizzle-orm'; import { - array, - date as valiDate, - email, - maxLength, - minLength, - minValue, - nullable, - number, - object, - optional, - parse, - picklist, - string, -} from 'valibot'; -import { expect, test } from 'vitest'; -import { createInsertSchema, createSelectSchema } from '../src'; -import { expectSchemaShape } from './utils.ts'; - -export const roleEnum = pgEnum('role', ['admin', 'user']); - -const users = pgTable('users', { - a: integer('a').array(), - id: serial('id').primaryKey(), - name: text('name'), - email: text('email').notNull(), - birthdayString: date('birthday_string').notNull(), - birthdayDate: date('birthday_date', { mode: 'date' }).notNull(), - createdAt: timestamp('created_at').notNull().defaultNow(), - role: roleEnum('role').notNull(), - roleText: text('role1', { enum: ['admin', 'user'] }).notNull(), - roleText2: text('role2', { enum: ['admin', 'user'] }) - .notNull() - .default('user'), - profession: varchar('profession', { length: 20 }).notNull(), - initials: char('initials', { length: 2 }).notNull(), -}); - -const testUser = { - a: [1, 2, 3], - id: 1, - name: 'John Doe', - email: 'john.doe@example.com', - birthdayString: '1990-01-01', - birthdayDate: new Date('1990-01-01'), - createdAt: new Date(), - role: 'admin' as const, - roleText: 'admin' as const, - roleText2: 'admin' as const, - profession: 'Software Engineer', - initials: 'JD', -}; - -test('users insert valid user', () => { - const schema = createInsertSchema(users); - - expect(parse(schema, testUser)).toStrictEqual(testUser); -}); - -test('users insert invalid varchar', () => { - const schema = createInsertSchema(users); - - expect(() => - parse(schema, { - ...testUser, - profession: 'Chief Executive Officer', - }) - ).toThrow(undefined); -}); - -test('users insert invalid char', () => { - const schema = createInsertSchema(users); - - expect(() => parse(schema, { ...testUser, initials: 'JoDo' })).toThrow(undefined); -}); - -test('users insert schema', (t) => { - const actual = createInsertSchema(users, { - id: () => number([minValue(0)]), - email: () => string([email()]), - roleText: picklist(['user', 'manager', 'admin']), - }); - - (() => { - { - createInsertSchema(users, { - // @ts-expect-error (missing property) - foobar: number(), - }); - } - - { - createInsertSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } - }); - - const expected = object({ - a: optional(nullable(array(number()))), - id: optional(number([minValue(0)])), - name: optional(nullable(string())), - email: string(), - birthdayString: string(), - birthdayDate: valiDate(), - createdAt: optional(valiDate()), - role: picklist(['admin', 'user']), - roleText: picklist(['user', 'manager', 'admin']), - roleText2: optional(picklist(['admin', 'user'])), - profession: string([maxLength(20), minLength(1)]), - initials: string([maxLength(2), minLength(1)]), - }); - - expectSchemaShape(t, expected).from(actual); -}); - -test('users insert schema w/ defaults', (t) => { - const actual = createInsertSchema(users); - - const expected = object({ - a: optional(nullable(array(number()))), - id: optional(number()), - name: optional(nullable(string())), - email: string(), - birthdayString: string(), - birthdayDate: valiDate(), - createdAt: optional(valiDate()), - role: picklist(['admin', 'user']), - roleText: picklist(['admin', 'user']), - roleText2: optional(picklist(['admin', 'user'])), - profession: string([maxLength(20), minLength(1)]), - initials: string([maxLength(2), minLength(1)]), - }); - - expectSchemaShape(t, expected).from(actual); + customType, + integer, + pgEnum, + pgMaterializedView, + pgSchema, + pgTable, + pgView, + serial, + text, +} from 'drizzle-orm/pg-core'; +import * as v from 'valibot'; +import { test } from 'vitest'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectEnumValues, expectSchemaShape } from './utils.ts'; + +const integerSchema = v.pipe(v.number(), v.minValue(CONSTANTS.INT32_MIN), v.maxValue(CONSTANTS.INT32_MAX), v.integer()); +const textSchema = v.string(); + +test('table - select', (t) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = v.object({ id: integerSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table in schema - select', (tc) => { + const schema = pgSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = v.object({ id: integerSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -test('users select schema', (t) => { - const actual = createSelectSchema(users, { - id: () => number([minValue(0)]), - email: () => string(), - roleText: picklist(['user', 'manager', 'admin']), - }); - - const expected = object({ - a: nullable(array(number())), - id: number([minValue(0)]), - name: nullable(string()), - email: string(), - birthdayString: string(), - birthdayDate: valiDate(), - createdAt: valiDate(), - role: picklist(['admin', 'user']), - roleText: picklist(['user', 'manager', 'admin']), - roleText2: picklist(['admin', 'user']), - profession: string([maxLength(20), minLength(1)]), - initials: string([maxLength(2), minLength(1)]), +test('table - insert', (t) => { + const table = pgTable('test', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer(), }); - - expectSchemaShape(t, expected).from(actual); + + const result = createInsertSchema(table); + const expected = v.object({ name: textSchema, age: v.optional(v.nullable(integerSchema)) }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users select schema w/ defaults', (t) => { - const actual = createSelectSchema(users); - - const expected = object({ - a: nullable(array(number())), - id: number(), - name: nullable(string()), - email: string(), - birthdayString: string(), - birthdayDate: valiDate(), - createdAt: valiDate(), - role: picklist(['admin', 'user']), - roleText: picklist(['admin', 'user']), - roleText2: picklist(['admin', 'user']), - profession: string([maxLength(20), minLength(1)]), - initials: string([maxLength(2), minLength(1)]), - }); - - expectSchemaShape(t, expected).from(actual); +test('table - update', (t) => { + const table = pgTable('test', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer(), + }); + + const result = createUpdateSchema(table); + const expected = v.object({ + name: v.optional(textSchema), + age: v.optional(v.nullable(integerSchema)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('view qb - select', (t) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = v.object({ id: integerSchema, age: v.any() }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('view columns - select', (t) => { + const view = pgView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = v.object({ id: integerSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('materialized view qb - select', (t) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgMaterializedView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = v.object({ id: integerSchema, age: v.any() }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); + +test('materialized view columns - select', (t) => { + const view = pgView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = v.object({ id: integerSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('view with nested fields - select', (t) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgMaterializedView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view); + const expected = v.object({ + id: integerSchema, + nested: v.object({ name: textSchema, age: v.any() }), + table: v.object({ id: integerSchema, name: textSchema }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('enum - select', (t) => { + const enum_ = pgEnum('test', ['a', 'b', 'c']); + + const result = createSelectSchema(enum_); + const expected = v.enum({ a: 'a', b: 'b', c: 'c' }); + expectEnumValues(t, expected).from(result); + Expect>(); +}); + +test('nullability - select', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + }); + + const result = createSelectSchema(table); + const expected = v.object({ + c1: v.nullable(integerSchema), + c2: integerSchema, + c3: v.nullable(integerSchema), + c4: integerSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - insert', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + c5: integer().generatedAlwaysAs(1), + c6: integer().generatedAlwaysAsIdentity(), + c7: integer().generatedByDefaultAsIdentity(), + }); + + const result = createInsertSchema(table); + const expected = v.object({ + c1: v.optional(v.nullable(integerSchema)), + c2: integerSchema, + c3: v.optional(v.nullable(integerSchema)), + c4: v.optional(integerSchema), + c7: v.optional(integerSchema), + }); + expectSchemaShape(t, expected).from(result); +}); + +test('nullability - update', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + c5: integer().generatedAlwaysAs(1), + c6: integer().generatedAlwaysAsIdentity(), + c7: integer().generatedByDefaultAsIdentity(), + }); + + const result = createUpdateSchema(table); + const expected = v.object({ + c1: v.optional(v.nullable(integerSchema)), + c2: v.optional(integerSchema), + c3: v.optional(v.nullable(integerSchema)), + c4: v.optional(integerSchema), + c7: v.optional(integerSchema), + }); + table.c5.generated?.type; + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.nullable(integerSchema), + c2: v.pipe(integerSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: customText(), + }); + + const customTextSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(100)); + const result = createSelectSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + c4: customTextSchema, + }); + const expected = v.object({ + c1: v.nullable(integerSchema), + c2: v.pipe(integerSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + c4: customTextSchema, + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: integer().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.optional(v.nullable(integerSchema)), + c2: v.pipe(integerSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - update', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: integer().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.optional(v.nullable(integerSchema)), + c2: v.optional(v.pipe(integerSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine view - select', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer(), + c3: integer(), + c4: integer(), + c5: integer(), + c6: integer(), + }); + const view = pgView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + nested: { + c5: (schema) => v.pipe(schema, v.maxValue(1000)), + c6: v.pipe(v.string(), v.transform(Number)), + }, + table: { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }, + }); + const expected = v.object({ + c1: v.nullable(integerSchema), + c2: v.nullable(v.pipe(integerSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + nested: v.object({ + c4: v.nullable(integerSchema), + c5: v.nullable(v.pipe(integerSchema, v.maxValue(1000))), + c6: v.pipe(v.string(), v.transform(Number)), + }), + table: v.object({ + c1: v.nullable(integerSchema), + c2: v.nullable(v.pipe(integerSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + c4: v.nullable(integerSchema), + c5: v.nullable(integerSchema), + c6: v.nullable(integerSchema), + }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('all data types', (t) => { + const table = pgTable('test', ({ + bigint, + bigserial, + bit, + boolean, + date, + char, + cidr, + doublePrecision, + geometry, + halfvec, + inet, + integer, + interval, + json, + jsonb, + line, + macaddr, + macaddr8, + numeric, + point, + real, + serial, + smallint, + smallserial, + text, + sparsevec, + time, + timestamp, + uuid, + varchar, + vector, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigserial1: bigserial({ mode: 'number' }).notNull(), + bigserial2: bigserial({ mode: 'bigint' }).notNull(), + bit: bit({ dimensions: 5 }).notNull(), + boolean: boolean().notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + cidr: cidr().notNull(), + doublePrecision: doublePrecision().notNull(), + geometry1: geometry({ type: 'point', mode: 'tuple' }).notNull(), + geometry2: geometry({ type: 'point', mode: 'xy' }).notNull(), + halfvec: halfvec({ dimensions: 3 }).notNull(), + inet: inet().notNull(), + integer: integer().notNull(), + interval: interval().notNull(), + json: json().notNull(), + jsonb: jsonb().notNull(), + line1: line({ mode: 'abc' }).notNull(), + line2: line({ mode: 'tuple' }).notNull(), + macaddr: macaddr().notNull(), + macaddr8: macaddr8().notNull(), + numeric: numeric().notNull(), + point1: point({ mode: 'xy' }).notNull(), + point2: point({ mode: 'tuple' }).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint: smallint().notNull(), + smallserial: smallserial().notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + sparsevec: sparsevec({ dimensions: 3 }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + uuid: uuid().notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + vector: vector({ dimensions: 3 }).notNull(), + array1: integer().array().notNull(), + array2: integer().array().array(2).notNull(), + array3: varchar({ length: 10 }).array().array(2).notNull(), + })); + + const result = createSelectSchema(table); + const expected = v.object({ + bigint1: v.pipe(v.number(), v.minValue(Number.MIN_SAFE_INTEGER), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), + bigint2: v.pipe(v.bigint(), v.minValue(CONSTANTS.INT64_MIN), v.maxValue(CONSTANTS.INT64_MAX)), + bigserial1: v.pipe( + v.number(), + v.minValue(Number.MIN_SAFE_INTEGER), + v.maxValue(Number.MAX_SAFE_INTEGER), + v.integer(), + ), + bigserial2: v.pipe(v.bigint(), v.minValue(CONSTANTS.INT64_MIN), v.maxValue(CONSTANTS.INT64_MAX)), + bit: v.pipe(v.string(), v.regex(/^[01]+$/), v.maxLength(5 as number)), + boolean: v.boolean(), + date1: v.date(), + date2: v.string(), + char1: v.pipe(v.string(), v.length(10 as number)), + char2: v.enum({ a: 'a', b: 'b', c: 'c' }), + cidr: v.string(), + doublePrecision: v.pipe(v.number(), v.minValue(CONSTANTS.INT48_MIN), v.maxValue(CONSTANTS.INT48_MAX)), + geometry1: v.tuple([v.number(), v.number()]), + geometry2: v.object({ x: v.number(), y: v.number() }), + halfvec: v.pipe(v.array(v.number()), v.length(3 as number)), + inet: v.string(), + integer: v.pipe(v.number(), v.minValue(CONSTANTS.INT32_MIN), v.maxValue(CONSTANTS.INT32_MAX), v.integer()), + interval: v.string(), + json: jsonSchema, + jsonb: jsonSchema, + line1: v.object({ a: v.number(), b: v.number(), c: v.number() }), + line2: v.tuple([v.number(), v.number(), v.number()]), + macaddr: v.string(), + macaddr8: v.string(), + numeric: v.string(), + point1: v.object({ x: v.number(), y: v.number() }), + point2: v.tuple([v.number(), v.number()]), + real: v.pipe(v.number(), v.minValue(CONSTANTS.INT24_MIN), v.maxValue(CONSTANTS.INT24_MAX)), + serial: v.pipe(v.number(), v.minValue(CONSTANTS.INT32_MIN), v.maxValue(CONSTANTS.INT32_MAX), v.integer()), + smallint: v.pipe(v.number(), v.minValue(CONSTANTS.INT16_MIN), v.maxValue(CONSTANTS.INT16_MAX), v.integer()), + smallserial: v.pipe(v.number(), v.minValue(CONSTANTS.INT16_MIN), v.maxValue(CONSTANTS.INT16_MAX), v.integer()), + text1: v.string(), + text2: v.enum({ a: 'a', b: 'b', c: 'c' }), + sparsevec: v.string(), + time: v.string(), + timestamp1: v.date(), + timestamp2: v.string(), + uuid: v.pipe(v.string(), v.uuid()), + varchar1: v.pipe(v.string(), v.maxLength(10 as number)), + varchar2: v.enum({ a: 'a', b: 'b', c: 'c' }), + vector: v.pipe(v.array(v.number()), v.length(3 as number)), + array1: v.array(integerSchema), + array2: v.pipe(v.array(v.array(integerSchema)), v.length(2 as number)), + array3: v.pipe(v.array(v.array(v.pipe(v.string(), v.maxLength(10 as number)))), v.length(2 as number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +/* Disallow unknown keys in table refinement - select */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createSelectSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createInsertSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = pgTable('test', { id: integer() }); + const view = pgView('test').as((qb) => qb.select().from(table)); + const mView = pgMaterializedView('test').as((qb) => qb.select().from(table)); + const nestedSelect = pgView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: v.string() }); + // @ts-expect-error + createSelectSchema(mView, { unknown: v.string() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: v.string() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = pgView('test', { id: integer() }).as(sql``); + const mView = pgView('test', { id: integer() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: v.string() }); + // @ts-expect-error + createSelectSchema(mView, { unknown: v.string() }); +} diff --git a/drizzle-valibot/tests/singlestore.test.ts b/drizzle-valibot/tests/singlestore.test.ts new file mode 100644 index 000000000..0827ba7a1 --- /dev/null +++ b/drizzle-valibot/tests/singlestore.test.ts @@ -0,0 +1,500 @@ +import { type Equal } from 'drizzle-orm'; +import { customType, int, serial, singlestoreSchema, singlestoreTable, text } from 'drizzle-orm/singlestore-core'; +import * as v from 'valibot'; +import { test } from 'vitest'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; + +const intSchema = v.pipe( + v.number(), + v.minValue(CONSTANTS.INT32_MIN as number), + v.maxValue(CONSTANTS.INT32_MAX as number), + v.integer(), +); +const serialNumberModeSchema = v.pipe( + v.number(), + v.minValue(0 as number), + v.maxValue(Number.MAX_SAFE_INTEGER as number), + v.integer(), +); +const textSchema = v.pipe(v.string(), v.maxLength(CONSTANTS.INT16_UNSIGNED_MAX as number)); + +test('table - select', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = v.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table in schema - select', (tc) => { + const schema = singlestoreSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = v.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table - insert', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = v.object({ + id: v.optional(serialNumberModeSchema), + name: textSchema, + age: v.optional(v.nullable(intSchema)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table - update', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createUpdateSchema(table); + const expected = v.object({ + id: v.optional(serialNumberModeSchema), + name: v.optional(textSchema), + age: v.optional(v.nullable(intSchema)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +// TODO: SingleStore doesn't support views yet. Add these tests when they're added + +// test('view qb - select', (t) => { +// const table = singlestoreTable('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }); +// const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + +// const result = createSelectSchema(view); +// const expected = v.object({ id: serialNumberModeSchema, age: v.any() }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +// test('view columns - select', (t) => { +// const view = mysqlView('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }).as(sql``); + +// const result = createSelectSchema(view); +// const expected = v.object({ id: serialNumberModeSchema, name: textSchema }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +// test('view with nested fields - select', (t) => { +// const table = singlestoreTable('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }); +// const view = mysqlView('test').as((qb) => +// qb.select({ +// id: table.id, +// nested: { +// name: table.name, +// age: sql``.as('age'), +// }, +// table, +// }).from(table) +// ); + +// const result = createSelectSchema(view); +// const expected = v.object({ +// id: serialNumberModeSchema, +// nested: v.object({ name: textSchema, age: v.any() }), +// table: v.object({ id: serialNumberModeSchema, name: textSchema }), +// }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +test('nullability - select', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + }); + + const result = createSelectSchema(table); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: intSchema, + c3: v.nullable(intSchema), + c4: intSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - insert', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: intSchema, + c3: v.optional(v.nullable(intSchema)), + c4: v.optional(intSchema), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - update', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.optional(intSchema), + c3: v.optional(v.nullable(intSchema)), + c4: v.optional(intSchema), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); + + const customTextSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(100)); + const result = createSelectSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + c4: customTextSchema, + }); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + c4: customTextSchema, + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - update', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.optional(v.pipe(intSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +// test('refine view - select', (t) => { +// const table = singlestoreTable('test', { +// c1: int(), +// c2: int(), +// c3: int(), +// c4: int(), +// c5: int(), +// c6: int(), +// }); +// const view = mysqlView('test').as((qb) => +// qb.select({ +// c1: table.c1, +// c2: table.c2, +// c3: table.c3, +// nested: { +// c4: table.c4, +// c5: table.c5, +// c6: table.c6, +// }, +// table, +// }).from(table) +// ); + +// const result = createSelectSchema(view, { +// c2: (schema) => v.pipe(schema, v.maxValue(1000)), +// c3: v.pipe(v.string(), v.transform(Number)), +// nested: { +// c5: (schema) => v.pipe(schema, v.maxValue(1000)), +// c6: v.pipe(v.string(), v.transform(Number)), +// }, +// table: { +// c2: (schema) => v.pipe(schema, v.maxValue(1000)), +// c3: v.pipe(v.string(), v.transform(Number)), +// }, +// }); +// const expected = v.object({ +// c1: v.nullable(intSchema), +// c2: v.nullable(v.pipe(intSchema, v.maxValue(1000))), +// c3: v.pipe(v.string(), v.transform(Number)), +// nested: v.object({ +// c4: v.nullable(intSchema), +// c5: v.nullable(v.pipe(intSchema, v.maxValue(1000))), +// c6: v.pipe(v.string(), v.transform(Number)), +// }), +// table: v.object({ +// c1: v.nullable(intSchema), +// c2: v.nullable(v.pipe(intSchema, v.maxValue(1000))), +// c3: v.pipe(v.string(), v.transform(Number)), +// c4: v.nullable(intSchema), +// c5: v.nullable(intSchema), +// c6: v.nullable(intSchema), +// }), +// }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +test('all data types', (t) => { + const table = singlestoreTable('test', ({ + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + singlestoreEnum, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varchar, + varbinary, + year, + longtext, + mediumtext, + tinytext, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(), + bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(), + binary: binary({ length: 10 }).notNull(), + boolean: boolean().notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + datetime1: datetime({ mode: 'date' }).notNull(), + datetime2: datetime({ mode: 'string' }).notNull(), + decimal1: decimal().notNull(), + decimal2: decimal({ unsigned: true }).notNull(), + double1: double().notNull(), + double2: double({ unsigned: true }).notNull(), + float1: float().notNull(), + float2: float({ unsigned: true }).notNull(), + int1: int().notNull(), + int2: int({ unsigned: true }).notNull(), + json: json().notNull(), + mediumint1: mediumint().notNull(), + mediumint2: mediumint({ unsigned: true }).notNull(), + enum: singlestoreEnum('enum', ['a', 'b', 'c']).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint1: smallint().notNull(), + smallint2: smallint({ unsigned: true }).notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + tinyint1: tinyint().notNull(), + tinyint2: tinyint({ unsigned: true }).notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + varbinary: varbinary({ length: 10 }).notNull(), + year: year().notNull(), + longtext1: longtext().notNull(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(), + mediumtext1: mediumtext().notNull(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(), + tinytext1: tinytext().notNull(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(), + })); + + const result = createSelectSchema(table); + const expected = v.object({ + bigint1: v.pipe(v.number(), v.minValue(Number.MIN_SAFE_INTEGER), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), + bigint2: v.pipe(v.bigint(), v.minValue(CONSTANTS.INT64_MIN), v.maxValue(CONSTANTS.INT64_MAX)), + bigint3: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), + bigint4: v.pipe(v.bigint(), v.minValue(0n as bigint), v.maxValue(CONSTANTS.INT64_UNSIGNED_MAX)), + binary: v.string(), + boolean: v.boolean(), + char1: v.pipe(v.string(), v.length(10 as number)), + char2: v.enum({ a: 'a', b: 'b', c: 'c' }), + date1: v.date(), + date2: v.string(), + datetime1: v.date(), + datetime2: v.string(), + decimal1: v.string(), + decimal2: v.string(), + double1: v.pipe(v.number(), v.minValue(CONSTANTS.INT48_MIN), v.maxValue(CONSTANTS.INT48_MAX)), + double2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT48_UNSIGNED_MAX)), + float1: v.pipe(v.number(), v.minValue(CONSTANTS.INT24_MIN), v.maxValue(CONSTANTS.INT24_MAX)), + float2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT24_UNSIGNED_MAX)), + int1: v.pipe(v.number(), v.minValue(CONSTANTS.INT32_MIN), v.maxValue(CONSTANTS.INT32_MAX), v.integer()), + int2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT32_UNSIGNED_MAX), v.integer()), + json: jsonSchema, + mediumint1: v.pipe(v.number(), v.minValue(CONSTANTS.INT24_MIN), v.maxValue(CONSTANTS.INT24_MAX), v.integer()), + mediumint2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT24_UNSIGNED_MAX), v.integer()), + enum: v.enum({ a: 'a', b: 'b', c: 'c' }), + real: v.pipe(v.number(), v.minValue(CONSTANTS.INT48_MIN), v.maxValue(CONSTANTS.INT48_MAX)), + serial: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), + smallint1: v.pipe(v.number(), v.minValue(CONSTANTS.INT16_MIN), v.maxValue(CONSTANTS.INT16_MAX), v.integer()), + smallint2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT16_UNSIGNED_MAX), v.integer()), + text1: v.pipe(v.string(), v.maxLength(CONSTANTS.INT16_UNSIGNED_MAX)), + text2: v.enum({ a: 'a', b: 'b', c: 'c' }), + time: v.string(), + timestamp1: v.date(), + timestamp2: v.string(), + tinyint1: v.pipe(v.number(), v.minValue(CONSTANTS.INT8_MIN), v.maxValue(CONSTANTS.INT8_MAX), v.integer()), + tinyint2: v.pipe(v.number(), v.minValue(0 as number), v.maxValue(CONSTANTS.INT8_UNSIGNED_MAX), v.integer()), + varchar1: v.pipe(v.string(), v.maxLength(10 as number)), + varchar2: v.enum({ a: 'a', b: 'b', c: 'c' }), + varbinary: v.string(), + year: v.pipe(v.number(), v.minValue(1901 as number), v.maxValue(2155 as number), v.integer()), + longtext1: v.pipe(v.string(), v.maxLength(CONSTANTS.INT32_UNSIGNED_MAX)), + longtext2: v.enum({ a: 'a', b: 'b', c: 'c' }), + mediumtext1: v.pipe(v.string(), v.maxLength(CONSTANTS.INT24_UNSIGNED_MAX)), + mediumtext2: v.enum({ a: 'a', b: 'b', c: 'c' }), + tinytext1: v.pipe(v.string(), v.maxLength(CONSTANTS.INT8_UNSIGNED_MAX)), + tinytext2: v.enum({ a: 'a', b: 'b', c: 'c' }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +/* Disallow unknown keys in table refinement - select */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: v.string() }); +} + +// /* Disallow unknown keys in view qb - select */ { +// const table = singlestoreTable('test', { id: int() }); +// const view = mysqlView('test').as((qb) => qb.select().from(table)); +// const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table)); +// // @ts-expect-error +// createSelectSchema(view, { unknown: v.string() }); +// // @ts-expect-error +// createSelectSchema(nestedSelect, { table: { unknown: v.string() } }); +// } + +// /* Disallow unknown keys in view columns - select */ { +// const view = mysqlView('test', { id: int() }).as(sql``); +// // @ts-expect-error +// createSelectSchema(view, { unknown: v.string() }); +// } diff --git a/drizzle-valibot/tests/sqlite.test.ts b/drizzle-valibot/tests/sqlite.test.ts index a520108f0..14e6b4bd6 100644 --- a/drizzle-valibot/tests/sqlite.test.ts +++ b/drizzle-valibot/tests/sqlite.test.ts @@ -1,178 +1,390 @@ -import { blob, integer, numeric, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; -import { - bigint as valibigint, - boolean, - date as valiDate, - minValue, - nullable, - number, - object, - optional, - type Output, - parse, - picklist, - string, -} from 'valibot'; -import { expect, test } from 'vitest'; -import { createInsertSchema, createSelectSchema, jsonSchema } from '../src'; -import { expectSchemaShape } from './utils.ts'; - -const blobJsonSchema = object({ - foo: string(), +import { type Equal, sql } from 'drizzle-orm'; +import { customType, int, sqliteTable, sqliteView, text } from 'drizzle-orm/sqlite-core'; +import * as v from 'valibot'; +import { test } from 'vitest'; +import { bufferSchema, jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; + +const intSchema = v.pipe( + v.number(), + v.minValue(Number.MIN_SAFE_INTEGER), + v.maxValue(Number.MAX_SAFE_INTEGER), + v.integer(), +); +const textSchema = v.string(); + +test('table - select', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = v.object({ id: intSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table - insert', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = v.object({ id: v.optional(intSchema), name: textSchema, age: v.optional(v.nullable(intSchema)) }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table - update', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + age: int(), + }); + + const result = createUpdateSchema(table); + const expected = v.object({ + id: v.optional(intSchema), + name: v.optional(textSchema), + age: v.optional(v.nullable(intSchema)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('view qb - select', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }); + const view = sqliteView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = v.object({ id: intSchema, age: v.any() }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('view columns - select', (t) => { + const view = sqliteView('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = v.object({ id: intSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -const users = sqliteTable('users', { - id: integer('id').primaryKey(), - blobJson: blob('blob', { mode: 'json' }) - .$type>() - .notNull(), - blobBigInt: blob('blob', { mode: 'bigint' }).notNull(), - numeric: numeric('numeric').notNull(), - createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), - createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }).notNull(), - boolean: integer('boolean', { mode: 'boolean' }).notNull(), - real: real('real').notNull(), - text: text('text', { length: 255 }), - role: text('role', { enum: ['admin', 'user'] }) - .notNull() - .default('user'), +test('view with nested fields - select', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }); + const view = sqliteView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view); + const expected = v.object({ + id: intSchema, + nested: v.object({ name: textSchema, age: v.any() }), + table: v.object({ id: intSchema, name: textSchema }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -const testUser = { - id: 1, - blobJson: { foo: 'bar' }, - blobBigInt: BigInt(123), - numeric: '123.45', - createdAt: new Date(), - createdAtMs: new Date(), - boolean: true, - real: 123.45, - text: 'foobar', - role: 'admin' as const, -}; - -test('users insert valid user', () => { - const schema = createInsertSchema(users); - // - expect(parse(schema, testUser)).toStrictEqual(testUser); +test('nullability - select', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + }); + + const result = createSelectSchema(table); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: intSchema, + c3: v.nullable(intSchema), + c4: intSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - insert', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: intSchema, + c3: v.optional(v.nullable(intSchema)), + c4: v.optional(intSchema), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert invalid text length', () => { - const schema = createInsertSchema(users); - expect(() => parse(schema, { ...testUser, text: 'a'.repeat(256) })).toThrow(undefined); +test('nullability - update', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.optional(intSchema), + c3: v.optional(v.nullable(intSchema)), + c4: v.optional(intSchema), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert schema', (t) => { - const actual = createInsertSchema(users, { - id: () => number([minValue(0)]), - blobJson: blobJsonSchema, - role: picklist(['admin', 'user', 'manager']), - }); - - (() => { - { - createInsertSchema(users, { - // @ts-expect-error (missing property) - foobar: number(), - }); - } - - { - createInsertSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } - }); - - const expected = object({ - id: optional(number([minValue(0)])), - blobJson: blobJsonSchema, - blobBigInt: valibigint(), - numeric: string(), - createdAt: valiDate(), - createdAtMs: valiDate(), - boolean: boolean(), - real: number(), - text: optional(nullable(string())), - role: optional(picklist(['admin', 'user', 'manager'])), - }); - - expectSchemaShape(t, expected).from(actual); +test('refine table - select', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert schema w/ defaults', (t) => { - const actual = createInsertSchema(users); +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); - const expected = object({ - id: optional(number()), - blobJson: jsonSchema, - blobBigInt: valibigint(), - numeric: string(), - createdAt: valiDate(), - createdAtMs: valiDate(), - boolean: boolean(), - real: number(), - text: optional(nullable(string())), - role: optional(picklist(['admin', 'user'])), + const customTextSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(100)); + const result = createSelectSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + c4: customTextSchema, + }); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + c4: customTextSchema, }); - expectSchemaShape(t, expected).from(actual); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.pipe(intSchema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users select schema', (t) => { - const actual = createSelectSchema(users, { - blobJson: jsonSchema, - role: picklist(['admin', 'user', 'manager']), - }); - - (() => { - { - createSelectSchema(users, { - // @ts-expect-error (missing property) - foobar: number(), - }); - } - - { - createSelectSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } - }); - - const expected = object({ - id: number(), - blobJson: jsonSchema, - blobBigInt: valibigint(), - numeric: string(), - createdAt: valiDate(), - createdAtMs: valiDate(), - boolean: boolean(), - real: number(), - text: nullable(string()), - role: picklist(['admin', 'user', 'manager']), - }); - - expectSchemaShape(t, expected).from(actual); +test('refine table - update', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }); + const expected = v.object({ + c1: v.optional(v.nullable(intSchema)), + c2: v.optional(v.pipe(intSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users select schema w/ defaults', (t) => { - const actual = createSelectSchema(users); +test('refine view - select', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int(), + c3: int(), + c4: int(), + c5: int(), + c6: int(), + }); + const view = sqliteView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); - const expected = object({ - id: number(), - blobJson: jsonSchema, - blobBigInt: valibigint(), - numeric: string(), - createdAt: valiDate(), - createdAtMs: valiDate(), - boolean: boolean(), - real: number(), - text: nullable(string()), - role: picklist(['admin', 'user']), + const result = createSelectSchema(view, { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + nested: { + c5: (schema) => v.pipe(schema, v.maxValue(1000)), + c6: v.pipe(v.string(), v.transform(Number)), + }, + table: { + c2: (schema) => v.pipe(schema, v.maxValue(1000)), + c3: v.pipe(v.string(), v.transform(Number)), + }, }); + const expected = v.object({ + c1: v.nullable(intSchema), + c2: v.nullable(v.pipe(intSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + nested: v.object({ + c4: v.nullable(intSchema), + c5: v.nullable(v.pipe(intSchema, v.maxValue(1000))), + c6: v.pipe(v.string(), v.transform(Number)), + }), + table: v.object({ + c1: v.nullable(intSchema), + c2: v.nullable(v.pipe(intSchema, v.maxValue(1000))), + c3: v.pipe(v.string(), v.transform(Number)), + c4: v.nullable(intSchema), + c5: v.nullable(intSchema), + c6: v.nullable(intSchema), + }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); - expectSchemaShape(t, expected).from(actual); +test('all data types', (t) => { + const table = sqliteTable('test', ({ + blob, + integer, + numeric, + real, + text, + }) => ({ + blob1: blob({ mode: 'buffer' }).notNull(), + blob2: blob({ mode: 'bigint' }).notNull(), + blob3: blob({ mode: 'json' }).notNull(), + integer1: integer({ mode: 'number' }).notNull(), + integer2: integer({ mode: 'boolean' }).notNull(), + integer3: integer({ mode: 'timestamp' }).notNull(), + integer4: integer({ mode: 'timestamp_ms' }).notNull(), + numeric: numeric().notNull(), + real: real().notNull(), + text1: text({ mode: 'text' }).notNull(), + text2: text({ mode: 'text', length: 10 }).notNull(), + text3: text({ mode: 'text', enum: ['a', 'b', 'c'] }).notNull(), + text4: text({ mode: 'json' }).notNull(), + })); + + const result = createSelectSchema(table); + const expected = v.object({ + blob1: bufferSchema, + blob2: v.pipe(v.bigint(), v.minValue(CONSTANTS.INT64_MIN), v.maxValue(CONSTANTS.INT64_MAX)), + blob3: jsonSchema, + integer1: v.pipe(v.number(), v.minValue(Number.MIN_SAFE_INTEGER), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), + integer2: v.boolean(), + integer3: v.date(), + integer4: v.date(), + numeric: v.string(), + real: v.pipe(v.number(), v.minValue(CONSTANTS.INT48_MIN), v.maxValue(CONSTANTS.INT48_MAX)), + text1: v.string(), + text2: v.pipe(v.string(), v.maxLength(10 as number)), + text3: v.enum({ a: 'a', b: 'b', c: 'c' }), + text4: jsonSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); + +/* Disallow unknown keys in table refinement - select */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: v.string() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = sqliteTable('test', { id: int() }); + const view = sqliteView('test').as((qb) => qb.select().from(table)); + const nestedSelect = sqliteView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: v.string() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: v.string() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = sqliteView('test', { id: int() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: v.string() }); +} diff --git a/drizzle-valibot/tests/utils.ts b/drizzle-valibot/tests/utils.ts index 189731956..7e1eae757 100644 --- a/drizzle-valibot/tests/utils.ts +++ b/drizzle-valibot/tests/utils.ts @@ -1,10 +1,43 @@ -import type { BaseSchema } from 'valibot'; +import type * as v from 'valibot'; import { expect, type TaskContext } from 'vitest'; -export function expectSchemaShape>(t: TaskContext, expected: T) { +function onlySpecifiedKeys(obj: Record, keys: string[]) { + return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key))); +} + +export function expectSchemaShape>(t: TaskContext, expected: T) { + return { + from(actual: T) { + expect(Object.keys(actual.entries)).toStrictEqual(Object.keys(expected.entries)); + + for (const key of Object.keys(actual.entries)) { + const actualEntry = actual.entries[key] as any; + const expectedEntry = expected.entries[key] as any; + const keys = ['kind', 'type', 'expects', 'async', 'message']; + actualEntry.pipe ??= []; + expectedEntry.pipe ??= []; + + expect(onlySpecifiedKeys(actualEntry, keys)).toStrictEqual(onlySpecifiedKeys(expectedEntry, keys)); + expect(actualEntry.pipe.length).toStrictEqual(expectedEntry.pipe.length); + + for (let i = 0; i < actualEntry.pipe.length; i++) { + const actualPipeElement = actualEntry.pipe[i]; + const expectedPipeElement = expectedEntry.pipe[i]; + expect(onlySpecifiedKeys(actualPipeElement, keys)).toStrictEqual( + onlySpecifiedKeys(expectedPipeElement, keys), + ); + } + } + }, + }; +} + +export function expectEnumValues>(t: TaskContext, expected: T) { return { from(actual: T) { - expect(Object.keys(actual)).toStrictEqual(Object.keys(expected)); + expect(actual.enum).toStrictEqual(expected.enum); }, }; } + +export function Expect<_ extends true>() {} diff --git a/drizzle-valibot/tsconfig.json b/drizzle-valibot/tsconfig.json index 038d79591..c25379c37 100644 --- a/drizzle-valibot/tsconfig.json +++ b/drizzle-valibot/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "dist", "baseUrl": ".", "declaration": true, + "noEmit": true, "paths": { "~/*": ["src/*"] } diff --git a/drizzle-zod/README.md b/drizzle-zod/README.md index a935d18cd..46eced034 100644 --- a/drizzle-zod/README.md +++ b/drizzle-zod/README.md @@ -10,11 +10,11 @@ `drizzle-zod` is a plugin for [Drizzle ORM](https://github.com/drizzle-team/drizzle-orm) that allows you to generate [Zod](https://zod.dev/) schemas from Drizzle ORM schemas. -| Database | Insert schema | Select schema | -|:-----------|:-------------:|:-------------:| -| PostgreSQL | ✅ | ✅ | -| MySQL | ✅ | ✅ | -| SQLite | ✅ | ✅ | +**Features** + +- Create a select schema for tables, views and enums. +- Create insert and update schemas for tables. +- Supports all dialects: PostgreSQL, MySQL and SQLite. # Usage @@ -34,6 +34,9 @@ const users = pgTable('users', { // Schema for inserting a user - can be used to validate API requests const insertUserSchema = createInsertSchema(users); +// Schema for updating a user - can be used to validate API requests +const updateUserSchema = createUpdateSchema(users); + // Schema for selecting a user - can be used to validate API responses const selectUserSchema = createSelectSchema(users); @@ -44,8 +47,8 @@ const insertUserSchema = createInsertSchema(users, { // Refining the fields - useful if you want to change the fields before they become nullable/optional in the final schema const insertUserSchema = createInsertSchema(users, { - id: (schema) => schema.id.positive(), - email: (schema) => schema.email.email(), + id: (schema) => schema.positive(), + email: (schema) => schema.email(), role: z.string(), }); diff --git a/drizzle-zod/package.json b/drizzle-zod/package.json index 4d3acef81..cb1e472fa 100644 --- a/drizzle-zod/package.json +++ b/drizzle-zod/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-zod", - "version": "0.5.1", + "version": "0.6.1", "description": "Generate Zod schemas from Drizzle ORM schemas", "type": "module", "scripts": { @@ -64,11 +64,10 @@ "author": "Drizzle Team", "license": "Apache-2.0", "peerDependencies": { - "drizzle-orm": ">=0.23.13", - "zod": "*" + "drizzle-orm": ">=0.36.0", + "zod": ">=3.0.0" }, "devDependencies": { - "@rollup/plugin-terser": "^0.4.1", "@rollup/plugin-typescript": "^11.1.0", "@types/node": "^18.15.10", "cpy": "^10.1.0", diff --git a/drizzle-zod/rollup.config.ts b/drizzle-zod/rollup.config.ts index 2ed2d33d3..2049cc5ad 100644 --- a/drizzle-zod/rollup.config.ts +++ b/drizzle-zod/rollup.config.ts @@ -1,4 +1,3 @@ -import terser from '@rollup/plugin-terser'; import typescript from '@rollup/plugin-typescript'; import { defineConfig } from 'rollup'; @@ -29,7 +28,6 @@ export default defineConfig([ typescript({ tsconfig: 'tsconfig.build.json', }), - terser(), ], }, ]); diff --git a/drizzle-zod/scripts/build.ts b/drizzle-zod/scripts/build.ts index 1910feac6..07330ffd0 100755 --- a/drizzle-zod/scripts/build.ts +++ b/drizzle-zod/scripts/build.ts @@ -13,3 +13,4 @@ await cpy('dist/**/*.d.ts', 'dist', { rename: (basename) => basename.replace(/\.d\.ts$/, '.d.cts'), }); await fs.copy('package.json', 'dist/package.json'); +await $`scripts/fix-imports.ts`; diff --git a/drizzle-zod/scripts/fix-imports.ts b/drizzle-zod/scripts/fix-imports.ts new file mode 100755 index 000000000..a90057c5b --- /dev/null +++ b/drizzle-zod/scripts/fix-imports.ts @@ -0,0 +1,136 @@ +#!/usr/bin/env -S pnpm tsx +import 'zx/globals'; + +import path from 'node:path'; +import { parse, print, visit } from 'recast'; +import parser from 'recast/parsers/typescript'; + +function resolvePathAlias(importPath: string, file: string) { + if (importPath.startsWith('~/')) { + const relativePath = path.relative(path.dirname(file), path.resolve('dist.new', importPath.slice(2))); + importPath = relativePath.startsWith('.') ? relativePath : './' + relativePath; + } + + return importPath; +} + +function fixImportPath(importPath: string, file: string, ext: string) { + importPath = resolvePathAlias(importPath, file); + + if (!/\..*\.(js|ts)$/.test(importPath)) { + return importPath; + } + + return importPath.replace(/\.(js|ts)$/, ext); +} + +const cjsFiles = await glob('dist/**/*.{cjs,d.cts}'); + +await Promise.all(cjsFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs'); + } + this.traverse(path); + }, + visitCallExpression(path) { + if (path.value.callee.type === 'Identifier' && path.value.callee.name === 'require') { + path.value.arguments[0].value = fixImportPath(path.value.arguments[0].value, file, '.cjs'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = resolvePathAlias(path.value.argument.value, file); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.cjs'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); + +let esmFiles = await glob('dist/**/*.{js,d.ts}'); + +await Promise.all(esmFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.js'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = fixImportPath(path.value.argument.value, file, '.js'); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.js'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); + +esmFiles = await glob('dist/**/*.{mjs,d.mts}'); + +await Promise.all(esmFiles.map(async (file) => { + const code = parse(await fs.readFile(file, 'utf8'), { parser }); + + visit(code, { + visitImportDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + this.traverse(path); + }, + visitExportAllDeclaration(path) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + this.traverse(path); + }, + visitExportNamedDeclaration(path) { + if (path.value.source) { + path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs'); + } + this.traverse(path); + }, + visitTSImportType(path) { + path.value.argument.value = fixImportPath(path.value.argument.value, file, '.mjs'); + this.traverse(path); + }, + visitAwaitExpression(path) { + if (print(path.value).code.startsWith(`await import("./`)) { + path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.mjs'); + } + this.traverse(path); + }, + }); + + await fs.writeFile(file, print(code).code); +})); diff --git a/drizzle-zod/src/column.ts b/drizzle-zod/src/column.ts new file mode 100644 index 000000000..23bc3c142 --- /dev/null +++ b/drizzle-zod/src/column.ts @@ -0,0 +1,284 @@ +import type { Column, ColumnBaseConfig } from 'drizzle-orm'; +import type { + MySqlBigInt53, + MySqlChar, + MySqlDouble, + MySqlFloat, + MySqlInt, + MySqlMediumInt, + MySqlReal, + MySqlSerial, + MySqlSmallInt, + MySqlText, + MySqlTinyInt, + MySqlVarChar, + MySqlYear, +} from 'drizzle-orm/mysql-core'; +import type { + PgArray, + PgBigInt53, + PgBigSerial53, + PgBinaryVector, + PgChar, + PgDoublePrecision, + PgGeometry, + PgGeometryObject, + PgHalfVector, + PgInteger, + PgLineABC, + PgLineTuple, + PgPointObject, + PgPointTuple, + PgReal, + PgSerial, + PgSmallInt, + PgSmallSerial, + PgUUID, + PgVarchar, + PgVector, +} from 'drizzle-orm/pg-core'; +import type { + SingleStoreBigInt53, + SingleStoreChar, + SingleStoreDouble, + SingleStoreFloat, + SingleStoreInt, + SingleStoreMediumInt, + SingleStoreReal, + SingleStoreSerial, + SingleStoreSmallInt, + SingleStoreText, + SingleStoreTinyInt, + SingleStoreVarChar, + SingleStoreYear, +} from 'drizzle-orm/singlestore-core'; +import type { SQLiteInteger, SQLiteReal, SQLiteText } from 'drizzle-orm/sqlite-core'; +import { z } from 'zod'; +import type { z as zod } from 'zod'; +import { CONSTANTS } from './constants.ts'; +import { isColumnType, isWithEnum } from './utils.ts'; +import type { Json } from './utils.ts'; + +export const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); +export const jsonSchema: z.ZodType = z.lazy(() => + z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]) +); +export const bufferSchema: z.ZodType = z.custom((v) => v instanceof Buffer); // eslint-disable-line no-instanceof/no-instanceof + +export function columnToSchema(column: Column, z: typeof zod): z.ZodTypeAny { + let schema!: z.ZodTypeAny; + + if (isWithEnum(column)) { + schema = column.enumValues.length ? z.enum(column.enumValues) : z.string(); + } + + if (!schema) { + // Handle specific types + if (isColumnType | PgPointTuple>(column, ['PgGeometry', 'PgPointTuple'])) { + schema = z.tuple([z.number(), z.number()]); + } else if ( + isColumnType | PgGeometryObject>(column, ['PgGeometryObject', 'PgPointObject']) + ) { + schema = z.object({ x: z.number(), y: z.number() }); + } else if (isColumnType | PgVector>(column, ['PgHalfVector', 'PgVector'])) { + schema = z.array(z.number()); + schema = column.dimensions ? (schema as z.ZodArray).length(column.dimensions) : schema; + } else if (isColumnType>(column, ['PgLine'])) { + schema = z.tuple([z.number(), z.number(), z.number()]); + } else if (isColumnType>(column, ['PgLineABC'])) { + schema = z.object({ + a: z.number(), + b: z.number(), + c: z.number(), + }); + } // Handle other types + else if (isColumnType>(column, ['PgArray'])) { + schema = z.array(columnToSchema(column.baseColumn, z)); + schema = column.size ? (schema as z.ZodArray).length(column.size) : schema; + } else if (column.dataType === 'array') { + schema = z.array(z.any()); + } else if (column.dataType === 'number') { + schema = numberColumnToSchema(column, z); + } else if (column.dataType === 'bigint') { + schema = bigintColumnToSchema(column, z); + } else if (column.dataType === 'boolean') { + schema = z.boolean(); + } else if (column.dataType === 'date') { + schema = z.date(); + } else if (column.dataType === 'string') { + schema = stringColumnToSchema(column, z); + } else if (column.dataType === 'json') { + schema = jsonSchema; + } else if (column.dataType === 'custom') { + schema = z.any(); + } else if (column.dataType === 'buffer') { + schema = bufferSchema; + } + } + + if (!schema) { + schema = z.any(); + } + + return schema; +} + +function numberColumnToSchema(column: Column, z: typeof zod): z.ZodTypeAny { + let unsigned = column.getSQLType().includes('unsigned'); + let min!: number; + let max!: number; + let integer = false; + + if (isColumnType | SingleStoreTinyInt>(column, ['MySqlTinyInt', 'SingleStoreTinyInt'])) { + min = unsigned ? 0 : CONSTANTS.INT8_MIN; + max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX; + integer = true; + } else if ( + isColumnType | PgSmallSerial | MySqlSmallInt | SingleStoreSmallInt>(column, [ + 'PgSmallInt', + 'PgSmallSerial', + 'MySqlSmallInt', + 'SingleStoreSmallInt', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT16_MIN; + max = unsigned ? CONSTANTS.INT16_UNSIGNED_MAX : CONSTANTS.INT16_MAX; + integer = true; + } else if ( + isColumnType< + PgReal | MySqlFloat | MySqlMediumInt | SingleStoreMediumInt | SingleStoreFloat + >(column, [ + 'PgReal', + 'MySqlFloat', + 'MySqlMediumInt', + 'SingleStoreMediumInt', + 'SingleStoreFloat', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT24_MIN; + max = unsigned ? CONSTANTS.INT24_UNSIGNED_MAX : CONSTANTS.INT24_MAX; + integer = isColumnType(column, ['MySqlMediumInt', 'SingleStoreMediumInt']); + } else if ( + isColumnType | PgSerial | MySqlInt | SingleStoreInt>(column, [ + 'PgInteger', + 'PgSerial', + 'MySqlInt', + 'SingleStoreInt', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT32_MIN; + max = unsigned ? CONSTANTS.INT32_UNSIGNED_MAX : CONSTANTS.INT32_MAX; + integer = true; + } else if ( + isColumnType< + | PgDoublePrecision + | MySqlReal + | MySqlDouble + | SingleStoreReal + | SingleStoreDouble + | SQLiteReal + >(column, [ + 'PgDoublePrecision', + 'MySqlReal', + 'MySqlDouble', + 'SingleStoreReal', + 'SingleStoreDouble', + 'SQLiteReal', + ]) + ) { + min = unsigned ? 0 : CONSTANTS.INT48_MIN; + max = unsigned ? CONSTANTS.INT48_UNSIGNED_MAX : CONSTANTS.INT48_MAX; + } else if ( + isColumnType< + | PgBigInt53 + | PgBigSerial53 + | MySqlBigInt53 + | MySqlSerial + | SingleStoreBigInt53 + | SingleStoreSerial + | SQLiteInteger + >( + column, + [ + 'PgBigInt53', + 'PgBigSerial53', + 'MySqlBigInt53', + 'MySqlSerial', + 'SingleStoreBigInt53', + 'SingleStoreSerial', + 'SQLiteInteger', + ], + ) + ) { + unsigned = unsigned || isColumnType(column, ['MySqlSerial', 'SingleStoreSerial']); + min = unsigned ? 0 : Number.MIN_SAFE_INTEGER; + max = Number.MAX_SAFE_INTEGER; + integer = true; + } else if (isColumnType | SingleStoreYear>(column, ['MySqlYear', 'SingleStoreYear'])) { + min = 1901; + max = 2155; + integer = true; + } else { + min = Number.MIN_SAFE_INTEGER; + max = Number.MAX_SAFE_INTEGER; + } + + const schema = z.number().min(min).max(max); + return integer ? schema.int() : schema; +} + +function bigintColumnToSchema(column: Column, z: typeof zod): z.ZodTypeAny { + const unsigned = column.getSQLType().includes('unsigned'); + const min = unsigned ? 0n : CONSTANTS.INT64_MIN; + const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX; + + return z.bigint().min(min).max(max); +} + +function stringColumnToSchema(column: Column, z: typeof zod): z.ZodTypeAny { + if (isColumnType>>(column, ['PgUUID'])) { + return z.string().uuid(); + } + + let max: number | undefined; + let regex: RegExp | undefined; + let fixed = false; + + if (isColumnType | SQLiteText>(column, ['PgVarchar', 'SQLiteText'])) { + max = column.length; + } else if ( + isColumnType | SingleStoreVarChar>(column, ['MySqlVarChar', 'SingleStoreVarChar']) + ) { + max = column.length ?? CONSTANTS.INT16_UNSIGNED_MAX; + } else if (isColumnType | SingleStoreText>(column, ['MySqlText', 'SingleStoreText'])) { + if (column.textType === 'longtext') { + max = CONSTANTS.INT32_UNSIGNED_MAX; + } else if (column.textType === 'mediumtext') { + max = CONSTANTS.INT24_UNSIGNED_MAX; + } else if (column.textType === 'text') { + max = CONSTANTS.INT16_UNSIGNED_MAX; + } else { + max = CONSTANTS.INT8_UNSIGNED_MAX; + } + } + + if ( + isColumnType | MySqlChar | SingleStoreChar>(column, [ + 'PgChar', + 'MySqlChar', + 'SingleStoreChar', + ]) + ) { + max = column.length; + fixed = true; + } + + if (isColumnType>(column, ['PgBinaryVector'])) { + regex = /^[01]+$/; + max = column.dimensions; + } + + let schema = z.string(); + schema = regex ? schema.regex(regex) : schema; + return max && fixed ? schema.length(max) : max ? schema.max(max) : schema; +} diff --git a/drizzle-zod/src/column.types.ts b/drizzle-zod/src/column.types.ts new file mode 100644 index 000000000..49c12cdbb --- /dev/null +++ b/drizzle-zod/src/column.types.ts @@ -0,0 +1,76 @@ +import type { Assume, Column } from 'drizzle-orm'; +import type { z } from 'zod'; +import type { ArrayHasAtLeastOneValue, ColumnIsGeneratedAlwaysAs, IsNever, Json } from './utils.ts'; + +export type GetEnumValuesFromColumn = TColumn['_'] extends { enumValues: [string, ...string[]] } + ? TColumn['_']['enumValues'] + : undefined; + +export type GetBaseColumn = TColumn['_'] extends { baseColumn: Column | never | undefined } + ? IsNever extends false ? TColumn['_']['baseColumn'] + : undefined + : undefined; + +export type GetZodType< + TData, + TDataType extends string, + TEnumValues extends [string, ...string[]] | undefined, + TBaseColumn extends Column | undefined, +> = TBaseColumn extends Column ? z.ZodArray< + GetZodType< + TBaseColumn['_']['data'], + TBaseColumn['_']['dataType'], + GetEnumValuesFromColumn, + GetBaseColumn + > + > + : ArrayHasAtLeastOneValue extends true ? z.ZodEnum> + : TData extends infer TTuple extends [any, ...any[]] + ? z.ZodTuple }, [any, ...any[]]>> + : TData extends Date ? z.ZodDate + : TData extends Buffer ? z.ZodType + : TDataType extends 'array' ? z.ZodArray[number], string, undefined, undefined>> + : TData extends infer TDict extends Record + ? z.ZodObject<{ [K in keyof TDict]: GetZodType }, 'strip'> + : TDataType extends 'json' ? z.ZodType + : TData extends number ? z.ZodNumber + : TData extends bigint ? z.ZodBigInt + : TData extends boolean ? z.ZodBoolean + : TData extends string ? z.ZodString + : z.ZodTypeAny; + +type HandleSelectColumn< + TSchema extends z.ZodTypeAny, + TColumn extends Column, +> = TColumn['_']['notNull'] extends true ? TSchema + : z.ZodNullable; + +type HandleInsertColumn< + TSchema extends z.ZodTypeAny, + TColumn extends Column, +> = ColumnIsGeneratedAlwaysAs extends true ? never + : TColumn['_']['notNull'] extends true ? TColumn['_']['hasDefault'] extends true ? z.ZodOptional + : TSchema + : z.ZodOptional>; + +type HandleUpdateColumn< + TSchema extends z.ZodTypeAny, + TColumn extends Column, +> = ColumnIsGeneratedAlwaysAs extends true ? never + : TColumn['_']['notNull'] extends true ? z.ZodOptional + : z.ZodOptional>; + +export type HandleColumn< + TType extends 'select' | 'insert' | 'update', + TColumn extends Column, +> = GetZodType< + TColumn['_']['data'], + TColumn['_']['dataType'], + GetEnumValuesFromColumn, + GetBaseColumn +> extends infer TSchema extends z.ZodTypeAny ? TSchema extends z.ZodAny ? z.ZodAny + : TType extends 'select' ? HandleSelectColumn + : TType extends 'insert' ? HandleInsertColumn + : TType extends 'update' ? HandleUpdateColumn + : TSchema + : z.ZodAny; diff --git a/drizzle-zod/src/constants.ts b/drizzle-zod/src/constants.ts new file mode 100644 index 000000000..99f5d7a42 --- /dev/null +++ b/drizzle-zod/src/constants.ts @@ -0,0 +1,20 @@ +export const CONSTANTS = { + INT8_MIN: -128, + INT8_MAX: 127, + INT8_UNSIGNED_MAX: 255, + INT16_MIN: -32768, + INT16_MAX: 32767, + INT16_UNSIGNED_MAX: 65535, + INT24_MIN: -8388608, + INT24_MAX: 8388607, + INT24_UNSIGNED_MAX: 16777215, + INT32_MIN: -2147483648, + INT32_MAX: 2147483647, + INT32_UNSIGNED_MAX: 4294967295, + INT48_MIN: -140737488355328, + INT48_MAX: 140737488355327, + INT48_UNSIGNED_MAX: 281474976710655, + INT64_MIN: -9223372036854775808n, + INT64_MAX: 9223372036854775807n, + INT64_UNSIGNED_MAX: 18446744073709551615n, +}; diff --git a/drizzle-zod/src/index.ts b/drizzle-zod/src/index.ts index 3f6547e0b..0a6499e5b 100644 --- a/drizzle-zod/src/index.ts +++ b/drizzle-zod/src/index.ts @@ -1,239 +1,2 @@ -import { - type Assume, - type Column, - type DrizzleTypeError, - type Equal, - getTableColumns, - is, - type Simplify, - type Table, -} from 'drizzle-orm'; -import { MySqlChar, MySqlVarBinary, MySqlVarChar } from 'drizzle-orm/mysql-core'; -import { type PgArray, PgChar, PgUUID, PgVarchar } from 'drizzle-orm/pg-core'; -import { SQLiteText } from 'drizzle-orm/sqlite-core'; -import { z } from 'zod'; - -const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); -type Literal = z.infer; -type Json = Literal | { [key: string]: Json } | Json[]; -export const jsonSchema: z.ZodType = z.lazy(() => - z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]) -); - -type MapInsertColumnToZod = TColumn['_']['notNull'] extends false - ? z.ZodOptional> - : TColumn['_']['hasDefault'] extends true ? z.ZodOptional - : TType; - -type MapSelectColumnToZod = TColumn['_']['notNull'] extends false - ? z.ZodNullable - : TType; - -type MapColumnToZod = - TMode extends 'insert' ? MapInsertColumnToZod : MapSelectColumnToZod; - -type MaybeOptional< - TColumn extends Column, - TType extends z.ZodTypeAny, - TMode extends 'insert' | 'select', - TNoOptional extends boolean, -> = TNoOptional extends true ? TType - : MapColumnToZod; - -type GetZodType = TColumn['_']['dataType'] extends infer TDataType - ? TDataType extends 'custom' ? z.ZodAny - : TDataType extends 'json' ? z.ZodType - : TColumn extends { enumValues: [string, ...string[]] } - ? Equal extends true ? z.ZodString : z.ZodEnum - : TDataType extends 'array' ? z.ZodArray['baseColumn']>> - : TDataType extends 'bigint' ? z.ZodBigInt - : TDataType extends 'number' ? z.ZodNumber - : TDataType extends 'string' ? z.ZodString - : TDataType extends 'boolean' ? z.ZodBoolean - : TDataType extends 'date' ? z.ZodDate - : z.ZodAny - : never; - -type ValueOrUpdater = T | ((arg: TUpdaterArg) => T); - -type UnwrapValueOrUpdater = T extends ValueOrUpdater ? U : never; - -export type Refine = { - [K in keyof TTable['_']['columns']]?: ValueOrUpdater< - z.ZodTypeAny, - TMode extends 'select' ? BuildSelectSchema : BuildInsertSchema - >; -}; - -export type BuildInsertSchema< - TTable extends Table, - TRefine extends Refine | {}, - TNoOptional extends boolean = false, -> = TTable['_']['columns'] extends infer TColumns extends Record> ? { - [K in keyof TColumns & string]: MaybeOptional< - TColumns[K], - (K extends keyof TRefine ? Assume, z.ZodTypeAny> - : GetZodType), - 'insert', - TNoOptional - >; - } - : never; - -export type BuildSelectSchema< - TTable extends Table, - TRefine extends Refine, - TNoOptional extends boolean = false, -> = Simplify< - { - [K in keyof TTable['_']['columns']]: MaybeOptional< - TTable['_']['columns'][K], - (K extends keyof TRefine ? Assume, z.ZodTypeAny> - : GetZodType), - 'select', - TNoOptional - >; - } ->; - -export function createInsertSchema< - TTable extends Table, - TRefine extends Refine = Refine, ->( - table: TTable, - /** - * @param refine Refine schema fields - */ - refine?: { - [K in keyof TRefine]: K extends keyof TTable['_']['columns'] ? TRefine[K] - : DrizzleTypeError<`Column '${K & string}' does not exist in table '${TTable['_']['name']}'`>; - }, -): z.ZodObject> extends true ? {} : TRefine>> { - const columns = getTableColumns(table); - const columnEntries = Object.entries(columns); - - let schemaEntries = Object.fromEntries(columnEntries.map(([name, column]) => { - return [name, mapColumnToSchema(column)]; - })); - - if (refine) { - schemaEntries = Object.assign( - schemaEntries, - Object.fromEntries( - Object.entries(refine).map(([name, refineColumn]) => { - return [ - name, - typeof refineColumn === 'function' - ? refineColumn(schemaEntries as BuildInsertSchema) - : refineColumn, - ]; - }), - ), - ); - } - - for (const [name, column] of columnEntries) { - if (!column.notNull) { - schemaEntries[name] = schemaEntries[name]!.nullable().optional(); - } else if (column.hasDefault) { - schemaEntries[name] = schemaEntries[name]!.optional(); - } - } - - return z.object(schemaEntries) as any; -} - -export function createSelectSchema< - TTable extends Table, - TRefine extends Refine = Refine, ->( - table: TTable, - /** - * @param refine Refine schema fields - */ - refine?: { - [K in keyof TRefine]: K extends keyof TTable['_']['columns'] ? TRefine[K] - : DrizzleTypeError<`Column '${K & string}' does not exist in table '${TTable['_']['name']}'`>; - }, -): z.ZodObject> extends true ? {} : TRefine>> { - const columns = getTableColumns(table); - const columnEntries = Object.entries(columns); - - let schemaEntries = Object.fromEntries(columnEntries.map(([name, column]) => { - return [name, mapColumnToSchema(column)]; - })); - - if (refine) { - schemaEntries = Object.assign( - schemaEntries, - Object.fromEntries( - Object.entries(refine).map(([name, refineColumn]) => { - return [ - name, - typeof refineColumn === 'function' - ? refineColumn(schemaEntries as BuildSelectSchema) - : refineColumn, - ]; - }), - ), - ); - } - - for (const [name, column] of columnEntries) { - if (!column.notNull) { - schemaEntries[name] = schemaEntries[name]!.nullable(); - } - } - - return z.object(schemaEntries) as any; -} - -function isWithEnum(column: Column): column is typeof column & { enumValues: [string, ...string[]] } { - return 'enumValues' in column && Array.isArray(column.enumValues) && column.enumValues.length > 0; -} - -function mapColumnToSchema(column: Column): z.ZodTypeAny { - let type: z.ZodTypeAny | undefined; - - if (isWithEnum(column)) { - type = column.enumValues.length ? z.enum(column.enumValues) : z.string(); - } - - if (!type) { - if (is(column, PgUUID)) { - type = z.string().uuid(); - } else if (column.dataType === 'custom') { - type = z.any(); - } else if (column.dataType === 'json') { - type = jsonSchema; - } else if (column.dataType === 'array') { - type = z.array(mapColumnToSchema((column as PgArray).baseColumn)); - } else if (column.dataType === 'number') { - type = z.number(); - } else if (column.dataType === 'bigint') { - type = z.bigint(); - } else if (column.dataType === 'boolean') { - type = z.boolean(); - } else if (column.dataType === 'date') { - type = z.date(); - } else if (column.dataType === 'string') { - let sType = z.string(); - - if ( - (is(column, PgChar) || is(column, PgVarchar) || is(column, MySqlVarChar) - || is(column, MySqlVarBinary) || is(column, MySqlChar) || is(column, SQLiteText)) - && (typeof column.length === 'number') - ) { - sType = sType.max(column.length); - } - - type = sType; - } - } - - if (!type) { - type = z.any(); - } - - return type; -} +export * from './schema.ts'; +export * from './schema.types.ts'; diff --git a/drizzle-zod/src/schema.ts b/drizzle-zod/src/schema.ts new file mode 100644 index 000000000..67a9cb733 --- /dev/null +++ b/drizzle-zod/src/schema.ts @@ -0,0 +1,143 @@ +import { Column, getTableColumns, getViewSelectedFields, is, isTable, isView, SQL } from 'drizzle-orm'; +import type { Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import { z } from 'zod'; +import { columnToSchema } from './column.ts'; +import type { Conditions } from './schema.types.internal.ts'; +import type { + CreateInsertSchema, + CreateSchemaFactoryOptions, + CreateSelectSchema, + CreateUpdateSchema, +} from './schema.types.ts'; +import { isPgEnum } from './utils.ts'; + +function getColumns(tableLike: Table | View) { + return isTable(tableLike) ? getTableColumns(tableLike) : getViewSelectedFields(tableLike); +} + +function handleColumns( + columns: Record, + refinements: Record, + conditions: Conditions, + factory?: CreateSchemaFactoryOptions, +): z.ZodTypeAny { + const columnSchemas: Record = {}; + + for (const [key, selected] of Object.entries(columns)) { + if (!is(selected, Column) && !is(selected, SQL) && !is(selected, SQL.Aliased) && typeof selected === 'object') { + const columns = isTable(selected) || isView(selected) ? getColumns(selected) : selected; + columnSchemas[key] = handleColumns(columns, refinements[key] ?? {}, conditions, factory); + continue; + } + + const refinement = refinements[key]; + if (refinement !== undefined && typeof refinement !== 'function') { + columnSchemas[key] = refinement; + continue; + } + + const column = is(selected, Column) ? selected : undefined; + const schema = column ? columnToSchema(column, factory?.zodInstance ?? z) : z.any(); + const refined = typeof refinement === 'function' ? refinement(schema) : schema; + + if (conditions.never(column)) { + continue; + } else { + columnSchemas[key] = refined; + } + + if (column) { + if (conditions.nullable(column)) { + columnSchemas[key] = columnSchemas[key]!.nullable(); + } + + if (conditions.optional(column)) { + columnSchemas[key] = columnSchemas[key]!.optional(); + } + } + } + + return z.object(columnSchemas) as any; +} + +function handleEnum(enum_: PgEnum, factory?: CreateSchemaFactoryOptions) { + const zod: typeof z = factory?.zodInstance ?? z; + return zod.enum(enum_.enumValues); +} + +const selectConditions: Conditions = { + never: () => false, + optional: () => false, + nullable: (column) => !column.notNull, +}; + +const insertConditions: Conditions = { + never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always', + optional: (column) => !column.notNull || (column.notNull && column.hasDefault), + nullable: (column) => !column.notNull, +}; + +const updateConditions: Conditions = { + never: (column) => column?.generated?.type === 'always' || column?.generatedIdentity?.type === 'always', + optional: () => true, + nullable: (column) => !column.notNull, +}; + +export const createSelectSchema: CreateSelectSchema = ( + entity: Table | View | PgEnum<[string, ...string[]]>, + refine?: Record, +) => { + if (isPgEnum(entity)) { + return handleEnum(entity); + } + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, selectConditions) as any; +}; + +export const createInsertSchema: CreateInsertSchema = ( + entity: Table, + refine?: Record, +) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, insertConditions) as any; +}; + +export const createUpdateSchema: CreateUpdateSchema = ( + entity: Table, + refine?: Record, +) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, updateConditions) as any; +}; + +export function createSchemaFactory(options?: CreateSchemaFactoryOptions) { + const createSelectSchema: CreateSelectSchema = ( + entity: Table | View | PgEnum<[string, ...string[]]>, + refine?: Record, + ) => { + if (isPgEnum(entity)) { + return handleEnum(entity, options); + } + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, selectConditions, options) as any; + }; + + const createInsertSchema: CreateInsertSchema = ( + entity: Table, + refine?: Record, + ) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, insertConditions, options) as any; + }; + + const createUpdateSchema: CreateUpdateSchema = ( + entity: Table, + refine?: Record, + ) => { + const columns = getColumns(entity); + return handleColumns(columns, refine ?? {}, updateConditions, options) as any; + }; + + return { createSelectSchema, createInsertSchema, createUpdateSchema }; +} diff --git a/drizzle-zod/src/schema.types.internal.ts b/drizzle-zod/src/schema.types.internal.ts new file mode 100644 index 000000000..8b89187f2 --- /dev/null +++ b/drizzle-zod/src/schema.types.internal.ts @@ -0,0 +1,94 @@ +import type { Assume, Column, DrizzleTypeError, SelectedFieldsFlat, Simplify, Table, View } from 'drizzle-orm'; +import type { z } from 'zod'; +import type { GetBaseColumn, GetEnumValuesFromColumn, GetZodType, HandleColumn } from './column.types.ts'; +import type { GetSelection, RemoveNever } from './utils.ts'; + +export interface Conditions { + never: (column?: Column) => boolean; + optional: (column: Column) => boolean; + nullable: (column: Column) => boolean; +} + +export type BuildRefineColumns< + TColumns extends Record, +> = Simplify< + RemoveNever< + { + [K in keyof TColumns]: TColumns[K] extends infer TColumn extends Column ? GetZodType< + TColumn['_']['data'], + TColumn['_']['dataType'], + GetEnumValuesFromColumn, + GetBaseColumn + > extends infer TSchema extends z.ZodTypeAny ? TSchema + : z.ZodAny + : TColumns[K] extends infer TObject extends SelectedFieldsFlat | Table | View + ? BuildRefineColumns> + : TColumns[K]; + } + > +>; + +export type BuildRefine< + TColumns extends Record, +> = BuildRefineColumns extends infer TBuildColumns ? { + [K in keyof TBuildColumns]?: TBuildColumns[K] extends z.ZodTypeAny + ? ((schema: TBuildColumns[K]) => z.ZodTypeAny) | z.ZodTypeAny + : TBuildColumns[K] extends Record ? Simplify> + : never; + } + : never; + +type HandleRefinement< + TType extends 'select' | 'insert' | 'update', + TRefinement extends z.ZodTypeAny | ((schema: z.ZodTypeAny) => z.ZodTypeAny), + TColumn extends Column, +> = TRefinement extends (schema: any) => z.ZodTypeAny ? (TColumn['_']['notNull'] extends true ? ReturnType + : z.ZodNullable>) extends infer TSchema + ? TType extends 'update' ? z.ZodOptional> : TSchema + : z.ZodTypeAny + : TRefinement; + +type IsRefinementDefined = TKey extends keyof TRefinements + ? TRefinements[TKey] extends z.ZodTypeAny | ((schema: any) => any) ? true + : false + : false; + +export type BuildSchema< + TType extends 'select' | 'insert' | 'update', + TColumns extends Record, + TRefinements extends Record | undefined, +> = z.ZodObject< + Simplify< + RemoveNever< + { + [K in keyof TColumns]: TColumns[K] extends infer TColumn extends Column + ? TRefinements extends object + ? IsRefinementDefined> extends true + ? HandleRefinement], TColumn> + : HandleColumn + : HandleColumn + : TColumns[K] extends infer TObject extends SelectedFieldsFlat | Table | View ? BuildSchema< + TType, + GetSelection, + TRefinements extends object + ? TRefinements[Assume] extends infer TNestedRefinements extends object + ? TNestedRefinements + : undefined + : undefined + > + : z.ZodAny; + } + > + >, + 'strip' +>; + +export type NoUnknownKeys< + TRefinement extends Record, + TCompare extends Record, +> = { + [K in keyof TRefinement]: K extends keyof TCompare + ? TRefinement[K] extends Record ? NoUnknownKeys + : TRefinement[K] + : DrizzleTypeError<`Found unknown key in refinement: "${K & string}"`>; +}; diff --git a/drizzle-zod/src/schema.types.ts b/drizzle-zod/src/schema.types.ts new file mode 100644 index 000000000..5873cd2a3 --- /dev/null +++ b/drizzle-zod/src/schema.types.ts @@ -0,0 +1,52 @@ +import type { Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import type { z } from 'zod'; +import type { BuildRefine, BuildSchema, NoUnknownKeys } from './schema.types.internal.ts'; + +export interface CreateSelectSchema { + (table: TTable): BuildSchema<'select', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine, + >( + table: TTable, + refine?: NoUnknownKeys, + ): BuildSchema<'select', TTable['_']['columns'], TRefine>; + + (view: TView): BuildSchema<'select', TView['_']['selectedFields'], undefined>; + < + TView extends View, + TRefine extends BuildRefine, + >( + view: TView, + refine: NoUnknownKeys, + ): BuildSchema<'select', TView['_']['selectedFields'], TRefine>; + + >(enum_: TEnum): z.ZodEnum; +} + +export interface CreateInsertSchema { + (table: TTable): BuildSchema<'insert', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine>, + >( + table: TTable, + refine?: NoUnknownKeys, + ): BuildSchema<'insert', TTable['_']['columns'], TRefine>; +} + +export interface CreateUpdateSchema { + (table: TTable): BuildSchema<'update', TTable['_']['columns'], undefined>; + < + TTable extends Table, + TRefine extends BuildRefine>, + >( + table: TTable, + refine?: TRefine, + ): BuildSchema<'update', TTable['_']['columns'], TRefine>; +} + +export interface CreateSchemaFactoryOptions { + zodInstance?: any; +} diff --git a/drizzle-zod/src/utils.ts b/drizzle-zod/src/utils.ts new file mode 100644 index 000000000..506b80565 --- /dev/null +++ b/drizzle-zod/src/utils.ts @@ -0,0 +1,40 @@ +import type { Column, SelectedFieldsFlat, Table, View } from 'drizzle-orm'; +import type { PgEnum } from 'drizzle-orm/pg-core'; +import type { z } from 'zod'; +import type { literalSchema } from './column.ts'; + +export function isColumnType(column: Column, columnTypes: string[]): column is T { + return columnTypes.includes(column.columnType); +} + +export function isWithEnum(column: Column): column is typeof column & { enumValues: [string, ...string[]] } { + return 'enumValues' in column && Array.isArray(column.enumValues) && column.enumValues.length > 0; +} + +export const isPgEnum: (entity: any) => entity is PgEnum<[string, ...string[]]> = isWithEnum as any; + +type Literal = z.infer; +export type Json = Literal | { [key: string]: Json } | Json[]; + +export type IsNever = [T] extends [never] ? true : false; + +export type ArrayHasAtLeastOneValue = TEnum extends [infer TString, ...any[]] + ? TString extends `${infer TLiteral}` ? TLiteral extends any ? true + : false + : false + : false; + +export type ColumnIsGeneratedAlwaysAs = TColumn['_']['identity'] extends 'always' ? true + : TColumn['_']['generated'] extends undefined ? false + : TColumn['_']['generated'] extends infer TGenerated extends { type: string } + ? TGenerated['type'] extends 'byDefault' ? false + : true + : true; + +export type RemoveNever = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +}; + +export type GetSelection | Table | View> = T extends Table ? T['_']['columns'] + : T extends View ? T['_']['selectedFields'] + : T; diff --git a/drizzle-zod/tests/mysql.test.ts b/drizzle-zod/tests/mysql.test.ts index f28d6a768..73ba48dae 100644 --- a/drizzle-zod/tests/mysql.test.ts +++ b/drizzle-zod/tests/mysql.test.ts @@ -1,289 +1,489 @@ -import { - bigint, - binary, - boolean, - char, - customType, - date, - datetime, - decimal, - double, - float, - int, - json, - longtext, - mediumint, - mediumtext, - mysqlEnum, - mysqlTable, - real, - serial, - smallint, - text, - time, - timestamp, - tinyint, - tinytext, - varbinary, - varchar, - year, -} from 'drizzle-orm/mysql-core'; -import { expect, test } from 'vitest'; +import { type Equal, sql } from 'drizzle-orm'; +import { customType, int, mysqlSchema, mysqlTable, mysqlView, serial, text } from 'drizzle-orm/mysql-core'; +import { test } from 'vitest'; import { z } from 'zod'; -import { createInsertSchema, createSelectSchema, jsonSchema } from '~/index'; -import { expectSchemaShape } from './utils.ts'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; -const customInt = customType<{ data: number }>({ - dataType() { - return 'int'; - }, +const intSchema = z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(); +const serialNumberModeSchema = z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(); +const textSchema = z.string().max(CONSTANTS.INT16_UNSIGNED_MAX); + +test('table - select', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -const testTable = mysqlTable('test', { - bigint: bigint('bigint', { mode: 'bigint' }).notNull(), - bigintNumber: bigint('bigintNumber', { mode: 'number' }).notNull(), - binary: binary('binary').notNull(), - boolean: boolean('boolean').notNull(), - char: char('char', { length: 4 }).notNull(), - charEnum: char('char', { enum: ['a', 'b', 'c'] }).notNull(), - customInt: customInt('customInt').notNull(), - date: date('date').notNull(), - dateString: date('dateString', { mode: 'string' }).notNull(), - datetime: datetime('datetime').notNull(), - datetimeString: datetime('datetimeString', { mode: 'string' }).notNull(), - decimal: decimal('decimal').notNull(), - double: double('double').notNull(), - enum: mysqlEnum('enum', ['a', 'b', 'c']).notNull(), - float: float('float').notNull(), - int: int('int').notNull(), - json: json('json').notNull(), - mediumint: mediumint('mediumint').notNull(), - real: real('real').notNull(), - serial: serial('serial').notNull(), - smallint: smallint('smallint').notNull(), - text: text('text').notNull(), - textEnum: text('textEnum', { enum: ['a', 'b', 'c'] }).notNull(), - tinytext: tinytext('tinytext').notNull(), - tinytextEnum: tinytext('tinytextEnum', { enum: ['a', 'b', 'c'] }).notNull(), - mediumtext: mediumtext('mediumtext').notNull(), - mediumtextEnum: mediumtext('mediumtextEnum', { enum: ['a', 'b', 'c'] }).notNull(), - longtext: longtext('longtext').notNull(), - longtextEnum: longtext('longtextEnum', { enum: ['a', 'b', 'c'] }).notNull(), - time: time('time').notNull(), - timestamp: timestamp('timestamp').notNull(), - timestampString: timestamp('timestampString', { mode: 'string' }).notNull(), - tinyint: tinyint('tinyint').notNull(), - varbinary: varbinary('varbinary', { length: 200 }).notNull(), - varchar: varchar('varchar', { length: 200 }).notNull(), - varcharEnum: varchar('varcharEnum', { length: 1, enum: ['a', 'b', 'c'] }).notNull(), - year: year('year').notNull(), - autoIncrement: int('autoIncrement').notNull().autoincrement(), +test('table in schema - select', (tc) => { + const schema = mysqlSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); }); -const testTableRow = { - bigint: BigInt(1), - bigintNumber: 1, - binary: 'binary', - boolean: true, - char: 'char', - charEnum: 'a', - customInt: { data: 1 }, - date: new Date(), - dateString: new Date().toISOString(), - datetime: new Date(), - datetimeString: new Date().toISOString(), - decimal: '1.1', - double: 1.1, - enum: 'a', - float: 1.1, - int: 1, - json: { data: 1 }, - mediumint: 1, - real: 1.1, - serial: 1, - smallint: 1, - text: 'text', - textEnum: 'a', - tinytext: 'tinytext', - tinytextEnum: 'a', - mediumtext: 'mediumtext', - mediumtextEnum: 'a', - longtext: 'longtext', - longtextEnum: 'a', - time: '00:00:00', - timestamp: new Date(), - timestampString: new Date().toISOString(), - tinyint: 1, - varbinary: 'A'.repeat(200), - varchar: 'A'.repeat(200), - varcharEnum: 'a', - year: 2021, - autoIncrement: 1, -}; - -test('insert valid row', () => { - const schema = createInsertSchema(testTable); - - expect(schema.safeParse(testTableRow).success).toBeTruthy(); +test('table - insert', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = z.object({ + id: serialNumberModeSchema.optional(), + name: textSchema, + age: intSchema.nullable().optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('insert invalid varchar length', () => { - const schema = createInsertSchema(testTable); +test('table - update', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); - expect(schema.safeParse({ ...testTableRow, varchar: 'A'.repeat(201) }).success).toBeFalsy(); + const result = createUpdateSchema(table); + const expected = z.object({ + id: serialNumberModeSchema.optional(), + name: textSchema.optional(), + age: intSchema.nullable().optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('insert smaller char length should work', () => { - const schema = createInsertSchema(testTable); +test('view qb - select', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); - expect(schema.safeParse({ ...testTableRow, char: 'abc' }).success).toBeTruthy(); + const result = createSelectSchema(view); + const expected = z.object({ id: serialNumberModeSchema, age: z.any() }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('insert larger char length should fail', () => { - const schema = createInsertSchema(testTable); +test('view columns - select', (t) => { + const view = mysqlView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); - expect(schema.safeParse({ ...testTableRow, char: 'abcde' }).success).toBeFalsy(); + const result = createSelectSchema(view); + const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('insert schema', (t) => { - const actual = createInsertSchema(testTable); +test('view with nested fields - select', (t) => { + const table = mysqlTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = mysqlView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) + ); + const result = createSelectSchema(view); const expected = z.object({ - bigint: z.bigint(), - bigintNumber: z.number(), - binary: z.string(), - boolean: z.boolean(), - char: z.string().length(4), - charEnum: z.enum(['a', 'b', 'c']), - customInt: z.any(), - date: z.date(), - dateString: z.string(), - datetime: z.date(), - datetimeString: z.string(), - decimal: z.string(), - double: z.number(), - enum: z.enum(['a', 'b', 'c']), - float: z.number(), - int: z.number(), - json: jsonSchema, - mediumint: z.number(), - real: z.number(), - serial: z.number().optional(), - smallint: z.number(), - text: z.string(), - textEnum: z.enum(['a', 'b', 'c']), - tinytext: z.string(), - tinytextEnum: z.enum(['a', 'b', 'c']), - mediumtext: z.string(), - mediumtextEnum: z.enum(['a', 'b', 'c']), - longtext: z.string(), - longtextEnum: z.enum(['a', 'b', 'c']), - time: z.string(), - timestamp: z.date(), - timestampString: z.string(), - tinyint: z.number(), - varbinary: z.string().max(200), - varchar: z.string().max(200), - varcharEnum: z.enum(['a', 'b', 'c']), - year: z.number(), - autoIncrement: z.number().optional(), + id: serialNumberModeSchema, + nested: z.object({ name: textSchema, age: z.any() }), + table: z.object({ id: serialNumberModeSchema, name: textSchema }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - select', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), }); - expectSchemaShape(t, expected).from(actual); + const result = createSelectSchema(table); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema, + c3: intSchema.nullable(), + c4: intSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('select schema', (t) => { - const actual = createSelectSchema(testTable); +test('nullability - insert', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + const result = createInsertSchema(table); const expected = z.object({ - bigint: z.bigint(), - bigintNumber: z.number(), - binary: z.string(), - boolean: z.boolean(), - char: z.string().length(4), - charEnum: z.enum(['a', 'b', 'c']), - customInt: z.any(), - date: z.date(), - dateString: z.string(), - datetime: z.date(), - datetimeString: z.string(), - decimal: z.string(), - double: z.number(), - enum: z.enum(['a', 'b', 'c']), - float: z.number(), - int: z.number(), - json: jsonSchema, - mediumint: z.number(), - real: z.number(), - serial: z.number(), - smallint: z.number(), - text: z.string(), - textEnum: z.enum(['a', 'b', 'c']), - tinytext: z.string(), - tinytextEnum: z.enum(['a', 'b', 'c']), - mediumtext: z.string(), - mediumtextEnum: z.enum(['a', 'b', 'c']), - longtext: z.string(), - longtextEnum: z.enum(['a', 'b', 'c']), - time: z.string(), - timestamp: z.date(), - timestampString: z.string(), - tinyint: z.number(), - varbinary: z.string().max(200), - varchar: z.string().max(200), - varcharEnum: z.enum(['a', 'b', 'c']), - year: z.number(), - autoIncrement: z.number(), + c1: intSchema.nullable().optional(), + c2: intSchema, + c3: intSchema.nullable().optional(), + c4: intSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - update', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), }); - expectSchemaShape(t, expected).from(actual); + const result = createUpdateSchema(table); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.optional(), + c3: intSchema.nullable().optional(), + c4: intSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('select schema w/ refine', (t) => { - const actual = createSelectSchema(testTable, { - bigint: (schema) => schema.bigint.positive(), +test('refine table - select', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), }); + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); const expected = z.object({ - bigint: z.bigint().positive(), - bigintNumber: z.number(), + c1: intSchema.nullable(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); + + const customTextSchema = z.string().min(1).max(100); + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - update', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.max(1000).optional(), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine view - select', (t) => { + const table = mysqlTable('test', { + c1: int(), + c2: int(), + c3: int(), + c4: int(), + c5: int(), + c6: int(), + }); + const view = mysqlView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + nested: { + c5: (schema) => schema.max(1000), + c6: z.string().transform(Number), + }, + table: { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }, + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000).nullable(), + c3: z.string().transform(Number), + nested: z.object({ + c4: intSchema.nullable(), + c5: intSchema.max(1000).nullable(), + c6: z.string().transform(Number), + }), + table: z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000).nullable(), + c3: z.string().transform(Number), + c4: intSchema.nullable(), + c5: intSchema.nullable(), + c6: intSchema.nullable(), + }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('all data types', (t) => { + const table = mysqlTable('test', ({ + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + mysqlEnum, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varchar, + varbinary, + year, + longtext, + mediumtext, + tinytext, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(), + bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(), + binary: binary({ length: 10 }).notNull(), + boolean: boolean().notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + datetime1: datetime({ mode: 'date' }).notNull(), + datetime2: datetime({ mode: 'string' }).notNull(), + decimal1: decimal().notNull(), + decimal2: decimal({ unsigned: true }).notNull(), + double1: double().notNull(), + double2: double({ unsigned: true }).notNull(), + float1: float().notNull(), + float2: float({ unsigned: true }).notNull(), + int1: int().notNull(), + int2: int({ unsigned: true }).notNull(), + json: json().notNull(), + mediumint1: mediumint().notNull(), + mediumint2: mediumint({ unsigned: true }).notNull(), + enum: mysqlEnum('enum', ['a', 'b', 'c']).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint1: smallint().notNull(), + smallint2: smallint({ unsigned: true }).notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + tinyint1: tinyint().notNull(), + tinyint2: tinyint({ unsigned: true }).notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + varbinary: varbinary({ length: 10 }).notNull(), + year: year().notNull(), + longtext1: longtext().notNull(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(), + mediumtext1: mediumtext().notNull(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(), + tinytext1: tinytext().notNull(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(), + })); + + const result = createSelectSchema(table); + const expected = z.object({ + bigint1: z.number().min(Number.MIN_SAFE_INTEGER).max(Number.MAX_SAFE_INTEGER).int(), + bigint2: z.bigint().min(CONSTANTS.INT64_MIN).max(CONSTANTS.INT64_MAX), + bigint3: z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(), + bigint4: z.bigint().min(0n).max(CONSTANTS.INT64_UNSIGNED_MAX), binary: z.string(), boolean: z.boolean(), - char: z.string().length(5), - charEnum: z.enum(['a', 'b', 'c']), - customInt: z.any(), - date: z.date(), - dateString: z.string(), - datetime: z.date(), - datetimeString: z.string(), - decimal: z.string(), - double: z.number(), - enum: z.enum(['a', 'b', 'c']), - float: z.number(), - int: z.number(), + char1: z.string().length(10), + char2: z.enum(['a', 'b', 'c']), + date1: z.date(), + date2: z.string(), + datetime1: z.date(), + datetime2: z.string(), + decimal1: z.string(), + decimal2: z.string(), + double1: z.number().min(CONSTANTS.INT48_MIN).max(CONSTANTS.INT48_MAX), + double2: z.number().min(0).max(CONSTANTS.INT48_UNSIGNED_MAX), + float1: z.number().min(CONSTANTS.INT24_MIN).max(CONSTANTS.INT24_MAX), + float2: z.number().min(0).max(CONSTANTS.INT24_UNSIGNED_MAX), + int1: z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(), + int2: z.number().min(0).max(CONSTANTS.INT32_UNSIGNED_MAX).int(), json: jsonSchema, - mediumint: z.number(), - real: z.number(), - serial: z.number(), - smallint: z.number(), - text: z.string(), - textEnum: z.enum(['a', 'b', 'c']), - tinytext: z.string(), - tinytextEnum: z.enum(['a', 'b', 'c']), - mediumtext: z.string(), - mediumtextEnum: z.enum(['a', 'b', 'c']), - longtext: z.string(), - longtextEnum: z.enum(['a', 'b', 'c']), + mediumint1: z.number().min(CONSTANTS.INT24_MIN).max(CONSTANTS.INT24_MAX).int(), + mediumint2: z.number().min(0).max(CONSTANTS.INT24_UNSIGNED_MAX).int(), + enum: z.enum(['a', 'b', 'c']), + real: z.number().min(CONSTANTS.INT48_MIN).max(CONSTANTS.INT48_MAX), + serial: z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(), + smallint1: z.number().min(CONSTANTS.INT16_MIN).max(CONSTANTS.INT16_MAX).int(), + smallint2: z.number().min(0).max(CONSTANTS.INT16_UNSIGNED_MAX).int(), + text1: z.string().max(CONSTANTS.INT16_UNSIGNED_MAX), + text2: z.enum(['a', 'b', 'c']), time: z.string(), - timestamp: z.date(), - timestampString: z.string(), - tinyint: z.number(), - varbinary: z.string().max(200), - varchar: z.string().max(200), - varcharEnum: z.enum(['a', 'b', 'c']), - year: z.number(), - autoIncrement: z.number(), + timestamp1: z.date(), + timestamp2: z.string(), + tinyint1: z.number().min(CONSTANTS.INT8_MIN).max(CONSTANTS.INT8_MAX).int(), + tinyint2: z.number().min(0).max(CONSTANTS.INT8_UNSIGNED_MAX).int(), + varchar1: z.string().max(10), + varchar2: z.enum(['a', 'b', 'c']), + varbinary: z.string(), + year: z.number().min(1901).max(2155).int(), + longtext1: z.string().max(CONSTANTS.INT32_UNSIGNED_MAX), + longtext2: z.enum(['a', 'b', 'c']), + mediumtext1: z.string().max(CONSTANTS.INT24_UNSIGNED_MAX), + mediumtext2: z.enum(['a', 'b', 'c']), + tinytext1: z.string().max(CONSTANTS.INT8_UNSIGNED_MAX), + tinytext2: z.enum(['a', 'b', 'c']), }); - - expectSchemaShape(t, expected).from(actual); + expectSchemaShape(t, expected).from(result); + Expect>(); }); + +/* Disallow unknown keys in table refinement - select */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = mysqlTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = mysqlTable('test', { id: int() }); + const view = mysqlView('test').as((qb) => qb.select().from(table)); + const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: z.string() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: z.string() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = mysqlView('test', { id: int() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: z.string() }); +} diff --git a/drizzle-zod/tests/pg.test.ts b/drizzle-zod/tests/pg.test.ts index b1f6e0c20..7964f65d6 100644 --- a/drizzle-zod/tests/pg.test.ts +++ b/drizzle-zod/tests/pg.test.ts @@ -1,163 +1,541 @@ -import { char, date, integer, pgEnum, pgTable, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core'; -import { expect, test } from 'vitest'; +import { type Equal, sql } from 'drizzle-orm'; +import { + customType, + integer, + pgEnum, + pgMaterializedView, + pgSchema, + pgTable, + pgView, + serial, + text, +} from 'drizzle-orm/pg-core'; +import { test } from 'vitest'; import { z } from 'zod'; -import { createInsertSchema, createSelectSchema } from '../src'; -import { expectSchemaShape } from './utils.ts'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectEnumValues, expectSchemaShape } from './utils.ts'; -export const roleEnum = pgEnum('role', ['admin', 'user']); +const integerSchema = z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(); +const textSchema = z.string(); -const users = pgTable('users', { - a: integer('a').array(), - id: serial('id').primaryKey(), - name: text('name'), - email: text('email').notNull(), - birthdayString: date('birthday_string').notNull(), - birthdayDate: date('birthday_date', { mode: 'date' }).notNull(), - createdAt: timestamp('created_at').notNull().defaultNow(), - role: roleEnum('role').notNull(), - roleText: text('role1', { enum: ['admin', 'user'] }).notNull(), - roleText2: text('role2', { enum: ['admin', 'user'] }).notNull().default('user'), - profession: varchar('profession', { length: 20 }).notNull(), - initials: char('initials', { length: 2 }).notNull(), +test('table - select', (t) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = z.object({ id: integerSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -const testUser = { - a: [1, 2, 3], - id: 1, - name: 'John Doe', - email: 'john.doe@example.com', - birthdayString: '1990-01-01', - birthdayDate: new Date('1990-01-01'), - createdAt: new Date(), - role: 'admin', - roleText: 'admin', - roleText2: 'admin', - profession: 'Software Engineer', - initials: 'JD', -}; +test('table in schema - select', (tc) => { + const schema = pgSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); -test('users insert valid user', () => { - const schema = createInsertSchema(users); + const result = createSelectSchema(table); + const expected = z.object({ id: integerSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); - expect(schema.safeParse(testUser).success).toBeTruthy(); +test('table - insert', (t) => { + const table = pgTable('test', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer(), + }); + + const result = createInsertSchema(table); + const expected = z.object({ name: textSchema, age: integerSchema.nullable().optional() }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert invalid varchar', () => { - const schema = createInsertSchema(users); +test('table - update', (t) => { + const table = pgTable('test', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer(), + }); - expect(schema.safeParse({ ...testUser, profession: 'Chief Executive Officer' }).success).toBeFalsy(); + const result = createUpdateSchema(table); + const expected = z.object({ + name: textSchema.optional(), + age: integerSchema.nullable().optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert invalid char', () => { - const schema = createInsertSchema(users); +test('view qb - select', (t) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = z.object({ id: integerSchema, age: z.any() }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('view columns - select', (t) => { + const view = pgView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = z.object({ id: integerSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('materialized view qb - select', (t) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgMaterializedView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + + const result = createSelectSchema(view); + const expected = z.object({ id: integerSchema, age: z.any() }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('materialized view columns - select', (t) => { + const view = pgView('test', { + id: serial().primaryKey(), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = z.object({ id: integerSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('view with nested fields - select', (t) => { + const table = pgTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + const view = pgMaterializedView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view); + const expected = z.object({ + id: integerSchema, + nested: z.object({ name: textSchema, age: z.any() }), + table: z.object({ id: integerSchema, name: textSchema }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('enum - select', (t) => { + const enum_ = pgEnum('test', ['a', 'b', 'c']); + + const result = createSelectSchema(enum_); + const expected = z.enum(['a', 'b', 'c']); + expectEnumValues(t, expected).from(result); + Expect>(); +}); + +test('nullability - select', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + }); - expect(schema.safeParse({ ...testUser, initials: 'JoDo' }).success).toBeFalsy(); + const result = createSelectSchema(table); + const expected = z.object({ + c1: integerSchema.nullable(), + c2: integerSchema, + c3: integerSchema.nullable(), + c4: integerSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert schema', (t) => { - const actual = createInsertSchema(users, { - id: ({ id }) => id.positive(), - email: ({ email }) => email.email(), - roleText: z.enum(['user', 'manager', 'admin']), +test('nullability - insert', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + c5: integer().generatedAlwaysAs(1), + c6: integer().generatedAlwaysAsIdentity(), + c7: integer().generatedByDefaultAsIdentity(), }); - (() => { - { - createInsertSchema(users, { - // @ts-expect-error (missing property) - foobar: z.number(), - }); - } + const result = createInsertSchema(table); + const expected = z.object({ + c1: integerSchema.nullable().optional(), + c2: integerSchema, + c3: integerSchema.nullable().optional(), + c4: integerSchema.optional(), + c7: integerSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); +}); - { - createInsertSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } +test('nullability - update', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().default(1), + c4: integer().notNull().default(1), + c5: integer().generatedAlwaysAs(1), + c6: integer().generatedAlwaysAsIdentity(), + c7: integer().generatedByDefaultAsIdentity(), }); + const result = createUpdateSchema(table); const expected = z.object({ - a: z.array(z.number()).nullable().optional(), - id: z.number().positive().optional(), - name: z.string().nullable().optional(), - email: z.string().email(), - birthdayString: z.string(), - birthdayDate: z.date(), - createdAt: z.date().optional(), - role: z.enum(['admin', 'user']), - roleText: z.enum(['user', 'manager', 'admin']), - roleText2: z.enum(['admin', 'user']).optional(), - profession: z.string().max(20).min(1), - initials: z.string().max(2).min(1), + c1: integerSchema.nullable().optional(), + c2: integerSchema.optional(), + c3: integerSchema.nullable().optional(), + c4: integerSchema.optional(), + c7: integerSchema.optional(), + }); + table.c5.generated?.type; + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), }); - expectSchemaShape(t, expected).from(actual); + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: integerSchema.nullable(), + c2: integerSchema.max(1000), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert schema w/ defaults', (t) => { - const actual = createInsertSchema(users); +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: customText(), + }); + const customTextSchema = z.string().min(1).max(100); + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); const expected = z.object({ - a: z.array(z.number()).nullable().optional(), - id: z.number().optional(), - name: z.string().nullable().optional(), - email: z.string(), - birthdayString: z.string(), - birthdayDate: z.date(), - createdAt: z.date().optional(), - role: z.enum(['admin', 'user']), - roleText: z.enum(['admin', 'user']), - roleText2: z.enum(['admin', 'user']).optional(), - profession: z.string().max(20).min(1), - initials: z.string().max(2).min(1), + c1: integerSchema.nullable(), + c2: integerSchema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, }); - expectSchemaShape(t, expected).from(actual); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users select schema', (t) => { - const actual = createSelectSchema(users, { - id: ({ id }) => id.positive(), - email: ({ email }) => email.email(), - roleText: z.enum(['user', 'manager', 'admin']), +test('refine table - insert', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: integer().generatedAlwaysAs(1), }); + const result = createInsertSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); const expected = z.object({ - a: z.array(z.number()).nullable(), - id: z.number().positive(), - name: z.string().nullable(), - email: z.string().email(), - birthdayString: z.string(), - birthdayDate: z.date(), - createdAt: z.date(), - role: z.enum(['admin', 'user']), - roleText: z.enum(['user', 'manager', 'admin']), - roleText2: z.enum(['admin', 'user']), - profession: z.string().max(20).min(1), - initials: z.string().max(2).min(1), + c1: integerSchema.nullable().optional(), + c2: integerSchema.max(1000), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - update', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer().notNull(), + c3: integer().notNull(), + c4: integer().generatedAlwaysAs(1), }); - expectSchemaShape(t, expected).from(actual); + const result = createUpdateSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: integerSchema.nullable().optional(), + c2: integerSchema.max(1000).optional(), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine view - select', (t) => { + const table = pgTable('test', { + c1: integer(), + c2: integer(), + c3: integer(), + c4: integer(), + c5: integer(), + c6: integer(), + }); + const view = pgView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + nested: { + c5: (schema) => schema.max(1000), + c6: z.string().transform(Number), + }, + table: { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }, + }); + const expected = z.object({ + c1: integerSchema.nullable(), + c2: integerSchema.max(1000).nullable(), + c3: z.string().transform(Number), + nested: z.object({ + c4: integerSchema.nullable(), + c5: integerSchema.max(1000).nullable(), + c6: z.string().transform(Number), + }), + table: z.object({ + c1: integerSchema.nullable(), + c2: integerSchema.max(1000).nullable(), + c3: z.string().transform(Number), + c4: integerSchema.nullable(), + c5: integerSchema.nullable(), + c6: integerSchema.nullable(), + }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users select schema w/ defaults', (t) => { - const actual = createSelectSchema(users); +test('all data types', (t) => { + const table = pgTable('test', ({ + bigint, + bigserial, + bit, + boolean, + date, + char, + cidr, + doublePrecision, + geometry, + halfvec, + inet, + integer, + interval, + json, + jsonb, + line, + macaddr, + macaddr8, + numeric, + point, + real, + serial, + smallint, + smallserial, + text, + sparsevec, + time, + timestamp, + uuid, + varchar, + vector, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigserial1: bigserial({ mode: 'number' }).notNull(), + bigserial2: bigserial({ mode: 'bigint' }).notNull(), + bit: bit({ dimensions: 5 }).notNull(), + boolean: boolean().notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + cidr: cidr().notNull(), + doublePrecision: doublePrecision().notNull(), + geometry1: geometry({ type: 'point', mode: 'tuple' }).notNull(), + geometry2: geometry({ type: 'point', mode: 'xy' }).notNull(), + halfvec: halfvec({ dimensions: 3 }).notNull(), + inet: inet().notNull(), + integer: integer().notNull(), + interval: interval().notNull(), + json: json().notNull(), + jsonb: jsonb().notNull(), + line1: line({ mode: 'abc' }).notNull(), + line2: line({ mode: 'tuple' }).notNull(), + macaddr: macaddr().notNull(), + macaddr8: macaddr8().notNull(), + numeric: numeric().notNull(), + point1: point({ mode: 'xy' }).notNull(), + point2: point({ mode: 'tuple' }).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint: smallint().notNull(), + smallserial: smallserial().notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + sparsevec: sparsevec({ dimensions: 3 }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + uuid: uuid().notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + vector: vector({ dimensions: 3 }).notNull(), + array1: integer().array().notNull(), + array2: integer().array().array(2).notNull(), + array3: varchar({ length: 10 }).array().array(2).notNull(), + })); + const result = createSelectSchema(table); const expected = z.object({ - a: z.array(z.number()).nullable(), - id: z.number(), - name: z.string().nullable(), - email: z.string(), - birthdayString: z.string(), - birthdayDate: z.date(), - createdAt: z.date(), - role: z.enum(['admin', 'user']), - roleText: z.enum(['admin', 'user']), - roleText2: z.enum(['admin', 'user']), - profession: z.string().max(20).min(1), - initials: z.string().max(2).min(1), - }); - - expectSchemaShape(t, expected).from(actual); + bigint1: z.number().min(Number.MIN_SAFE_INTEGER).max(Number.MAX_SAFE_INTEGER).int(), + bigint2: z.bigint().min(CONSTANTS.INT64_MIN).max(CONSTANTS.INT64_MAX), + bigserial1: z.number().min(Number.MIN_SAFE_INTEGER).max(Number.MAX_SAFE_INTEGER).int(), + bigserial2: z.bigint().min(CONSTANTS.INT64_MIN).max(CONSTANTS.INT64_MAX), + bit: z.string().regex(/^[01]+$/).max(5), + boolean: z.boolean(), + date1: z.date(), + date2: z.string(), + char1: z.string().length(10), + char2: z.enum(['a', 'b', 'c']), + cidr: z.string(), + doublePrecision: z.number().min(CONSTANTS.INT48_MIN).max(CONSTANTS.INT48_MAX), + geometry1: z.tuple([z.number(), z.number()]), + geometry2: z.object({ x: z.number(), y: z.number() }), + halfvec: z.array(z.number()).length(3), + inet: z.string(), + integer: z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(), + interval: z.string(), + json: jsonSchema, + jsonb: jsonSchema, + line1: z.object({ a: z.number(), b: z.number(), c: z.number() }), + line2: z.tuple([z.number(), z.number(), z.number()]), + macaddr: z.string(), + macaddr8: z.string(), + numeric: z.string(), + point1: z.object({ x: z.number(), y: z.number() }), + point2: z.tuple([z.number(), z.number()]), + real: z.number().min(CONSTANTS.INT24_MIN).max(CONSTANTS.INT24_MAX), + serial: z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(), + smallint: z.number().min(CONSTANTS.INT16_MIN).max(CONSTANTS.INT16_MAX).int(), + smallserial: z.number().min(CONSTANTS.INT16_MIN).max(CONSTANTS.INT16_MAX).int(), + text1: z.string(), + text2: z.enum(['a', 'b', 'c']), + sparsevec: z.string(), + time: z.string(), + timestamp1: z.date(), + timestamp2: z.string(), + uuid: z.string().uuid(), + varchar1: z.string().max(10), + varchar2: z.enum(['a', 'b', 'c']), + vector: z.array(z.number()).length(3), + array1: z.array(integerSchema), + array2: z.array(z.array(integerSchema).length(2)), + array3: z.array(z.array(z.string().max(10)).length(2)), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); + +/* Disallow unknown keys in table refinement - select */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createSelectSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createInsertSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = pgTable('test', { id: integer() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = pgTable('test', { id: integer() }); + const view = pgView('test').as((qb) => qb.select().from(table)); + const mView = pgMaterializedView('test').as((qb) => qb.select().from(table)); + const nestedSelect = pgView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: z.string() }); + // @ts-expect-error + createSelectSchema(mView, { unknown: z.string() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: z.string() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = pgView('test', { id: integer() }).as(sql``); + const mView = pgView('test', { id: integer() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: z.string() }); + // @ts-expect-error + createSelectSchema(mView, { unknown: z.string() }); +} diff --git a/drizzle-zod/tests/singlestore.test.ts b/drizzle-zod/tests/singlestore.test.ts new file mode 100644 index 000000000..b91c74be8 --- /dev/null +++ b/drizzle-zod/tests/singlestore.test.ts @@ -0,0 +1,491 @@ +import { type Equal } from 'drizzle-orm'; +import { customType, int, serial, singlestoreSchema, singlestoreTable, text } from 'drizzle-orm/singlestore-core'; +import { test } from 'vitest'; +import { z } from 'zod'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; + +const intSchema = z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(); +const serialNumberModeSchema = z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(); +const textSchema = z.string().max(CONSTANTS.INT16_UNSIGNED_MAX); + +test('table - select', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table in schema - select', (tc) => { + const schema = singlestoreSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table - insert', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = z.object({ + id: serialNumberModeSchema.optional(), + name: textSchema, + age: intSchema.nullable().optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table - update', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createUpdateSchema(table); + const expected = z.object({ + id: serialNumberModeSchema.optional(), + name: textSchema.optional(), + age: intSchema.nullable().optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +// TODO: SingleStore doesn't support views yet. Add these tests when they're added + +// test('view qb - select', (t) => { +// const table = singlestoreTable('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }); +// const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + +// const result = createSelectSchema(view); +// const expected = z.object({ id: serialNumberModeSchema, age: z.any() }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +// test('view columns - select', (t) => { +// const view = mysqlView('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }).as(sql``); + +// const result = createSelectSchema(view); +// const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +// test('view with nested fields - select', (t) => { +// const table = singlestoreTable('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }); +// const view = mysqlView('test').as((qb) => +// qb.select({ +// id: table.id, +// nested: { +// name: table.name, +// age: sql``.as('age'), +// }, +// table, +// }).from(table) +// ); + +// const result = createSelectSchema(view); +// const expected = z.object({ +// id: serialNumberModeSchema, +// nested: z.object({ name: textSchema, age: z.any() }), +// table: z.object({ id: serialNumberModeSchema, name: textSchema }), +// }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +test('nullability - select', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + }); + + const result = createSelectSchema(table); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema, + c3: intSchema.nullable(), + c4: intSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - insert', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema, + c3: intSchema.nullable().optional(), + c4: intSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - update', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.optional(), + c3: intSchema.nullable().optional(), + c4: intSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); + + const customTextSchema = z.string().min(1).max(100); + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - update', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.max(1000).optional(), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +// test('refine view - select', (t) => { +// const table = singlestoreTable('test', { +// c1: int(), +// c2: int(), +// c3: int(), +// c4: int(), +// c5: int(), +// c6: int(), +// }); +// const view = mysqlView('test').as((qb) => +// qb.select({ +// c1: table.c1, +// c2: table.c2, +// c3: table.c3, +// nested: { +// c4: table.c4, +// c5: table.c5, +// c6: table.c6, +// }, +// table, +// }).from(table) +// ); + +// const result = createSelectSchema(view, { +// c2: (schema) => schema.max(1000), +// c3: z.string().transform(Number), +// nested: { +// c5: (schema) => schema.max(1000), +// c6: z.string().transform(Number), +// }, +// table: { +// c2: (schema) => schema.max(1000), +// c3: z.string().transform(Number), +// }, +// }); +// const expected = z.object({ +// c1: intSchema.nullable(), +// c2: intSchema.max(1000).nullable(), +// c3: z.string().transform(Number), +// nested: z.object({ +// c4: intSchema.nullable(), +// c5: intSchema.max(1000).nullable(), +// c6: z.string().transform(Number), +// }), +// table: z.object({ +// c1: intSchema.nullable(), +// c2: intSchema.max(1000).nullable(), +// c3: z.string().transform(Number), +// c4: intSchema.nullable(), +// c5: intSchema.nullable(), +// c6: intSchema.nullable(), +// }), +// }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +test('all data types', (t) => { + const table = singlestoreTable('test', ({ + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + singlestoreEnum, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varchar, + varbinary, + year, + longtext, + mediumtext, + tinytext, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(), + bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(), + binary: binary({ length: 10 }).notNull(), + boolean: boolean().notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + datetime1: datetime({ mode: 'date' }).notNull(), + datetime2: datetime({ mode: 'string' }).notNull(), + decimal1: decimal().notNull(), + decimal2: decimal({ unsigned: true }).notNull(), + double1: double().notNull(), + double2: double({ unsigned: true }).notNull(), + float1: float().notNull(), + float2: float({ unsigned: true }).notNull(), + int1: int().notNull(), + int2: int({ unsigned: true }).notNull(), + json: json().notNull(), + mediumint1: mediumint().notNull(), + mediumint2: mediumint({ unsigned: true }).notNull(), + enum: singlestoreEnum('enum', ['a', 'b', 'c']).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint1: smallint().notNull(), + smallint2: smallint({ unsigned: true }).notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + tinyint1: tinyint().notNull(), + tinyint2: tinyint({ unsigned: true }).notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + varbinary: varbinary({ length: 10 }).notNull(), + year: year().notNull(), + longtext1: longtext().notNull(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(), + mediumtext1: mediumtext().notNull(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(), + tinytext1: tinytext().notNull(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(), + })); + + const result = createSelectSchema(table); + const expected = z.object({ + bigint1: z.number().min(Number.MIN_SAFE_INTEGER).max(Number.MAX_SAFE_INTEGER).int(), + bigint2: z.bigint().min(CONSTANTS.INT64_MIN).max(CONSTANTS.INT64_MAX), + bigint3: z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(), + bigint4: z.bigint().min(0n).max(CONSTANTS.INT64_UNSIGNED_MAX), + binary: z.string(), + boolean: z.boolean(), + char1: z.string().length(10), + char2: z.enum(['a', 'b', 'c']), + date1: z.date(), + date2: z.string(), + datetime1: z.date(), + datetime2: z.string(), + decimal1: z.string(), + decimal2: z.string(), + double1: z.number().min(CONSTANTS.INT48_MIN).max(CONSTANTS.INT48_MAX), + double2: z.number().min(0).max(CONSTANTS.INT48_UNSIGNED_MAX), + float1: z.number().min(CONSTANTS.INT24_MIN).max(CONSTANTS.INT24_MAX), + float2: z.number().min(0).max(CONSTANTS.INT24_UNSIGNED_MAX), + int1: z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(), + int2: z.number().min(0).max(CONSTANTS.INT32_UNSIGNED_MAX).int(), + json: jsonSchema, + mediumint1: z.number().min(CONSTANTS.INT24_MIN).max(CONSTANTS.INT24_MAX).int(), + mediumint2: z.number().min(0).max(CONSTANTS.INT24_UNSIGNED_MAX).int(), + enum: z.enum(['a', 'b', 'c']), + real: z.number().min(CONSTANTS.INT48_MIN).max(CONSTANTS.INT48_MAX), + serial: z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(), + smallint1: z.number().min(CONSTANTS.INT16_MIN).max(CONSTANTS.INT16_MAX).int(), + smallint2: z.number().min(0).max(CONSTANTS.INT16_UNSIGNED_MAX).int(), + text1: z.string().max(CONSTANTS.INT16_UNSIGNED_MAX), + text2: z.enum(['a', 'b', 'c']), + time: z.string(), + timestamp1: z.date(), + timestamp2: z.string(), + tinyint1: z.number().min(CONSTANTS.INT8_MIN).max(CONSTANTS.INT8_MAX).int(), + tinyint2: z.number().min(0).max(CONSTANTS.INT8_UNSIGNED_MAX).int(), + varchar1: z.string().max(10), + varchar2: z.enum(['a', 'b', 'c']), + varbinary: z.string(), + year: z.number().min(1901).max(2155).int(), + longtext1: z.string().max(CONSTANTS.INT32_UNSIGNED_MAX), + longtext2: z.enum(['a', 'b', 'c']), + mediumtext1: z.string().max(CONSTANTS.INT24_UNSIGNED_MAX), + mediumtext2: z.enum(['a', 'b', 'c']), + tinytext1: z.string().max(CONSTANTS.INT8_UNSIGNED_MAX), + tinytext2: z.enum(['a', 'b', 'c']), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +/* Disallow unknown keys in table refinement - select */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: z.string() }); +} + +// /* Disallow unknown keys in view qb - select */ { +// const table = singlestoreTable('test', { id: int() }); +// const view = mysqlView('test').as((qb) => qb.select().from(table)); +// const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table)); +// // @ts-expect-error +// createSelectSchema(view, { unknown: z.string() }); +// // @ts-expect-error +// createSelectSchema(nestedSelect, { table: { unknown: z.string() } }); +// } + +// /* Disallow unknown keys in view columns - select */ { +// const view = mysqlView('test', { id: int() }).as(sql``); +// // @ts-expect-error +// createSelectSchema(view, { unknown: z.string() }); +// } diff --git a/drizzle-zod/tests/sqlite.test.ts b/drizzle-zod/tests/sqlite.test.ts index 5a2c3a04e..bb0f254b5 100644 --- a/drizzle-zod/tests/sqlite.test.ts +++ b/drizzle-zod/tests/sqlite.test.ts @@ -1,162 +1,385 @@ -import { blob, integer, numeric, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; -import { expect, test } from 'vitest'; +import { type Equal, sql } from 'drizzle-orm'; +import { customType, int, sqliteTable, sqliteView, text } from 'drizzle-orm/sqlite-core'; +import { test } from 'vitest'; import { z } from 'zod'; -import { createInsertSchema, createSelectSchema, jsonSchema } from '../src'; -import { expectSchemaShape } from './utils.ts'; +import { bufferSchema, jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; -const blobJsonSchema = z.object({ - foo: z.string(), +const intSchema = z.number().min(Number.MIN_SAFE_INTEGER).max(Number.MAX_SAFE_INTEGER).int(); +const textSchema = z.string(); + +test('table - select', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = z.object({ id: intSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -const users = sqliteTable('users', { - id: integer('id').primaryKey(), - blobJson: blob('blob', { mode: 'json' }).$type>().notNull(), - blobBigInt: blob('blob', { mode: 'bigint' }).notNull(), - numeric: numeric('numeric').notNull(), - createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), - createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }).notNull(), - boolean: integer('boolean', { mode: 'boolean' }).notNull(), - real: real('real').notNull(), - text: text('text', { length: 255 }), - role: text('role', { enum: ['admin', 'user'] }).notNull().default('user'), +test('table - insert', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = z.object({ id: intSchema.optional(), name: textSchema, age: intSchema.nullable().optional() }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -const testUser = { - id: 1, - blobJson: { foo: 'bar' }, - blobBigInt: BigInt(123), - numeric: '123.45', - createdAt: new Date(), - createdAtMs: new Date(), - boolean: true, - real: 123.45, - text: 'foobar', - role: 'admin', -}; - -test('users insert valid user', () => { - const schema = createInsertSchema(users); - - expect(schema.safeParse(testUser).success).toBeTruthy(); +test('table - update', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + age: int(), + }); + + const result = createUpdateSchema(table); + const expected = z.object({ + id: intSchema.optional(), + name: textSchema.optional(), + age: intSchema.nullable().optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert invalid text length', () => { - const schema = createInsertSchema(users); +test('view qb - select', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }); + const view = sqliteView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); - expect(schema.safeParse({ ...testUser, text: 'a'.repeat(256) }).success).toBeFalsy(); + const result = createSelectSchema(view); + const expected = z.object({ id: intSchema, age: z.any() }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert schema', (t) => { - const actual = createInsertSchema(users, { - id: ({ id }) => id.positive(), - blobJson: blobJsonSchema, - role: z.enum(['admin', 'manager', 'user']), +test('view columns - select', (t) => { + const view = sqliteView('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), + }).as(sql``); + + const result = createSelectSchema(view); + const expected = z.object({ id: intSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('view with nested fields - select', (t) => { + const table = sqliteTable('test', { + id: int().primaryKey({ autoIncrement: true }), + name: text().notNull(), }); + const view = sqliteView('test').as((qb) => + qb.select({ + id: table.id, + nested: { + name: table.name, + age: sql``.as('age'), + }, + table, + }).from(table) + ); - (() => { - { - createInsertSchema(users, { - // @ts-expect-error (missing property) - foobar: z.number(), - }); - } + const result = createSelectSchema(view); + const expected = z.object({ + id: intSchema, + nested: z.object({ name: textSchema, age: z.any() }), + table: z.object({ id: intSchema, name: textSchema }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); - { - createInsertSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } +test('nullability - select', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), }); + const result = createSelectSchema(table); const expected = z.object({ - id: z.number().positive().optional(), - blobJson: blobJsonSchema, - blobBigInt: z.bigint(), - numeric: z.string(), - createdAt: z.date(), - createdAtMs: z.date(), - boolean: z.boolean(), - real: z.number(), - text: z.string().nullable().optional(), - role: z.enum(['admin', 'manager', 'user']).optional(), + c1: intSchema.nullable(), + c2: intSchema, + c3: intSchema.nullable(), + c4: intSchema, }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); - expectSchemaShape(t, expected).from(actual); +test('nullability - insert', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema, + c3: intSchema.nullable().optional(), + c4: intSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users insert schema w/ defaults', (t) => { - const actual = createInsertSchema(users); +test('nullability - update', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + const result = createUpdateSchema(table); const expected = z.object({ - id: z.number().optional(), - blobJson: jsonSchema, - blobBigInt: z.bigint(), - numeric: z.string(), - createdAt: z.date(), - createdAtMs: z.date(), - boolean: z.boolean(), - real: z.number(), - text: z.string().nullable().optional(), - role: z.enum(['admin', 'user']).optional(), + c1: intSchema.nullable().optional(), + c2: intSchema.optional(), + c3: intSchema.nullable().optional(), + c4: intSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), }); - expectSchemaShape(t, expected).from(actual); + const customTextSchema = z.string().min(1).max(100); + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users select schema', (t) => { - const actual = createSelectSchema(users, { - blobJson: jsonSchema, - role: z.enum(['admin', 'manager', 'user']), +test('refine table - insert', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), }); - (() => { - { - createSelectSchema(users, { - // @ts-expect-error (missing property) - foobar: z.number(), - }); - } + const result = createInsertSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); - { - createSelectSchema(users, { - // @ts-expect-error (invalid type) - id: 123, - }); - } +test('refine table - update', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), }); + const result = createUpdateSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); const expected = z.object({ - id: z.number(), - blobJson: jsonSchema, - blobBigInt: z.bigint(), - numeric: z.string(), - createdAt: z.date(), - createdAtMs: z.date(), - boolean: z.boolean(), - real: z.number(), - text: z.string().nullable(), - role: z.enum(['admin', 'manager', 'user']), - }).required(); - - expectSchemaShape(t, expected).from(actual); + c1: intSchema.nullable().optional(), + c2: intSchema.max(1000).optional(), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine view - select', (t) => { + const table = sqliteTable('test', { + c1: int(), + c2: int(), + c3: int(), + c4: int(), + c5: int(), + c6: int(), + }); + const view = sqliteView('test').as((qb) => + qb.select({ + c1: table.c1, + c2: table.c2, + c3: table.c3, + nested: { + c4: table.c4, + c5: table.c5, + c6: table.c6, + }, + table, + }).from(table) + ); + + const result = createSelectSchema(view, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + nested: { + c5: (schema) => schema.max(1000), + c6: z.string().transform(Number), + }, + table: { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }, + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000).nullable(), + c3: z.string().transform(Number), + nested: z.object({ + c4: intSchema.nullable(), + c5: intSchema.max(1000).nullable(), + c6: z.string().transform(Number), + }), + table: z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000).nullable(), + c3: z.string().transform(Number), + c4: intSchema.nullable(), + c5: intSchema.nullable(), + c6: intSchema.nullable(), + }), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); -test('users select schema w/ defaults', (t) => { - const actual = createSelectSchema(users); +test('all data types', (t) => { + const table = sqliteTable('test', ({ + blob, + integer, + numeric, + real, + text, + }) => ({ + blob1: blob({ mode: 'buffer' }).notNull(), + blob2: blob({ mode: 'bigint' }).notNull(), + blob3: blob({ mode: 'json' }).notNull(), + integer1: integer({ mode: 'number' }).notNull(), + integer2: integer({ mode: 'boolean' }).notNull(), + integer3: integer({ mode: 'timestamp' }).notNull(), + integer4: integer({ mode: 'timestamp_ms' }).notNull(), + numeric: numeric().notNull(), + real: real().notNull(), + text1: text({ mode: 'text' }).notNull(), + text2: text({ mode: 'text', length: 10 }).notNull(), + text3: text({ mode: 'text', enum: ['a', 'b', 'c'] }).notNull(), + text4: text({ mode: 'json' }).notNull(), + })); + const result = createSelectSchema(table); const expected = z.object({ - id: z.number(), - blobJson: jsonSchema, - blobBigInt: z.bigint(), + blob1: bufferSchema, + blob2: z.bigint().min(CONSTANTS.INT64_MIN).max(CONSTANTS.INT64_MAX), + blob3: jsonSchema, + integer1: z.number().min(Number.MIN_SAFE_INTEGER).max(Number.MAX_SAFE_INTEGER).int(), + integer2: z.boolean(), + integer3: z.date(), + integer4: z.date(), numeric: z.string(), - createdAt: z.date(), - createdAtMs: z.date(), - boolean: z.boolean(), - real: z.number(), - text: z.string().nullable(), - role: z.enum(['admin', 'user']), - }).required(); - - expectSchemaShape(t, expected).from(actual); + real: z.number().min(CONSTANTS.INT48_MIN).max(CONSTANTS.INT48_MAX), + text1: z.string(), + text2: z.string().max(10), + text3: z.enum(['a', 'b', 'c']), + text4: jsonSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); }); + +/* Disallow unknown keys in table refinement - select */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = sqliteTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in view qb - select */ { + const table = sqliteTable('test', { id: int() }); + const view = sqliteView('test').as((qb) => qb.select().from(table)); + const nestedSelect = sqliteView('test').as((qb) => qb.select({ table }).from(table)); + // @ts-expect-error + createSelectSchema(view, { unknown: z.string() }); + // @ts-expect-error + createSelectSchema(nestedSelect, { table: { unknown: z.string() } }); +} + +/* Disallow unknown keys in view columns - select */ { + const view = sqliteView('test', { id: int() }).as(sql``); + // @ts-expect-error + createSelectSchema(view, { unknown: z.string() }); +} diff --git a/drizzle-zod/tests/utils.ts b/drizzle-zod/tests/utils.ts index 1c28be260..6a36f66c5 100644 --- a/drizzle-zod/tests/utils.ts +++ b/drizzle-zod/tests/utils.ts @@ -1,13 +1,14 @@ import { expect, type TaskContext } from 'vitest'; import type { z } from 'zod'; -export function expectSchemaShape(t: TaskContext, expected: z.ZodObject) { +export function expectSchemaShape>(t: TaskContext, expected: T) { return { - from(actual: z.ZodObject) { + from(actual: T) { expect(Object.keys(actual.shape)).toStrictEqual(Object.keys(expected.shape)); for (const key of Object.keys(actual.shape)) { expect(actual.shape[key]!._def.typeName).toStrictEqual(expected.shape[key]?._def.typeName); + expect(actual.shape[key]!._def?.checks).toEqual(expected.shape[key]?._def?.checks); if (actual.shape[key]?._def.typeName === 'ZodOptional') { expect(actual.shape[key]!._def.innerType._def.typeName).toStrictEqual( actual.shape[key]!._def.innerType._def.typeName, @@ -17,3 +18,13 @@ export function expectSchemaShape(t: TaskContext, expec }, }; } + +export function expectEnumValues>(t: TaskContext, expected: T) { + return { + from(actual: T) { + expect(actual._def.values).toStrictEqual(expected._def.values); + }, + }; +} + +export function Expect<_ extends true>() {} diff --git a/drizzle-zod/tsconfig.build.json b/drizzle-zod/tsconfig.build.json index 3377281ba..1af9f8b40 100644 --- a/drizzle-zod/tsconfig.build.json +++ b/drizzle-zod/tsconfig.build.json @@ -1,7 +1,8 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "rootDir": "src" + "rootDir": "src", + "stripInternal": true }, "include": ["src"] } diff --git a/drizzle-zod/tsconfig.json b/drizzle-zod/tsconfig.json index 038d79591..c25379c37 100644 --- a/drizzle-zod/tsconfig.json +++ b/drizzle-zod/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "dist", "baseUrl": ".", "declaration": true, + "noEmit": true, "paths": { "~/*": ["src/*"] } diff --git a/examples/mysql-proxy/README.md b/examples/mysql-proxy/README.md index 8bbdf3f7b..fadc843d3 100644 --- a/examples/mysql-proxy/README.md +++ b/examples/mysql-proxy/README.md @@ -30,7 +30,7 @@ This project has simple example of defining http proxy server, that will proxy a
-> **Warning**: +> [!WARNING] > You will be responsible for proper error handling in this part. Drizzle always waits for `{rows: string[][] | object[]}` so if any error was on http call(or any other call) - be sure, that you return at least empty array back > > For `all` method you should return `{rows: string[][]}` @@ -72,7 +72,7 @@ In current MySQL Proxy version - drizzle don't handle transactions for migration
-> **Warning**: +> [!WARNING] > You will be responsible for proper error handling in this part. Drizzle just finds migrations, that need to be executed on this iteration and if finds some -> provide `queries` array to callback
@@ -97,7 +97,7 @@ await migrate(db, async (queries) => { --- -> **Note**: +> [!WARNING] > It's just a suggestion on how proxy server could be set up and a simple example of params handling on `query` and `migration` calls ```typescript diff --git a/examples/neon-cloudflare/readme.md b/examples/neon-cloudflare/readme.md index 0585ccecc..f69581b21 100644 --- a/examples/neon-cloudflare/readme.md +++ b/examples/neon-cloudflare/readme.md @@ -1,4 +1,4 @@ -### Example project for [DrizzleORM](https://driz.li/orm) + [Cloudflare Worker](https://workers.cloudflare.com) + [Neon Serverless](https://github.com/neondatabase/serverless) +### Example project for [Drizzle ORM](https://driz.li/orm) + [Cloudflare Worker](https://workers.cloudflare.com) + [Neon Serverless](https://github.com/neondatabase/serverless) --- diff --git a/examples/pg-proxy/README.md b/examples/pg-proxy/README.md index d9ff24fb1..b47fcd5e3 100644 --- a/examples/pg-proxy/README.md +++ b/examples/pg-proxy/README.md @@ -30,7 +30,7 @@ This project has simple example of defining http proxy server, that will proxy a
-> **Warning**: +> [!WARNING] > You will be responsible for proper error handling in this part. Drizzle always waits for `{rows: string[][] | object[]}` so if any error was on http call(or any other call) - be sure, that you return at least empty array back > > For `all` method you should return `{rows: string[][]}` @@ -72,7 +72,7 @@ In current Postgres Proxy version - drizzle don't handle transactions for migrat
-> **Warning**: +> [!WARNING] > You will be responsible for proper error handling in this part. Drizzle just finds migrations, that need to be executed on this iteration and if finds some -> provide `queries` array to callback
@@ -97,7 +97,7 @@ await migrate(db, async (queries) => { --- -> **Note**: +> [!NOTE] > It's just a suggestion on how proxy server could be set up and a simple example of params handling on `query` and `migration` calls ```typescript diff --git a/examples/sqlite-proxy/README.md b/examples/sqlite-proxy/README.md index b187e35fb..d25cd7d8b 100644 --- a/examples/sqlite-proxy/README.md +++ b/examples/sqlite-proxy/README.md @@ -30,7 +30,7 @@ This project has simple example of defining http proxy server, that will proxy a
-> **Warning**: +> [!WARNING] > You will be responsible for proper error handling in this part. Drizzle always waits for `{rows: string[][]}` so if any error was on http call(or any other call) - be sure, that you return at least empty array back > > For `get` method you should return `{rows: string[]}` @@ -71,7 +71,7 @@ In current SQLite Proxy version - drizzle don't handle transactions for migratio
-> **Warning**: +> [!WARNING] > You will be responsible for proper error handling in this part. Drizzle just finds migrations, that need to be executed on this iteration and if finds some -> provide `queries` array to callback
@@ -96,7 +96,7 @@ await migrate(db, async (queries) => { --- -> **Note**: +> [!NOTE] > It's just a suggestion on how proxy server could be set up and a simple example of params handling on `query` and `migration` calls ```typescript diff --git a/integration-tests/.env.example b/integration-tests/.env.example index 50b3c95f2..cad737330 100644 --- a/integration-tests/.env.example +++ b/integration-tests/.env.example @@ -1,11 +1,15 @@ PG_CONNECTION_STRING="postgres://postgres:postgres@localhost:55432/postgres" MYSQL_CONNECTION_STRING="mysql://root:mysql@127.0.0.1:33306/drizzle" +SINGLESTORE_CONNECTION_STRING="singlestore://root:singlestore@localhost:3306/drizzle" PLANETSCALE_CONNECTION_STRING= TIDB_CONNECTION_STRING= NEON_CONNECTION_STRING= LIBSQL_URL="file:local.db" LIBSQL_AUTH_TOKEN="ey..." # For Turso only +LIBSQL_REMOTE_URL="libsql://..." +LIBSQL_REMOTE_TOKEN="ey..." AWS_DATA_API_DB= AWS_DATA_API_SECRET_ARN= AWS_DATA_API_RESOURCE_ARN= AWS_TEST_PROFILE= +VERCEL_CONNECTION_STRING= # For driver-init, utils/is-config tests. Must not be local DB - breaks with Vercel drivers \ No newline at end of file diff --git a/integration-tests/drizzle2/singlestore/0000_nostalgic_carnage.sql b/integration-tests/drizzle2/singlestore/0000_nostalgic_carnage.sql new file mode 100644 index 000000000..50efe47da --- /dev/null +++ b/integration-tests/drizzle2/singlestore/0000_nostalgic_carnage.sql @@ -0,0 +1,20 @@ +CREATE TABLE `cities_migration` ( + `id` int, + `fullname_name` text, + `state` text +); +--> statement-breakpoint +CREATE TABLE `users_migration` ( + `id` int PRIMARY KEY NOT NULL, + `full_name` text, + `phone` int, + `invited_by` int, + `city_id` int, + `date` timestamp DEFAULT now() +); +--> statement-breakpoint +CREATE TABLE `users12` ( + `id` serial AUTO_INCREMENT PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `email` text NOT NULL +); diff --git a/integration-tests/drizzle2/singlestore/meta/0000_snapshot.json b/integration-tests/drizzle2/singlestore/meta/0000_snapshot.json new file mode 100644 index 000000000..63d5ad187 --- /dev/null +++ b/integration-tests/drizzle2/singlestore/meta/0000_snapshot.json @@ -0,0 +1,132 @@ +{ + "version": "1", + "dialect": "singlestore", + "id": "8e8c8378-0496-40f6-88e3-98aab8282b1f", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "cities_migration": { + "name": "cities_migration", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fullname_name": { + "name": "fullname_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "users_migration": { + "name": "users_migration", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invited_by": { + "name": "invited_by", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "city_id": { + "name": "city_id", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "users12": { + "name": "users12", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "my_unique_index": { + "name": "my_unique_index", + "columns": [ + "name" + ], + "isUnique": true, + "using": "btree" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {} + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} diff --git a/integration-tests/drizzle2/singlestore/meta/_journal.json b/integration-tests/drizzle2/singlestore/meta/_journal.json new file mode 100644 index 000000000..49e74f169 --- /dev/null +++ b/integration-tests/drizzle2/singlestore/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "1", + "dialect": "singlestore", + "entries": [ + { + "idx": 0, + "version": "1", + "when": 1680270921944, + "tag": "0000_nostalgic_carnage", + "breakpoints": true + } + ] +} diff --git a/integration-tests/js-tests/driver-init/commonjs/better-sqlite3.test.cjs b/integration-tests/js-tests/driver-init/commonjs/better-sqlite3.test.cjs new file mode 100644 index 000000000..1a9941e30 --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/better-sqlite3.test.cjs @@ -0,0 +1,124 @@ +require('dotenv/config'); +const Database = require('better-sqlite3'); +const { drizzle } = require('drizzle-orm/better-sqlite3'); +const { sqlite: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; + +describe('better-sqlite3', async (it) => { + it('drizzle()', async () => { + const db = drizzle(); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(string)', async () => { + const db = drizzle(':memory:'); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle(':memory:', { + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: ':memory:', + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + source: ':memory:', + }, + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: {}, ...config})', async () => { + const db = drizzle({ + connection: {}, + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({...config})', async () => { + const db = drizzle({ + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = new Database(':memory:'); + const db = drizzle(client); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(client, config)', async () => { + const client = new Database(':memory:'); + const db = drizzle(client, { + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Database(':memory:'); + const db = drizzle({ + client, + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/libsql.test.cjs b/integration-tests/js-tests/driver-init/commonjs/libsql.test.cjs new file mode 100644 index 000000000..e070af1bc --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/libsql.test.cjs @@ -0,0 +1,97 @@ +require('dotenv/config'); +const { createClient } = require('@libsql/client'); +const { drizzle } = require('drizzle-orm/libsql'); +const { sqlite: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; + +describe('libsql', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle(':memory:'); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle(':memory:', { + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: ':memory:', + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + url: ':memory:', + }, + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = createClient({ + url: ':memory:', + }); + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(client, config)', async () => { + const client = createClient({ + url: ':memory:', + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createClient({ + url: ':memory:', + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/mysql2.test.cjs b/integration-tests/js-tests/driver-init/commonjs/mysql2.test.cjs new file mode 100644 index 000000000..44a0987d1 --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/mysql2.test.cjs @@ -0,0 +1,153 @@ +require('dotenv/config'); +const { drizzle } = require('drizzle-orm/mysql2'); +const { createPool, createConnection, Connection } = require('mysql2'); +const { mysql: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; + +if (!process.env['MYSQL_CONNECTION_STRING']) { + throw new Error('MYSQL_CONNECTION_STRING is not defined'); +} + +describe('mysql2', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['MYSQL_CONNECTION_STRING'], + ); + + await db.$client.execute(`SELECT 1`); + + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['MYSQL_CONNECTION_STRING'], + { + schema, + mode: 'default', + }, + ); + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['MYSQL_CONNECTION_STRING'], + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + uri: process.env['MYSQL_CONNECTION_STRING'], + }, + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = createPool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle(client, config)', async () => { + const client = createPool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createPool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const db = drizzle({ + client, + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); +}); + +describe('mysql2:connection', async (it) => { + it('drizzle(client)', async () => { + const client = createConnection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client.getConnection).toStrictEqual(undefined); + }); + + it('drizzle(client, config)', async () => { + const client = createConnection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createConnection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const db = drizzle({ + client, + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/neon-http.test.cjs b/integration-tests/js-tests/driver-init/commonjs/neon-http.test.cjs new file mode 100644 index 000000000..ad68b96a3 --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/neon-http.test.cjs @@ -0,0 +1,92 @@ +require('dotenv/config'); +const { neon: pg } = require('@neondatabase/serverless'); +const { drizzle } = require('drizzle-orm/neon-http'); +const { pg: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; + +if (!process.env['NEON_CONNECTION_STRING']) { + throw new Error('NEON_CONNECTION_STRING is not defined'); +} + +describe('neon-http', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['NEON_CONNECTION_STRING'], + ); + + await db.$client('SELECT 1;'); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['NEON_CONNECTION_STRING'], + { + schema, + }, + ); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['NEON_CONNECTION_STRING'], + schema, + }); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + connectionString: process.env['NEON_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = pg( + process.env['NEON_CONNECTION_STRING'], + ); + const db = drizzle(client); + + await db.$client('SELECT 1;'); + }); + + it('drizzle(client, config)', async () => { + const client = pg( + process.env['NEON_CONNECTION_STRING'], + ); + const db = drizzle(client, { + schema, + }); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = pg( + process.env['NEON_CONNECTION_STRING'], + ); + const db = drizzle({ + client, + schema, + }); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/neon-ws.test.cjs b/integration-tests/js-tests/driver-init/commonjs/neon-ws.test.cjs new file mode 100644 index 000000000..28b5026c6 --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/neon-ws.test.cjs @@ -0,0 +1,211 @@ +require('dotenv/config'); +const { neonConfig, Pool, Client } = require('@neondatabase/serverless'); +const { drizzle } = require('drizzle-orm/neon-serverless'); +const { pg: schema } = require('./schema.cjs'); +const ws = require('ws'); +import { describe, expect } from 'vitest'; + +neonConfig.webSocketConstructor = ws; + +if (!process.env['NEON_CONNECTION_STRING']) { + throw new Error('NEON_CONNECTION_STRING is not defined'); +} + +describe('neon-ws', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['NEON_CONNECTION_STRING'], + ); + + await db.$client.query('SELECT 1;'); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['NEON_CONNECTION_STRING'], + { + schema, + }, + ); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['NEON_CONNECTION_STRING'], + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + connectionString: process.env['NEON_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(client)', async () => { + const client = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const client = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); +}); + +describe('neon-ws:Client', async (it) => { + it('drizzle(client)', async () => { + const client = new Client({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + + await client.connect(); + + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const client = new Client({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Client({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); +}); + +describe('neon-ws:PoolClient', async (it) => { + it('drizzle(client)', async () => { + const pool = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + client.release(); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const pool = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + client.release(); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); + + it('drizzle({client, ...config})', async () => { + const pool = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle({ + client, + schema, + }); + + await db.$client.query('SELECT 1;'); + + client.release(); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/node-pg.test.cjs b/integration-tests/js-tests/driver-init/commonjs/node-pg.test.cjs new file mode 100644 index 000000000..53338d061 --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/node-pg.test.cjs @@ -0,0 +1,201 @@ +require('dotenv/config'); +const { drizzle } = require('drizzle-orm/node-postgres'); +const pg = require('pg'); +const { pg: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; + +const Pool = pg.Pool; +const Client = pg.Client; + +if (!process.env['PG_CONNECTION_STRING']) { + throw new Error('PG_CONNECTION_STRING is not defined'); +} + +describe('node-pg', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle(process.env['PG_CONNECTION_STRING']); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle(process.env['PG_CONNECTION_STRING'], { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['PG_CONNECTION_STRING'], + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + connectionString: process.env['PG_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const client = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('node-pg:Client', async (it) => { + it('drizzle(client)', async () => { + const client = new Client({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const client = new Client({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Client({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('node-pg:PoolClient', async (it) => { + it('drizzle(client)', async () => { + const pool = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const client = await pool.connect(); + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const pool = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const client = await pool.connect(); + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const pool = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const client = await pool.connect(); + const db = drizzle({ + client, + schema, + }); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/pglite.test.cjs b/integration-tests/js-tests/driver-init/commonjs/pglite.test.cjs new file mode 100644 index 000000000..a00afb041 --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/pglite.test.cjs @@ -0,0 +1,88 @@ +require('dotenv/config'); +const { drizzle } = require('drizzle-orm/pglite'); +const { pg: schema } = require('./schema.cjs'); +const { PGlite: Database } = require('@electric-sql/pglite'); +import { describe, expect } from 'vitest'; + +describe('pglite', async (it) => { + it('drizzle()', async () => { + const db = drizzle(); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + }); + + it('drizzle(string)', async () => { + const db = drizzle('memory://'); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle('memory://', { + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: {}, ...config})', async () => { + const db = drizzle({ + connection: {}, + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({...config})', async () => { + const db = drizzle({ + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = new Database('memory://'); + const db = drizzle(client); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + }); + + it('drizzle(client, config)', async () => { + const client = new Database('memory://'); + const db = drizzle(client, { + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Database('memory://'); + const db = drizzle({ + client, + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/planetscale.test.cjs b/integration-tests/js-tests/driver-init/commonjs/planetscale.test.cjs new file mode 100644 index 000000000..0894d30c2 --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/planetscale.test.cjs @@ -0,0 +1,101 @@ +require('dotenv/config'); +const { Client } = require('@planetscale/database'); +const { drizzle } = require('drizzle-orm/planetscale-serverless'); +const { mysql: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; + +if (!process.env['PLANETSCALE_CONNECTION_STRING']) { + throw new Error('PLANETSCALE_CONNECTION_STRING is not defined'); +} + +describe('planetscale', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['PLANETSCALE_CONNECTION_STRING'], + ); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['PLANETSCALE_CONNECTION_STRING'], + { + schema, + }, + ); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['PLANETSCALE_CONNECTION_STRING'], + schema, + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = new Client({ + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const client = new Client({ + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Client({ + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/postgres-js.test.cjs b/integration-tests/js-tests/driver-init/commonjs/postgres-js.test.cjs new file mode 100644 index 000000000..da79953f3 --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/postgres-js.test.cjs @@ -0,0 +1,81 @@ +require('dotenv/config'); +const { drizzle } = require('drizzle-orm/postgres-js'); +const pg = require('postgres'); +const { pg: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; + +if (!process.env['PG_CONNECTION_STRING']) { + throw new Error('PG_CONNECTION_STRING is not defined'); +} + +describe('postgres-js', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle(process.env['PG_CONNECTION_STRING']); + + await db.$client.unsafe('SELECT 1;'); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle(process.env['PG_CONNECTION_STRING'], { + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['PG_CONNECTION_STRING'], + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + url: process.env['PG_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = pg(process.env['PG_CONNECTION_STRING']); + const db = drizzle(client); + + await db.$client.unsafe('SELECT 1;'); + }); + + it('drizzle(client, config)', async () => { + const client = pg(process.env['PG_CONNECTION_STRING']); + const db = drizzle(client, { + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = pg(process.env['PG_CONNECTION_STRING']); + const db = drizzle({ + client, + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/schema.cjs b/integration-tests/js-tests/driver-init/commonjs/schema.cjs new file mode 100644 index 000000000..7015a068d --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/schema.cjs @@ -0,0 +1,21 @@ +const { int: mysqlInt, mysqlTable } = require('drizzle-orm/mysql-core'); +const { integer: pgInt, pgTable } = require('drizzle-orm/pg-core'); +const { integer: sqliteInt, sqliteTable } = require('drizzle-orm/sqlite-core'); + +module.exports.sqlite = { + User: sqliteTable('test', { + id: sqliteInt('id').primaryKey().notNull(), + }), +}; + +module.exports.pg = { + User: pgTable('test', { + id: pgInt('id').primaryKey().notNull(), + }), +}; + +module.exports.mysql = { + User: mysqlTable('test', { + id: mysqlInt('id').primaryKey().notNull(), + }), +}; diff --git a/integration-tests/js-tests/driver-init/commonjs/tidb.test.cjs b/integration-tests/js-tests/driver-init/commonjs/tidb.test.cjs new file mode 100644 index 000000000..a64588b8a --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/tidb.test.cjs @@ -0,0 +1,88 @@ +require('dotenv/config'); +const { connect } = require('@tidbcloud/serverless'); +const { drizzle } = require('drizzle-orm/tidb-serverless'); +const { mysql: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; + +if (!process.env['TIDB_CONNECTION_STRING']) { + throw new Error('TIDB_CONNECTION_STRING is not defined'); +} + +describe('tidb', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['TIDB_CONNECTION_STRING'], + ); + + await db.$client.execute(`SELECT 1`); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['TIDB_CONNECTION_STRING'], + { + schema, + }, + ); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['TIDB_CONNECTION_STRING'], + schema, + }); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + url: process.env['TIDB_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = connect({ + url: process.env['TIDB_CONNECTION_STRING'], + }); + + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + }); + + it('drizzle(client, config)', async () => { + const client = connect({ + url: process.env['TIDB_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = connect({ + url: process.env['TIDB_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/commonjs/vercel.test.cjs b/integration-tests/js-tests/driver-init/commonjs/vercel.test.cjs new file mode 100644 index 000000000..ad418a26e --- /dev/null +++ b/integration-tests/js-tests/driver-init/commonjs/vercel.test.cjs @@ -0,0 +1,229 @@ +require('dotenv/config'); +const vc = require('@vercel/postgres'); +const { drizzle } = require('drizzle-orm/vercel-postgres'); +const { pg: schema } = require('./schema.cjs'); +import { describe, expect } from 'vitest'; +const { sql, createClient, createPool } = vc; + +const Pool = vc.VercelPool; +const Client = vc.VercelClient; + +if (!process.env['VERCEL_CONNECTION_STRING']) { + throw new Error('VERCEL_CONNECTION_STRING is not defined'); +} + +// Used for non-pooled connection +if (!process.env['NEON_CONNECTION_STRING']) { + throw new Error('NEON_CONNECTION_STRING is not defined'); +} +process.env['POSTGRES_URL'] = process.env['VERCEL_CONNECTION_STRING']; + +describe('vercel:sql', async (it) => { + it('drizzle()', async () => { + const db = drizzle(); + + await sql.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + }); + + it('drizzle(client)', async () => { + const db = drizzle(sql); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + }); + + it('drizzle(client, config)', async () => { + const db = drizzle(sql, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const db = drizzle({ + client: sql, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({...config})', async () => { + const db = drizzle({ + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('vercel:Pool', async (it) => { + it('drizzle(client)', async () => { + const client = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const client = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const db = drizzle({ + client: client, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('vercel:Client', async (it) => { + it('drizzle(client)', async () => { + const client = createClient({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const client = createClient({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createClient({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle({ + client: client, + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('vercel:PoolClient', async (it) => { + it('drizzle(client)', async () => { + const pool = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const pool = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const pool = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle({ + client: client, + schema, + }); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/better-sqlite3.test.mjs b/integration-tests/js-tests/driver-init/module/better-sqlite3.test.mjs new file mode 100644 index 000000000..063eb958f --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/better-sqlite3.test.mjs @@ -0,0 +1,124 @@ +import 'dotenv/config'; +import Database from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { describe, expect } from 'vitest'; +import { sqlite as schema } from './schema.mjs'; + +describe('better-sqlite3', async (it) => { + it('drizzle()', async () => { + const db = drizzle(); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(string)', async () => { + const db = drizzle(':memory:'); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle(':memory:', { + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: ':memory:', + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + source: ':memory:', + }, + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: {}, ...config})', async () => { + const db = drizzle({ + connection: {}, + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({...config})', async () => { + const db = drizzle({ + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = new Database(':memory:'); + const db = drizzle(client); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(client, config)', async () => { + const client = new Database(':memory:'); + const db = drizzle(client, { + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Database(':memory:'); + const db = drizzle({ + client, + schema, + }); + + await db.$client.exec('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/libsql.test.mjs b/integration-tests/js-tests/driver-init/module/libsql.test.mjs new file mode 100644 index 000000000..816d0eb0e --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/libsql.test.mjs @@ -0,0 +1,97 @@ +import 'dotenv/config'; +import { createClient } from '@libsql/client'; +import { drizzle } from 'drizzle-orm/libsql'; +import { describe, expect } from 'vitest'; +import { sqlite as schema } from './schema.mjs'; + +describe('libsql', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle(':memory:'); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle(':memory:', { + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: ':memory:', + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + url: ':memory:', + }, + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = createClient({ + url: ':memory:', + }); + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + }); + + it('drizzle(client, config)', async () => { + const client = createClient({ + url: ':memory:', + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createClient({ + url: ':memory:', + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.execute('SELECT 1;'); + + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/mysql2.test.mjs b/integration-tests/js-tests/driver-init/module/mysql2.test.mjs new file mode 100644 index 000000000..1a1565c67 --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/mysql2.test.mjs @@ -0,0 +1,153 @@ +import 'dotenv/config'; +import { drizzle } from 'drizzle-orm/mysql2'; +import { Connection, createConnection, createPool } from 'mysql2'; +import { describe, expect } from 'vitest'; +import { mysql as schema } from './schema.mjs'; + +if (!process.env['MYSQL_CONNECTION_STRING']) { + throw new Error('MYSQL_CONNECTION_STRING is not defined'); +} + +describe('mysql2', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['MYSQL_CONNECTION_STRING'], + ); + + await db.$client.execute(`SELECT 1`); + + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['MYSQL_CONNECTION_STRING'], + { + schema, + mode: 'default', + }, + ); + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['MYSQL_CONNECTION_STRING'], + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + uri: process.env['MYSQL_CONNECTION_STRING'], + }, + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = createPool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle(client, config)', async () => { + const client = createPool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createPool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const db = drizzle({ + client, + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).not.toStrictEqual(undefined); + }); +}); + +describe('mysql2:connection', async (it) => { + it('drizzle(client)', async () => { + const client = createConnection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client.getConnection).toStrictEqual(undefined); + }); + + it('drizzle(client, config)', async () => { + const client = createConnection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createConnection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const db = drizzle({ + client, + schema, + mode: 'default', + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client.getConnection).toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/neon-http.test.mjs b/integration-tests/js-tests/driver-init/module/neon-http.test.mjs new file mode 100644 index 000000000..b9c07f4d9 --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/neon-http.test.mjs @@ -0,0 +1,92 @@ +import 'dotenv/config'; +import { neon as pg } from '@neondatabase/serverless'; +import { drizzle } from 'drizzle-orm/neon-http'; +import { describe, expect } from 'vitest'; +import { pg as schema } from './schema.mjs'; + +if (!process.env['NEON_CONNECTION_STRING']) { + throw new Error('NEON_CONNECTION_STRING is not defined'); +} + +describe('neon-http', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['NEON_CONNECTION_STRING'], + ); + + await db.$client('SELECT 1;'); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['NEON_CONNECTION_STRING'], + { + schema, + }, + ); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['NEON_CONNECTION_STRING'], + schema, + }); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + connectionString: process.env['NEON_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = pg( + process.env['NEON_CONNECTION_STRING'], + ); + const db = drizzle(client); + + await db.$client('SELECT 1;'); + }); + + it('drizzle(client, config)', async () => { + const client = pg( + process.env['NEON_CONNECTION_STRING'], + ); + const db = drizzle(client, { + schema, + }); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = pg( + process.env['NEON_CONNECTION_STRING'], + ); + const db = drizzle({ + client, + schema, + }); + + await db.$client('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/neon-ws.test.mjs b/integration-tests/js-tests/driver-init/module/neon-ws.test.mjs new file mode 100644 index 000000000..5a045b76a --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/neon-ws.test.mjs @@ -0,0 +1,211 @@ +import 'dotenv/config'; +import { Client, neonConfig, Pool } from '@neondatabase/serverless'; +import { drizzle } from 'drizzle-orm/neon-serverless'; +import { describe, expect } from 'vitest'; +import ws from 'ws'; +import { pg as schema } from './schema.mjs'; + +neonConfig.webSocketConstructor = ws; + +if (!process.env['NEON_CONNECTION_STRING']) { + throw new Error('NEON_CONNECTION_STRING is not defined'); +} + +describe('neon-ws', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['NEON_CONNECTION_STRING'], + ); + + await db.$client.query('SELECT 1;'); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['NEON_CONNECTION_STRING'], + { + schema, + }, + ); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['NEON_CONNECTION_STRING'], + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + connectionString: process.env['NEON_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(client)', async () => { + const client = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const client = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Pool); + }); +}); + +describe('neon-ws:Client', async (it) => { + it('drizzle(client)', async () => { + const client = new Client({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + + await client.connect(); + + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const client = new Client({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Client({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); +}); + +describe('neon-ws:PoolClient', async (it) => { + it('drizzle(client)', async () => { + const pool = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + client.release(); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const pool = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + client.release(); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); + + it('drizzle({client, ...config})', async () => { + const pool = new Pool({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle({ + client, + schema, + }); + + await db.$client.query('SELECT 1;'); + + client.release(); + + expect(db.query.User).not.toStrictEqual(undefined); + expect(db.$client).toBeInstanceOf(Client); + expect(db.$client).not.toBeInstanceOf(Pool); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/node-pg.test.mjs b/integration-tests/js-tests/driver-init/module/node-pg.test.mjs new file mode 100644 index 000000000..75dd391f1 --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/node-pg.test.mjs @@ -0,0 +1,201 @@ +import 'dotenv/config'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import pg from 'pg'; +import { describe, expect } from 'vitest'; +import { pg as schema } from './schema.mjs'; + +const Pool = pg.Pool; +const Client = pg.Client; + +if (!process.env['PG_CONNECTION_STRING']) { + throw new Error('PG_CONNECTION_STRING is not defined'); +} + +describe('node-pg', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle(process.env['PG_CONNECTION_STRING']); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle(process.env['PG_CONNECTION_STRING'], { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['PG_CONNECTION_STRING'], + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + connectionString: process.env['PG_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const client = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('node-pg:Client', async (it) => { + it('drizzle(client)', async () => { + const client = new Client({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const client = new Client({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Client({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('node-pg:PoolClient', async (it) => { + it('drizzle(client)', async () => { + const pool = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const client = await pool.connect(); + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const pool = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const client = await pool.connect(); + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const pool = new Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + const client = await pool.connect(); + const db = drizzle({ + client, + schema, + }); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/pglite.test.mjs b/integration-tests/js-tests/driver-init/module/pglite.test.mjs new file mode 100644 index 000000000..bb67f383b --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/pglite.test.mjs @@ -0,0 +1,88 @@ +import 'dotenv/config'; +import { PGlite as Database } from '@electric-sql/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; +import { describe, expect } from 'vitest'; +import { pg as schema } from './schema.mjs'; + +describe('pglite', async (it) => { + it('drizzle()', async () => { + const db = drizzle(); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + }); + + it('drizzle(string)', async () => { + const db = drizzle('memory://'); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle('memory://', { + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: {}, ...config})', async () => { + const db = drizzle({ + connection: {}, + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({...config})', async () => { + const db = drizzle({ + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = new Database('memory://'); + const db = drizzle(client); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + }); + + it('drizzle(client, config)', async () => { + const client = new Database('memory://'); + const db = drizzle(client, { + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Database('memory://'); + const db = drizzle({ + client, + schema, + }); + + await db.$client.exec('SELECT 1;'); + await db.$client.close(); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/planetscale.test.mjs b/integration-tests/js-tests/driver-init/module/planetscale.test.mjs new file mode 100644 index 000000000..0a68b7e9f --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/planetscale.test.mjs @@ -0,0 +1,101 @@ +import 'dotenv/config'; +import { Client } from '@planetscale/database'; +import { drizzle } from 'drizzle-orm/planetscale-serverless'; +import { describe, expect } from 'vitest'; +import { mysql as schema } from './schema.mjs'; + +if (!process.env['PLANETSCALE_CONNECTION_STRING']) { + throw new Error('PLANETSCALE_CONNECTION_STRING is not defined'); +} + +describe('planetscale', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['PLANETSCALE_CONNECTION_STRING'], + ); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['PLANETSCALE_CONNECTION_STRING'], + { + schema, + }, + ); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['PLANETSCALE_CONNECTION_STRING'], + schema, + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = new Client({ + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const client = new Client({ + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = new Client({ + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.execute('SELECT 1;'); + + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/postgres-js.test.mjs b/integration-tests/js-tests/driver-init/module/postgres-js.test.mjs new file mode 100644 index 000000000..40925aceb --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/postgres-js.test.mjs @@ -0,0 +1,81 @@ +import 'dotenv/config'; +import { drizzle } from 'drizzle-orm/postgres-js'; +import pg from 'postgres'; +import { describe, expect } from 'vitest'; +import { pg as schema } from './schema.mjs'; + +if (!process.env['PG_CONNECTION_STRING']) { + throw new Error('PG_CONNECTION_STRING is not defined'); +} + +describe('postgres-js', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle(process.env['PG_CONNECTION_STRING']); + + await db.$client.unsafe('SELECT 1;'); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle(process.env['PG_CONNECTION_STRING'], { + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['PG_CONNECTION_STRING'], + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + url: process.env['PG_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = pg(process.env['PG_CONNECTION_STRING']); + const db = drizzle(client); + + await db.$client.unsafe('SELECT 1;'); + }); + + it('drizzle(client, config)', async () => { + const client = pg(process.env['PG_CONNECTION_STRING']); + const db = drizzle(client, { + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = pg(process.env['PG_CONNECTION_STRING']); + const db = drizzle({ + client, + schema, + }); + + await db.$client.unsafe('SELECT 1;'); + + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/schema.mjs b/integration-tests/js-tests/driver-init/module/schema.mjs new file mode 100644 index 000000000..9c0d4d689 --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/schema.mjs @@ -0,0 +1,21 @@ +import { int as mysqlInt, mysqlTable } from 'drizzle-orm/mysql-core'; +import { integer as pgInt, pgTable } from 'drizzle-orm/pg-core'; +import { integer as sqliteInt, sqliteTable } from 'drizzle-orm/sqlite-core'; + +export const sqlite = { + User: sqliteTable('test', { + id: sqliteInt('id').primaryKey().notNull(), + }), +}; + +export const pg = { + User: pgTable('test', { + id: pgInt('id').primaryKey().notNull(), + }), +}; + +export const mysql = { + User: mysqlTable('test', { + id: mysqlInt('id').primaryKey().notNull(), + }), +}; diff --git a/integration-tests/js-tests/driver-init/module/tidb.test.mjs b/integration-tests/js-tests/driver-init/module/tidb.test.mjs new file mode 100644 index 000000000..0cd62f00a --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/tidb.test.mjs @@ -0,0 +1,88 @@ +import 'dotenv/config'; +import { connect } from '@tidbcloud/serverless'; +import { drizzle } from 'drizzle-orm/tidb-serverless'; +import { describe, expect } from 'vitest'; +import { mysql as schema } from './schema.mjs'; + +if (!process.env['TIDB_CONNECTION_STRING']) { + throw new Error('TIDB_CONNECTION_STRING is not defined'); +} + +describe('tidb', async (it) => { + it('drizzle(string)', async () => { + const db = drizzle( + process.env['TIDB_CONNECTION_STRING'], + ); + + await db.$client.execute(`SELECT 1`); + }); + + it('drizzle(string, config)', async () => { + const db = drizzle( + process.env['TIDB_CONNECTION_STRING'], + { + schema, + }, + ); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: string, ...config})', async () => { + const db = drizzle({ + connection: process.env['TIDB_CONNECTION_STRING'], + schema, + }); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({connection: params, ...config})', async () => { + const db = drizzle({ + connection: { + url: process.env['TIDB_CONNECTION_STRING'], + }, + schema, + }); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle(client)', async () => { + const client = connect({ + url: process.env['TIDB_CONNECTION_STRING'], + }); + + const db = drizzle(client); + + await db.$client.execute('SELECT 1;'); + }); + + it('drizzle(client, config)', async () => { + const client = connect({ + url: process.env['TIDB_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = connect({ + url: process.env['TIDB_CONNECTION_STRING'], + }); + const db = drizzle({ + client, + schema, + }); + + await db.$client.execute('SELECT 1;'); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/js-tests/driver-init/module/vercel.test.mjs b/integration-tests/js-tests/driver-init/module/vercel.test.mjs new file mode 100644 index 000000000..0ef7c460e --- /dev/null +++ b/integration-tests/js-tests/driver-init/module/vercel.test.mjs @@ -0,0 +1,229 @@ +import 'dotenv/config'; +import { createClient, createPool, sql, VercelClient, VercelPool } from '@vercel/postgres'; +import { drizzle } from 'drizzle-orm/vercel-postgres'; +import { describe, expect } from 'vitest'; +import { pg as schema } from './schema.mjs'; + +const Pool = VercelPool; +const Client = VercelClient; + +if (!process.env['VERCEL_CONNECTION_STRING']) { + throw new Error('VERCEL_CONNECTION_STRING is not defined'); +} + +// Used for non-pooled connection +if (!process.env['NEON_CONNECTION_STRING']) { + throw new Error('NEON_CONNECTION_STRING is not defined'); +} + +process.env['POSTGRES_URL'] = process.env['VERCEL_CONNECTION_STRING']; + +describe('vercel:sql', async (it) => { + it('drizzle()', async () => { + const db = drizzle(); + + await sql.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + }); + + it('drizzle(client)', async () => { + const db = drizzle(sql); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + }); + + it('drizzle(client, config)', async () => { + const db = drizzle(sql, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const db = drizzle({ + client: sql, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({...config})', async () => { + const db = drizzle({ + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).toBeTypeOf('function'); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('vercel:Pool', async (it) => { + it('drizzle(client)', async () => { + const client = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).toBeInstanceOf(Pool); + }); + + it('drizzle(client, config)', async () => { + const client = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const db = drizzle({ + client: client, + schema, + }); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).toBeInstanceOf(Pool); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('vercel:Client', async (it) => { + it('drizzle(client)', async () => { + const client = createClient({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const client = createClient({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle(client, { + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const client = createClient({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + const db = drizzle({ + client: client, + schema, + }); + + await client.connect(); + + await db.$client.query('SELECT 1;'); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); + +describe('vercel:PoolClient', async (it) => { + it('drizzle(client)', async () => { + const pool = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle(client); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + }); + + it('drizzle(client, config)', async () => { + const pool = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle(client, { + schema, + }); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); + + it('drizzle({client, ...config})', async () => { + const pool = createPool({ + connectionString: process.env['VERCEL_CONNECTION_STRING'], + }); + const client = await pool.connect(); + + const db = drizzle({ + client: client, + schema, + }); + + await db.$client.query('SELECT 1;'); + client.release(); + + expect(db.$client).not.toBeTypeOf('function'); + expect(db.$client).not.toBeInstanceOf(Pool); + expect(db.$client).toBeInstanceOf(Client); + expect(db.query.User).not.toStrictEqual(undefined); + }); +}); diff --git a/integration-tests/package.json b/integration-tests/package.json index 20c2d1fc3..598f87adb 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -8,14 +8,16 @@ "test": "pnpm test:vitest", "test:vitest": "vitest run", "test:esm": "node tests/imports.test.mjs && node tests/imports.test.cjs", - "test:data-api": "sst shell vitest run tests/awsdatapi.test.ts" + "test:data-api": "sst shell vitest run tests/pg/awsdatapi.test.ts" }, "keywords": [], "author": "Drizzle Team", "license": "Apache-2.0", "private": true, "devDependencies": { - "@neondatabase/serverless": "0.9.0", + "@cloudflare/workers-types": "^4.20241004.0", + "@libsql/client": "^0.10.0", + "@neondatabase/serverless": "0.10.0", "@originjs/vite-plugin-commonjs": "^1.0.3", "@paralleldrive/cuid2": "^2.2.2", "@types/async-retry": "^1.4.8", @@ -27,6 +29,7 @@ "@types/pg": "^8.10.1", "@types/sql.js": "^1.4.4", "@types/uuid": "^9.0.1", + "@types/ws": "^8.5.10", "@vitest/ui": "^1.6.0", "ava": "^5.3.0", "axios": "^1.4.0", @@ -40,10 +43,10 @@ "dependencies": { "@aws-sdk/client-rds-data": "^3.549.0", "@aws-sdk/credential-providers": "^3.549.0", - "@electric-sql/pglite": "^0.1.1", - "@libsql/client": "^0.5.6", - "@miniflare/d1": "^2.14.2", - "@miniflare/shared": "^2.14.2", + "@electric-sql/pglite": "0.2.12", + "@libsql/client": "^0.10.0", + "@miniflare/d1": "^2.14.4", + "@miniflare/shared": "^2.14.4", "@planetscale/database": "^1.16.0", "@prisma/client": "5.14.0", "@tidbcloud/serverless": "^0.1.1", @@ -58,6 +61,7 @@ "drizzle-typebox": "workspace:../drizzle-typebox/dist", "drizzle-valibot": "workspace:../drizzle-valibot/dist", "drizzle-zod": "workspace:../drizzle-zod/dist", + "drizzle-seed": "workspace:../drizzle-seed/dist", "express": "^4.18.2", "get-port": "^7.0.0", "mysql2": "^3.3.3", @@ -70,7 +74,8 @@ "sst": "^3.0.4", "uuid": "^9.0.0", "uvu": "^0.5.6", - "vitest": "^1.6.0", + "vitest": "^2.1.2", + "ws": "^8.16.0", "zod": "^3.20.2" } } diff --git a/integration-tests/tests/bun/sqlite-nw.test.ts b/integration-tests/tests/bun/sqlite-nw.test.ts index f6c11a698..d61d1ab4e 100644 --- a/integration-tests/tests/bun/sqlite-nw.test.ts +++ b/integration-tests/tests/bun/sqlite-nw.test.ts @@ -1,3 +1,4 @@ +/// import { Database } from 'bun:sqlite'; import { DefaultLogger, sql } from 'drizzle-orm'; import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite'; diff --git a/integration-tests/tests/bun/sqlite.test.ts b/integration-tests/tests/bun/sqlite.test.ts index faa3f8eb1..c6ce9d4d1 100644 --- a/integration-tests/tests/bun/sqlite.test.ts +++ b/integration-tests/tests/bun/sqlite.test.ts @@ -1,40 +1,34 @@ import { Database } from 'bun:sqlite'; -import { DefaultLogger, sql } from 'drizzle-orm'; +import { beforeAll, beforeEach, expect, test } from 'bun:test'; +import { sql } from 'drizzle-orm'; import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite'; import { drizzle } from 'drizzle-orm/bun-sqlite'; import { blob, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; -import { suite } from 'uvu'; -import * as assert from 'uvu/assert'; const usersTable = sqliteTable('users', { id: integer('id').primaryKey(), name: text('name').notNull(), verified: integer('verified').notNull().default(0), json: blob('json', { mode: 'json' }).$type(), - createdAt: integer('created_at', { mode: 'timestamp' }).notNull().defaultNow(), + bigInt: blob('big_int', { mode: 'bigint' }), + createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull().default(sql`strftime('%s', 'now')`), }); -interface Context { - db: BunSQLiteDatabase; -} +let db: BunSQLiteDatabase; -const test = suite('sqlite-bun'); - -test.before((ctx) => { +beforeAll(async () => { try { const dbPath = process.env['SQLITE_DB_PATH'] ?? ':memory:'; const client = new Database(dbPath); - ctx.db = drizzle(client, { logger: new DefaultLogger() }); + db = drizzle(client); } catch (e) { console.error(e); } }); -test.before.each((ctx) => { +beforeEach(async () => { try { - const { db } = ctx; - db.run(sql`drop table if exists ${usersTable}`); db.run(sql` create table ${usersTable} ( @@ -42,7 +36,8 @@ test.before.each((ctx) => { name text not null, verified integer not null default 0, json blob, - created_at text not null default (strftime('%s', 'now')) + big_int blob, + created_at integer not null default (strftime('%s', 'now')) ) `); } catch (e) { @@ -50,34 +45,30 @@ test.before.each((ctx) => { } }); -test.skip('select large integer', async (ctx) => { +test.skip('select large integer', () => { const a = 1667476703000; - const res = await ctx.db.all<{ a: number }>(sql`select ${sql.raw(String(a))} as a`); + const res = db.all<{ a: number }>(sql`select ${sql.raw(String(a))} as a`); const result = res[0]!; - assert.equal(result.a, a); + expect(result.a).toEqual(a); }); -test('select all fields', (ctx) => { - const { db } = ctx; - +test('select all fields', () => { const now = Date.now(); db.insert(usersTable).values({ name: 'John' }).run(); const result = db.select().from(usersTable).all()[0]!; - assert.ok(result.createdAt instanceof Date, 'createdAt is a Date'); // eslint-disable-line no-instanceof/no-instanceof - assert.ok( - Math.abs(result.createdAt.getTime() - now) < 100, - `${result.createdAt.getTime()} is within 100ms of ${now}`, - ); - assert.equal( - result, - { id: 1, name: 'John', verified: 0, json: null, createdAt: result.createdAt }, - 'result is correct', - ); + expect(result.createdAt).toBeInstanceOf(Date); + expect(Math.abs(result.createdAt.getTime() - now)).toBeLessThan(100); + expect(result).toEqual({ id: 1, name: 'John', verified: 0, json: null, createdAt: result.createdAt, bigInt: null }); }); -test.run(); +test('select bigint', () => { + db.insert(usersTable).values({ name: 'John', bigInt: BigInt(100) }).run(); + const result = db.select({ bigInt: usersTable.bigInt }).from(usersTable).all()[0]!; + + expect(result).toEqual({ bigInt: BigInt(100) }); +}); // test.serial('select partial', (t) => { // const { db } = t.context; diff --git a/integration-tests/tests/common.ts b/integration-tests/tests/common.ts index 55daa43ce..0a4a61e94 100644 --- a/integration-tests/tests/common.ts +++ b/integration-tests/tests/common.ts @@ -2,7 +2,7 @@ import { beforeEach } from 'vitest'; export function skipTests(names: string[]) { beforeEach((ctx) => { - if (ctx.task.suite.name === 'common' && names.includes(ctx.task.name)) { + if (ctx.task.suite?.name === 'common' && names.includes(ctx.task.name)) { ctx.skip(); } }); diff --git a/integration-tests/tests/imports/index.test.ts b/integration-tests/tests/imports/index.test.ts index dee96e84f..7a44942fa 100644 --- a/integration-tests/tests/imports/index.test.ts +++ b/integration-tests/tests/imports/index.test.ts @@ -18,7 +18,10 @@ it('dynamic imports check for CommonJS', async () => { const promises: ProcessPromise[] = []; for (const [i, key] of Object.keys(pj['exports']).entries()) { const o1 = path.join('drizzle-orm', key); - if (o1.startsWith('drizzle-orm/pglite') || o1.startsWith('drizzle-orm/expo-sqlite')) { + if ( + o1.startsWith('drizzle-orm/bun-sqlite') || o1.startsWith('drizzle-orm/pglite') + || o1.startsWith('drizzle-orm/expo-sqlite') || o1.startsWith('drizzle-orm/libsql/wasm') + ) { continue; } fs.writeFileSync(`${IMPORTS_FOLDER}/imports_${i}.cjs`, 'requ'); @@ -43,7 +46,7 @@ it('dynamic imports check for ESM', async () => { const promises: ProcessPromise[] = []; for (const [i, key] of Object.keys(pj['exports']).entries()) { const o1 = path.join('drizzle-orm', key); - if (o1.startsWith('drizzle-orm/expo-sqlite')) { + if (o1.startsWith('drizzle-orm/bun-sqlite') || o1.startsWith('drizzle-orm/expo-sqlite')) { continue; } fs.writeFileSync(`${IMPORTS_FOLDER}/imports_${i}.mjs`, 'imp'); diff --git a/integration-tests/tests/mysql/mysql-common.ts b/integration-tests/tests/mysql/mysql-common.ts index c96ae9319..98e425f9c 100644 --- a/integration-tests/tests/mysql/mysql-common.ts +++ b/integration-tests/tests/mysql/mysql-common.ts @@ -19,7 +19,6 @@ import { min, Name, notInArray, - placeholder, sql, sum, sumDistinct, @@ -38,6 +37,7 @@ import { foreignKey, getTableConfig, getViewConfig, + index, int, intersect, intersectAll, @@ -1155,12 +1155,36 @@ export function tests(driver?: string) { expect(result).toEqual([{ id: 1, name: 'John' }]); }); + test('insert: placeholders on columns with encoder', async (ctx) => { + const { db } = ctx.mysql; + + const date = new Date('2024-08-07T15:30:00Z'); + + const statement = db.insert(usersTable).values({ + name: 'John', + createdAt: sql.placeholder('createdAt'), + }).prepare(); + + await statement.execute({ createdAt: date }); + + const result = await db + .select({ + id: usersTable.id, + createdAt: usersTable.createdAt, + }) + .from(usersTable); + + expect(result).toEqual([ + { id: 1, createdAt: date }, + ]); + }); + test('prepared statement reuse', async (ctx) => { const { db } = ctx.mysql; const stmt = db.insert(usersTable).values({ verified: true, - name: placeholder('name'), + name: sql.placeholder('name'), }).prepare(); for (let i = 0; i < 10; i++) { @@ -1195,13 +1219,75 @@ export function tests(driver?: string) { id: usersTable.id, name: usersTable.name, }).from(usersTable) - .where(eq(usersTable.id, placeholder('id'))) + .where(eq(usersTable.id, sql.placeholder('id'))) .prepare(); const result = await stmt.execute({ id: 1 }); expect(result).toEqual([{ id: 1, name: 'John' }]); }); + test('prepared statement with placeholder in .limit', async (ctx) => { + const { db } = ctx.mysql; + + await db.insert(usersTable).values({ name: 'John' }); + const stmt = db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .where(eq(usersTable.id, sql.placeholder('id'))) + .limit(sql.placeholder('limit')) + .prepare(); + + const result = await stmt.execute({ id: 1, limit: 1 }); + + expect(result).toEqual([{ id: 1, name: 'John' }]); + expect(result).toHaveLength(1); + }); + + test('prepared statement with placeholder in .offset', async (ctx) => { + const { db } = ctx.mysql; + + await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]); + const stmt = db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .prepare(); + + const result = await stmt.execute({ limit: 1, offset: 1 }); + + expect(result).toEqual([{ id: 2, name: 'John1' }]); + }); + + test('prepared statement built using $dynamic', async (ctx) => { + const { db } = ctx.mysql; + + function withLimitOffset(qb: any) { + return qb.limit(sql.placeholder('limit')).offset(sql.placeholder('offset')); + } + + await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]); + const stmt = db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .$dynamic(); + withLimitOffset(stmt).prepare('stmt_limit'); + + const result = await stmt.execute({ limit: 1, offset: 1 }); + + expect(result).toEqual([{ id: 2, name: 'John1' }]); + expect(result).toHaveLength(1); + }); + test('migrator', async (ctx) => { const { db } = ctx.mysql; @@ -2059,6 +2145,45 @@ export function tests(driver?: string) { await db.execute(sql`drop table ${products}`); }); + test('transaction with options (set isolationLevel)', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users_transactions', { + id: serial('id').primaryKey(), + balance: int('balance').notNull(), + }); + const products = mysqlTable('products_transactions', { + id: serial('id').primaryKey(), + price: int('price').notNull(), + stock: int('stock').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`drop table if exists ${products}`); + + await db.execute(sql`create table users_transactions (id serial not null primary key, balance int not null)`); + await db.execute( + sql`create table products_transactions (id serial not null primary key, price int not null, stock int not null)`, + ); + + const [{ insertId: userId }] = await db.insert(users).values({ balance: 100 }); + const user = await db.select().from(users).where(eq(users.id, userId)).then((rows) => rows[0]!); + const [{ insertId: productId }] = await db.insert(products).values({ price: 10, stock: 10 }); + const product = await db.select().from(products).where(eq(products.id, productId)).then((rows) => rows[0]!); + + await db.transaction(async (tx) => { + await tx.update(users).set({ balance: user.balance - product.price }).where(eq(users.id, user.id)); + await tx.update(products).set({ stock: product.stock - 1 }).where(eq(products.id, product.id)); + }, { isolationLevel: 'serializable' }); + + const result = await db.select().from(users); + + expect(result).toEqual([{ id: 1, balance: 90 }]); + + await db.execute(sql`drop table ${users}`); + await db.execute(sql`drop table ${products}`); + }); + test('transaction rollback', async (ctx) => { const { db } = ctx.mysql; @@ -3292,7 +3417,7 @@ export function tests(driver?: string) { expect(query).toEqual({ sql: - `select \`id\`, \`name\` from \`mySchema\`.\`userstest\` group by \`userstest\`.\`id\`, \`userstest\`.\`name\``, + `select \`id\`, \`name\` from \`mySchema\`.\`userstest\` group by \`mySchema\`.\`userstest\`.\`id\`, \`mySchema\`.\`userstest\`.\`name\``, params: [], }); }); @@ -3382,6 +3507,23 @@ export function tests(driver?: string) { expect(result).toStrictEqual([{ id: 1 }]); }); + test('insert $returningId: serial as id, not first column', async (ctx) => { + const { db } = ctx.mysql; + + const usersTableDefNotFirstColumn = mysqlTable('users2', { + name: text('name').notNull(), + id: serial('id').primaryKey(), + }); + + const result = await db.insert(usersTableDefNotFirstColumn).values({ name: 'John' }).$returningId(); + + expectTypeOf(result).toEqualTypeOf<{ + id: number; + }[]>(); + + expect(result).toStrictEqual([{ id: 1 }]); + }); + test('insert $returningId: serial as id, batch insert', async (ctx) => { const { db } = ctx.mysql; @@ -3514,29 +3656,1128 @@ export function tests(driver?: string) { await db.execute(sql`drop view ${newYorkers1}`); }); + + test('$count separate', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(4); + }); + + test('$count embedded', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + }); + + test('$count separate reuse', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.$count(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual(4); + expect(count2).toStrictEqual(5); + expect(count3).toStrictEqual(6); + }); + + test('$count embedded reuse', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + expect(count2).toStrictEqual([ + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + ]); + expect(count3).toStrictEqual([ + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + ]); + }); + + test('$count separate with filters', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable, gt(countTestTable.id, 1)); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(3); + }); + + test('$count embedded with filters', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable, gt(countTestTable.id, 1)), + }).from(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 3 }, + { count: 3 }, + { count: 3 }, + { count: 3 }, + ]); + }); + + test('limit 0', async (ctx) => { + const { db } = ctx.mysql; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db + .select() + .from(usersTable) + .limit(0); + + expect(users).toEqual([]); + }); + + test('limit -1', async (ctx) => { + const { db } = ctx.mysql; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db + .select() + .from(usersTable) + .limit(-1); + + expect(users.length).toBeGreaterThan(0); + }); + + test('define constraints as array', async (ctx) => { + const { db } = ctx.mysql; + + const table = mysqlTable('name', { + id: int(), + }, (t) => [ + index('name').on(t.id), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + + test('define constraints as array inside third param', async (ctx) => { + const { db } = ctx.mysql; + + const table = mysqlTable('name', { + id: int(), + }, (t) => [ + [index('name').on(t.id), primaryKey({ columns: [t.id], name: 'custom' })], + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + + test('update with limit and order by', async (ctx) => { + const { db } = ctx.mysql; + + await db.insert(usersTable).values([ + { name: 'Barry', verified: false }, + { name: 'Alan', verified: false }, + { name: 'Carl', verified: false }, + ]); + + await db.update(usersTable).set({ verified: true }).limit(2).orderBy(asc(usersTable.name)); + + const result = await db.select({ name: usersTable.name, verified: usersTable.verified }).from(usersTable).orderBy( + asc(usersTable.name), + ); + expect(result).toStrictEqual([ + { name: 'Alan', verified: true }, + { name: 'Barry', verified: true }, + { name: 'Carl', verified: false }, + ]); + }); + + test('delete with limit and order by', async (ctx) => { + const { db } = ctx.mysql; + + await db.insert(usersTable).values([ + { name: 'Barry', verified: false }, + { name: 'Alan', verified: false }, + { name: 'Carl', verified: false }, + ]); + + await db.delete(usersTable).where(eq(usersTable.verified, false)).limit(1).orderBy(asc(usersTable.name)); + + const result = await db.select({ name: usersTable.name, verified: usersTable.verified }).from(usersTable).orderBy( + asc(usersTable.name), + ); + expect(result).toStrictEqual([ + { name: 'Barry', verified: false }, + { name: 'Carl', verified: false }, + ]); + }); + + test('Object keys as column names', async (ctx) => { + const { db } = ctx.mysql; + + // Tests the following: + // Column with required config + // Column with optional config without providing a value + // Column with optional config providing a value + // Column without config + const users = mysqlTable('users', { + id: bigint({ mode: 'number' }).autoincrement().primaryKey(), + createdAt: timestamp(), + updatedAt: timestamp({ fsp: 3 }), + admin: boolean(), + }); + + await db.execute(sql`drop table if exists users`); + await db.execute( + sql` + create table users ( + \`id\` bigint auto_increment primary key, + \`createdAt\` timestamp, + \`updatedAt\` timestamp(3), + \`admin\` boolean + ) + `, + ); + + await db.insert(users).values([ + { createdAt: sql`now() - interval 30 day`, updatedAt: sql`now() - interval 1 day`, admin: true }, + { createdAt: sql`now() - interval 1 day`, updatedAt: sql`now() - interval 30 day`, admin: true }, + { createdAt: sql`now() - interval 1 day`, updatedAt: sql`now() - interval 1 day`, admin: false }, + ]); + const result = await db + .select({ id: users.id, admin: users.admin }) + .from(users) + .where( + and( + gt(users.createdAt, sql`now() - interval 7 day`), + gt(users.updatedAt, sql`now() - interval 7 day`), + ), + ); + + expect(result).toEqual([ + { id: 3, admin: false }, + ]); + + await db.execute(sql`drop table users`); + }); + }); + + test('insert into ... select', async (ctx) => { + const { db } = ctx.mysql; + + const notifications = mysqlTable('notifications', { + id: serial('id').primaryKey(), + sentAt: timestamp('sent_at').notNull().defaultNow(), + message: text('message').notNull(), + }); + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + const userNotications = mysqlTable('user_notifications', { + userId: int('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + notificationId: int('notification_id').notNull().references(() => notifications.id, { onDelete: 'cascade' }), + }, (t) => ({ + pk: primaryKey({ columns: [t.userId, t.notificationId] }), + })); + + await db.execute(sql`drop table if exists ${notifications}`); + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`drop table if exists ${userNotications}`); + await db.execute(sql` + create table ${notifications} ( + \`id\` serial primary key, + \`sent_at\` timestamp not null default now(), + \`message\` text not null + ) + `); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` text not null + ) + `); + await db.execute(sql` + create table ${userNotications} ( + \`user_id\` int references users(id) on delete cascade, + \`notification_id\` int references notifications(id) on delete cascade, + primary key (user_id, notification_id) + ) + `); + + await db + .insert(notifications) + .values({ message: 'You are one of the 3 lucky winners!' }); + const newNotification = await db + .select({ id: notifications.id }) + .from(notifications) + .then((result) => result[0]); + + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + await db + .insert(userNotications) + .select( + db + .select({ + userId: users.id, + notificationId: sql`(${newNotification!.id})`.as('notification_id'), + }) + .from(users) + .where(inArray(users.name, ['Alice', 'Charlie', 'Eve'])) + .orderBy(asc(users.id)), + ); + const sentNotifications = await db.select().from(userNotications); + + expect(sentNotifications).toStrictEqual([ + { userId: 1, notificationId: newNotification!.id }, + { userId: 3, notificationId: newNotification!.id }, + { userId: 5, notificationId: newNotification!.id }, + ]); + }); + + test('insert into ... select with keys in different order', async (ctx) => { + const { db } = ctx.mysql; + + const users1 = mysqlTable('users1', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + const users2 = mysqlTable('users2', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users1}`); + await db.execute(sql`drop table if exists ${users2}`); + await db.execute(sql` + create table ${users1} ( + \`id\` serial primary key, + \`name\` text not null + ) + `); + await db.execute(sql` + create table ${users2} ( + \`id\` serial primary key, + \`name\` text not null + ) + `); + + expect( + () => + db + .insert(users1) + .select( + db + .select({ + name: users2.name, + id: users2.id, + }) + .from(users2), + ), + ).toThrowError(); + }); + + test('MySqlTable :: select with `use index` hint', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }, () => [usersTableNameIndex]); + const usersTableNameIndex = index('users_name_index').on(users.name); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql`create index users_name_index ON users(name)`); + + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + const result = await db.select() + .from(users, { + useIndex: [usersTableNameIndex], + }) + .where(eq(users.name, 'David')); + + expect(result).toHaveLength(1); + expect(result).toEqual([{ id: 4, name: 'David' }]); + }); + + test('MySqlTable :: select with `use index` hint on 1 index', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }, () => [usersTableNameIndex]); + const usersTableNameIndex = index('users_name_index').on(users.name); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql`create index users_name_index ON users(name)`); + + const query = db.select() + .from(users, { + useIndex: usersTableNameIndex, + }) + .where(eq(users.name, 'David')) + .toSQL(); + + expect(query.sql).to.include('USE INDEX (users_name_index)'); + }); + + test('MySqlTable :: select with `use index` hint on multiple indexes', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + age: int('age').notNull(), + }, () => [usersTableNameIndex, usersTableAgeIndex]); + const usersTableNameIndex = index('users_name_index').on(users.name); + const usersTableAgeIndex = index('users_age_index').on(users.age); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null, + \`age\` int not null + ) + `); + await db.execute(sql`create index users_name_index ON users(name)`); + await db.execute(sql`create index users_age_index ON users(age)`); + + const query = db.select() + .from(users, { + useIndex: [usersTableNameIndex, usersTableAgeIndex], + }) + .where(eq(users.name, 'David')) + .toSQL(); + + expect(query.sql).to.include('USE INDEX (users_name_index, users_age_index)'); + }); + + test('MySqlTable :: select with `use index` hint on not existed index', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }, () => [usersTableNameIndex]); + const usersTableNameIndex = index('users_name_index').on(users.name); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql`create index users_name_index ON users(name)`); + + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + await expect((async () => { + return await db.select() + .from(users, { + useIndex: ['some_other_index'], + }) + .where(eq(users.name, 'David')); + })()).rejects.toThrowError(); + }); + + test('MySqlTable :: select with `use index` + `force index` incompatible hints', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + age: int('age').notNull(), + }, () => [usersTableNameIndex, usersTableAgeIndex]); + const usersTableNameIndex = index('users_name_index').on(users.name); + const usersTableAgeIndex = index('users_age_index').on(users.age); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null, + \`age\` int not null + ) + `); + await db.execute(sql`create index users_name_index ON users(name)`); + await db.execute(sql`create index users_age_index ON users(age)`); + + await db.insert(users).values([ + { name: 'Alice', age: 18 }, + { name: 'Bob', age: 19 }, + { name: 'Charlie', age: 20 }, + { name: 'David', age: 21 }, + { name: 'Eve', age: 22 }, + ]); + + await expect((async () => { + return await db.select() + .from(users, { + useIndex: [usersTableNameIndex], + forceIndex: [usersTableAgeIndex], + }) + .where(eq(users.name, 'David')); + })()).rejects.toThrowError(); + }); + + test('MySqlTable :: select with join `use index` hint', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }); + + const posts = mysqlTable('posts', { + id: serial('id').primaryKey(), + text: varchar('text', { length: 100 }).notNull(), + userId: int('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + }, () => [postsTableUserIdIndex]); + const postsTableUserIdIndex = index('posts_user_id_index').on(posts.userId); + + await db.execute(sql`drop table if exists ${posts}`); + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql` + create table ${posts} ( + \`id\` serial primary key, + \`text\` varchar(100) not null, + \`user_id\` int not null references users(id) on delete cascade + ) + `); + await db.execute(sql`create index posts_user_id_index ON posts(user_id)`); + + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + await db.insert(posts).values([ + { text: 'Alice post', userId: 1 }, + { text: 'Bob post', userId: 2 }, + { text: 'Charlie post', userId: 3 }, + { text: 'David post', userId: 4 }, + { text: 'Eve post', userId: 5 }, + ]); + + const result = await db.select({ + userId: users.id, + name: users.name, + postId: posts.id, + text: posts.text, + }) + .from(users) + .leftJoin(posts, eq(users.id, posts.userId), { + useIndex: [postsTableUserIdIndex], + }) + .where(and( + eq(users.name, 'David'), + eq(posts.text, 'David post'), + )); + + expect(result).toHaveLength(1); + expect(result).toEqual([{ userId: 4, name: 'David', postId: 4, text: 'David post' }]); + }); + + test('MySqlTable :: select with join `use index` hint on 1 index', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }); + + const posts = mysqlTable('posts', { + id: serial('id').primaryKey(), + text: varchar('text', { length: 100 }).notNull(), + userId: int('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + }, () => [postsTableUserIdIndex]); + const postsTableUserIdIndex = index('posts_user_id_index').on(posts.userId); + + await db.execute(sql`drop table if exists ${posts}`); + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql` + create table ${posts} ( + \`id\` serial primary key, + \`text\` varchar(100) not null, + \`user_id\` int not null references users(id) on delete cascade + ) + `); + await db.execute(sql`create index posts_user_id_index ON posts(user_id)`); + + const query = db.select({ + userId: users.id, + name: users.name, + postId: posts.id, + text: posts.text, + }) + .from(users) + .leftJoin(posts, eq(users.id, posts.userId), { + useIndex: postsTableUserIdIndex, + }) + .where(and( + eq(users.name, 'David'), + eq(posts.text, 'David post'), + )).toSQL(); + + expect(query.sql).to.include('USE INDEX (posts_user_id_index)'); }); - test('limit 0', async (ctx) => { + test('MySqlTable :: select with join `use index` hint on multiple indexes', async (ctx) => { const { db } = ctx.mysql; - await db.insert(usersTable).values({ name: 'John' }); - const users = await db - .select() - .from(usersTable) - .limit(0); + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }); - expect(users).toEqual([]); + const posts = mysqlTable('posts', { + id: serial('id').primaryKey(), + text: varchar('text', { length: 100 }).notNull(), + userId: int('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + }, () => [postsTableUserIdIndex, postsTableTextIndex]); + const postsTableUserIdIndex = index('posts_user_id_index').on(posts.userId); + const postsTableTextIndex = index('posts_text_index').on(posts.text); + + await db.execute(sql`drop table if exists ${posts}`); + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql` + create table ${posts} ( + \`id\` serial primary key, + \`text\` varchar(100) not null, + \`user_id\` int not null references users(id) on delete cascade + ) + `); + await db.execute(sql`create index posts_user_id_index ON posts(user_id)`); + await db.execute(sql`create index posts_text_index ON posts(text)`); + + const query = db.select({ + userId: users.id, + name: users.name, + postId: posts.id, + text: posts.text, + }) + .from(users) + .leftJoin(posts, eq(users.id, posts.userId), { + useIndex: [postsTableUserIdIndex, postsTableTextIndex], + }) + .where(and( + eq(users.name, 'David'), + eq(posts.text, 'David post'), + )).toSQL(); + + expect(query.sql).to.include('USE INDEX (posts_user_id_index, posts_text_index)'); }); - test('limit -1', async (ctx) => { + test('MySqlTable :: select with join `use index` hint on not existed index', async (ctx) => { const { db } = ctx.mysql; - await db.insert(usersTable).values({ name: 'John' }); - const users = await db - .select() - .from(usersTable) - .limit(-1); + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }); + + const posts = mysqlTable('posts', { + id: serial('id').primaryKey(), + text: varchar('text', { length: 100 }).notNull(), + userId: int('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + }, () => [postsTableUserIdIndex]); + const postsTableUserIdIndex = index('posts_user_id_index').on(posts.userId); + + await db.execute(sql`drop table if exists ${posts}`); + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql` + create table ${posts} ( + \`id\` serial primary key, + \`text\` varchar(100) not null, + \`user_id\` int not null references users(id) on delete cascade + ) + `); + await db.execute(sql`create index posts_user_id_index ON posts(user_id)`); + + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + await db.insert(posts).values([ + { text: 'Alice post', userId: 1 }, + { text: 'Bob post', userId: 2 }, + { text: 'Charlie post', userId: 3 }, + { text: 'David post', userId: 4 }, + { text: 'Eve post', userId: 5 }, + ]); + + await expect((async () => { + return await db.select({ + userId: users.id, + name: users.name, + postId: posts.id, + text: posts.text, + }) + .from(users) + .leftJoin(posts, eq(users.id, posts.userId), { + useIndex: ['some_other_index'], + }) + .where(and( + eq(users.name, 'David'), + eq(posts.text, 'David post'), + )); + })()).rejects.toThrowError(); + }); + + test('MySqlTable :: select with join `use index` + `force index` incompatible hints', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }); + + const posts = mysqlTable('posts', { + id: serial('id').primaryKey(), + text: varchar('text', { length: 100 }).notNull(), + userId: int('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + }, () => [postsTableUserIdIndex, postsTableTextIndex]); + const postsTableUserIdIndex = index('posts_user_id_index').on(posts.userId); + const postsTableTextIndex = index('posts_text_index').on(posts.text); + + await db.execute(sql`drop table if exists ${posts}`); + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql` + create table ${posts} ( + \`id\` serial primary key, + \`text\` varchar(100) not null, + \`user_id\` int not null references users(id) on delete cascade + ) + `); + await db.execute(sql`create index posts_user_id_index ON posts(user_id)`); + await db.execute(sql`create index posts_text_index ON posts(text)`); + + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + await db.insert(posts).values([ + { text: 'Alice post', userId: 1 }, + { text: 'Bob post', userId: 2 }, + { text: 'Charlie post', userId: 3 }, + { text: 'David post', userId: 4 }, + { text: 'Eve post', userId: 5 }, + ]); + + await expect((async () => { + return await db.select({ + userId: users.id, + name: users.name, + postId: posts.id, + text: posts.text, + }) + .from(users) + .leftJoin(posts, eq(users.id, posts.userId), { + useIndex: [postsTableUserIdIndex], + forceIndex: [postsTableTextIndex], + }) + .where(and( + eq(users.name, 'David'), + eq(posts.text, 'David post'), + )); + })()).rejects.toThrowError(); + }); + + test('MySqlTable :: select with Subquery join `use index`', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }); + + const posts = mysqlTable('posts', { + id: serial('id').primaryKey(), + text: varchar('text', { length: 100 }).notNull(), + userId: int('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + }, () => [postsTableUserIdIndex]); + const postsTableUserIdIndex = index('posts_user_id_index').on(posts.userId); + + await db.execute(sql`drop table if exists ${posts}`); + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql` + create table ${posts} ( + \`id\` serial primary key, + \`text\` varchar(100) not null, + \`user_id\` int not null references users(id) on delete cascade + ) + `); + await db.execute(sql`create index posts_user_id_index ON posts(user_id)`); + + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + await db.insert(posts).values([ + { text: 'Alice post', userId: 1 }, + { text: 'Bob post', userId: 2 }, + { text: 'Charlie post', userId: 3 }, + { text: 'David post', userId: 4 }, + { text: 'Eve post', userId: 5 }, + ]); + + const sq = db.select().from(posts, { useIndex: [postsTableUserIdIndex] }).where(eq(posts.userId, 1)).as('sq'); + + const result = await db.select({ + userId: users.id, + name: users.name, + postId: sq.id, + text: sq.text, + }) + .from(users) + .leftJoin(sq, eq(users.id, sq.userId)) + .where(eq(users.name, 'Alice')); + + expect(result).toHaveLength(1); + expect(result).toEqual([{ userId: 1, name: 'Alice', postId: 1, text: 'Alice post' }]); + }); + + test('MySqlTable :: select with Subquery join with `use index` in join', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }); + + const posts = mysqlTable('posts', { + id: serial('id').primaryKey(), + text: varchar('text', { length: 100 }).notNull(), + userId: int('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + }, () => [postsTableUserIdIndex]); + const postsTableUserIdIndex = index('posts_user_id_index').on(posts.userId); + + await db.execute(sql`drop table if exists ${posts}`); + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql` + create table ${posts} ( + \`id\` serial primary key, + \`text\` varchar(100) not null, + \`user_id\` int not null references users(id) on delete cascade + ) + `); + await db.execute(sql`create index posts_user_id_index ON posts(user_id)`); + + const sq = db.select().from(posts).where(eq(posts.userId, 1)).as('sq'); + + const query = db.select({ + userId: users.id, + name: users.name, + postId: sq.id, + text: sq.text, + }) + .from(users) + // @ts-expect-error + .leftJoin(sq, eq(users.id, sq.userId, { useIndex: [postsTableUserIdIndex] })) + .where(eq(users.name, 'Alice')) + .toSQL(); + + expect(query.sql).not.include('USE INDEX'); + }); + + test('View :: select with `use index` hint', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }, () => [usersTableNameIndex]); + + const usersTableNameIndex = index('users_name_index').on(users.name); + + const usersView = mysqlView('users_view').as((qb) => qb.select().from(users)); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql`create index users_name_index ON users(name)`); + await db.execute(sql`create view ${usersView} as select * from ${users}`); + + // @ts-expect-error + const query = db.select().from(usersView, { + useIndex: [usersTableNameIndex], + }).toSQL(); + + expect(query.sql).not.include('USE INDEX'); + + await db.execute(sql`drop view ${usersView}`); + }); + + test('Subquery :: select with `use index` hint', async (ctx) => { + const { db } = ctx.mysql; + + const users = mysqlTable('users', { + id: serial('id').primaryKey(), + name: varchar('name', { length: 100 }).notNull(), + }, () => [usersTableNameIndex]); + const usersTableNameIndex = index('users_name_index').on(users.name); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql` + create table ${users} ( + \`id\` serial primary key, + \`name\` varchar(100) not null + ) + `); + await db.execute(sql`create index users_name_index ON users(name)`); + + const sq = db.select().from(users).as('sq'); + + // @ts-expect-error + const query = db.select().from(sq, { + useIndex: [usersTableNameIndex], + }).toSQL(); - expect(users.length).toBeGreaterThan(0); + expect(query.sql).not.include('USE INDEX'); }); } diff --git a/integration-tests/tests/mysql/mysql-custom.test.ts b/integration-tests/tests/mysql/mysql-custom.test.ts index f1ed8e8e7..3dafc34d1 100644 --- a/integration-tests/tests/mysql/mysql-custom.test.ts +++ b/integration-tests/tests/mysql/mysql-custom.test.ts @@ -1,6 +1,6 @@ import retry from 'async-retry'; import type Docker from 'dockerode'; -import { asc, eq, Name, placeholder, sql } from 'drizzle-orm'; +import { asc, eq, Name, sql } from 'drizzle-orm'; import { alias, binary, @@ -646,7 +646,7 @@ test('prepared statement reuse', async (ctx) => { const stmt = db.insert(usersTable).values({ verified: true, - name: placeholder('name'), + name: sql.placeholder('name'), }).prepare(); for (let i = 0; i < 10; i++) { @@ -681,7 +681,7 @@ test('prepared statement with placeholder in .where', async (ctx) => { id: usersTable.id, name: usersTable.name, }).from(usersTable) - .where(eq(usersTable.id, placeholder('id'))) + .where(eq(usersTable.id, sql.placeholder('id'))) .prepare(); const result = await stmt.execute({ id: 1 }); diff --git a/integration-tests/tests/mysql/mysql-planetscale.test.ts b/integration-tests/tests/mysql/mysql-planetscale.test.ts index 8c7e74543..763b9c8e6 100644 --- a/integration-tests/tests/mysql/mysql-planetscale.test.ts +++ b/integration-tests/tests/mysql/mysql-planetscale.test.ts @@ -59,6 +59,7 @@ skipTests([ // to redefine in this file 'utc config for datetime', 'transaction', + 'transaction with options (set isolationLevel)', 'having', 'select count()', 'insert via db.execute w/ query builder', diff --git a/integration-tests/tests/mysql/mysql-proxy.test.ts b/integration-tests/tests/mysql/mysql-proxy.test.ts index cb8e4b758..1cf8345c4 100644 --- a/integration-tests/tests/mysql/mysql-proxy.test.ts +++ b/integration-tests/tests/mysql/mysql-proxy.test.ts @@ -129,6 +129,7 @@ skipTests([ 'nested transaction', 'transaction rollback', 'transaction', + 'transaction with options (set isolationLevel)', 'migrator', ]); diff --git a/integration-tests/tests/mysql/tidb-serverless.test.ts b/integration-tests/tests/mysql/tidb-serverless.test.ts index 8187882af..9121c31de 100644 --- a/integration-tests/tests/mysql/tidb-serverless.test.ts +++ b/integration-tests/tests/mysql/tidb-serverless.test.ts @@ -66,6 +66,7 @@ skipTests([ 'select iterator w/ prepared statement', 'select iterator', 'transaction', + 'transaction with options (set isolationLevel)', 'Insert all defaults in multiple rows', 'Insert all defaults in 1 row', '$default with empty array', diff --git a/integration-tests/tests/pg/awsdatapi.test.ts b/integration-tests/tests/pg/awsdatapi.test.ts index 22ad8e770..3bb884c0c 100644 --- a/integration-tests/tests/pg/awsdatapi.test.ts +++ b/integration-tests/tests/pg/awsdatapi.test.ts @@ -2,7 +2,7 @@ import 'dotenv/config'; import { RDSDataClient } from '@aws-sdk/client-rds-data'; import * as dotenv from 'dotenv'; -import { asc, eq, inArray, notInArray, sql, TransactionRollbackError } from 'drizzle-orm'; +import { asc, eq, inArray, notInArray, relations, sql, TransactionRollbackError } from 'drizzle-orm'; import type { AwsDataApiPgDatabase } from 'drizzle-orm/aws-data-api/pg'; import { drizzle } from 'drizzle-orm/aws-data-api/pg'; import { migrate } from 'drizzle-orm/aws-data-api/pg/migrator'; @@ -18,6 +18,7 @@ import { text, time, timestamp, + uuid, } from 'drizzle-orm/pg-core'; import { Resource } from 'sst'; import { afterAll, beforeAll, beforeEach, expect, expectTypeOf, test } from 'vitest'; @@ -34,8 +35,13 @@ const usersTable = pgTable('users', { name: text('name').notNull(), verified: boolean('verified').notNull().default(false), jsonb: jsonb('jsonb').$type(), - bestTexts: text('best_texts').array().default(sql`'{}'`).notNull(), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + bestTexts: text('best_texts') + .array() + .default(sql`'{}'`) + .notNull(), + createdAt: timestamp('created_at', { withTimezone: true }) + .notNull() + .defaultNow(), }); const usersMigratorTable = pgTable('users12', { @@ -44,7 +50,51 @@ const usersMigratorTable = pgTable('users12', { email: text('email').notNull(), }); -let db: AwsDataApiPgDatabase; +const todo = pgTable('todo', { + id: uuid('id').primaryKey(), + title: text('title').notNull(), + description: text('description'), +}); + +const todoRelations = relations(todo, (ctx) => ({ + user: ctx.many(todoUser), +})); + +const user = pgTable('user', { + id: uuid('id').primaryKey(), + email: text('email').notNull(), +}); + +const userRelations = relations(user, (ctx) => ({ + todos: ctx.many(todoUser), +})); + +const todoUser = pgTable('todo_user', { + todoId: uuid('todo_id').references(() => todo.id), + userId: uuid('user_id').references(() => user.id), +}); + +const todoToGroupRelations = relations(todoUser, (ctx) => ({ + todo: ctx.one(todo, { + fields: [todoUser.todoId], + references: [todo.id], + }), + user: ctx.one(user, { + fields: [todoUser.userId], + references: [user.id], + }), +})); + +const schema = { + todo, + todoRelations, + user, + userRelations, + todoUser, + todoToGroupRelations, +}; + +let db: AwsDataApiPgDatabase; beforeAll(async () => { const rdsClient = new RDSDataClient(); @@ -57,6 +107,7 @@ beforeAll(async () => { // @ts-ignore resourceArn: Resource.Postgres.clusterArn, logger: ENABLE_LOGGING, + schema, }); }); @@ -75,6 +126,35 @@ beforeEach(async () => { ) `, ); + + await db.execute( + sql` + create table todo ( + id uuid primary key, + title text not null, + description text + ) + `, + ); + + await db.execute( + sql` + create table "user" ( + id uuid primary key, + email text not null + ) + + `, + ); + + await db.execute( + sql` + create table todo_user ( + todo_id uuid references todo(id), + user_id uuid references "user"(id) + ) + `, + ); }); test('select all fields', async () => { @@ -86,56 +166,68 @@ test('select all fields', async () => { expect(result[0]!.createdAt).toBeInstanceOf(Date); // t.assert(Math.abs(result[0]!.createdAt.getTime() - now) < 100); - expect(result).toEqual([{ - bestTexts: [], - id: 1, - name: 'John', - verified: false, - jsonb: null, - createdAt: result[0]!.createdAt, - }]); + expect(result).toEqual([ + { + bestTexts: [], + id: 1, + name: 'John', + verified: false, + jsonb: null, + createdAt: result[0]!.createdAt, + }, + ]); }); test('select sql', async () => { await db.insert(usersTable).values({ name: 'John' }); - const users = await db.select({ - name: sql`upper(${usersTable.name})`, - }).from(usersTable); + const users = await db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable); expect(users).toEqual([{ name: 'JOHN' }]); }); test('select with empty array in inArray', async () => { - await db.insert(usersTable).values([ - { name: 'John' }, - { name: 'Jane' }, - { name: 'Jane' }, - ]); - const users = await db.select({ - name: sql`upper(${usersTable.name})`, - }).from(usersTable).where(inArray(usersTable.id, [])); + await db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + const users = await db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable) + .where(inArray(usersTable.id, [])); expect(users).toEqual([]); }); test('select with empty array in notInArray', async () => { - await db.insert(usersTable).values([ - { name: 'John' }, - { name: 'Jane' }, - { name: 'Jane' }, - ]); - const result = await db.select({ - name: sql`upper(${usersTable.name})`, - }).from(usersTable).where(notInArray(usersTable.id, [])); + await db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + const result = await db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable) + .where(notInArray(usersTable.id, [])); - expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + expect(result).toEqual([ + { name: 'JOHN' }, + { name: 'JANE' }, + { name: 'JANE' }, + ]); }); test('select typed sql', async () => { await db.insert(usersTable).values({ name: 'John' }); - const users = await db.select({ - name: sql`upper(${usersTable.name})`, - }).from(usersTable); + const users = await db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable); expect(users).toEqual([{ name: 'JOHN' }]); }); @@ -147,7 +239,9 @@ test('select distinct', async () => { }); await db.execute(sql`drop table if exists ${usersDistinctTable}`); - await db.execute(sql`create table ${usersDistinctTable} (id integer, name text)`); + await db.execute( + sql`create table ${usersDistinctTable} (id integer, name text)`, + ); await db.insert(usersDistinctTable).values([ { id: 1, name: 'John' }, @@ -155,20 +249,28 @@ test('select distinct', async () => { { id: 2, name: 'John' }, { id: 1, name: 'Jane' }, ]); - const users1 = await db.selectDistinct().from(usersDistinctTable).orderBy( - usersDistinctTable.id, - usersDistinctTable.name, - ); - const users2 = await db.selectDistinctOn([usersDistinctTable.id]).from(usersDistinctTable).orderBy( - usersDistinctTable.id, - ); - const users3 = await db.selectDistinctOn([usersDistinctTable.name], { name: usersDistinctTable.name }).from( - usersDistinctTable, - ).orderBy(usersDistinctTable.name); + const users1 = await db + .selectDistinct() + .from(usersDistinctTable) + .orderBy(usersDistinctTable.id, usersDistinctTable.name); + const users2 = await db + .selectDistinctOn([usersDistinctTable.id]) + .from(usersDistinctTable) + .orderBy(usersDistinctTable.id); + const users3 = await db + .selectDistinctOn([usersDistinctTable.name], { + name: usersDistinctTable.name, + }) + .from(usersDistinctTable) + .orderBy(usersDistinctTable.name); await db.execute(sql`drop table ${usersDistinctTable}`); - expect(users1).toEqual([{ id: 1, name: 'Jane' }, { id: 1, name: 'John' }, { id: 2, name: 'John' }]); + expect(users1).toEqual([ + { id: 1, name: 'Jane' }, + { id: 1, name: 'John' }, + { id: 2, name: 'John' }, + ]); expect(users2.length).toEqual(2); expect(users2[0]?.id).toEqual(1); @@ -180,79 +282,107 @@ test('select distinct', async () => { }); test('insert returning sql', async () => { - const users = await db.insert(usersTable).values({ name: 'John' }).returning({ - name: sql`upper(${usersTable.name})`, - }); + const users = await db + .insert(usersTable) + .values({ name: 'John' }) + .returning({ + name: sql`upper(${usersTable.name})`, + }); expect(users).toEqual([{ name: 'JOHN' }]); }); test('delete returning sql', async () => { await db.insert(usersTable).values({ name: 'John' }); - const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({ - name: sql`upper(${usersTable.name})`, - }); + const users = await db + .delete(usersTable) + .where(eq(usersTable.name, 'John')) + .returning({ + name: sql`upper(${usersTable.name})`, + }); expect(users).toEqual([{ name: 'JOHN' }]); }); test('update returning sql', async () => { await db.insert(usersTable).values({ name: 'John' }); - const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({ - name: sql`upper(${usersTable.name})`, - }); + const users = await db + .update(usersTable) + .set({ name: 'Jane' }) + .where(eq(usersTable.name, 'John')) + .returning({ + name: sql`upper(${usersTable.name})`, + }); expect(users).toEqual([{ name: 'JANE' }]); }); test('update with returning all fields', async () => { await db.insert(usersTable).values({ name: 'John' }); - const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning(); + const users = await db + .update(usersTable) + .set({ name: 'Jane' }) + .where(eq(usersTable.name, 'John')) + .returning(); expect(users[0]!.createdAt).toBeInstanceOf(Date); // t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 100); - expect(users).toEqual([{ - id: 1, - bestTexts: [], - name: 'Jane', - verified: false, - jsonb: null, - createdAt: users[0]!.createdAt, - }]); + expect(users).toEqual([ + { + id: 1, + bestTexts: [], + name: 'Jane', + verified: false, + jsonb: null, + createdAt: users[0]!.createdAt, + }, + ]); }); test('update with returning partial', async () => { await db.insert(usersTable).values({ name: 'John' }); - const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({ - id: usersTable.id, - name: usersTable.name, - }); + const users = await db + .update(usersTable) + .set({ name: 'Jane' }) + .where(eq(usersTable.name, 'John')) + .returning({ + id: usersTable.id, + name: usersTable.name, + }); expect(users).toEqual([{ id: 1, name: 'Jane' }]); }); test('delete with returning all fields', async () => { await db.insert(usersTable).values({ name: 'John' }); - const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning(); + const users = await db + .delete(usersTable) + .where(eq(usersTable.name, 'John')) + .returning(); expect(users[0]!.createdAt).toBeInstanceOf(Date); // t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 100); - expect(users).toEqual([{ - bestTexts: [], - id: 1, - name: 'John', - verified: false, - jsonb: null, - createdAt: users[0]!.createdAt, - }]); + expect(users).toEqual([ + { + bestTexts: [], + id: 1, + name: 'John', + verified: false, + jsonb: null, + createdAt: users[0]!.createdAt, + }, + ]); }); test('delete with returning partial', async () => { await db.insert(usersTable).values({ name: 'John' }); - const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({ - id: usersTable.id, - name: usersTable.name, - }); + const users = await db + .delete(usersTable) + .where(eq(usersTable.name, 'John')) + .returning({ + id: usersTable.id, + name: usersTable.name, + }); expect(users).toEqual([{ id: 1, name: 'John' }]); }); @@ -260,30 +390,48 @@ test('delete with returning partial', async () => { test('insert + select', async () => { await db.insert(usersTable).values({ name: 'John' }); const result = await db.select().from(usersTable); - expect(result).toEqual([{ - bestTexts: [], - id: 1, - name: 'John', - verified: false, - jsonb: null, - createdAt: result[0]!.createdAt, - }]); + expect(result).toEqual([ + { + bestTexts: [], + id: 1, + name: 'John', + verified: false, + jsonb: null, + createdAt: result[0]!.createdAt, + }, + ]); await db.insert(usersTable).values({ name: 'Jane' }); const result2 = await db.select().from(usersTable); expect(result2).toEqual([ - { bestTexts: [], id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt }, - { bestTexts: [], id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt }, + { + bestTexts: [], + id: 1, + name: 'John', + verified: false, + jsonb: null, + createdAt: result2[0]!.createdAt, + }, + { + bestTexts: [], + id: 2, + name: 'Jane', + verified: false, + jsonb: null, + createdAt: result2[1]!.createdAt, + }, ]); }); test('json insert', async () => { await db.insert(usersTable).values({ name: 'John', jsonb: ['foo', 'bar'] }); - const result = await db.select({ - id: usersTable.id, - name: usersTable.name, - jsonb: usersTable.jsonb, - }).from(usersTable); + const result = await db + .select({ + id: usersTable.id, + name: usersTable.name, + jsonb: usersTable.jsonb, + }) + .from(usersTable); expect(result).toEqual([{ id: 1, name: 'John', jsonb: ['foo', 'bar'] }]); }); @@ -292,29 +440,35 @@ test('insert with overridden default values', async () => { await db.insert(usersTable).values({ name: 'John', verified: true }); const result = await db.select().from(usersTable); - expect(result).toEqual([{ - bestTexts: [], - id: 1, - name: 'John', - verified: true, - jsonb: null, - createdAt: result[0]!.createdAt, - }]); + expect(result).toEqual([ + { + bestTexts: [], + id: 1, + name: 'John', + verified: true, + jsonb: null, + createdAt: result[0]!.createdAt, + }, + ]); }); test('insert many', async () => { - await db.insert(usersTable).values([ - { name: 'John' }, - { name: 'Bruce', jsonb: ['foo', 'bar'] }, - { name: 'Jane' }, - { name: 'Austin', verified: true }, - ]); - const result = await db.select({ - id: usersTable.id, - name: usersTable.name, - jsonb: usersTable.jsonb, - verified: usersTable.verified, - }).from(usersTable); + await db + .insert(usersTable) + .values([ + { name: 'John' }, + { name: 'Bruce', jsonb: ['foo', 'bar'] }, + { name: 'Jane' }, + { name: 'Austin', verified: true }, + ]); + const result = await db + .select({ + id: usersTable.id, + name: usersTable.name, + jsonb: usersTable.jsonb, + verified: usersTable.verified, + }) + .from(usersTable); expect(result).toEqual([ { id: 1, name: 'John', jsonb: null, verified: false }, @@ -325,12 +479,14 @@ test('insert many', async () => { }); test('insert many with returning', async () => { - const result = await db.insert(usersTable).values([ - { name: 'John' }, - { name: 'Bruce', jsonb: ['foo', 'bar'] }, - { name: 'Jane' }, - { name: 'Austin', verified: true }, - ]) + const result = await db + .insert(usersTable) + .values([ + { name: 'John' }, + { name: 'Bruce', jsonb: ['foo', 'bar'] }, + { name: 'Jane' }, + { name: 'Austin', verified: true }, + ]) .returning({ id: usersTable.id, name: usersTable.name, @@ -347,45 +503,73 @@ test('insert many with returning', async () => { }); test('select with group by as field', async () => { - await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + await db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); - const result = await db.select({ name: usersTable.name }).from(usersTable) + const result = await db + .select({ name: usersTable.name }) + .from(usersTable) .groupBy(usersTable.name); expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]); }); test('select with group by as sql', async () => { - await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + await db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); - const result = await db.select({ name: usersTable.name }).from(usersTable) + const result = await db + .select({ name: usersTable.name }) + .from(usersTable) .groupBy(sql`${usersTable.name}`); expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]); }); test('select with group by as sql + column', async () => { - await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + await db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); - const result = await db.select({ name: usersTable.name }).from(usersTable) + const result = await db + .select({ name: usersTable.name }) + .from(usersTable) .groupBy(sql`${usersTable.name}`, usersTable.id); - expect(result).toEqual([{ name: 'Jane' }, { name: 'Jane' }, { name: 'John' }]); + expect(result).toEqual([ + { name: 'Jane' }, + { name: 'Jane' }, + { name: 'John' }, + ]); }); test('select with group by as column + sql', async () => { - await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + await db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); - const result = await db.select({ name: usersTable.name }).from(usersTable) + const result = await db + .select({ name: usersTable.name }) + .from(usersTable) .groupBy(usersTable.id, sql`${usersTable.name}`); - expect(result).toEqual([{ name: 'Jane' }, { name: 'Jane' }, { name: 'John' }]); + expect(result).toEqual([ + { name: 'Jane' }, + { name: 'Jane' }, + { name: 'John' }, + ]); }); test('select with group by complex query', async () => { - await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + await db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); - const result = await db.select({ name: usersTable.name }).from(usersTable) + const result = await db + .select({ name: usersTable.name }) + .from(usersTable) .groupBy(usersTable.id, sql`${usersTable.name}`) .orderBy(asc(usersTable.name)) .limit(1); @@ -394,7 +578,9 @@ test('select with group by complex query', async () => { }); test('build query', async () => { - const query = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable) + const query = db + .select({ id: usersTable.id, name: usersTable.name }) + .from(usersTable) .groupBy(usersTable.id, usersTable.name) .toSQL(); @@ -407,14 +593,19 @@ test('build query', async () => { test('insert sql', async () => { await db.insert(usersTable).values({ name: sql`${'John'}` }); - const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable); + const result = await db + .select({ id: usersTable.id, name: usersTable.name }) + .from(usersTable); expect(result).toEqual([{ id: 1, name: 'John' }]); }); test('partial join with alias', async () => { const customerAlias = alias(usersTable, 'customer'); - await db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + await db.insert(usersTable).values([ + { id: 10, name: 'Ivan' }, + { id: 11, name: 'Hans' }, + ]); const result = await db .select({ user: { @@ -425,44 +616,53 @@ test('partial join with alias', async () => { id: customerAlias.id, name: customerAlias.name, }, - }).from(usersTable) + }) + .from(usersTable) .leftJoin(customerAlias, eq(customerAlias.id, 11)) .where(eq(usersTable.id, 10)); - expect(result).toEqual([{ - user: { id: 10, name: 'Ivan' }, - customer: { id: 11, name: 'Hans' }, - }]); + expect(result).toEqual([ + { + user: { id: 10, name: 'Ivan' }, + customer: { id: 11, name: 'Hans' }, + }, + ]); }); test('full join with alias', async () => { const customerAlias = alias(usersTable, 'customer'); - await db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + await db.insert(usersTable).values([ + { id: 10, name: 'Ivan' }, + { id: 11, name: 'Hans' }, + ]); const result = await db - .select().from(usersTable) + .select() + .from(usersTable) .leftJoin(customerAlias, eq(customerAlias.id, 11)) .where(eq(usersTable.id, 10)); - expect(result).toEqual([{ - users: { - id: 10, - bestTexts: [], - name: 'Ivan', - verified: false, - jsonb: null, - createdAt: result[0]!.users.createdAt, - }, - customer: { - bestTexts: [], - id: 11, - name: 'Hans', - verified: false, - jsonb: null, - createdAt: result[0]!.customer!.createdAt, + expect(result).toEqual([ + { + users: { + id: 10, + bestTexts: [], + name: 'Ivan', + verified: false, + jsonb: null, + createdAt: result[0]!.users.createdAt, + }, + customer: { + bestTexts: [], + id: 11, + name: 'Hans', + verified: false, + jsonb: null, + createdAt: result[0]!.customer!.createdAt, + }, }, - }]); + ]); }); test('select from alias', async () => { @@ -474,35 +674,44 @@ test('select from alias', async () => { }); await db.execute(sql`drop table if exists ${users}`); - await db.execute(sql`create table ${users} (id serial primary key, name text not null)`); + await db.execute( + sql`create table ${users} (id serial primary key, name text not null)`, + ); const user = alias(users, 'user'); const customers = alias(users, 'customer'); - await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + await db.insert(users).values([ + { id: 10, name: 'Ivan' }, + { id: 11, name: 'Hans' }, + ]); const result = await db .select() .from(user) .leftJoin(customers, eq(customers.id, 11)) .where(eq(user.id, 10)); - expect(result).toEqual([{ - user: { - id: 10, - name: 'Ivan', - }, - customer: { - id: 11, - name: 'Hans', + expect(result).toEqual([ + { + user: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, }, - }]); + ]); await db.execute(sql`drop table ${users}`); }); test('insert with spaces', async () => { await db.insert(usersTable).values({ name: sql`'Jo h n'` }); - const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable); + const result = await db + .select({ id: usersTable.id, name: usersTable.name }) + .from(usersTable); expect(result).toEqual([{ id: 1, name: 'Jo h n' }]); }); @@ -522,20 +731,25 @@ test('prepared statement', async () => { }); test('prepared statement reuse', async () => { - const stmt = db.insert(usersTable).values({ - verified: true, - name: sql.placeholder('name'), - }).prepare('stmt2'); + const stmt = db + .insert(usersTable) + .values({ + verified: true, + name: sql.placeholder('name'), + }) + .prepare('stmt2'); for (let i = 0; i < 10; i++) { await stmt.execute({ name: `John ${i}` }); } - const result = await db.select({ - id: usersTable.id, - name: usersTable.name, - verified: usersTable.verified, - }).from(usersTable); + const result = await db + .select({ + id: usersTable.id, + name: usersTable.name, + verified: usersTable.verified, + }) + .from(usersTable); expect(result).toEqual([ { id: 1, name: 'John 0', verified: true }, @@ -585,15 +799,19 @@ test('migrator : default migration strategy', async () => { }); test('migrator : migrate with custom schema', async () => { - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); - await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema }); + await migrate(db, { + migrationsFolder: './drizzle2/pg', + migrationsSchema: 'custom_migrations', + }); // test if the custom migrations table was created - const { rows } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`); + const { rows } = await db.execute( + sql`select * from custom_migrations."__drizzle_migrations";`, + ); expect(rows).toBeTruthy(); expect(rows!.length).toBeGreaterThan(0); @@ -604,7 +822,9 @@ test('migrator : migrate with custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`); + await db.execute( + sql`drop table custom_migrations."__drizzle_migrations"`, + ); }); test('migrator : migrate with custom table', async () => { @@ -613,10 +833,15 @@ test('migrator : migrate with custom table', async () => { await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); - await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable }); + await migrate(db, { + migrationsFolder: './drizzle2/pg', + migrationsTable: customTable, + }); // test if the custom migrations table was created - const { rows } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`); + const { rows } = await db.execute( + sql`select * from "drizzle".${sql.identifier(customTable)};`, + ); expect(rows).toBeTruthy(); expect(rows!.length).toBeGreaterThan(0); @@ -632,7 +857,6 @@ test('migrator : migrate with custom table', async () => { test('migrator : migrate with custom table and custom schema', async () => { const customTable = randomString(); - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); @@ -640,12 +864,16 @@ test('migrator : migrate with custom table and custom schema', async () => { await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable, - migrationsSchema: customSchema, + migrationsSchema: 'custom_migrations', }); // test if the custom migrations table was created const { rows } = await db.execute( - sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`, + sql`select * from custom_migrations.${ + sql.identifier( + customTable, + ) + };`, ); expect(rows).toBeTruthy(); expect(rows!.length).toBeGreaterThan(0); @@ -657,13 +885,27 @@ test('migrator : migrate with custom table and custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`); + await db.execute( + sql`drop table custom_migrations.${ + sql.identifier( + customTable, + ) + }`, + ); }); test('insert via db.execute + select via db.execute', async () => { - await db.execute(sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`); + await db.execute( + sql`insert into ${usersTable} (${ + sql.identifier( + usersTable.name.name, + ) + }) values (${'John'})`, + ); - const result = await db.execute<{ id: number; name: string }>(sql`select id, name from "users"`); + const result = await db.execute<{ id: number; name: string }>( + sql`select id, name from "users"`, + ); expectTypeOf(result.rows).toEqualTypeOf<{ id: number; name: string }[]>(); expect(result.rows).toEqual([{ id: 1, name: 'John' }]); }); @@ -671,7 +913,9 @@ test('insert via db.execute + select via db.execute', async () => { test('insert via db.execute + returning', async () => { const inserted = await db.execute( sql`insert into ${usersTable} (${ - sql.identifier(usersTable.name.name) + sql.identifier( + usersTable.name.name, + ) }) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`, ); expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]); @@ -679,13 +923,17 @@ test('insert via db.execute + returning', async () => { test('insert via db.execute w/ query builder', async () => { const inserted = await db.execute( - db.insert(usersTable).values({ name: 'John' }).returning({ id: usersTable.id, name: usersTable.name }), + db + .insert(usersTable) + .values({ name: 'John' }) + .returning({ id: usersTable.id, name: usersTable.name }), ); expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]); }); test('build query insert with onConflict do update', async () => { - const query = db.insert(usersTable) + const query = db + .insert(usersTable) .values({ name: 'John', jsonb: ['foo', 'bar'] }) .onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } }) .toSQL(); @@ -699,9 +947,13 @@ test('build query insert with onConflict do update', async () => { }); test('build query insert with onConflict do update / multiple columns', async () => { - const query = db.insert(usersTable) + const query = db + .insert(usersTable) .values({ name: 'John', jsonb: ['foo', 'bar'] }) - .onConflictDoUpdate({ target: [usersTable.id, usersTable.name], set: { name: 'John1' } }) + .onConflictDoUpdate({ + target: [usersTable.id, usersTable.name], + set: { name: 'John1' }, + }) .toSQL(); expect(query).toEqual({ @@ -713,7 +965,8 @@ test('build query insert with onConflict do update / multiple columns', async () }); test('build query insert with onConflict do nothing', async () => { - const query = db.insert(usersTable) + const query = db + .insert(usersTable) .values({ name: 'John', jsonb: ['foo', 'bar'] }) .onConflictDoNothing() .toSQL(); @@ -727,7 +980,8 @@ test('build query insert with onConflict do nothing', async () => { }); test('build query insert with onConflict do nothing + target', async () => { - const query = db.insert(usersTable) + const query = db + .insert(usersTable) .values({ name: 'John', jsonb: ['foo', 'bar'] }) .onConflictDoNothing({ target: usersTable.id }) .toSQL(); @@ -741,46 +995,49 @@ test('build query insert with onConflict do nothing + target', async () => { }); test('insert with onConflict do update', async () => { - await db.insert(usersTable) - .values({ name: 'John' }); + await db.insert(usersTable).values({ name: 'John' }); - await db.insert(usersTable) + await db + .insert(usersTable) .values({ id: 1, name: 'John' }) .onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } }); - const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( - eq(usersTable.id, 1), - ); + const res = await db + .select({ id: usersTable.id, name: usersTable.name }) + .from(usersTable) + .where(eq(usersTable.id, 1)); expect(res).toEqual([{ id: 1, name: 'John1' }]); }); test('insert with onConflict do nothing', async () => { - await db.insert(usersTable) - .values({ name: 'John' }); + await db.insert(usersTable).values({ name: 'John' }); - await db.insert(usersTable) + await db + .insert(usersTable) .values({ id: 1, name: 'John' }) .onConflictDoNothing(); - const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( - eq(usersTable.id, 1), - ); + const res = await db + .select({ id: usersTable.id, name: usersTable.name }) + .from(usersTable) + .where(eq(usersTable.id, 1)); expect(res).toEqual([{ id: 1, name: 'John' }]); }); test('insert with onConflict do nothing + target', async () => { - await db.insert(usersTable) - .values({ name: 'John' }); + await db.insert(usersTable).values({ name: 'John' }); - await db.insert(usersTable) + await db + .insert(usersTable) .values({ id: 1, name: 'John' }) .onConflictDoNothing({ target: usersTable.id }); - const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( - eq(usersTable.id, 1), - ); + const res = await db + .select({ id: usersTable.id, name: usersTable.name }) + .from(usersTable) + .where(eq(usersTable.id, 1)); expect(res).toEqual([{ id: 1, name: 'John' }]); }); @@ -799,17 +1056,33 @@ test('transaction', async () => { await db.execute(sql`drop table if exists ${users}`); await db.execute(sql`drop table if exists ${products}`); - await db.execute(sql`create table users_transactions (id serial not null primary key, balance integer not null)`); + await db.execute( + sql`create table users_transactions (id serial not null primary key, balance integer not null)`, + ); await db.execute( sql`create table products_transactions (id serial not null primary key, price integer not null, stock integer not null)`, ); - const user = await db.insert(users).values({ balance: 100 }).returning().then((rows) => rows[0]!); - const product = await db.insert(products).values({ price: 10, stock: 10 }).returning().then((rows) => rows[0]!); + const user = await db + .insert(users) + .values({ balance: 100 }) + .returning() + .then((rows) => rows[0]!); + const product = await db + .insert(products) + .values({ price: 10, stock: 10 }) + .returning() + .then((rows) => rows[0]!); await db.transaction(async (tx) => { - await tx.update(users).set({ balance: user.balance - product.price }).where(eq(users.id, user.id)); - await tx.update(products).set({ stock: product.stock - 1 }).where(eq(products.id, product.id)); + await tx + .update(users) + .set({ balance: user.balance - product.price }) + .where(eq(users.id, user.id)); + await tx + .update(products) + .set({ stock: product.stock - 1 }) + .where(eq(products.id, product.id)); }); const result = await db.select().from(users); @@ -906,81 +1179,109 @@ test('nested transaction rollback', async () => { test('select from raw sql', async () => { const result = await db.execute(sql`select 1 as id, 'John' as name`); - expect(result.rows).toEqual([ - { id: 1, name: 'John' }, - ]); + expect(result.rows).toEqual([{ id: 1, name: 'John' }]); }); test('select from raw sql with mapped values', async () => { - const result = await db.select({ - id: sql`id`, - name: sql`name`, - }).from(sql`(select 1 as id, 'John' as name) as users`); + const result = await db + .select({ + id: sql`id`, + name: sql`name`, + }) + .from(sql`(select 1 as id, 'John' as name) as users`); - expect(result).toEqual([ - { id: 1, name: 'John' }, - ]); + expect(result).toEqual([{ id: 1, name: 'John' }]); }); test('insert with array values works', async () => { const bestTexts = ['text1', 'text2', 'text3']; - const [insertResult] = await db.insert(usersTable).values({ - name: 'John', - bestTexts, - }).returning(); + const [insertResult] = await db + .insert(usersTable) + .values({ + name: 'John', + bestTexts, + }) + .returning(); expect(insertResult?.bestTexts).toEqual(bestTexts); }); test('update with array values works', async () => { - const [newUser] = await db.insert(usersTable).values({ name: 'John' }).returning(); + const [newUser] = await db + .insert(usersTable) + .values({ name: 'John' }) + .returning(); const bestTexts = ['text4', 'text5', 'text6']; - const [insertResult] = await db.update(usersTable).set({ - bestTexts, - }).where(eq(usersTable.id, newUser!.id)).returning(); + const [insertResult] = await db + .update(usersTable) + .set({ + bestTexts, + }) + .where(eq(usersTable.id, newUser!.id)) + .returning(); expect(insertResult?.bestTexts).toEqual(bestTexts); }); test('insert with array values works', async () => { const bestTexts = ['text1', 'text2', 'text3']; - const [insertResult] = await db.insert(usersTable).values({ - name: 'John', - bestTexts, - }).returning(); + const [insertResult] = await db + .insert(usersTable) + .values({ + name: 'John', + bestTexts, + }) + .returning(); expect(insertResult?.bestTexts).toEqual(bestTexts); }); test('update with array values works', async () => { - const [newUser] = await db.insert(usersTable).values({ name: 'John' }).returning(); + const [newUser] = await db + .insert(usersTable) + .values({ name: 'John' }) + .returning(); const bestTexts = ['text4', 'text5', 'text6']; - const [insertResult] = await db.update(usersTable).set({ - bestTexts, - }).where(eq(usersTable.id, newUser!.id)).returning(); + const [insertResult] = await db + .update(usersTable) + .set({ + bestTexts, + }) + .where(eq(usersTable.id, newUser!.id)) + .returning(); expect(insertResult?.bestTexts).toEqual(bestTexts); }); test('insert with array values works', async () => { const bestTexts = ['text1', 'text2', 'text3']; - const [insertResult] = await db.insert(usersTable).values({ - name: 'John', - bestTexts, - }).returning(); + const [insertResult] = await db + .insert(usersTable) + .values({ + name: 'John', + bestTexts, + }) + .returning(); expect(insertResult?.bestTexts).toEqual(bestTexts); }); test('update with array values works', async () => { - const [newUser] = await db.insert(usersTable).values({ name: 'John' }).returning(); + const [newUser] = await db + .insert(usersTable) + .values({ name: 'John' }) + .returning(); const bestTexts = ['text4', 'text5', 'text6']; - const [insertResult] = await db.update(usersTable).set({ - bestTexts, - }).where(eq(usersTable.id, newUser!.id)).returning(); + const [insertResult] = await db + .update(usersTable) + .set({ + bestTexts, + }) + .where(eq(usersTable.id, newUser!.id)) + .returning(); expect(insertResult?.bestTexts).toEqual(bestTexts); }); @@ -993,7 +1294,10 @@ test('all date and time columns', async () => { datetime: timestamp('datetime').notNull(), // datetimeWTZ: timestamp('datetime_wtz', { withTimezone: true }).notNull(), datetimeString: timestamp('datetime_string', { mode: 'string' }).notNull(), - datetimeFullPrecision: timestamp('datetime_full_precision', { precision: 6, mode: 'string' }).notNull(), + datetimeFullPrecision: timestamp('datetime_full_precision', { + precision: 6, + mode: 'string', + }).notNull(), // datetimeWTZString: timestamp('datetime_wtz_string', { withTimezone: true, mode: 'string' }).notNull(), }); @@ -1029,29 +1333,35 @@ test('all date and time columns', async () => { const result = await db.select().from(table); Expect< - Equal<{ - id: number; - dateString: string; - time: string; - datetime: Date; - // datetimeWTZ: Date; - datetimeString: string; - datetimeFullPrecision: string; - // datetimeWTZString: string; - }[], typeof result> + Equal< + { + id: number; + dateString: string; + time: string; + datetime: Date; + // datetimeWTZ: Date; + datetimeString: string; + datetimeFullPrecision: string; + // datetimeWTZString: string; + }[], + typeof result + > >; Expect< - Equal<{ - dateString: string; - time: string; - datetime: Date; - // datetimeWTZ: Date; - datetimeString: string; - datetimeFullPrecision: string; - // datetimeWTZString: string; - id?: number | undefined; - }, typeof table.$inferInsert> + Equal< + { + dateString: string; + time: string; + datetime: Date; + // datetimeWTZ: Date; + datetimeString: string; + datetimeFullPrecision: string; + // datetimeWTZString: string; + id?: number | undefined; + }, + typeof table.$inferInsert + > >; expect(result).toEqual([ @@ -1073,9 +1383,19 @@ test('all date and time columns', async () => { test.skip('all date and time columns with timezone', async () => { const table = pgTable('all_columns', { id: serial('id').primaryKey(), - timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(), - timestampAsDate: timestamp('timestamp_date', { withTimezone: true, precision: 3 }).notNull(), - timestampTimeZones: timestamp('timestamp_date_2', { withTimezone: true, precision: 3 }).notNull(), + timestamp: timestamp('timestamp_string', { + mode: 'string', + withTimezone: true, + precision: 6, + }).notNull(), + timestampAsDate: timestamp('timestamp_date', { + withTimezone: true, + precision: 3, + }).notNull(), + timestampTimeZones: timestamp('timestamp_date_2', { + withTimezone: true, + precision: 3, + }).notNull(), }); await db.execute(sql`drop table if exists ${table}`); @@ -1098,8 +1418,16 @@ test.skip('all date and time columns with timezone', async () => { const timestampDateWTZ2 = new Date('2022-01-01 00:00:00.123 +0200'); await db.insert(table).values([ - { timestamp: timestampString, timestampAsDate: timestampDate, timestampTimeZones: timestampDateWTZ }, - { timestamp: timestampString2, timestampAsDate: timestampDate2, timestampTimeZones: timestampDateWTZ2 }, + { + timestamp: timestampString, + timestampAsDate: timestampDate, + timestampTimeZones: timestampDateWTZ, + }, + { + timestamp: timestampString2, + timestampAsDate: timestampDate2, + timestampTimeZones: timestampDateWTZ2, + }, ]); const result = await db.select().from(table); @@ -1132,18 +1460,21 @@ test.skip('all date and time columns with timezone', async () => { id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00', timestamp_date: timestampDate.toISOString().replace('T', ' ').replace('Z', '') + '+00', - timestamp_date_2: timestampDateWTZ.toISOString().replace('T', ' ').replace('Z', '') + '+00', + timestamp_date_2: timestampDateWTZ.toISOString().replace('T', ' ').replace('Z', '') + + '+00', }, { id: 2, timestamp_string: '2022-01-01 04:00:00.123456+00', timestamp_date: timestampDate2.toISOString().replace('T', ' ').replace('Z', '') + '+00', - timestamp_date_2: timestampDateWTZ2.toISOString().replace('T', ' ').replace('Z', '') + '+00', + timestamp_date_2: timestampDateWTZ2.toISOString().replace('T', ' ').replace('Z', '') + + '+00', }, ]); - expect(result[0]?.timestampTimeZones.getTime()) - .toEqual(new Date((result2.rows?.[0] as any).timestamp_date_2 as any).getTime()); + expect(result[0]?.timestampTimeZones.getTime()).toEqual( + new Date((result2.rows?.[0] as any).timestamp_date_2 as any).getTime(), + ); await db.execute(sql`drop table if exists ${table}`); }); @@ -1151,8 +1482,14 @@ test.skip('all date and time columns with timezone', async () => { test('all date and time columns without timezone', async () => { const table = pgTable('all_columns', { id: serial('id').primaryKey(), - timestampString: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(), - timestampString2: timestamp('timestamp_string2', { precision: 3, mode: 'string' }).notNull(), + timestampString: timestamp('timestamp_string', { + mode: 'string', + precision: 6, + }).notNull(), + timestampString2: timestamp('timestamp_string2', { + precision: 3, + mode: 'string', + }).notNull(), timestampDate: timestamp('timestamp_date', { precision: 3 }).notNull(), }); @@ -1180,7 +1517,11 @@ test('all date and time columns without timezone', async () => { await db.insert(table).values([ { timestampString, timestampString2, timestampDate }, - { timestampString: timestampString_2, timestampString2: timestampString2_2, timestampDate: timestampDate2 }, + { + timestampString: timestampString_2, + timestampString2: timestampString2_2, + timestampDate: timestampDate2, + }, ]); const result = await db.select().from(table); @@ -1213,26 +1554,66 @@ test('all date and time columns without timezone', async () => { id: 1, timestamp_string: timestampString, timestamp_string2: '2022-01-02 00:00:00.123', - timestamp_date: timestampDate.toISOString().replace('T', ' ').replace('Z', ''), + timestamp_date: timestampDate + .toISOString() + .replace('T', ' ') + .replace('Z', ''), }, { id: 2, timestamp_string: timestampString_2, timestamp_string2: '2022-01-01 00:00:00.123', - timestamp_date: timestampDate2.toISOString().replace('T', ' ').replace('Z', ''), + timestamp_date: timestampDate2 + .toISOString() + .replace('T', ' ') + .replace('Z', ''), }, ]); - expect((result2.rows?.[0] as any).timestamp_string).toEqual('2022-01-01 00:00:00.123456'); - // need to add the 'Z', otherwise javascript assumes it's in local time - expect(new Date((result2.rows?.[0] as any).timestamp_date + 'Z' as any).getTime()).toEqual( - timestampDate.getTime(), + expect((result2.rows?.[0] as any).timestamp_string).toEqual( + '2022-01-01 00:00:00.123456', ); + // need to add the 'Z', otherwise javascript assumes it's in local time + expect( + new Date(((result2.rows?.[0] as any).timestamp_date + 'Z') as any).getTime(), + ).toEqual(timestampDate.getTime()); await db.execute(sql`drop table if exists ${table}`); }); +test('Typehints mix for RQB', async () => { + const uuid = 'd997d46d-5769-4c78-9a35-93acadbe6076'; + + const res = await db.query.user.findMany({ + where: eq(user.id, uuid), + with: { + todos: { + with: { + todo: true, + }, + }, + }, + }); + + expect(res).toStrictEqual([]); +}); + +test('Typehints mix for findFirst', async () => { + const uuid = 'd997d46d-5769-4c78-9a35-93acadbe6076'; + + await db.insert(user).values({ id: uuid, email: 'd' }); + + const res = await db.query.user.findFirst({ + where: eq(user.id, uuid), + }); + + expect(res).toStrictEqual({ id: 'd997d46d-5769-4c78-9a35-93acadbe6076', email: 'd' }); +}); + afterAll(async () => { await db.execute(sql`drop table if exists "users"`); + await db.execute(sql`drop table if exists "todo_user"`); + await db.execute(sql`drop table if exists "user"`); + await db.execute(sql`drop table if exists "todo"`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); }); diff --git a/integration-tests/tests/pg/neon-http.test.ts b/integration-tests/tests/pg/neon-http.test.ts index 1476e9628..8fee5b82d 100644 --- a/integration-tests/tests/pg/neon-http.test.ts +++ b/integration-tests/tests/pg/neon-http.test.ts @@ -1,11 +1,11 @@ import { neon, type NeonQueryFunction } from '@neondatabase/serverless'; import retry from 'async-retry'; -import { sql } from 'drizzle-orm'; +import { eq, sql } from 'drizzle-orm'; import { drizzle, type NeonHttpDatabase } from 'drizzle-orm/neon-http'; import { migrate } from 'drizzle-orm/neon-http/migrator'; -import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core'; +import { pgMaterializedView, pgTable, serial, timestamp } from 'drizzle-orm/pg-core'; import { Client } from 'pg'; -import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; import { skipTests } from '~/common'; import { randomString } from '~/utils'; import { tests, usersMigratorTable, usersTable } from './pg-common'; @@ -68,15 +68,14 @@ test('migrator : default migration strategy', async () => { }); test('migrator : migrate with custom schema', async () => { - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); - await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema }); + await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' }); // test if the custom migrations table was created - const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`); + const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`); expect(rowCount && rowCount > 0).toBeTruthy(); // test if the migrated table are working as expected @@ -86,7 +85,7 @@ test('migrator : migrate with custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`); + await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`); }); test('migrator : migrate with custom table', async () => { @@ -113,7 +112,6 @@ test('migrator : migrate with custom table', async () => { test('migrator : migrate with custom table and custom schema', async () => { const customTable = randomString(); - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); @@ -121,12 +119,12 @@ test('migrator : migrate with custom table and custom schema', async () => { await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable, - migrationsSchema: customSchema, + migrationsSchema: 'custom_migrations', }); // test if the custom migrations table was created const { rowCount } = await db.execute( - sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`, + sql`select * from custom_migrations.${sql.identifier(customTable)};`, ); expect(rowCount && rowCount > 0).toBeTruthy(); @@ -137,7 +135,7 @@ test('migrator : migrate with custom table and custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`); + await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`); }); test('all date and time columns without timezone first case mode string', async () => { @@ -445,7 +443,7 @@ beforeEach(async () => { create table users ( id serial primary key, name text not null, - verified boolean not null default false, + verified boolean not null default false, jsonb jsonb, created_at timestamptz not null default now() ) @@ -484,3 +482,309 @@ test('insert via db.execute w/ query builder', async () => { ); expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]); }); + +describe('$withAuth tests', (it) => { + const client = vi.fn(); + const db = drizzle({ + client: client as any as NeonQueryFunction, + schema: { + usersTable, + }, + }); + + it('$count', async () => { + await db.$withAuth('$count').$count(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: '$count' }); + }); + + it('delete', async () => { + await db.$withAuth('delete').delete(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'delete' }); + }); + + it('select', async () => { + await db.$withAuth('select').select().from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'select' }); + }); + + it('selectDistinct', async () => { + await db.$withAuth('selectDistinct').selectDistinct().from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ + arrayMode: true, + fullResults: true, + authToken: 'selectDistinct', + }); + }); + + it('selectDistinctOn', async () => { + await db.$withAuth('selectDistinctOn').selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ + arrayMode: true, + fullResults: true, + authToken: 'selectDistinctOn', + }); + }); + + it('update', async () => { + await db.$withAuth('update').update(usersTable).set({ + name: 'CHANGED', + }).where(eq(usersTable.name, 'TARGET')).catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'update' }); + }); + + it('insert', async () => { + await db.$withAuth('insert').insert(usersTable).values({ + name: 'WITHAUTHUSER', + }).catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'insert' }); + }); + + it('with', async () => { + await db.$withAuth('with').with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from(usersTable) + .catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'with' }); + }); + + it('rqb', async () => { + await db.$withAuth('rqb').query.usersTable.findFirst().catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'rqb' }); + }); + + it('exec', async () => { + await db.$withAuth('exec').execute(`SELECT 1`).catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'exec' }); + }); + + it('prepared', async () => { + const prep = db.$withAuth('prepared').select().from(usersTable).prepare('withAuthPrepared'); + + await prep.execute().catch(() => null); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'prepared' }); + }); + + it('refreshMaterializedView', async () => { + const johns = pgMaterializedView('johns') + .as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John'))); + + await db.$withAuth('refreshMaterializedView').refreshMaterializedView(johns); + + expect(client.mock.lastCall?.[2]).toStrictEqual({ + arrayMode: false, + fullResults: true, + authToken: 'refreshMaterializedView', + }); + }); +}); + +describe('$withAuth callback tests', (it) => { + const client = vi.fn(); + const db = drizzle({ + client: client as any as NeonQueryFunction, + schema: { + usersTable, + }, + }); + const auth = (token: string) => () => token; + + it('$count', async () => { + await db.$withAuth(auth('$count')).$count(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('$count'); + }); + + it('delete', async () => { + await db.$withAuth(auth('delete')).delete(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('delete'); + }); + + it('select', async () => { + await db.$withAuth(auth('select')).select().from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('select'); + }); + + it('selectDistinct', async () => { + await db.$withAuth(auth('selectDistinct')).selectDistinct().from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinct'); + }); + + it('selectDistinctOn', async () => { + await db.$withAuth(auth('selectDistinctOn')).selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinctOn'); + }); + + it('update', async () => { + await db.$withAuth(auth('update')).update(usersTable).set({ + name: 'CHANGED', + }).where(eq(usersTable.name, 'TARGET')).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('update'); + }); + + it('insert', async () => { + await db.$withAuth(auth('insert')).insert(usersTable).values({ + name: 'WITHAUTHUSER', + }).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('insert'); + }); + + it('with', async () => { + await db.$withAuth(auth('with')).with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from( + usersTable, + ) + .catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('with'); + }); + + it('rqb', async () => { + await db.$withAuth(auth('rqb')).query.usersTable.findFirst().catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('rqb'); + }); + + it('exec', async () => { + await db.$withAuth(auth('exec')).execute(`SELECT 1`).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('exec'); + }); + + it('prepared', async () => { + const prep = db.$withAuth(auth('prepared')).select().from(usersTable).prepare('withAuthPrepared'); + + await prep.execute().catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('prepared'); + }); + + it('refreshMaterializedView', async () => { + const johns = pgMaterializedView('johns') + .as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John'))); + + await db.$withAuth(auth('refreshMaterializedView')).refreshMaterializedView(johns); + + expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('refreshMaterializedView'); + }); +}); + +describe('$withAuth async callback tests', (it) => { + const client = vi.fn(); + const db = drizzle({ + client: client as any as NeonQueryFunction, + schema: { + usersTable, + }, + }); + const auth = (token: string) => async () => token; + + it('$count', async () => { + await db.$withAuth(auth('$count')).$count(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('$count'); + }); + + it('delete', async () => { + await db.$withAuth(auth('delete')).delete(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('delete'); + }); + + it('select', async () => { + await db.$withAuth(auth('select')).select().from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('select'); + }); + + it('selectDistinct', async () => { + await db.$withAuth(auth('selectDistinct')).selectDistinct().from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinct'); + }); + + it('selectDistinctOn', async () => { + await db.$withAuth(auth('selectDistinctOn')).selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinctOn'); + }); + + it('update', async () => { + await db.$withAuth(auth('update')).update(usersTable).set({ + name: 'CHANGED', + }).where(eq(usersTable.name, 'TARGET')).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('update'); + }); + + it('insert', async () => { + await db.$withAuth(auth('insert')).insert(usersTable).values({ + name: 'WITHAUTHUSER', + }).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('insert'); + }); + + it('with', async () => { + await db.$withAuth(auth('with')).with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from( + usersTable, + ) + .catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('with'); + }); + + it('rqb', async () => { + await db.$withAuth(auth('rqb')).query.usersTable.findFirst().catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('rqb'); + }); + + it('exec', async () => { + await db.$withAuth(auth('exec')).execute(`SELECT 1`).catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('exec'); + }); + + it('prepared', async () => { + const prep = db.$withAuth(auth('prepared')).select().from(usersTable).prepare('withAuthPrepared'); + + await prep.execute().catch(() => null); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('prepared'); + }); + + it('refreshMaterializedView', async () => { + const johns = pgMaterializedView('johns') + .as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John'))); + + await db.$withAuth(auth('refreshMaterializedView')).refreshMaterializedView(johns); + + expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise); + expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('refreshMaterializedView'); + }); +}); diff --git a/integration-tests/tests/pg/neon-serverless.test.ts b/integration-tests/tests/pg/neon-serverless.test.ts new file mode 100644 index 000000000..5a77809fa --- /dev/null +++ b/integration-tests/tests/pg/neon-serverless.test.ts @@ -0,0 +1,582 @@ +import { neonConfig, Pool } from '@neondatabase/serverless'; +import retry from 'async-retry'; +import { eq, sql } from 'drizzle-orm'; +import { drizzle, type NeonDatabase } from 'drizzle-orm/neon-serverless'; +import { migrate } from 'drizzle-orm/neon-serverless/migrator'; +import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import ws from 'ws'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { mySchema, tests, usersMigratorTable, usersMySchemaTable, usersTable } from './pg-common'; + +const ENABLE_LOGGING = false; + +let db: NeonDatabase; +let client: Pool; + +beforeAll(async () => { + const connectionString = process.env['NEON_CONNECTION_STRING']; + if (!connectionString) { + throw new Error('NEON_CONNECTION_STRING is not defined'); + } + + neonConfig.webSocketConstructor = ws; + + client = await retry(async () => { + client = new Pool({ connectionString }); + + const cnt = await client.connect(); + cnt.release(); + + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.end(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + await client?.end(); +}); + +beforeEach((ctx) => { + ctx.pg = { + db, + }; +}); + +test('migrator : default migration strategy', async () => { + await db.execute(sql`drop table if exists all_columns`); + await db.execute(sql`drop table if exists users12`); + await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); + + await migrate(db, { migrationsFolder: './drizzle2/pg' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + + const result = await db.select().from(usersMigratorTable); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.execute(sql`drop table all_columns`); + await db.execute(sql`drop table users12`); + await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`); +}); + +test('migrator : migrate with custom schema', async () => { + await db.execute(sql`drop table if exists all_columns`); + await db.execute(sql`drop table if exists users12`); + await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); + + await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' }); + + // test if the custom migrations table was created + const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`); + expect(rowCount && rowCount > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.execute(sql`drop table all_columns`); + await db.execute(sql`drop table users12`); + await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.execute(sql`drop table if exists all_columns`); + await db.execute(sql`drop table if exists users12`); + await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); + + await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable }); + + // test if the custom migrations table was created + const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`); + expect(rowCount && rowCount > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.execute(sql`drop table all_columns`); + await db.execute(sql`drop table users12`); + await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`); +}); + +test('migrator : migrate with custom table and custom schema', async () => { + const customTable = randomString(); + await db.execute(sql`drop table if exists all_columns`); + await db.execute(sql`drop table if exists users12`); + await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); + + await migrate(db, { + migrationsFolder: './drizzle2/pg', + migrationsTable: customTable, + migrationsSchema: 'custom_migrations', + }); + + // test if the custom migrations table was created + const { rowCount } = await db.execute( + sql`select * from custom_migrations.${sql.identifier(customTable)};`, + ); + expect(rowCount && rowCount > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.execute(sql`drop table all_columns`); + await db.execute(sql`drop table users12`); + await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`); +}); + +test('all date and time columns without timezone first case mode string', async () => { + const table = pgTable('all_columns', { + id: serial('id').primaryKey(), + timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(), + }); + + await db.execute(sql`drop table if exists ${table}`); + + await db.execute(sql` + create table ${table} ( + id serial primary key, + timestamp_string timestamp(6) not null + ) + `); + + // 1. Insert date in string format without timezone in it + await db.insert(table).values([ + { timestamp: '2022-01-01 02:00:00.123456' }, + ]); + + // 2, Select in string format and check that values are the same + const result = await db.select().from(table); + + expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]); + + // 3. Select as raw query and check that values are the same + const result2 = await db.execute<{ + id: number; + timestamp_string: string; + }>(sql`select * from ${table}`); + + expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]); + + await db.execute(sql`drop table if exists ${table}`); +}); + +test('all date and time columns without timezone second case mode string', async () => { + const table = pgTable('all_columns', { + id: serial('id').primaryKey(), + timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(), + }); + + await db.execute(sql`drop table if exists ${table}`); + + await db.execute(sql` + create table ${table} ( + id serial primary key, + timestamp_string timestamp(6) not null + ) + `); + + // 1. Insert date in string format with timezone in it + await db.insert(table).values([ + { timestamp: '2022-01-01T02:00:00.123456-02' }, + ]); + + // 2, Select as raw query and check that values are the same + const result = await db.execute<{ + id: number; + timestamp_string: string; + }>(sql`select * from ${table}`); + + expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]); + + await db.execute(sql`drop table if exists ${table}`); +}); + +test('all date and time columns without timezone third case mode date', async () => { + const table = pgTable('all_columns', { + id: serial('id').primaryKey(), + timestamp: timestamp('timestamp_string', { mode: 'date', precision: 3 }).notNull(), + }); + + await db.execute(sql`drop table if exists ${table}`); + + await db.execute(sql` + create table ${table} ( + id serial primary key, + timestamp_string timestamp(3) not null + ) + `); + + const insertedDate = new Date('2022-01-01 20:00:00.123+04'); + + // 1. Insert date as new date + await db.insert(table).values([ + { timestamp: insertedDate }, + ]); + + // 2, Select as raw query as string + const result = await db.execute<{ + id: number; + timestamp_string: string; + }>(sql`select * from ${table}`); + + // 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC + expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime()); + + await db.execute(sql`drop table if exists ${table}`); +}); + +test('test mode string for timestamp with timezone', async () => { + const table = pgTable('all_columns', { + id: serial('id').primaryKey(), + timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(), + }); + + await db.execute(sql`drop table if exists ${table}`); + + await db.execute(sql` + create table ${table} ( + id serial primary key, + timestamp_string timestamp(6) with time zone not null + ) + `); + + const timestampString = '2022-01-01 00:00:00.123456-0200'; + + // 1. Insert date in string format with timezone in it + await db.insert(table).values([ + { timestamp: timestampString }, + ]); + + // 2. Select date in string format and check that the values are the same + const result = await db.select().from(table); + + // 2.1 Notice that postgres will return the date in UTC, but it is exactly the same + expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]); + + // 3. Select as raw query and checke that values are the same + const result2 = await db.execute<{ + id: number; + timestamp_string: string; + }>(sql`select * from ${table}`); + + // 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same + expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]); + + await db.execute(sql`drop table if exists ${table}`); +}); + +test('test mode date for timestamp with timezone', async () => { + const table = pgTable('all_columns', { + id: serial('id').primaryKey(), + timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull(), + }); + + await db.execute(sql`drop table if exists ${table}`); + + await db.execute(sql` + create table ${table} ( + id serial primary key, + timestamp_string timestamp(3) with time zone not null + ) + `); + + const timestampString = new Date('2022-01-01 00:00:00.456-0200'); + + // 1. Insert date in string format with timezone in it + await db.insert(table).values([ + { timestamp: timestampString }, + ]); + + // 2. Select date in string format and check that the values are the same + const result = await db.select().from(table); + + // 2.1 Notice that postgres will return the date in UTC, but it is exactly the same + expect(result).toEqual([{ id: 1, timestamp: timestampString }]); + + // 3. Select as raw query and checke that values are the same + const result2 = await db.execute<{ + id: number; + timestamp_string: string; + }>(sql`select * from ${table}`); + + // 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same + expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]); + + await db.execute(sql`drop table if exists ${table}`); +}); + +test('test mode string for timestamp with timezone in UTC timezone', async () => { + // get current timezone from db + const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`); + + // set timezone to UTC + await db.execute(sql`set time zone 'UTC'`); + + const table = pgTable('all_columns', { + id: serial('id').primaryKey(), + timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(), + }); + + await db.execute(sql`drop table if exists ${table}`); + + await db.execute(sql` + create table ${table} ( + id serial primary key, + timestamp_string timestamp(6) with time zone not null + ) + `); + + const timestampString = '2022-01-01 00:00:00.123456-0200'; + + // 1. Insert date in string format with timezone in it + await db.insert(table).values([ + { timestamp: timestampString }, + ]); + + // 2. Select date in string format and check that the values are the same + const result = await db.select().from(table); + + // 2.1 Notice that postgres will return the date in UTC, but it is exactly the same + expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]); + + // 3. Select as raw query and checke that values are the same + const result2 = await db.execute<{ + id: number; + timestamp_string: string; + }>(sql`select * from ${table}`); + + // 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same + expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]); + + await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`); + + await db.execute(sql`drop table if exists ${table}`); +}); + +test.skip('test mode string for timestamp with timezone in different timezone', async () => { + // get current timezone from db + const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`); + + // set timezone to HST (UTC - 10) + await db.execute(sql`set time zone 'HST'`); + + const table = pgTable('all_columns', { + id: serial('id').primaryKey(), + timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(), + }); + + await db.execute(sql`drop table if exists ${table}`); + + await db.execute(sql` + create table ${table} ( + id serial primary key, + timestamp_string timestamp(6) with time zone not null + ) + `); + + const timestampString = '2022-01-01 00:00:00.123456-1000'; + + // 1. Insert date in string format with timezone in it + await db.insert(table).values([ + { timestamp: timestampString }, + ]); + + // 2. Select date in string format and check that the values are the same + const result = await db.select().from(table); + + expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]); + + // 3. Select as raw query and checke that values are the same + const result2 = await db.execute<{ + id: number; + timestamp_string: string; + }>(sql`select * from ${table}`); + + expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456+00' }]); + + await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`); + + await db.execute(sql`drop table if exists ${table}`); +}); + +test('select all fields', async (ctx) => { + const { db } = ctx.pg; + + const now = Date.now(); + + await db.insert(usersTable).values({ name: 'John' }); + const result = await db.select().from(usersTable); + + expect(result[0]!.createdAt).toBeInstanceOf(Date); + expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(3000); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); +}); + +test('update with returning all fields', async (ctx) => { + const { db } = ctx.pg; + + const now = Date.now(); + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db + .update(usersTable) + .set({ name: 'Jane' }) + .where(eq(usersTable.name, 'John')) + .returning(); + + expect(users[0]!.createdAt).toBeInstanceOf(Date); + expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000); + expect(users).toEqual([ + { id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt }, + ]); +}); + +test('delete with returning all fields', async (ctx) => { + const { db } = ctx.pg; + + const now = Date.now(); + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning(); + + expect(users[0]!.createdAt).toBeInstanceOf(Date); + expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000); + expect(users).toEqual([ + { id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt }, + ]); +}); + +test('mySchema :: select all fields', async (ctx) => { + const { db } = ctx.pg; + + const now = Date.now(); + + await db.insert(usersMySchemaTable).values({ name: 'John' }); + const result = await db.select().from(usersMySchemaTable); + + expect(result[0]!.createdAt).toBeInstanceOf(Date); + expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(3000); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); +}); + +test('mySchema :: delete with returning all fields', async (ctx) => { + const { db } = ctx.pg; + + const now = Date.now(); + + await db.insert(usersMySchemaTable).values({ name: 'John' }); + const users = await db.delete(usersMySchemaTable).where(eq(usersMySchemaTable.name, 'John')).returning(); + + expect(users[0]!.createdAt).toBeInstanceOf(Date); + expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000); + expect(users).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]); +}); + +skipTests([ + 'migrator : default migration strategy', + 'migrator : migrate with custom schema', + 'migrator : migrate with custom table', + 'migrator : migrate with custom table and custom schema', + 'insert via db.execute + select via db.execute', + 'insert via db.execute + returning', + 'insert via db.execute w/ query builder', + 'all date and time columns without timezone first case mode string', + 'all date and time columns without timezone third case mode date', + 'test mode string for timestamp with timezone', + 'test mode date for timestamp with timezone', + 'test mode string for timestamp with timezone in UTC timezone', + 'nested transaction rollback', + 'transaction rollback', + 'nested transaction', + 'transaction', + 'timestamp timezone', + 'test $onUpdateFn and $onUpdate works as $default', + 'select all fields', + 'update with returning all fields', + 'delete with returning all fields', + 'mySchema :: select all fields', + 'mySchema :: delete with returning all fields', +]); +tests(); + +beforeEach(async () => { + await db.execute(sql`drop schema if exists public cascade`); + await db.execute(sql`drop schema if exists ${mySchema} cascade`); + + await db.execute(sql`create schema public`); + await db.execute(sql`create schema ${mySchema}`); + + await db.execute( + sql` + create table users ( + id serial primary key, + name text not null, + verified boolean not null default false, + jsonb jsonb, + created_at timestamptz not null default now() + ) + `, + ); + + await db.execute( + sql` + create table ${usersMySchemaTable} ( + id serial primary key, + name text not null, + verified boolean not null default false, + jsonb jsonb, + created_at timestamptz not null default now() + ) + `, + ); +}); + +test('insert via db.execute + select via db.execute', async () => { + await db.execute( + sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`, + ); + + const result = await db.execute<{ id: number; name: string }>( + sql`select id, name from "users"`, + ); + expect(result.rows).toEqual([{ id: 1, name: 'John' }]); +}); + +test('insert via db.execute + returning', async () => { + const inserted = await db.execute<{ id: number; name: string }>( + sql`insert into ${usersTable} (${ + sql.identifier( + usersTable.name.name, + ) + }) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`, + ); + expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]); +}); + +test('insert via db.execute w/ query builder', async () => { + const inserted = await db.execute>( + db + .insert(usersTable) + .values({ name: 'John' }) + .returning({ id: usersTable.id, name: usersTable.name }), + ); + expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]); +}); diff --git a/integration-tests/tests/pg/pg-common.ts b/integration-tests/tests/pg/pg-common.ts index fb69c5877..5e5f4ec72 100644 --- a/integration-tests/tests/pg/pg-common.ts +++ b/integration-tests/tests/pg/pg-common.ts @@ -18,6 +18,7 @@ import { gte, ilike, inArray, + is, lt, max, min, @@ -30,10 +31,12 @@ import { sumDistinct, TransactionRollbackError, } from 'drizzle-orm'; +import { authenticatedRole, crudPolicy } from 'drizzle-orm/neon'; import type { NeonHttpDatabase } from 'drizzle-orm/neon-http'; import type { PgColumn, PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core'; import { alias, + bigserial, boolean, char, cidr, @@ -44,17 +47,22 @@ import { getMaterializedViewConfig, getTableConfig, getViewConfig, + index, inet, integer, intersect, intersectAll, interval, + json, jsonb, macaddr, macaddr8, numeric, + PgDialect, pgEnum, pgMaterializedView, + PgPolicy, + pgPolicy, pgSchema, pgTable, pgTableCreator, @@ -73,7 +81,7 @@ import { } from 'drizzle-orm/pg-core'; import getPort from 'get-port'; import { v4 as uuidV4 } from 'uuid'; -import { afterAll, beforeEach, describe, expect, test } from 'vitest'; +import { afterAll, afterEach, beforeEach, describe, expect, test } from 'vitest'; import { Expect } from '~/utils'; import type { schema } from './neon-http-batch.test'; // eslint-disable-next-line @typescript-eslint/no-import-type-side-effects @@ -177,9 +185,9 @@ const aggregateTable = pgTable('aggregate_table', { }); // To test another schema and multischema -const mySchema = pgSchema('mySchema'); +export const mySchema = pgSchema('mySchema'); -const usersMySchemaTable = mySchema.table('users', { +export const usersMySchemaTable = mySchema.table('users', { id: serial('id').primaryKey(), name: text('name').notNull(), verified: boolean('verified').notNull().default(false), @@ -199,6 +207,12 @@ const users2MySchemaTable = mySchema.table('users2', { cityId: integer('city_id').references(() => citiesTable.id), }); +const jsonTestTable = pgTable('jsontest', { + id: serial('id').primaryKey(), + json: json('json').$type<{ string: string; number: number }>(), + jsonb: jsonb('jsonb').$type<{ string: string; number: number }>(), +}); + let pgContainer: Docker.Container; export async function createDockerDB(): Promise<{ connectionString: string; container: Docker.Container }> { @@ -239,6 +253,7 @@ export function tests() { await db.execute(sql`drop schema if exists public cascade`); await db.execute(sql`drop schema if exists ${mySchema} cascade`); await db.execute(sql`create schema public`); + await db.execute(sql`create schema if not exists custom_migrations`); await db.execute(sql`create schema ${mySchema}`); // public users await db.execute( @@ -246,7 +261,7 @@ export function tests() { create table users ( id serial primary key, name text not null, - verified boolean not null default false, + verified boolean not null default false, jsonb jsonb, created_at timestamptz not null default now() ) @@ -358,6 +373,21 @@ export function tests() { ) `, ); + + await db.execute( + sql` + create table jsontest ( + id serial primary key, + json json, + jsonb jsonb + ) + `, + ); + }); + + afterEach(async (ctx) => { + const { db } = ctx.pg; + await db.execute(sql`drop schema if exists custom_migrations cascade`); }); async function setupSetOperationTest(db: PgDatabase) { @@ -1116,6 +1146,28 @@ export function tests() { expect(result).toEqual([{ id: 1, name: 'John' }]); }); + test('insert: placeholders on columns with encoder', async (ctx) => { + const { db } = ctx.pg; + + const statement = db.insert(usersTable).values({ + name: 'John', + jsonb: sql.placeholder('jsonb'), + }).prepare('encoder_statement'); + + await statement.execute({ jsonb: ['foo', 'bar'] }); + + const result = await db + .select({ + id: usersTable.id, + jsonb: usersTable.jsonb, + }) + .from(usersTable); + + expect(result).toEqual([ + { id: 1, jsonb: ['foo', 'bar'] }, + ]); + }); + test('prepared statement reuse', async (ctx) => { const { db } = ctx.pg; @@ -1208,6 +1260,29 @@ export function tests() { expect(result).toEqual([{ id: 2, name: 'John1' }]); }); + test('prepared statement built using $dynamic', async (ctx) => { + const { db } = ctx.pg; + + function withLimitOffset(qb: any) { + return qb.limit(sql.placeholder('limit')).offset(sql.placeholder('offset')); + } + + await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]); + const stmt = db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .$dynamic(); + withLimitOffset(stmt).prepare('stmt_limit'); + + const result = await stmt.execute({ limit: 1, offset: 1 }); + + expect(result).toEqual([{ id: 2, name: 'John1' }]); + expect(result).toHaveLength(1); + }); + // TODO change tests to new structure test('Query check: Insert all defaults in 1 row', async (ctx) => { const { db } = ctx.pg; @@ -4203,7 +4278,7 @@ export function tests() { .toSQL(); expect(query).toEqual({ - sql: 'select "id", "name" from "mySchema"."users" group by "users"."id", "users"."name"', + sql: 'select "id", "name" from "mySchema"."users" group by "mySchema"."users"."id", "mySchema"."users"."name"', params: [], }); }); @@ -4481,5 +4556,867 @@ export function tests() { expect(users.length).toBeGreaterThan(0); }); + + test('Object keys as column names', async (ctx) => { + const { db } = ctx.pg; + + // Tests the following: + // Column with required config + // Column with optional config without providing a value + // Column with optional config providing a value + // Column without config + const users = pgTable('users', { + id: bigserial({ mode: 'number' }).primaryKey(), + firstName: varchar(), + lastName: varchar({ length: 50 }), + admin: boolean(), + }); + + await db.execute(sql`drop table if exists users`); + await db.execute( + sql` + create table users ( + "id" bigserial primary key, + "firstName" varchar, + "lastName" varchar(50), + "admin" boolean + ) + `, + ); + + await db.insert(users).values([ + { firstName: 'John', lastName: 'Doe', admin: true }, + { firstName: 'Jane', lastName: 'Smith', admin: false }, + ]); + const result = await db + .select({ id: users.id, firstName: users.firstName, lastName: users.lastName }) + .from(users) + .where(eq(users.admin, true)); + + expect(result).toEqual([ + { id: 1, firstName: 'John', lastName: 'Doe' }, + ]); + + await db.execute(sql`drop table users`); + }); + + test('proper json and jsonb handling', async (ctx) => { + const { db } = ctx.pg; + + const jsonTable = pgTable('json_table', { + json: json('json').$type<{ name: string; age: number }>(), + jsonb: jsonb('jsonb').$type<{ name: string; age: number }>(), + }); + + await db.execute(sql`drop table if exists ${jsonTable}`); + + await db.execute(sql`create table ${jsonTable} (json json, jsonb jsonb)`); + + await db.insert(jsonTable).values({ json: { name: 'Tom', age: 75 }, jsonb: { name: 'Pete', age: 23 } }); + + const result = await db.select().from(jsonTable); + + const justNames = await db.select({ + name1: sql`${jsonTable.json}->>'name'`.as('name1'), + name2: sql`${jsonTable.jsonb}->>'name'`.as('name2'), + }).from(jsonTable); + + expect(result).toStrictEqual([ + { + json: { name: 'Tom', age: 75 }, + jsonb: { name: 'Pete', age: 23 }, + }, + ]); + + expect(justNames).toStrictEqual([ + { + name1: 'Tom', + name2: 'Pete', + }, + ]); + }); + + test('set json/jsonb fields with objects and retrieve with the ->> operator', async (ctx) => { + const { db } = ctx.pg; + + const obj = { string: 'test', number: 123 }; + const { string: testString, number: testNumber } = obj; + + await db.insert(jsonTestTable).values({ + json: obj, + jsonb: obj, + }); + + const result = await db.select({ + jsonStringField: sql`${jsonTestTable.json}->>'string'`, + jsonNumberField: sql`${jsonTestTable.json}->>'number'`, + jsonbStringField: sql`${jsonTestTable.jsonb}->>'string'`, + jsonbNumberField: sql`${jsonTestTable.jsonb}->>'number'`, + }).from(jsonTestTable); + + expect(result).toStrictEqual([{ + jsonStringField: testString, + jsonNumberField: String(testNumber), + jsonbStringField: testString, + jsonbNumberField: String(testNumber), + }]); + }); + + test('set json/jsonb fields with strings and retrieve with the ->> operator', async (ctx) => { + const { db } = ctx.pg; + + const obj = { string: 'test', number: 123 }; + const { string: testString, number: testNumber } = obj; + + await db.insert(jsonTestTable).values({ + json: sql`${JSON.stringify(obj)}`, + jsonb: sql`${JSON.stringify(obj)}`, + }); + + const result = await db.select({ + jsonStringField: sql`${jsonTestTable.json}->>'string'`, + jsonNumberField: sql`${jsonTestTable.json}->>'number'`, + jsonbStringField: sql`${jsonTestTable.jsonb}->>'string'`, + jsonbNumberField: sql`${jsonTestTable.jsonb}->>'number'`, + }).from(jsonTestTable); + + expect(result).toStrictEqual([{ + jsonStringField: testString, + jsonNumberField: String(testNumber), + jsonbStringField: testString, + jsonbNumberField: String(testNumber), + }]); + }); + + test('set json/jsonb fields with objects and retrieve with the -> operator', async (ctx) => { + const { db } = ctx.pg; + + const obj = { string: 'test', number: 123 }; + const { string: testString, number: testNumber } = obj; + + await db.insert(jsonTestTable).values({ + json: obj, + jsonb: obj, + }); + + const result = await db.select({ + jsonStringField: sql`${jsonTestTable.json}->'string'`, + jsonNumberField: sql`${jsonTestTable.json}->'number'`, + jsonbStringField: sql`${jsonTestTable.jsonb}->'string'`, + jsonbNumberField: sql`${jsonTestTable.jsonb}->'number'`, + }).from(jsonTestTable); + + expect(result).toStrictEqual([{ + jsonStringField: testString, + jsonNumberField: testNumber, + jsonbStringField: testString, + jsonbNumberField: testNumber, + }]); + }); + + test('set json/jsonb fields with strings and retrieve with the -> operator', async (ctx) => { + const { db } = ctx.pg; + + const obj = { string: 'test', number: 123 }; + const { string: testString, number: testNumber } = obj; + + await db.insert(jsonTestTable).values({ + json: sql`${JSON.stringify(obj)}`, + jsonb: sql`${JSON.stringify(obj)}`, + }); + + const result = await db.select({ + jsonStringField: sql`${jsonTestTable.json}->'string'`, + jsonNumberField: sql`${jsonTestTable.json}->'number'`, + jsonbStringField: sql`${jsonTestTable.jsonb}->'string'`, + jsonbNumberField: sql`${jsonTestTable.jsonb}->'number'`, + }).from(jsonTestTable); + + expect(result).toStrictEqual([{ + jsonStringField: testString, + jsonNumberField: testNumber, + jsonbStringField: testString, + jsonbNumberField: testNumber, + }]); + }); + + test('update ... from', async (ctx) => { + const { db } = ctx.pg; + + await db.insert(cities2Table).values([ + { name: 'New York City' }, + { name: 'Seattle' }, + ]); + await db.insert(users2Table).values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 2 }, + ]); + + const result = await db + .update(users2Table) + .set({ + cityId: cities2Table.id, + }) + .from(cities2Table) + .where(and(eq(cities2Table.name, 'Seattle'), eq(users2Table.name, 'John'))) + .returning(); + + expect(result).toStrictEqual([{ + id: 1, + name: 'John', + cityId: 2, + cities: { + id: 2, + name: 'Seattle', + }, + }]); + }); + + test('update ... from with alias', async (ctx) => { + const { db } = ctx.pg; + + await db.insert(cities2Table).values([ + { name: 'New York City' }, + { name: 'Seattle' }, + ]); + await db.insert(users2Table).values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 2 }, + ]); + + const users = alias(users2Table, 'u'); + const cities = alias(cities2Table, 'c'); + const result = await db + .update(users) + .set({ + cityId: cities.id, + }) + .from(cities) + .where(and(eq(cities.name, 'Seattle'), eq(users.name, 'John'))) + .returning(); + + expect(result).toStrictEqual([{ + id: 1, + name: 'John', + cityId: 2, + c: { + id: 2, + name: 'Seattle', + }, + }]); + }); + + test('update ... from with join', async (ctx) => { + const { db } = ctx.pg; + + const states = pgTable('states', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + const cities = pgTable('cities', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + stateId: integer('state_id').references(() => states.id), + }); + const users = pgTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: integer('city_id').notNull().references(() => cities.id), + }); + + await db.execute(sql`drop table if exists "states" cascade`); + await db.execute(sql`drop table if exists "cities" cascade`); + await db.execute(sql`drop table if exists "users" cascade`); + await db.execute(sql` + create table "states" ( + "id" serial primary key, + "name" text not null + ) + `); + await db.execute(sql` + create table "cities" ( + "id" serial primary key, + "name" text not null, + "state_id" integer references "states"("id") + ) + `); + await db.execute(sql` + create table "users" ( + "id" serial primary key, + "name" text not null, + "city_id" integer not null references "cities"("id") + ) + `); + + await db.insert(states).values([ + { name: 'New York' }, + { name: 'Washington' }, + ]); + await db.insert(cities).values([ + { name: 'New York City', stateId: 1 }, + { name: 'Seattle', stateId: 2 }, + { name: 'London' }, + ]); + await db.insert(users).values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 2 }, + { name: 'Jack', cityId: 3 }, + ]); + + const result1 = await db + .update(users) + .set({ + cityId: cities.id, + }) + .from(cities) + .leftJoin(states, eq(cities.stateId, states.id)) + .where(and(eq(cities.name, 'Seattle'), eq(users.name, 'John'))) + .returning(); + const result2 = await db + .update(users) + .set({ + cityId: cities.id, + }) + .from(cities) + .leftJoin(states, eq(cities.stateId, states.id)) + .where(and(eq(cities.name, 'London'), eq(users.name, 'Jack'))) + .returning(); + + expect(result1).toStrictEqual([{ + id: 1, + name: 'John', + cityId: 2, + cities: { + id: 2, + name: 'Seattle', + stateId: 2, + }, + states: { + id: 2, + name: 'Washington', + }, + }]); + expect(result2).toStrictEqual([{ + id: 3, + name: 'Jack', + cityId: 3, + cities: { + id: 3, + name: 'London', + stateId: null, + }, + states: null, + }]); + }); + + test('insert into ... select', async (ctx) => { + const { db } = ctx.pg; + + const notifications = pgTable('notifications', { + id: serial('id').primaryKey(), + sentAt: timestamp('sent_at').notNull().defaultNow(), + message: text('message').notNull(), + }); + const users = pgTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + const userNotications = pgTable('user_notifications', { + userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + notificationId: integer('notification_id').notNull().references(() => notifications.id, { + onDelete: 'cascade', + }), + }, (t) => ({ + pk: primaryKey({ columns: [t.userId, t.notificationId] }), + })); + + await db.execute(sql`drop table if exists notifications`); + await db.execute(sql`drop table if exists users`); + await db.execute(sql`drop table if exists user_notifications`); + await db.execute(sql` + create table notifications ( + id serial primary key, + sent_at timestamp not null default now(), + message text not null + ) + `); + await db.execute(sql` + create table users ( + id serial primary key, + name text not null + ) + `); + await db.execute(sql` + create table user_notifications ( + user_id int references users(id) on delete cascade, + notification_id int references notifications(id) on delete cascade, + primary key (user_id, notification_id) + ) + `); + + const newNotification = await db + .insert(notifications) + .values({ message: 'You are one of the 3 lucky winners!' }) + .returning({ id: notifications.id }) + .then((result) => result[0]); + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + const sentNotifications = await db + .insert(userNotications) + .select( + db + .select({ + userId: users.id, + notificationId: sql`${newNotification!.id}`.as('notification_id'), + }) + .from(users) + .where(inArray(users.name, ['Alice', 'Charlie', 'Eve'])) + .orderBy(asc(users.id)), + ) + .returning(); + + expect(sentNotifications).toStrictEqual([ + { userId: 1, notificationId: newNotification!.id }, + { userId: 3, notificationId: newNotification!.id }, + { userId: 5, notificationId: newNotification!.id }, + ]); + }); + + test('insert into ... select with keys in different order', async (ctx) => { + const { db } = ctx.pg; + + const users1 = pgTable('users1', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + const users2 = pgTable('users2', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists users1`); + await db.execute(sql`drop table if exists users2`); + await db.execute(sql` + create table users1 ( + id serial primary key, + name text not null + ) + `); + await db.execute(sql` + create table users2 ( + id serial primary key, + name text not null + ) + `); + + expect( + () => + db + .insert(users1) + .select( + db + .select({ + name: users2.name, + id: users2.id, + }) + .from(users2), + ), + ).toThrowError(); + }); + + test('policy', () => { + { + const policy = pgPolicy('test policy'); + + expect(is(policy, PgPolicy)).toBe(true); + expect(policy.name).toBe('test policy'); + } + + { + const policy = pgPolicy('test policy', { + as: 'permissive', + for: 'all', + to: 'public', + using: sql`1=1`, + withCheck: sql`1=1`, + }); + + expect(is(policy, PgPolicy)).toBe(true); + expect(policy.name).toBe('test policy'); + expect(policy.as).toBe('permissive'); + expect(policy.for).toBe('all'); + expect(policy.to).toBe('public'); + const dialect = new PgDialect(); + expect(is(policy.using, SQL)).toBe(true); + expect(dialect.sqlToQuery(policy.using!).sql).toBe('1=1'); + expect(is(policy.withCheck, SQL)).toBe(true); + expect(dialect.sqlToQuery(policy.withCheck!).sql).toBe('1=1'); + } + + { + const policy = pgPolicy('test policy', { + to: 'custom value', + }); + + expect(policy.to).toBe('custom value'); + } + + { + const p1 = pgPolicy('test policy'); + const p2 = pgPolicy('test policy 2', { + as: 'permissive', + for: 'all', + to: 'public', + using: sql`1=1`, + withCheck: sql`1=1`, + }); + const table = pgTable('table_with_policy', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }, () => ({ + p1, + p2, + })); + const config = getTableConfig(table); + expect(config.policies).toHaveLength(2); + expect(config.policies[0]).toBe(p1); + expect(config.policies[1]).toBe(p2); + } + }); + + test('neon: policy', () => { + { + const policy = crudPolicy({ + read: true, + modify: true, + role: authenticatedRole, + }); + + for (const it of Object.values(policy)) { + expect(is(it, PgPolicy)).toBe(true); + expect(it?.to).toStrictEqual(authenticatedRole); + it?.using ? expect(it.using).toStrictEqual(sql`true`) : ''; + it?.withCheck ? expect(it.withCheck).toStrictEqual(sql`true`) : ''; + } + } + + { + const table = pgTable('name', { + id: integer('id'), + }, (t) => [ + index('name').on(t.id), + crudPolicy({ + read: true, + modify: true, + role: authenticatedRole, + }), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { policies, indexes, primaryKeys } = getTableConfig(table); + + expect(policies.length).toBe(4); + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + + expect(policies[0]?.name === 'crud-custom-policy-modify'); + expect(policies[1]?.name === 'crud-custom-policy-read'); + } + }); + + test('Enable RLS function', () => { + const usersWithRLS = pgTable('users', { + id: integer(), + }).enableRLS(); + + const config1 = getTableConfig(usersWithRLS); + + const usersNoRLS = pgTable('users', { + id: integer(), + }); + + const config2 = getTableConfig(usersNoRLS); + + expect(config1.enableRLS).toBeTruthy(); + expect(config2.enableRLS).toBeFalsy(); + }); + + test('$count separate', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(4); + }); + + test('$count embedded', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + }); + + test('$count separate reuse', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.$count(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual(4); + expect(count2).toStrictEqual(5); + expect(count3).toStrictEqual(6); + }); + + test('$count embedded reuse', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + expect(count2).toStrictEqual([ + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + ]); + expect(count3).toStrictEqual([ + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + ]); + }); + + test('$count separate with filters', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable, gt(countTestTable.id, 1)); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(3); + }); + + test('$count embedded with filters', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable, gt(countTestTable.id, 1)), + }).from(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 3 }, + { count: 3 }, + { count: 3 }, + { count: 3 }, + ]); + }); + + test('insert multiple rows into table with generated identity column', async (ctx) => { + const { db } = ctx.pg; + + const identityColumnsTable = pgTable('identity_columns_table', { + id: integer('id').generatedAlwaysAsIdentity(), + id1: integer('id1').generatedByDefaultAsIdentity(), + name: text('name').notNull(), + }); + + // not passing identity columns + await db.execute(sql`drop table if exists ${identityColumnsTable}`); + await db.execute( + sql`create table ${identityColumnsTable} ("id" integer generated always as identity, "id1" integer generated by default as identity, "name" text)`, + ); + + let result = await db.insert(identityColumnsTable).values([ + { name: 'John' }, + { name: 'Jane' }, + { name: 'Bob' }, + ]).returning(); + + expect(result).toEqual([ + { id: 1, id1: 1, name: 'John' }, + { id: 2, id1: 2, name: 'Jane' }, + { id: 3, id1: 3, name: 'Bob' }, + ]); + + // passing generated by default as identity column + await db.execute(sql`drop table if exists ${identityColumnsTable}`); + await db.execute( + sql`create table ${identityColumnsTable} ("id" integer generated always as identity, "id1" integer generated by default as identity, "name" text)`, + ); + + result = await db.insert(identityColumnsTable).values([ + { name: 'John', id1: 3 }, + { name: 'Jane', id1: 5 }, + { name: 'Bob', id1: 5 }, + ]).returning(); + + expect(result).toEqual([ + { id: 1, id1: 3, name: 'John' }, + { id: 2, id1: 5, name: 'Jane' }, + { id: 3, id1: 5, name: 'Bob' }, + ]); + + // passing all identity columns + await db.execute(sql`drop table if exists ${identityColumnsTable}`); + await db.execute( + sql`create table ${identityColumnsTable} ("id" integer generated always as identity, "id1" integer generated by default as identity, "name" text)`, + ); + + result = await db.insert(identityColumnsTable).overridingSystemValue().values([ + { name: 'John', id: 2, id1: 3 }, + { name: 'Jane', id: 4, id1: 5 }, + { name: 'Bob', id: 4, id1: 5 }, + ]).returning(); + + expect(result).toEqual([ + { id: 2, id1: 3, name: 'John' }, + { id: 4, id1: 5, name: 'Jane' }, + { id: 4, id1: 5, name: 'Bob' }, + ]); + }); }); } diff --git a/integration-tests/tests/pg/pglite.test.ts b/integration-tests/tests/pg/pglite.test.ts index 37cd3fe62..2a3d6a2ff 100644 --- a/integration-tests/tests/pg/pglite.test.ts +++ b/integration-tests/tests/pg/pglite.test.ts @@ -86,6 +86,10 @@ skipTests([ 'subquery with view', 'mySchema :: materialized view', 'select count()', + // not working in 0.2.12 + 'select with group by as sql + column', + 'select with group by as column + sql', + 'mySchema :: select with group by as column + sql', ]); tests(); diff --git a/integration-tests/tests/pg/rls/rls.definition.test.ts b/integration-tests/tests/pg/rls/rls.definition.test.ts new file mode 100644 index 000000000..2365a2e6d --- /dev/null +++ b/integration-tests/tests/pg/rls/rls.definition.test.ts @@ -0,0 +1,16 @@ +import { crudPolicy } from 'drizzle-orm/neon'; +import { getTableConfig, integer, pgPolicy, pgRole, pgTable } from 'drizzle-orm/pg-core'; +import { test } from 'vitest'; + +test.skip('getTableConfig: policies', async () => { + const schema = pgTable('hhh', { + id: integer(), + }, () => [ + pgPolicy('name'), + crudPolicy({ role: pgRole('users'), read: true, modify: true }), + ]); + + const tc = getTableConfig(schema); + + console.log(tc.policies); +}); diff --git a/integration-tests/tests/pg/vercel-pg.test.ts b/integration-tests/tests/pg/vercel-pg.test.ts index 3f1248d9b..ecf1d22ac 100644 --- a/integration-tests/tests/pg/vercel-pg.test.ts +++ b/integration-tests/tests/pg/vercel-pg.test.ts @@ -77,15 +77,14 @@ test('migrator : default migration strategy', async () => { }); test('migrator : migrate with custom schema', async () => { - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); - await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema }); + await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' }); // test if the custom migrations table was created - const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`); + const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`); expect(rowCount && rowCount > 0).toBeTruthy(); // test if the migrated table are working as expected @@ -95,7 +94,7 @@ test('migrator : migrate with custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`); + await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`); }); test('migrator : migrate with custom table', async () => { @@ -122,7 +121,6 @@ test('migrator : migrate with custom table', async () => { test('migrator : migrate with custom table and custom schema', async () => { const customTable = randomString(); - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); @@ -130,12 +128,12 @@ test('migrator : migrate with custom table and custom schema', async () => { await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable, - migrationsSchema: customSchema, + migrationsSchema: 'custom_migrations', }); // test if the custom migrations table was created const { rowCount } = await db.execute( - sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`, + sql`select * from custom_migrations.${sql.identifier(customTable)};`, ); expect(rowCount && rowCount > 0).toBeTruthy(); @@ -146,7 +144,7 @@ test('migrator : migrate with custom table and custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`); + await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`); }); test('all date and time columns without timezone first case mode string', async () => { diff --git a/integration-tests/tests/prisma/.gitignore b/integration-tests/tests/prisma/.gitignore deleted file mode 100644 index 794cddf53..000000000 --- a/integration-tests/tests/prisma/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*/client -*/drizzle diff --git a/integration-tests/tests/prisma/mysql/prisma.test.ts b/integration-tests/tests/prisma/mysql/prisma.test.ts deleted file mode 100644 index ee5511a25..000000000 --- a/integration-tests/tests/prisma/mysql/prisma.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import 'dotenv/config'; -import 'zx/globals'; - -import type { PrismaMySqlDatabase } from 'drizzle-orm/prisma/mysql'; -import { drizzle } from 'drizzle-orm/prisma/mysql'; -import { beforeAll, expect, expectTypeOf, test } from 'vitest'; - -import { PrismaClient } from './client'; -import { User } from './drizzle/schema.ts'; - -const ENABLE_LOGGING = false; - -let db: PrismaMySqlDatabase; - -beforeAll(async () => { - await $`prisma generate --schema tests/prisma/mysql/schema.prisma`.quiet(); - await $`prisma db push --force-reset --schema tests/prisma/mysql/schema.prisma`.quiet(); - const prisma = new PrismaClient().$extends(drizzle({ logger: ENABLE_LOGGING })); - db = prisma.$drizzle; -}); - -test('extension works', async () => { - const insert = await db.insert(User).values({ email: 'test@test.com' }); - expectTypeOf(insert).toEqualTypeOf<[]>(); - expect(insert).toEqual([]); - - const result = await db.select().from(User); - expectTypeOf(result).toEqualTypeOf(); - expect(result).toEqual([{ id: 1, email: 'test@test.com', name: null }]); -}); diff --git a/integration-tests/tests/prisma/mysql/schema.prisma b/integration-tests/tests/prisma/mysql/schema.prisma deleted file mode 100644 index 5bb496dcb..000000000 --- a/integration-tests/tests/prisma/mysql/schema.prisma +++ /dev/null @@ -1,20 +0,0 @@ -generator client { - provider = "prisma-client-js" - output = "./client" -} - -generator drizzle { - provider = "drizzle-prisma-generator" - output = "./drizzle" -} - -datasource db { - provider = "mysql" - url = env("MYSQL_CONNECTION_STRING") -} - -model User { - id Int @id @default(autoincrement()) - email String @unique - name String? -} diff --git a/integration-tests/tests/prisma/pg/prisma.test.ts b/integration-tests/tests/prisma/pg/prisma.test.ts deleted file mode 100644 index 16c5ce106..000000000 --- a/integration-tests/tests/prisma/pg/prisma.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import 'dotenv/config'; -import 'zx/globals'; - -import { drizzle } from 'drizzle-orm/prisma/pg'; -import type { PrismaPgDatabase } from 'drizzle-orm/prisma/pg'; -import { beforeAll, expect, expectTypeOf, test } from 'vitest'; - -import { PrismaClient } from './client'; -import { User } from './drizzle/schema.ts'; - -const ENABLE_LOGGING = false; - -let db: PrismaPgDatabase; - -beforeAll(async () => { - await $`prisma db push --force-reset --schema tests/prisma/pg/schema.prisma`.quiet(); - const prisma = new PrismaClient().$extends(drizzle({ logger: ENABLE_LOGGING })); - db = prisma.$drizzle; -}); - -test('extension works', async () => { - const insert = await db.insert(User).values({ email: 'test@test.com' }); - expectTypeOf(insert).toEqualTypeOf<[]>(); - expect(insert).toEqual([]); - - const result = await db.select().from(User); - expectTypeOf(result).toEqualTypeOf(); - expect(result).toEqual([{ id: 1, email: 'test@test.com', name: null }]); -}); diff --git a/integration-tests/tests/prisma/pg/schema.prisma b/integration-tests/tests/prisma/pg/schema.prisma deleted file mode 100644 index a5345d047..000000000 --- a/integration-tests/tests/prisma/pg/schema.prisma +++ /dev/null @@ -1,20 +0,0 @@ -generator client { - provider = "prisma-client-js" - output = "./client" -} - -generator drizzle { - provider = "drizzle-prisma-generator" - output = "./drizzle" -} - -datasource db { - provider = "postgresql" - url = env("PG_CONNECTION_STRING") -} - -model User { - id Int @id @default(autoincrement()) - email String @unique - name String? -} diff --git a/integration-tests/tests/prisma/sqlite/.gitignore b/integration-tests/tests/prisma/sqlite/.gitignore deleted file mode 100644 index 2fa69c243..000000000 --- a/integration-tests/tests/prisma/sqlite/.gitignore +++ /dev/null @@ -1 +0,0 @@ -db.sqlite diff --git a/integration-tests/tests/prisma/sqlite/prisma.test.ts b/integration-tests/tests/prisma/sqlite/prisma.test.ts deleted file mode 100644 index 4e8979cb8..000000000 --- a/integration-tests/tests/prisma/sqlite/prisma.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import 'dotenv/config'; -import 'zx/globals'; - -import { drizzle } from 'drizzle-orm/prisma/sqlite'; -import type { PrismaSQLiteDatabase } from 'drizzle-orm/prisma/sqlite'; -import { beforeAll, expect, expectTypeOf, test } from 'vitest'; - -import { PrismaClient } from './client'; -import { User } from './drizzle/schema.ts'; - -const ENABLE_LOGGING = false; - -let db: PrismaSQLiteDatabase; - -beforeAll(async () => { - await $`prisma db push --force-reset --schema tests/prisma/sqlite/schema.prisma`.quiet(); - const prisma = new PrismaClient().$extends(drizzle({ logger: ENABLE_LOGGING })); - db = prisma.$drizzle; -}); - -test('extension works', async () => { - const insert = await db.insert(User).values({ email: 'test@test.com' }); - expectTypeOf(insert).toEqualTypeOf<[]>(); - expect(insert).toEqual([]); - - const result = await db.select().from(User); - expectTypeOf(result).toEqualTypeOf(); - expect(result).toEqual([{ id: 1, email: 'test@test.com', name: null }]); - - const all = await db.select().from(User).all(); - expectTypeOf(all).toEqualTypeOf(); - expect(all).toEqual([{ id: 1, email: 'test@test.com', name: null }]); - - const get = await db.select().from(User).get(); - expectTypeOf(get).toEqualTypeOf(); - expect(get).toEqual({ id: 1, email: 'test@test.com', name: null }); - - const run = await db.insert(User).values({ email: 'test2@test.com' }).run(); - expectTypeOf(run).toEqualTypeOf<[]>(); - expect(run).toEqual([]); -}); diff --git a/integration-tests/tests/prisma/sqlite/schema.prisma b/integration-tests/tests/prisma/sqlite/schema.prisma deleted file mode 100644 index 6dbf2643e..000000000 --- a/integration-tests/tests/prisma/sqlite/schema.prisma +++ /dev/null @@ -1,20 +0,0 @@ -generator client { - provider = "prisma-client-js" - output = "./client" -} - -generator drizzle { - provider = "drizzle-prisma-generator" - output = "./drizzle" -} - -datasource db { - provider = "sqlite" - url = "file:./db.sqlite" -} - -model User { - id Int @id @default(autoincrement()) - email String @unique - name String? -} diff --git a/integration-tests/tests/relational/singlestore.schema.ts b/integration-tests/tests/relational/singlestore.schema.ts new file mode 100644 index 000000000..ca3386ba0 --- /dev/null +++ b/integration-tests/tests/relational/singlestore.schema.ts @@ -0,0 +1,106 @@ +import { bigint, boolean, primaryKey, serial, singlestoreTable, text, timestamp } from 'drizzle-orm/singlestore-core'; + +import { relations } from 'drizzle-orm'; + +export const usersTable = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + verified: boolean('verified').notNull().default(false), + invitedBy: bigint('invited_by', { mode: 'number' }), +}); +export const usersConfig = relations(usersTable, ({ one, many }) => ({ + invitee: one(usersTable, { + fields: [usersTable.invitedBy], + references: [usersTable.id], + }), + usersToGroups: many(usersToGroupsTable), + posts: many(postsTable), + comments: many(commentsTable), +})); + +export const groupsTable = singlestoreTable('groups', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + description: text('description'), +}); +export const groupsConfig = relations(groupsTable, ({ many }) => ({ + usersToGroups: many(usersToGroupsTable), +})); + +export const usersToGroupsTable = singlestoreTable( + 'users_to_groups', + { + id: serial('id').primaryKey(), + userId: bigint('user_id', { mode: 'number' }).notNull(), + groupId: bigint('group_id', { mode: 'number' }).notNull(), + }, + (t) => ({ + pk: primaryKey(t.userId, t.groupId), + }), +); +export const usersToGroupsConfig = relations(usersToGroupsTable, ({ one }) => ({ + group: one(groupsTable, { + fields: [usersToGroupsTable.groupId], + references: [groupsTable.id], + }), + user: one(usersTable, { + fields: [usersToGroupsTable.userId], + references: [usersTable.id], + }), +})); + +export const postsTable = singlestoreTable('posts', { + id: serial('id').primaryKey(), + content: text('content').notNull(), + ownerId: bigint('owner_id', { mode: 'number' }), + createdAt: timestamp('created_at') + .notNull() + .defaultNow(), +}); +export const postsConfig = relations(postsTable, ({ one, many }) => ({ + author: one(usersTable, { + fields: [postsTable.ownerId], + references: [usersTable.id], + }), + comments: many(commentsTable), +})); + +export const commentsTable = singlestoreTable('comments', { + id: serial('id').primaryKey(), + content: text('content').notNull(), + creator: bigint('creator', { mode: 'number' }), + postId: bigint('post_id', { mode: 'number' }), + createdAt: timestamp('created_at') + .notNull() + .defaultNow(), +}); +export const commentsConfig = relations(commentsTable, ({ one, many }) => ({ + post: one(postsTable, { + fields: [commentsTable.postId], + references: [postsTable.id], + }), + author: one(usersTable, { + fields: [commentsTable.creator], + references: [usersTable.id], + }), + likes: many(commentLikesTable), +})); + +export const commentLikesTable = singlestoreTable('comment_likes', { + id: serial('id').primaryKey(), + creator: bigint('creator', { mode: 'number' }), + commentId: bigint('comment_id', { mode: 'number' }), + createdAt: timestamp('created_at') + .notNull() + .defaultNow(), +}); +export const commentLikesConfig = relations(commentLikesTable, ({ one }) => ({ + comment: one(commentsTable, { + fields: [commentLikesTable.commentId], + references: [commentsTable.id], + }), + author: one(usersTable, { + fields: [commentLikesTable.creator], + references: [usersTable.id], + }), +})); diff --git a/integration-tests/tests/relational/singlestore.test.ts b/integration-tests/tests/relational/singlestore.test.ts new file mode 100644 index 000000000..0e20a7046 --- /dev/null +++ b/integration-tests/tests/relational/singlestore.test.ts @@ -0,0 +1,6403 @@ +// import retry from 'async-retry'; +// import Docker from 'dockerode'; +// import 'dotenv/config'; +// import { desc, DrizzleError, eq, gt, gte, or, placeholder, sql, TransactionRollbackError } from 'drizzle-orm'; +// import { drizzle, type SingleStoreDriverDatabase } from 'drizzle-orm/singlestore'; +// import getPort from 'get-port'; +// import * as mysql from 'mysql2/promise'; +// import { v4 as uuid } from 'uuid'; +// import { afterAll, beforeAll, beforeEach, expect, expectTypeOf, test } from 'vitest'; +// import * as schema from './singlestore.schema.ts'; + +// const { usersTable, postsTable, commentsTable, usersToGroupsTable, groupsTable } = schema; + +// const ENABLE_LOGGING = false; + +// /* +// Test cases: +// - querying nested relation without PK with additional fields +// */ + +// declare module 'vitest' { +// export interface TestContext { +// docker: Docker; +// singlestoreContainer: Docker.Container; +// singlestoreDb: SingleStoreDriverDatabase; +// singlestoreClient: mysql.Connection; +// } +// } + +// let globalDocker: Docker; +// let singlestoreContainer: Docker.Container; +// let db: SingleStoreDriverDatabase; +// let client: mysql.Connection; + +// async function createDockerDB(): Promise { +// const docker = new Docker(); +// const port = await getPort({ port: 3306 }); +// const image = 'ghcr.io/singlestore-labs/singlestoredb-dev:latest'; + +// const pullStream = await docker.pull(image); +// await new Promise((resolve, reject) => +// docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err))) +// ); + +// singlestoreContainer = await docker.createContainer({ +// Image: image, +// Env: ['ROOT_PASSWORD=singlestore'], +// name: `drizzle-integration-tests-${uuid()}`, +// HostConfig: { +// AutoRemove: true, +// PortBindings: { +// '3306/tcp': [{ HostPort: `${port}` }], +// }, +// }, +// }); + +// await singlestoreContainer.start(); +// await new Promise((resolve) => setTimeout(resolve, 4000)); + +// return `singlestore://root:singlestore@localhost:${port}/`; +// } + +// beforeAll(async () => { +// const connectionString = process.env['SINGLESTORE_CONNECTION_STRING'] ?? (await createDockerDB()); +// client = await retry(async () => { +// client = await mysql.createConnection(connectionString); +// await client.connect(); +// return client; +// }, { +// retries: 20, +// factor: 1, +// minTimeout: 250, +// maxTimeout: 250, +// randomize: false, +// onRetry() { +// client?.end(); +// }, +// }); + +// await client.query(`CREATE DATABASE IF NOT EXISTS drizzle;`); +// await client.changeUser({ database: 'drizzle' }); +// db = drizzle(client, { schema, logger: ENABLE_LOGGING }); +// }); + +// afterAll(async () => { +// await client?.end().catch(console.error); +// await singlestoreContainer?.stop().catch(console.error); +// }); + +// beforeEach(async (ctx) => { +// ctx.singlestoreDb = db; +// ctx.singlestoreClient = client; +// ctx.docker = globalDocker; +// ctx.singlestoreContainer = singlestoreContainer; + +// await ctx.singlestoreDb.execute(sql`drop table if exists \`users\``); +// await ctx.singlestoreDb.execute(sql`drop table if exists \`groups\``); +// await ctx.singlestoreDb.execute(sql`drop table if exists \`users_to_groups\``); +// await ctx.singlestoreDb.execute(sql`drop table if exists \`posts\``); +// await ctx.singlestoreDb.execute(sql`drop table if exists \`comments\``); +// await ctx.singlestoreDb.execute(sql`drop table if exists \`comment_likes\``); + +// await ctx.singlestoreDb.execute( +// sql` +// CREATE TABLE \`users\` ( +// \`id\` serial PRIMARY KEY NOT NULL, +// \`name\` text NOT NULL, +// \`verified\` boolean DEFAULT false NOT NULL, +// \`invited_by\` bigint +// ); +// `, +// ); +// await ctx.singlestoreDb.execute( +// sql` +// CREATE TABLE \`groups\` ( +// \`id\` serial PRIMARY KEY NOT NULL, +// \`name\` text NOT NULL, +// \`description\` text +// ); +// `, +// ); +// await ctx.singlestoreDb.execute( +// sql` +// CREATE TABLE \`users_to_groups\` ( +// \`id\` serial PRIMARY KEY NOT NULL, +// \`user_id\` bigint, +// \`group_id\` bigint +// ); +// `, +// ); +// await ctx.singlestoreDb.execute( +// sql` +// CREATE TABLE \`posts\` ( +// \`id\` serial PRIMARY KEY NOT NULL, +// \`content\` text NOT NULL, +// \`owner_id\` bigint, +// \`created_at\` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL +// ); +// `, +// ); +// await ctx.singlestoreDb.execute( +// sql` +// CREATE TABLE \`comments\` ( +// \`id\` serial PRIMARY KEY NOT NULL, +// \`content\` text NOT NULL, +// \`creator\` bigint, +// \`post_id\` bigint, +// \`created_at\` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL +// ); +// `, +// ); +// await ctx.singlestoreDb.execute( +// sql` +// CREATE TABLE \`comment_likes\` ( +// \`id\` serial PRIMARY KEY NOT NULL, +// \`creator\` bigint, +// \`comment_id\` bigint, +// \`created_at\` timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL +// ); +// `, +// ); +// }); + +// /* +// [Find Many] One relation users+posts +// */ + +// test('[Find Many] Get users with posts', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// with: { +// posts: true, +// }, +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// usersWithPosts.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(usersWithPosts.length).eq(3); +// expect(usersWithPosts[0]?.posts.length).eq(1); +// expect(usersWithPosts[1]?.posts.length).eq(1); +// expect(usersWithPosts[2]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// posts: [{ id: 2, ownerId: 2, content: 'Post2', createdAt: usersWithPosts[1]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts[2]).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// posts: [{ id: 3, ownerId: 3, content: 'Post3', createdAt: usersWithPosts[2]?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('[Find Many] Get users with posts + limit posts', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// with: { +// posts: { +// limit: 1, +// }, +// }, +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// usersWithPosts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// usersWithPosts[0]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// usersWithPosts[1]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// usersWithPosts[2]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(usersWithPosts.length).eq(3); +// expect(usersWithPosts[0]?.posts.length).eq(1); +// expect(usersWithPosts[1]?.posts.length).eq(1); +// expect(usersWithPosts[2]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// posts: [{ id: 4, ownerId: 2, content: 'Post2', createdAt: usersWithPosts[1]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts[2]).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// posts: [{ id: 6, ownerId: 3, content: 'Post3', createdAt: usersWithPosts[2]?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('[Find Many] Get users with posts + limit posts and users', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// limit: 2, +// with: { +// posts: { +// limit: 1, +// }, +// }, +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// usersWithPosts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// usersWithPosts[0]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// usersWithPosts[1]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(usersWithPosts.length).eq(2); +// expect(usersWithPosts[0]?.posts.length).eq(1); +// expect(usersWithPosts[1]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// posts: [{ id: 4, ownerId: 2, content: 'Post2', createdAt: usersWithPosts[1]?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find Many] Get users with posts + custom fields', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// with: { +// posts: true, +// }, +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// lowerName: string; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// usersWithPosts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// usersWithPosts[0]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// usersWithPosts[1]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// usersWithPosts[2]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(usersWithPosts.length).toEqual(3); +// expect(usersWithPosts[0]?.posts.length).toEqual(3); +// expect(usersWithPosts[1]?.posts.length).toEqual(2); +// expect(usersWithPosts[2]?.posts.length).toEqual(2); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// lowerName: 'dan', +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }, { +// id: 2, +// ownerId: 1, +// content: 'Post1.2', +// createdAt: usersWithPosts[0]?.posts[1]?.createdAt, +// }, { id: 3, ownerId: 1, content: 'Post1.3', createdAt: usersWithPosts[0]?.posts[2]?.createdAt }], +// }); +// expect(usersWithPosts[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// lowerName: 'andrew', +// verified: false, +// invitedBy: null, +// posts: [{ id: 4, ownerId: 2, content: 'Post2', createdAt: usersWithPosts[1]?.posts[0]?.createdAt }, { +// id: 5, +// ownerId: 2, +// content: 'Post2.1', +// createdAt: usersWithPosts[1]?.posts[1]?.createdAt, +// }], +// }); +// expect(usersWithPosts[2]).toEqual({ +// id: 3, +// name: 'Alex', +// lowerName: 'alex', +// verified: false, +// invitedBy: null, +// posts: [{ id: 6, ownerId: 3, content: 'Post3', createdAt: usersWithPosts[2]?.posts[0]?.createdAt }, { +// id: 7, +// ownerId: 3, +// content: 'Post3.1', +// createdAt: usersWithPosts[2]?.posts[1]?.createdAt, +// }], +// }); +// }); + +// test.skip('[Find Many] Get users with posts + custom fields + limits', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// limit: 1, +// with: { +// posts: { +// limit: 1, +// }, +// }, +// extras: (usersTable, { sql }) => ({ +// lowerName: sql`lower(${usersTable.name})`.as('name_lower'), +// }), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// lowerName: string; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).toEqual(1); +// expect(usersWithPosts[0]?.posts.length).toEqual(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// lowerName: 'dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('[Find Many] Get users with posts + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: '1' }, +// { ownerId: 1, content: '2' }, +// { ownerId: 1, content: '3' }, +// { ownerId: 2, content: '4' }, +// { ownerId: 2, content: '5' }, +// { ownerId: 3, content: '6' }, +// { ownerId: 3, content: '7' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// with: { +// posts: { +// orderBy: (postsTable, { desc }) => [desc(postsTable.content)], +// }, +// }, +// orderBy: (usersTable, { desc }) => [desc(usersTable.id)], +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(3); +// expect(usersWithPosts[0]?.posts.length).eq(2); +// expect(usersWithPosts[1]?.posts.length).eq(2); +// expect(usersWithPosts[2]?.posts.length).eq(3); + +// expect(usersWithPosts[2]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 3, ownerId: 1, content: '3', createdAt: usersWithPosts[2]?.posts[2]?.createdAt }, { +// id: 2, +// ownerId: 1, +// content: '2', +// createdAt: usersWithPosts[2]?.posts[1]?.createdAt, +// }, { id: 1, ownerId: 1, content: '1', createdAt: usersWithPosts[2]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// posts: [{ +// id: 5, +// ownerId: 2, +// content: '5', +// createdAt: usersWithPosts[1]?.posts[1]?.createdAt, +// }, { id: 4, ownerId: 2, content: '4', createdAt: usersWithPosts[1]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts[0]).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// posts: [{ +// id: 7, +// ownerId: 3, +// content: '7', +// createdAt: usersWithPosts[0]?.posts[1]?.createdAt, +// }, { id: 6, ownerId: 3, content: '6', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find Many] Get users with posts + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// where: (({ id }, { eq }) => eq(id, 1)), +// with: { +// posts: { +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(1); +// expect(usersWithPosts[0]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find Many] Get users with posts + where + partial', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// columns: { +// id: true, +// name: true, +// }, +// with: { +// posts: { +// columns: { +// id: true, +// content: true, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// posts: { +// id: number; +// content: string; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(1); +// expect(usersWithPosts[0]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// posts: [{ id: 1, content: 'Post1' }], +// }); +// }); + +// test('[Find Many] Get users with posts + where + partial. Did not select posts id, but used it in where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// columns: { +// id: true, +// name: true, +// }, +// with: { +// posts: { +// columns: { +// id: true, +// content: true, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// posts: { +// id: number; +// content: string; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(1); +// expect(usersWithPosts[0]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// posts: [{ id: 1, content: 'Post1' }], +// }); +// }); + +// test('[Find Many] Get users with posts + where + partial(true + false)', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// columns: { +// id: true, +// name: false, +// }, +// with: { +// posts: { +// columns: { +// id: true, +// content: false, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// posts: { +// id: number; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(1); +// expect(usersWithPosts[0]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// posts: [{ id: 1 }], +// }); +// }); + +// test('[Find Many] Get users with posts + where + partial(false)', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// columns: { +// name: false, +// }, +// with: { +// posts: { +// columns: { +// content: false, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(1); +// expect(usersWithPosts[0]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find Many] Get users with posts in transaction', async (t) => { +// const { singlestoreDb: db } = t; + +// let usersWithPosts: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[] = []; + +// await db.transaction(async (tx) => { +// await tx.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await tx.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// usersWithPosts = await tx.query.usersTable.findMany({ +// where: (({ id }, { eq }) => eq(id, 1)), +// with: { +// posts: { +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// }); +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(1); +// expect(usersWithPosts[0]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find Many] Get users with posts in rollbacked transaction', async (t) => { +// const { singlestoreDb: db } = t; + +// let usersWithPosts: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[] = []; + +// await expect(db.transaction(async (tx) => { +// await tx.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await tx.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// tx.rollback(); + +// usersWithPosts = await tx.query.usersTable.findMany({ +// where: (({ id }, { eq }) => eq(id, 1)), +// with: { +// posts: { +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// }); +// })).rejects.toThrowError(new TransactionRollbackError()); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(0); +// }); + +// // select only custom +// test('[Find Many] Get only custom fields', async () => { +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { id: 1, ownerId: 1, content: 'Post1' }, +// { id: 2, ownerId: 1, content: 'Post1.2' }, +// { id: 3, ownerId: 1, content: 'Post1.3' }, +// { id: 4, ownerId: 2, content: 'Post2' }, +// { id: 5, ownerId: 2, content: 'Post2.1' }, +// { id: 6, ownerId: 3, content: 'Post3' }, +// { id: 7, ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// extras: ({ content }) => ({ +// lowerName: sql`lower(${content})`.as('content_lower'), +// }), +// }, +// }, +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// // Type Assertion +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// lowerName: string; +// posts: { +// lowerName: string; +// }[]; +// }[]>(); + +// // General Assertions +// expect(usersWithPosts).toHaveLength(3); + +// // Helper function to find user by lowerName +// const findUser = (lowerName: string) => usersWithPosts.find((user) => user.lowerName === lowerName); + +// // Assertions for each user +// const dan = findUser('dan'); +// const andrew = findUser('andrew'); +// const alex = findUser('alex'); + +// expect(dan).toBeDefined(); +// expect(andrew).toBeDefined(); +// expect(alex).toBeDefined(); + +// // Verify the number of posts for each user +// expect(dan?.posts).toHaveLength(3); +// expect(andrew?.posts).toHaveLength(2); +// expect(alex?.posts).toHaveLength(2); + +// // Define expected posts for each user +// const expectedDanPosts = ['post1', 'post1.2', 'post1.3']; +// const expectedAndrewPosts = ['post2', 'post2.1']; +// const expectedAlexPosts = ['post3', 'post3.1']; + +// // Helper function to extract lowerNames from posts +// const getPostLowerNames = (posts: { lowerName: string }[]) => posts.map((post) => post.lowerName); + +// // Assertions for Dan's posts +// expect(getPostLowerNames(dan!.posts)).toEqual(expect.arrayContaining(expectedDanPosts)); +// expect(getPostLowerNames(dan!.posts)).toHaveLength(expectedDanPosts.length); + +// // Assertions for Andrew's posts +// expect(getPostLowerNames(andrew!.posts)).toEqual(expect.arrayContaining(expectedAndrewPosts)); +// expect(getPostLowerNames(andrew!.posts)).toHaveLength(expectedAndrewPosts.length); + +// // Assertions for Alex's posts +// expect(getPostLowerNames(alex!.posts)).toEqual(expect.arrayContaining(expectedAlexPosts)); +// expect(getPostLowerNames(alex!.posts)).toHaveLength(expectedAlexPosts.length); +// }); + +// // select only custom with where clause (Order Agnostic) +// test('[Find Many] Get only custom fields + where', async (t) => { +// const { singlestoreDb: db } = t; + +// // Insert Users +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// // Insert Posts +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// // Query Users with Posts where users.id = 1 and posts.id >= 2 +// const usersWithPosts = await db.query.usersTable.findMany({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// where: gte(postsTable.id, 2), +// extras: ({ content }) => ({ +// lowerName: sql`lower(${content})`.as('content_lower'), +// }), +// }, +// }, +// where: eq(usersTable.id, 1), +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// // Type Assertion +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// lowerName: string; +// posts: { +// lowerName: string; +// }[]; +// }[]>(); + +// // General Assertions +// expect(usersWithPosts).toHaveLength(1); + +// // Since we expect only one user, we can extract it directly +// const danWithPosts = usersWithPosts[0]; + +// // Assert that the user exists and has the correct lowerName +// expect(danWithPosts).toBeDefined(); +// expect(danWithPosts?.lowerName).toBe('dan'); + +// // Assert that the user has the expected number of posts +// expect(danWithPosts?.posts).toHaveLength(2); + +// // Define the expected posts +// const expectedPosts = ['post1.2', 'post1.3']; + +// // Extract the lowerName of each post +// const actualPostLowerNames = danWithPosts?.posts.map((post) => post.lowerName); + +// // Assert that all expected posts are present, regardless of order +// for (const expectedPost of expectedPosts) { +// expect(actualPostLowerNames).toContain(expectedPost); +// } + +// // Additionally, ensure no unexpected posts are present +// expect(actualPostLowerNames).toHaveLength(expectedPosts.length); +// }); + +// test.skip('[Find Many] Get only custom fields + where + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// where: gte(postsTable.id, 2), +// limit: 1, +// extras: ({ content }) => ({ +// lowerName: sql`lower(${content})`.as('content_lower'), +// }), +// }, +// }, +// where: eq(usersTable.id, 1), +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// lowerName: string; +// posts: { +// lowerName: string; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).toEqual(1); +// expect(usersWithPosts[0]?.posts.length).toEqual(1); + +// expect(usersWithPosts).toContainEqual({ +// lowerName: 'dan', +// posts: [{ lowerName: 'post1.2' }], +// }); +// }); + +// test.skip('[Find Many] Get only custom fields + where + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findMany({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// where: gte(postsTable.id, 2), +// orderBy: [desc(postsTable.id)], +// extras: ({ content }) => ({ +// lowerName: sql`lower(${content})`.as('content_lower'), +// }), +// }, +// }, +// where: eq(usersTable.id, 1), +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// lowerName: string; +// posts: { +// lowerName: string; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).toEqual(1); +// expect(usersWithPosts[0]?.posts.length).toEqual(2); + +// expect(usersWithPosts).toContainEqual({ +// lowerName: 'dan', +// posts: [{ lowerName: 'post1.3' }, { lowerName: 'post1.2' }], +// }); +// }); + +// // select only custom find one (Order Agnostic) +// test('[Find One] Get only custom fields (Order Agnostic)', async () => { +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// // Query to find the first user without any specific order +// const usersWithPosts = await db.query.usersTable.findFirst({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// extras: ({ content }) => ({ +// lowerName: sql`lower(${content})`.as('content_lower'), +// }), +// }, +// }, +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// // Type Assertion +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// lowerName: string; +// posts: { +// lowerName: string; +// }[]; +// } | undefined +// >(); + +// // General Assertions +// expect(usersWithPosts).toBeDefined(); + +// // Since findFirst without orderBy can return any user, we'll verify the returned user and their posts +// if (usersWithPosts) { +// // Define expected users and their corresponding posts +// const expectedUsers: { [key: string]: string[] } = { +// dan: ['post1', 'post1.2', 'post1.3'], +// andrew: ['post2', 'post2.1'], +// alex: ['post3', 'post3.1'], +// }; + +// // Verify that the returned user is one of the expected users +// expect(Object.keys(expectedUsers)).toContain(usersWithPosts.lowerName); + +// // Get the expected posts for the returned user +// const expectedPosts = expectedUsers[usersWithPosts.lowerName] as string[]; + +// // Verify the number of posts +// expect(usersWithPosts.posts).toHaveLength(expectedPosts.length); + +// // Extract the lowerName of each post +// const actualPostLowerNames = usersWithPosts.posts.map((post) => post.lowerName); + +// // Assert that all expected posts are present, regardless of order +// for (const expectedPost of expectedPosts) { +// expect(actualPostLowerNames).toContain(expectedPost.toLowerCase()); +// } +// } +// }); + +// // select only custom find one with where clause (Order Agnostic) +// test('[Find One] Get only custom fields + where (Order Agnostic)', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// // Query to find the first user with id = 1 and posts with id >= 2 +// const usersWithPosts = await db.query.usersTable.findFirst({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// where: gte(postsTable.id, 2), +// extras: ({ content }) => ({ +// lowerName: sql`lower(${content})`.as('content_lower'), +// }), +// }, +// }, +// where: eq(usersTable.id, 1), +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// // Type Assertion +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// lowerName: string; +// posts: { +// lowerName: string; +// }[]; +// } | undefined +// >(); + +// // General Assertions +// expect(usersWithPosts).toBeDefined(); + +// if (usersWithPosts) { +// // Assert that the returned user has the expected lowerName +// expect(usersWithPosts.lowerName).toBe('dan'); + +// // Assert that the user has exactly two posts +// expect(usersWithPosts.posts).toHaveLength(2); + +// // Define the expected posts +// const expectedPosts = ['post1.2', 'post1.3']; + +// // Extract the lowerName of each post +// const actualPostLowerNames = usersWithPosts.posts.map((post) => post.lowerName); + +// // Assert that all expected posts are present, regardless of order +// for (const expectedPost of expectedPosts) { +// expect(actualPostLowerNames).toContain(expectedPost.toLowerCase()); +// } + +// // Additionally, ensure no unexpected posts are present +// expect(actualPostLowerNames).toHaveLength(expectedPosts.length); +// } +// }); + +// test.skip('[Find One] Get only custom fields + where + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// where: gte(postsTable.id, 2), +// limit: 1, +// extras: ({ content }) => ({ +// lowerName: sql`lower(${content})`.as('content_lower'), +// }), +// }, +// }, +// where: eq(usersTable.id, 1), +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// lowerName: string; +// posts: { +// lowerName: string; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts?.posts.length).toEqual(1); + +// expect(usersWithPosts).toEqual({ +// lowerName: 'dan', +// posts: [{ lowerName: 'post1.2' }], +// }); +// }); + +// test.skip('[Find One] Get only custom fields + where + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// where: gte(postsTable.id, 2), +// orderBy: [desc(postsTable.id)], +// extras: ({ content }) => ({ +// lowerName: sql`lower(${content})`.as('content_lower'), +// }), +// }, +// }, +// where: eq(usersTable.id, 1), +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// lowerName: string; +// posts: { +// lowerName: string; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts?.posts.length).toEqual(2); + +// expect(usersWithPosts).toEqual({ +// lowerName: 'dan', +// posts: [{ lowerName: 'post1.3' }, { lowerName: 'post1.2' }], +// }); +// }); + +// // columns {} +// test('[Find Many] Get select {}', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await expect( +// async () => +// await db.query.usersTable.findMany({ +// columns: {}, +// }), +// ).rejects.toThrow(DrizzleError); +// }); + +// // columns {} +// test('[Find One] Get select {}', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await expect(async () => +// await db.query.usersTable.findFirst({ +// columns: {}, +// }) +// ).rejects.toThrow(DrizzleError); +// }); + +// // deep select {} +// test('[Find Many] Get deep select {}', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// await expect(async () => +// await db.query.usersTable.findMany({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// }, +// }, +// }) +// ).rejects.toThrow(DrizzleError); +// }); + +// // deep select {} +// test('[Find One] Get deep select {}', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// await expect(async () => +// await db.query.usersTable.findFirst({ +// columns: {}, +// with: { +// posts: { +// columns: {}, +// }, +// }, +// }) +// ).rejects.toThrow(DrizzleError); +// }); + +// /* +// Prepared statements for users+posts +// */ +// test.skip('[Find Many] Get users with posts + prepared limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const prepared = db.query.usersTable.findMany({ +// with: { +// posts: { +// limit: placeholder('limit'), +// }, +// }, +// }).prepare(); + +// const usersWithPosts = await prepared.execute({ limit: 1 }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(3); +// expect(usersWithPosts[0]?.posts.length).eq(1); +// expect(usersWithPosts[1]?.posts.length).eq(1); +// expect(usersWithPosts[2]?.posts.length).eq(1); + +// expect(usersWithPosts).toContainEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts).toContainEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// posts: [{ id: 4, ownerId: 2, content: 'Post2', createdAt: usersWithPosts[1]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// posts: [{ id: 6, ownerId: 3, content: 'Post3', createdAt: usersWithPosts[2]?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('[Find Many] Get users with posts + prepared limit + offset', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const prepared = db.query.usersTable.findMany({ +// limit: placeholder('uLimit'), +// offset: placeholder('uOffset'), +// with: { +// posts: { +// limit: placeholder('pLimit'), +// }, +// }, +// }).prepare(); + +// const usersWithPosts = await prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1 }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(2); +// expect(usersWithPosts[0]?.posts.length).eq(1); +// expect(usersWithPosts[1]?.posts.length).eq(1); + +// expect(usersWithPosts).toContainEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// posts: [{ id: 4, ownerId: 2, content: 'Post2', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// expect(usersWithPosts).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// posts: [{ id: 6, ownerId: 3, content: 'Post3', createdAt: usersWithPosts[1]?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find Many] Get users with posts + prepared where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const prepared = db.query.usersTable.findMany({ +// where: (({ id }, { eq }) => eq(id, placeholder('id'))), +// with: { +// posts: { +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// }).prepare(); + +// const usersWithPosts = await prepared.execute({ id: 1 }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(1); +// expect(usersWithPosts[0]?.posts.length).eq(1); + +// expect(usersWithPosts[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('[Find Many] Get users with posts + prepared + limit + offset + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const prepared = db.query.usersTable.findMany({ +// limit: placeholder('uLimit'), +// offset: placeholder('uOffset'), +// where: (({ id }, { eq, or }) => or(eq(id, placeholder('id')), eq(id, 3))), +// with: { +// posts: { +// where: (({ id }, { eq }) => eq(id, placeholder('pid'))), +// limit: placeholder('pLimit'), +// }, +// }, +// }).prepare(); + +// const usersWithPosts = await prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1, id: 2, pid: 6 }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// }[]>(); + +// expect(usersWithPosts.length).eq(1); +// expect(usersWithPosts[0]?.posts.length).eq(1); + +// expect(usersWithPosts).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// posts: [{ id: 6, ownerId: 3, content: 'Post3', createdAt: usersWithPosts[0]?.posts[0]?.createdAt }], +// }); +// }); + +// /* +// [Find One] One relation users+posts +// */ + +// test.only('[Find One] Get users with posts', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// with: { +// posts: true, +// }, +// }); + +// // Type Assertion +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// // General Assertions +// expect(usersWithPosts).toBeDefined(); + +// if (usersWithPosts) { +// const { id, name, posts } = usersWithPosts; + +// // Verify that the user is one of the inserted users +// const validUsers: { [key: number]: string } = { +// 1: 'dan', +// 2: 'andrew', +// 3: 'alex', +// }; +// expect(validUsers[id]).toBe(name.toLowerCase()); + +// // Assert that the user has exactly one post +// expect(posts).toHaveLength(1); + +// const post = posts[0]; + +// // Verify that the post belongs to the user +// expect(post?.ownerId).toBe(id); + +// // Verify that the post content matches the user +// const expectedPostContent = `Post${id}`; +// expect(post?.content.toLowerCase()).toBe(expectedPostContent.toLowerCase()); + +// // Optionally, verify the presence of `createdAt` +// expect(post?.createdAt).toBeInstanceOf(Date); +// } +// }); + +// test.skip('[Find One] Get users with posts + limit posts', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// with: { +// posts: { +// limit: 1, +// }, +// }, +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).eq(1); + +// expect(usersWithPosts).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('[Find One] Get users with posts no results found', async (t) => { +// const { singlestoreDb: db } = t; + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// with: { +// posts: { +// limit: 1, +// }, +// }, +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts).toBeUndefined(); +// }); + +// test.skip('[Find One] Get users with posts + limit posts and users', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// with: { +// posts: { +// limit: 1, +// }, +// }, +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).eq(1); + +// expect(usersWithPosts).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find One] Get users with posts + custom fields', async () => { +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// with: { +// posts: true, +// }, +// extras: ({ name }) => ({ +// lowerName: sql`lower(${name})`.as('name_lower'), +// }), +// }); + +// // Type Assertion +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// lowerName: string; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// // General Assertions +// expect(usersWithPosts).toBeDefined(); + +// if (usersWithPosts) { +// const { id, lowerName, posts } = usersWithPosts; + +// // Define valid users and their expected lower names +// const validUsers: { [key: number]: string } = { +// 1: 'dan', +// 2: 'andrew', +// 3: 'alex', +// }; + +// // Verify that the returned user's lowerName matches the expected value +// expect(validUsers[id]).toBe(lowerName); + +// // Define the expected posts based on the user ID +// const expectedPostsByUser: Record = { +// 1: ['post1', 'post1.2', 'post1.3'], +// 2: ['post2', 'post2.1'], +// 3: ['post3', 'post3.1'], +// }; + +// // Get the expected posts for the returned user +// const expectedPosts = expectedPostsByUser[id] || []; + +// // Extract the lowerName of each post +// const actualPostContents = posts.map((post) => post.content.toLowerCase()); + +// // Assert that all expected posts are present, regardless of order +// for (const expectedPost of expectedPosts) { +// expect(actualPostContents).toContain(expectedPost.toLowerCase()); +// } + +// // Optionally, ensure that no unexpected posts are present +// expect(actualPostContents).toHaveLength(expectedPosts.length); +// } +// }); + +// test.skip('[Find One] Get users with posts + custom fields + limits', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.2' }, +// { ownerId: 1, content: 'Post1.3' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// with: { +// posts: { +// limit: 1, +// }, +// }, +// extras: (usersTable, { sql }) => ({ +// lowerName: sql`lower(${usersTable.name})`.as('name_lower'), +// }), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// lowerName: string; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).toEqual(1); + +// expect(usersWithPosts).toEqual({ +// id: 1, +// name: 'Dan', +// lowerName: 'dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('[Find One] Get users with posts + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: '1' }, +// { ownerId: 1, content: '2' }, +// { ownerId: 1, content: '3' }, +// { ownerId: 2, content: '4' }, +// { ownerId: 2, content: '5' }, +// { ownerId: 3, content: '6' }, +// { ownerId: 3, content: '7' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// with: { +// posts: { +// orderBy: (postsTable, { desc }) => [desc(postsTable.content)], +// }, +// }, +// orderBy: (usersTable, { desc }) => [desc(usersTable.id)], +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).eq(2); + +// expect(usersWithPosts).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// posts: [{ +// id: 7, +// ownerId: 3, +// content: '7', +// createdAt: usersWithPosts?.posts[1]?.createdAt, +// }, { id: 6, ownerId: 3, content: '6', createdAt: usersWithPosts?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find One] Get users with posts + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// where: (({ id }, { eq }) => eq(id, 1)), +// with: { +// posts: { +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).eq(1); + +// expect(usersWithPosts).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: usersWithPosts?.posts[0]?.createdAt }], +// }); +// }); + +// test('[Find One] Get users with posts + where + partial', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// columns: { +// id: true, +// name: true, +// }, +// with: { +// posts: { +// columns: { +// id: true, +// content: true, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// posts: { +// id: number; +// content: string; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).eq(1); + +// expect(usersWithPosts).toEqual({ +// id: 1, +// name: 'Dan', +// posts: [{ id: 1, content: 'Post1' }], +// }); +// }); + +// test.skip('[Find One] Get users with posts + where + partial. Did not select posts id, but used it in where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// columns: { +// id: true, +// name: true, +// }, +// with: { +// posts: { +// columns: { +// id: true, +// content: true, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// name: string; +// posts: { +// id: number; +// content: string; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).eq(1); + +// expect(usersWithPosts).toEqual({ +// id: 1, +// name: 'Dan', +// posts: [{ id: 1, content: 'Post1' }], +// }); +// }); + +// test.skip('[Find One] Get users with posts + where + partial(true + false)', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// columns: { +// id: true, +// name: false, +// }, +// with: { +// posts: { +// columns: { +// id: true, +// content: false, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// posts: { +// id: number; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).eq(1); + +// expect(usersWithPosts).toEqual({ +// id: 1, +// posts: [{ id: 1 }], +// }); +// }); + +// test.skip('[Find One] Get users with posts + where + partial(false)', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const usersWithPosts = await db.query.usersTable.findFirst({ +// columns: { +// name: false, +// }, +// with: { +// posts: { +// columns: { +// content: false, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }, +// }, +// where: (({ id }, { eq }) => eq(id, 1)), +// }); + +// expectTypeOf(usersWithPosts).toEqualTypeOf< +// { +// id: number; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// ownerId: number | null; +// createdAt: Date; +// }[]; +// } | undefined +// >(); + +// expect(usersWithPosts!.posts.length).eq(1); + +// expect(usersWithPosts).toEqual({ +// id: 1, +// verified: false, +// invitedBy: null, +// posts: [{ id: 1, ownerId: 1, createdAt: usersWithPosts?.posts[0]?.createdAt }], +// }); +// }); + +// /* +// One relation users+users. Self referencing +// */ + +// test.skip('Get user with invitee', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// with: { +// invitee: true, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// usersWithInvitee.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(usersWithInvitee.length).eq(4); +// expect(usersWithInvitee[0]?.invitee).toBeNull(); +// expect(usersWithInvitee[1]?.invitee).toBeNull(); +// expect(usersWithInvitee[2]?.invitee).not.toBeNull(); +// expect(usersWithInvitee[3]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[2]).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// }); +// expect(usersWithInvitee[3]).toEqual({ +// id: 4, +// name: 'John', +// verified: false, +// invitedBy: 2, +// invitee: { id: 2, name: 'Andrew', verified: false, invitedBy: null }, +// }); +// }); + +// test.skip('Get user + limit with invitee', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew', invitedBy: 1 }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// with: { +// invitee: true, +// }, +// limit: 2, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// usersWithInvitee.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(usersWithInvitee.length).eq(2); +// expect(usersWithInvitee[0]?.invitee).toBeNull(); +// expect(usersWithInvitee[1]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// }); +// }); + +// test.skip('Get user with invitee and custom fields', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// extras: (users, { sql }) => ({ lower: sql`lower(${users.name})`.as('lower_name') }), +// with: { +// invitee: { +// extras: (invitee, { sql }) => ({ lower: sql`lower(${invitee.name})`.as('lower_name') }), +// }, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// lower: string; +// invitedBy: number | null; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// lower: string; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// usersWithInvitee.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(usersWithInvitee.length).eq(4); +// expect(usersWithInvitee[0]?.invitee).toBeNull(); +// expect(usersWithInvitee[1]?.invitee).toBeNull(); +// expect(usersWithInvitee[2]?.invitee).not.toBeNull(); +// expect(usersWithInvitee[3]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee[0]).toEqual({ +// id: 1, +// name: 'Dan', +// lower: 'dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// lower: 'andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[2]).toEqual({ +// id: 3, +// name: 'Alex', +// lower: 'alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', lower: 'dan', verified: false, invitedBy: null }, +// }); +// expect(usersWithInvitee[3]).toEqual({ +// id: 4, +// name: 'John', +// lower: 'john', +// verified: false, +// invitedBy: 2, +// invitee: { id: 2, name: 'Andrew', lower: 'andrew', verified: false, invitedBy: null }, +// }); +// }); + +// test.skip('Get user with invitee and custom fields + limits', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// extras: (users, { sql }) => ({ lower: sql`lower(${users.name})`.as('lower_name') }), +// limit: 3, +// with: { +// invitee: { +// extras: (invitee, { sql }) => ({ lower: sql`lower(${invitee.name})`.as('lower_name') }), +// }, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// lower: string; +// invitedBy: number | null; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// lower: string; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// usersWithInvitee.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(usersWithInvitee.length).eq(3); +// expect(usersWithInvitee[0]?.invitee).toBeNull(); +// expect(usersWithInvitee[1]?.invitee).toBeNull(); +// expect(usersWithInvitee[2]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee[0]).toEqual({ +// id: 1, +// name: 'Dan', +// lower: 'dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// lower: 'andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[2]).toEqual({ +// id: 3, +// name: 'Alex', +// lower: 'alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', lower: 'dan', verified: false, invitedBy: null }, +// }); +// }); + +// test.skip('Get user with invitee + order by', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// orderBy: (users, { desc }) => [desc(users.id)], +// with: { +// invitee: true, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// expect(usersWithInvitee.length).eq(4); +// expect(usersWithInvitee[3]?.invitee).toBeNull(); +// expect(usersWithInvitee[2]?.invitee).toBeNull(); +// expect(usersWithInvitee[1]?.invitee).not.toBeNull(); +// expect(usersWithInvitee[0]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee[3]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[2]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// }); +// expect(usersWithInvitee[1]).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// }); +// expect(usersWithInvitee[0]).toEqual({ +// id: 4, +// name: 'John', +// verified: false, +// invitedBy: 2, +// invitee: { id: 2, name: 'Andrew', verified: false, invitedBy: null }, +// }); +// }); + +// test.skip('Get user with invitee + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// where: (users, { eq, or }) => (or(eq(users.id, 3), eq(users.id, 4))), +// with: { +// invitee: true, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// expect(usersWithInvitee.length).eq(2); +// expect(usersWithInvitee[0]?.invitee).not.toBeNull(); +// expect(usersWithInvitee[1]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// }); +// expect(usersWithInvitee).toContainEqual({ +// id: 4, +// name: 'John', +// verified: false, +// invitedBy: 2, +// invitee: { id: 2, name: 'Andrew', verified: false, invitedBy: null }, +// }); +// }); + +// test.skip('Get user with invitee + where + partial', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// where: (users, { eq, or }) => (or(eq(users.id, 3), eq(users.id, 4))), +// columns: { +// id: true, +// name: true, +// }, +// with: { +// invitee: { +// columns: { +// id: true, +// name: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// invitee: { +// id: number; +// name: string; +// } | null; +// }[] +// >(); + +// expect(usersWithInvitee.length).eq(2); +// expect(usersWithInvitee[0]?.invitee).not.toBeNull(); +// expect(usersWithInvitee[1]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee).toContainEqual({ +// id: 3, +// name: 'Alex', +// invitee: { id: 1, name: 'Dan' }, +// }); +// expect(usersWithInvitee).toContainEqual({ +// id: 4, +// name: 'John', +// invitee: { id: 2, name: 'Andrew' }, +// }); +// }); + +// test.skip('Get user with invitee + where + partial. Did not select users id, but used it in where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// where: (users, { eq, or }) => (or(eq(users.id, 3), eq(users.id, 4))), +// columns: { +// name: true, +// }, +// with: { +// invitee: { +// columns: { +// id: true, +// name: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// name: string; +// invitee: { +// id: number; +// name: string; +// } | null; +// }[] +// >(); + +// expect(usersWithInvitee.length).eq(2); +// expect(usersWithInvitee[0]?.invitee).not.toBeNull(); +// expect(usersWithInvitee[1]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee).toContainEqual({ +// name: 'Alex', +// invitee: { id: 1, name: 'Dan' }, +// }); +// expect(usersWithInvitee).toContainEqual({ +// name: 'John', +// invitee: { id: 2, name: 'Andrew' }, +// }); +// }); + +// test.skip('Get user with invitee + where + partial(true+false)', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// where: (users, { eq, or }) => (or(eq(users.id, 3), eq(users.id, 4))), +// columns: { +// id: true, +// name: true, +// verified: false, +// }, +// with: { +// invitee: { +// columns: { +// id: true, +// name: true, +// verified: false, +// }, +// }, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// invitee: { +// id: number; +// name: string; +// } | null; +// }[] +// >(); + +// expect(usersWithInvitee.length).eq(2); +// expect(usersWithInvitee[0]?.invitee).not.toBeNull(); +// expect(usersWithInvitee[1]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee).toContainEqual({ +// id: 3, +// name: 'Alex', +// invitee: { id: 1, name: 'Dan' }, +// }); +// expect(usersWithInvitee).toContainEqual({ +// id: 4, +// name: 'John', +// invitee: { id: 2, name: 'Andrew' }, +// }); +// }); + +// test.skip('Get user with invitee + where + partial(false)', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// const usersWithInvitee = await db.query.usersTable.findMany({ +// where: (users, { eq, or }) => (or(eq(users.id, 3), eq(users.id, 4))), +// columns: { +// verified: false, +// }, +// with: { +// invitee: { +// columns: { +// name: false, +// }, +// }, +// }, +// }); + +// expectTypeOf(usersWithInvitee).toEqualTypeOf< +// { +// id: number; +// name: string; +// invitedBy: number | null; +// invitee: { +// id: number; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// expect(usersWithInvitee.length).eq(2); +// expect(usersWithInvitee[0]?.invitee).not.toBeNull(); +// expect(usersWithInvitee[1]?.invitee).not.toBeNull(); + +// expect(usersWithInvitee).toContainEqual({ +// id: 3, +// name: 'Alex', +// invitedBy: 1, +// invitee: { id: 1, verified: false, invitedBy: null }, +// }); +// expect(usersWithInvitee).toContainEqual({ +// id: 4, +// name: 'John', +// invitedBy: 2, +// invitee: { id: 2, verified: false, invitedBy: null }, +// }); +// }); + +// /* +// Two first-level relations users+users and users+posts +// */ + +// test.skip('Get user with invitee and posts', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// with: { +// invitee: true, +// posts: true, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { id: number; ownerId: number | null; content: string; createdAt: Date }[]; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).eq(4); + +// expect(response[0]?.invitee).toBeNull(); +// expect(response[1]?.invitee).toBeNull(); +// expect(response[2]?.invitee).not.toBeNull(); +// expect(response[3]?.invitee).not.toBeNull(); + +// expect(response[0]?.posts.length).eq(1); +// expect(response[1]?.posts.length).eq(1); +// expect(response[2]?.posts.length).eq(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: response[0]?.posts[0]?.createdAt }], +// }); +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 2, ownerId: 2, content: 'Post2', createdAt: response[1]?.posts[0]?.createdAt }], +// }); +// expect(response).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// posts: [{ id: 3, ownerId: 3, content: 'Post3', createdAt: response[2]?.posts[0]?.createdAt }], +// }); +// expect(response).toContainEqual({ +// id: 4, +// name: 'John', +// verified: false, +// invitedBy: 2, +// invitee: { id: 2, name: 'Andrew', verified: false, invitedBy: null }, +// posts: [], +// }); +// }); + +// test.skip('Get user with invitee and posts + limit posts and users', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// limit: 3, +// with: { +// invitee: true, +// posts: { +// limit: 1, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { id: number; ownerId: number | null; content: string; createdAt: Date }[]; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).eq(3); + +// expect(response[0]?.invitee).toBeNull(); +// expect(response[1]?.invitee).toBeNull(); +// expect(response[2]?.invitee).not.toBeNull(); + +// expect(response[0]?.posts.length).eq(1); +// expect(response[1]?.posts.length).eq(1); +// expect(response[2]?.posts.length).eq(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', createdAt: response[0]?.posts[0]?.createdAt }], +// }); +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 3, ownerId: 2, content: 'Post2', createdAt: response[1]?.posts[0]?.createdAt }], +// }); +// expect(response).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// posts: [{ id: 5, ownerId: 3, content: 'Post3', createdAt: response[2]?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('Get user with invitee and posts + limits + custom fields in each', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// limit: 3, +// extras: (users, { sql }) => ({ lower: sql`lower(${users.name})`.as('lower_name') }), +// with: { +// invitee: { +// extras: (users, { sql }) => ({ lower: sql`lower(${users.name})`.as('lower_invitee_name') }), +// }, +// posts: { +// limit: 1, +// extras: (posts, { sql }) => ({ lower: sql`lower(${posts.content})`.as('lower_content') }), +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// lower: string; +// invitedBy: number | null; +// posts: { id: number; lower: string; ownerId: number | null; content: string; createdAt: Date }[]; +// invitee: { +// id: number; +// name: string; +// lower: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).eq(3); + +// expect(response[0]?.invitee).toBeNull(); +// expect(response[1]?.invitee).toBeNull(); +// expect(response[2]?.invitee).not.toBeNull(); + +// expect(response[0]?.posts.length).eq(1); +// expect(response[1]?.posts.length).eq(1); +// expect(response[2]?.posts.length).eq(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// lower: 'dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', lower: 'post1', createdAt: response[0]?.posts[0]?.createdAt }], +// }); +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// lower: 'andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 3, ownerId: 2, content: 'Post2', lower: 'post2', createdAt: response[1]?.posts[0]?.createdAt }], +// }); +// expect(response).toContainEqual({ +// id: 3, +// name: 'Alex', +// lower: 'alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', lower: 'dan', verified: false, invitedBy: null }, +// posts: [{ id: 5, ownerId: 3, content: 'Post3', lower: 'post3', createdAt: response[2]?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('Get user with invitee and posts + custom fields in each', async () => { +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// extras: (users, { sql }) => ({ lower: sql`lower(${users.name})`.as('lower_name') }), +// with: { +// invitee: { +// extras: (users, { sql }) => ({ lower: sql`lower(${users.name})`.as('lower_name') }), +// }, +// posts: { +// extras: (posts, { sql }) => ({ lower: sql`lower(${posts.content})`.as('lower_name') }), +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// lower: string; +// invitedBy: number | null; +// posts: { id: number; lower: string; ownerId: number | null; content: string; createdAt: Date }[]; +// invitee: { +// id: number; +// name: string; +// lower: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// response[0]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// response[1]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); +// response[2]?.posts.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).eq(4); + +// expect(response[0]?.invitee).toBeNull(); +// expect(response[1]?.invitee).toBeNull(); +// expect(response[2]?.invitee).not.toBeNull(); +// expect(response[3]?.invitee).not.toBeNull(); + +// expect(response[0]?.posts.length).eq(2); +// expect(response[1]?.posts.length).eq(2); +// expect(response[2]?.posts.length).eq(2); +// expect(response[3]?.posts.length).eq(0); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// lower: 'dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 1, ownerId: 1, content: 'Post1', lower: 'post1', createdAt: response[0]?.posts[0]?.createdAt }, { +// id: 2, +// ownerId: 1, +// content: 'Post1.1', +// lower: 'post1.1', +// createdAt: response[0]?.posts[1]?.createdAt, +// }], +// }); +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// lower: 'andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 3, ownerId: 2, content: 'Post2', lower: 'post2', createdAt: response[1]?.posts[0]?.createdAt }, { +// id: 4, +// ownerId: 2, +// content: 'Post2.1', +// lower: 'post2.1', +// createdAt: response[1]?.posts[1]?.createdAt, +// }], +// }); +// expect(response).toContainEqual({ +// id: 3, +// name: 'Alex', +// lower: 'alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', lower: 'dan', verified: false, invitedBy: null }, +// posts: [{ id: 5, ownerId: 3, content: 'Post3', lower: 'post3', createdAt: response[2]?.posts[0]?.createdAt }, { +// id: 6, +// ownerId: 3, +// content: 'Post3.1', +// lower: 'post3.1', +// createdAt: response[2]?.posts[1]?.createdAt, +// }], +// }); +// expect(response).toContainEqual({ +// id: 4, +// name: 'John', +// lower: 'john', +// verified: false, +// invitedBy: 2, +// invitee: { id: 2, name: 'Andrew', lower: 'andrew', verified: false, invitedBy: null }, +// posts: [], +// }); +// }); + +// test.skip('Get user with invitee and posts + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// orderBy: (users, { desc }) => [desc(users.id)], +// with: { +// invitee: true, +// posts: { +// orderBy: (posts, { desc }) => [desc(posts.id)], +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { id: number; ownerId: number | null; content: string; createdAt: Date }[]; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// expect(response.length).eq(4); + +// expect(response[3]?.invitee).toBeNull(); +// expect(response[2]?.invitee).toBeNull(); +// expect(response[1]?.invitee).not.toBeNull(); +// expect(response[0]?.invitee).not.toBeNull(); + +// expect(response[0]?.posts.length).eq(0); +// expect(response[1]?.posts.length).eq(1); +// expect(response[2]?.posts.length).eq(2); +// expect(response[3]?.posts.length).eq(2); + +// expect(response[3]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 2, ownerId: 1, content: 'Post1.1', createdAt: response[3]?.posts[0]?.createdAt }, { +// id: 1, +// ownerId: 1, +// content: 'Post1', +// createdAt: response[3]?.posts[1]?.createdAt, +// }], +// }); +// expect(response[2]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 4, ownerId: 2, content: 'Post2.1', createdAt: response[2]?.posts[0]?.createdAt }, { +// id: 3, +// ownerId: 2, +// content: 'Post2', +// createdAt: response[2]?.posts[1]?.createdAt, +// }], +// }); +// expect(response[1]).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// posts: [{ +// id: 5, +// ownerId: 3, +// content: 'Post3', +// createdAt: response[3]?.posts[1]?.createdAt, +// }], +// }); +// expect(response[0]).toEqual({ +// id: 4, +// name: 'John', +// verified: false, +// invitedBy: 2, +// invitee: { id: 2, name: 'Andrew', verified: false, invitedBy: null }, +// posts: [], +// }); +// }); + +// test.skip('Get user with invitee and posts + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// where: (users, { eq, or }) => (or(eq(users.id, 2), eq(users.id, 3))), +// with: { +// invitee: true, +// posts: { +// where: (posts, { eq }) => (eq(posts.ownerId, 2)), +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { id: number; ownerId: number | null; content: string; createdAt: Date }[]; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).eq(2); + +// expect(response[0]?.invitee).toBeNull(); +// expect(response[1]?.invitee).not.toBeNull(); + +// expect(response[0]?.posts.length).eq(1); +// expect(response[1]?.posts.length).eq(0); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// invitee: null, +// posts: [{ id: 2, ownerId: 2, content: 'Post2', createdAt: response[0]?.posts[0]?.createdAt }], +// }); +// expect(response).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// posts: [], +// }); +// }); + +// test.skip('Get user with invitee and posts + limit posts and users + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// { ownerId: 3, content: 'Post3.1' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// where: (users, { eq, or }) => (or(eq(users.id, 3), eq(users.id, 4))), +// limit: 1, +// with: { +// invitee: true, +// posts: { +// where: (posts, { eq }) => (eq(posts.ownerId, 3)), +// limit: 1, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { id: number; ownerId: number | null; content: string; createdAt: Date }[]; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// expect(response.length).eq(1); + +// expect(response[0]?.invitee).not.toBeNull(); +// expect(response[0]?.posts.length).eq(1); + +// expect(response).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// posts: [{ id: 5, ownerId: 3, content: 'Post3', createdAt: response[0]?.posts[0]?.createdAt }], +// }); +// }); + +// test.skip('Get user with invitee and posts + orderBy + where + custom', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// orderBy: [desc(usersTable.id)], +// where: or(eq(usersTable.id, 3), eq(usersTable.id, 4)), +// extras: { +// lower: sql`lower(${usersTable.name})`.as('lower_name'), +// }, +// with: { +// invitee: true, +// posts: { +// where: eq(postsTable.ownerId, 3), +// orderBy: [desc(postsTable.id)], +// extras: { +// lower: sql`lower(${postsTable.content})`.as('lower_name'), +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// lower: string; +// posts: { id: number; lower: string; ownerId: number | null; content: string; createdAt: Date }[]; +// invitee: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[] +// >(); + +// expect(response.length).eq(2); + +// expect(response[1]?.invitee).not.toBeNull(); +// expect(response[0]?.invitee).not.toBeNull(); + +// expect(response[0]?.posts.length).eq(0); +// expect(response[1]?.posts.length).eq(1); + +// expect(response[1]).toEqual({ +// id: 3, +// name: 'Alex', +// lower: 'alex', +// verified: false, +// invitedBy: 1, +// invitee: { id: 1, name: 'Dan', verified: false, invitedBy: null }, +// posts: [{ +// id: 5, +// ownerId: 3, +// content: 'Post3', +// lower: 'post3', +// createdAt: response[1]?.posts[0]?.createdAt, +// }], +// }); +// expect(response[0]).toEqual({ +// id: 4, +// name: 'John', +// lower: 'john', +// verified: false, +// invitedBy: 2, +// invitee: { id: 2, name: 'Andrew', verified: false, invitedBy: null }, +// posts: [], +// }); +// }); + +// test.skip('Get user with invitee and posts + orderBy + where + partial + custom', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex', invitedBy: 1 }, +// { id: 4, name: 'John', invitedBy: 2 }, +// ]); + +// await db.insert(postsTable).values([ +// { ownerId: 1, content: 'Post1' }, +// { ownerId: 1, content: 'Post1.1' }, +// { ownerId: 2, content: 'Post2' }, +// { ownerId: 2, content: 'Post2.1' }, +// { ownerId: 3, content: 'Post3' }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// orderBy: [desc(usersTable.id)], +// where: or(eq(usersTable.id, 3), eq(usersTable.id, 4)), +// extras: { +// lower: sql`lower(${usersTable.name})`.as('lower_name'), +// }, +// columns: { +// id: true, +// name: true, +// }, +// with: { +// invitee: { +// columns: { +// id: true, +// name: true, +// }, +// extras: { +// lower: sql`lower(${usersTable.name})`.as('lower_name'), +// }, +// }, +// posts: { +// columns: { +// id: true, +// content: true, +// }, +// where: eq(postsTable.ownerId, 3), +// orderBy: [desc(postsTable.id)], +// extras: { +// lower: sql`lower(${postsTable.content})`.as('lower_name'), +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// lower: string; +// posts: { id: number; lower: string; content: string }[]; +// invitee: { +// id: number; +// name: string; +// lower: string; +// } | null; +// }[] +// >(); + +// expect(response.length).eq(2); + +// expect(response[1]?.invitee).not.toBeNull(); +// expect(response[0]?.invitee).not.toBeNull(); + +// expect(response[0]?.posts.length).eq(0); +// expect(response[1]?.posts.length).eq(1); + +// expect(response[1]).toEqual({ +// id: 3, +// name: 'Alex', +// lower: 'alex', +// invitee: { id: 1, name: 'Dan', lower: 'dan' }, +// posts: [{ +// id: 5, +// content: 'Post3', +// lower: 'post3', +// }], +// }); +// expect(response[0]).toEqual({ +// id: 4, +// name: 'John', +// lower: 'john', +// invitee: { id: 2, name: 'Andrew', lower: 'andrew' }, +// posts: [], +// }); +// }); + +// /* +// One two-level relation users+posts+comments +// */ + +// test.skip('Get user with posts and posts with comments', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { id: 1, ownerId: 1, content: 'Post1' }, +// { id: 2, ownerId: 2, content: 'Post2' }, +// { id: 3, ownerId: 3, content: 'Post3' }, +// ]); + +// await db.insert(commentsTable).values([ +// { postId: 1, content: 'Comment1', creator: 2 }, +// { postId: 2, content: 'Comment2', creator: 2 }, +// { postId: 3, content: 'Comment3', creator: 3 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// with: { +// posts: { +// with: { +// comments: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// comments: { +// id: number; +// content: string; +// createdAt: Date; +// creator: number | null; +// postId: number | null; +// }[]; +// }[]; +// }[] +// >(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).eq(3); +// expect(response[0]?.posts.length).eq(1); +// expect(response[1]?.posts.length).eq(1); +// expect(response[2]?.posts.length).eq(1); + +// expect(response[0]?.posts[0]?.comments.length).eq(1); +// expect(response[1]?.posts[0]?.comments.length).eq(1); +// expect(response[2]?.posts[0]?.comments.length).eq(1); + +// expect(response[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ +// id: 1, +// ownerId: 1, +// content: 'Post1', +// createdAt: response[0]?.posts[0]?.createdAt, +// comments: [ +// { +// id: 1, +// content: 'Comment1', +// creator: 2, +// postId: 1, +// createdAt: response[0]?.posts[0]?.comments[0]?.createdAt, +// }, +// ], +// }], +// }); +// expect(response[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// posts: [{ +// id: 2, +// ownerId: 2, +// content: 'Post2', +// createdAt: response[1]?.posts[0]?.createdAt, +// comments: [ +// { +// id: 2, +// content: 'Comment2', +// creator: 2, +// postId: 2, +// createdAt: response[1]?.posts[0]?.comments[0]?.createdAt, +// }, +// ], +// }], +// }); +// // expect(response[2]).toEqual({ +// // id: 3, +// // name: 'Alex', +// // verified: false, +// // invitedBy: null, +// // posts: [{ +// // id: 3, +// // ownerId: 3, +// // content: 'Post3', +// // createdAt: response[2]?.posts[0]?.createdAt, +// // comments: [ +// // { +// // id: , +// // content: 'Comment3', +// // creator: 3, +// // postId: 3, +// // createdAt: response[2]?.posts[0]?.comments[0]?.createdAt, +// // }, +// // ], +// // }], +// // }); +// }); + +// // Get user with limit posts and limit comments + +// // Get user with custom field + post + comment with custom field + +// // Get user with limit + posts orderBy + comment orderBy + +// // Get user with where + posts where + comment where + +// // Get user with where + posts partial where + comment where + +// // Get user with where + posts partial where + comment partial(false) where + +// // Get user with where partial(false) + posts partial where partial(false) + comment partial(false+true) where + +// // Get user with where + posts partial where + comment where. Didn't select field from where in posts + +// // Get user with where + posts partial where + comment where. Didn't select field from where for all + +// // Get with limit+offset in each + +// /* +// One two-level + One first-level relation users+posts+comments and users+users +// */ + +// /* +// One three-level relation users+posts+comments+comment_owner +// */ + +// test.skip('Get user with posts and posts with comments and comments with owner', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { id: 1, ownerId: 1, content: 'Post1' }, +// { id: 2, ownerId: 2, content: 'Post2' }, +// { id: 3, ownerId: 3, content: 'Post3' }, +// ]); + +// await db.insert(commentsTable).values([ +// { postId: 1, content: 'Comment1', creator: 2 }, +// { postId: 2, content: 'Comment2', creator: 2 }, +// { postId: 3, content: 'Comment3', creator: 3 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// with: { +// posts: { +// with: { +// comments: { +// with: { +// author: true, +// }, +// }, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// comments: { +// id: number; +// content: string; +// createdAt: Date; +// creator: number | null; +// postId: number | null; +// author: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[]; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).eq(3); +// expect(response[0]?.posts.length).eq(1); +// expect(response[1]?.posts.length).eq(1); +// expect(response[2]?.posts.length).eq(1); + +// expect(response[0]?.posts[0]?.comments.length).eq(1); +// expect(response[1]?.posts[0]?.comments.length).eq(1); +// expect(response[2]?.posts[0]?.comments.length).eq(1); + +// expect(response[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ +// id: 1, +// ownerId: 1, +// content: 'Post1', +// createdAt: response[0]?.posts[0]?.createdAt, +// comments: [ +// { +// id: 1, +// content: 'Comment1', +// creator: 2, +// author: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// postId: 1, +// createdAt: response[0]?.posts[0]?.comments[0]?.createdAt, +// }, +// ], +// }], +// }); +// expect(response[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// posts: [{ +// id: 2, +// ownerId: 2, +// content: 'Post2', +// createdAt: response[1]?.posts[0]?.createdAt, +// comments: [ +// { +// id: 2, +// content: 'Comment2', +// creator: 2, +// author: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// postId: 2, +// createdAt: response[1]?.posts[0]?.comments[0]?.createdAt, +// }, +// ], +// }], +// }); +// }); + +// test.skip('Get user with posts and posts with comments and comments with owner where exists', async () => { +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(postsTable).values([ +// { id: 1, ownerId: 1, content: 'Post1' }, +// { id: 2, ownerId: 2, content: 'Post2' }, +// { id: 3, ownerId: 3, content: 'Post3' }, +// ]); + +// await db.insert(commentsTable).values([ +// { postId: 1, content: 'Comment1', creator: 2 }, +// { postId: 2, content: 'Comment2', creator: 2 }, +// { postId: 3, content: 'Comment3', creator: 3 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// with: { +// posts: { +// with: { +// comments: { +// with: { +// author: true, +// }, +// }, +// }, +// }, +// }, +// where: (table, { exists, eq }) => exists(db.select({ one: sql`1` }).from(usersTable).where(eq(sql`1`, table.id))), +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// posts: { +// id: number; +// content: string; +// ownerId: number | null; +// createdAt: Date; +// comments: { +// id: number; +// content: string; +// createdAt: Date; +// creator: number | null; +// postId: number | null; +// author: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// } | null; +// }[]; +// }[]; +// }[]>(); + +// expect(response.length).eq(1); +// expect(response[0]?.posts.length).eq(1); + +// expect(response[0]?.posts[0]?.comments.length).eq(1); + +// expect(response[0]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// posts: [{ +// id: 1, +// ownerId: 1, +// content: 'Post1', +// createdAt: response[0]?.posts[0]?.createdAt, +// comments: [ +// { +// id: 1, +// content: 'Comment1', +// creator: 2, +// author: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// postId: 1, +// createdAt: response[0]?.posts[0]?.comments[0]?.createdAt, +// }, +// ], +// }], +// }); +// }); + +// /* +// One three-level relation + 1 first-level relatioon +// 1. users+posts+comments+comment_owner +// 2. users+users +// */ + +// /* +// One four-level relation users+posts+comments+coment_likes +// */ + +// /* +// [Find Many] Many-to-many cases + +// Users+users_to_groups+groups +// */ + +// test.skip('[Find Many] Get users with groups', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// with: { +// usersToGroups: { +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(3); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(1); +// expect(response[2]?.usersToGroups.length).toEqual(2); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 1, +// name: 'Group1', +// description: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 2, +// name: 'Group2', +// description: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 3, +// name: 'Group3', +// description: null, +// }, +// }, { +// group: { +// id: 2, +// name: 'Group2', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get groups with users', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findMany({ +// with: { +// usersToGroups: { +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(3); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(2); +// expect(response[2]?.usersToGroups.length).toEqual(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Group1', +// description: null, +// usersToGroups: [{ +// user: { +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// }, { +// user: { +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 3, +// name: 'Group3', +// description: null, +// usersToGroups: [{ +// user: { +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get users with groups + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 2, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// limit: 2, +// with: { +// usersToGroups: { +// limit: 1, +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(2); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 1, +// name: 'Group1', +// description: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 2, +// name: 'Group2', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get groups with users + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findMany({ +// limit: 2, +// with: { +// usersToGroups: { +// limit: 1, +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(2); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Group1', +// description: null, +// usersToGroups: [{ +// user: { +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get users with groups + limit + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 2, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// limit: 1, +// where: (_, { eq, or }) => or(eq(usersTable.id, 1), eq(usersTable.id, 2)), +// with: { +// usersToGroups: { +// where: eq(usersToGroupsTable.groupId, 1), +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(1); + +// expect(response[0]?.usersToGroups.length).toEqual(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 1, +// name: 'Group1', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get groups with users + limit + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findMany({ +// limit: 1, +// where: gt(groupsTable.id, 1), +// with: { +// usersToGroups: { +// where: eq(usersToGroupsTable.userId, 2), +// limit: 1, +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(1); + +// expect(response[0]?.usersToGroups.length).toEqual(1); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get users with groups + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 2, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// where: (_, { eq, or }) => or(eq(usersTable.id, 1), eq(usersTable.id, 2)), +// with: { +// usersToGroups: { +// where: eq(usersToGroupsTable.groupId, 2), +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(2); + +// expect(response[0]?.usersToGroups.length).toEqual(0); +// expect(response[1]?.usersToGroups.length).toEqual(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [], +// }); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 2, +// name: 'Group2', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get groups with users + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findMany({ +// where: gt(groupsTable.id, 1), +// with: { +// usersToGroups: { +// where: eq(usersToGroupsTable.userId, 2), +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// }[]>(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(2); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(0); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 3, +// name: 'Group3', +// description: null, +// usersToGroups: [], +// }); +// }); + +// test.skip('[Find Many] Get users with groups + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// orderBy: (users, { desc }) => [desc(users.id)], +// with: { +// usersToGroups: { +// orderBy: [desc(usersToGroupsTable.groupId)], +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// }[]>(); + +// expect(response.length).toEqual(3); + +// expect(response[0]?.usersToGroups.length).toEqual(2); +// expect(response[1]?.usersToGroups.length).toEqual(1); +// expect(response[2]?.usersToGroups.length).toEqual(1); + +// expect(response[2]).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 1, +// name: 'Group1', +// description: null, +// }, +// }], +// }); + +// expect(response[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 2, +// name: 'Group2', +// description: null, +// }, +// }], +// }); + +// expect(response[0]).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 3, +// name: 'Group3', +// description: null, +// }, +// }, { +// group: { +// id: 2, +// name: 'Group2', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get groups with users + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findMany({ +// orderBy: [desc(groupsTable.id)], +// with: { +// usersToGroups: { +// orderBy: (utg, { desc }) => [desc(utg.userId)], +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// }[]>(); + +// expect(response.length).toEqual(3); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(2); +// expect(response[2]?.usersToGroups.length).toEqual(1); + +// expect(response[2]).toEqual({ +// id: 1, +// name: 'Group1', +// description: null, +// usersToGroups: [{ +// user: { +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response[1]).toEqual({ +// id: 2, +// name: 'Group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// }, +// }, { +// user: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response[0]).toEqual({ +// id: 3, +// name: 'Group3', +// description: null, +// usersToGroups: [{ +// user: { +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find Many] Get users with groups + orderBy + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// orderBy: (users, { desc }) => [desc(users.id)], +// limit: 2, +// with: { +// usersToGroups: { +// limit: 1, +// orderBy: [desc(usersToGroupsTable.groupId)], +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf<{ +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// }[]>(); + +// expect(response.length).toEqual(2); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(1); + +// expect(response[1]).toEqual({ +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 2, +// name: 'Group2', +// description: null, +// }, +// }], +// }); + +// expect(response[0]).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 3, +// name: 'Group3', +// description: null, +// }, +// }], +// }); +// }); + +// /* +// [Find One] Many-to-many cases + +// Users+users_to_groups+groups +// */ + +// test.skip('[Find One] Get users with groups', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findFirst({ +// with: { +// usersToGroups: { +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 1, +// name: 'Group1', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get groups with users', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findFirst({ +// with: { +// usersToGroups: { +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 1, +// name: 'Group1', +// description: null, +// usersToGroups: [{ +// user: { +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get users with groups + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 2, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findFirst({ +// with: { +// usersToGroups: { +// limit: 1, +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 1, +// name: 'Group1', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get groups with users + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findFirst({ +// with: { +// usersToGroups: { +// limit: 1, +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 1, +// name: 'Group1', +// description: null, +// usersToGroups: [{ +// user: { +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get users with groups + limit + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 2, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findFirst({ +// where: (_, { eq, or }) => or(eq(usersTable.id, 1), eq(usersTable.id, 2)), +// with: { +// usersToGroups: { +// where: eq(usersToGroupsTable.groupId, 1), +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 1, +// name: 'Group1', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get groups with users + limit + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findFirst({ +// where: gt(groupsTable.id, 1), +// with: { +// usersToGroups: { +// where: eq(usersToGroupsTable.userId, 2), +// limit: 1, +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 2, +// name: 'Group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get users with groups + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 2, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findFirst({ +// where: (_, { eq, or }) => or(eq(usersTable.id, 1), eq(usersTable.id, 2)), +// with: { +// usersToGroups: { +// where: eq(usersToGroupsTable.groupId, 2), +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(0); + +// expect(response).toEqual({ +// id: 1, +// name: 'Dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [], +// }); +// }); + +// test.skip('[Find One] Get groups with users + where', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findFirst({ +// where: gt(groupsTable.id, 1), +// with: { +// usersToGroups: { +// where: eq(usersToGroupsTable.userId, 2), +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 2, +// name: 'Group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 2, +// name: 'Andrew', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get users with groups + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findFirst({ +// orderBy: (users, { desc }) => [desc(users.id)], +// with: { +// usersToGroups: { +// orderBy: [desc(usersToGroupsTable.groupId)], +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(2); + +// expect(response).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 3, +// name: 'Group3', +// description: null, +// }, +// }, { +// group: { +// id: 2, +// name: 'Group2', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get groups with users + orderBy', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findFirst({ +// orderBy: [desc(groupsTable.id)], +// with: { +// usersToGroups: { +// orderBy: (utg, { desc }) => [desc(utg.userId)], +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 3, +// name: 'Group3', +// description: null, +// usersToGroups: [{ +// user: { +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('[Find One] Get users with groups + orderBy + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findFirst({ +// orderBy: (users, { desc }) => [desc(users.id)], +// with: { +// usersToGroups: { +// limit: 1, +// orderBy: [desc(usersToGroupsTable.groupId)], +// columns: {}, +// with: { +// group: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// }; +// }[]; +// } | undefined +// >(); + +// expect(response?.usersToGroups.length).toEqual(1); + +// expect(response).toEqual({ +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 3, +// name: 'Group3', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('Get groups with users + orderBy + limit', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findMany({ +// orderBy: [desc(groupsTable.id)], +// limit: 2, +// with: { +// usersToGroups: { +// limit: 1, +// orderBy: (utg, { desc }) => [desc(utg.userId)], +// columns: {}, +// with: { +// user: true, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// description: string | null; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// }; +// }[]; +// }[] +// >(); + +// expect(response.length).toEqual(2); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(1); + +// expect(response[1]).toEqual({ +// id: 2, +// name: 'Group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response[0]).toEqual({ +// id: 3, +// name: 'Group3', +// description: null, +// usersToGroups: [{ +// user: { +// id: 3, +// name: 'Alex', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test.skip('Get users with groups + custom', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.usersTable.findMany({ +// extras: { +// lower: sql`lower(${usersTable.name})`.as('lower_name'), +// }, +// with: { +// usersToGroups: { +// columns: {}, +// with: { +// group: { +// extras: { +// lower: sql`lower(${groupsTable.name})`.as('lower_name'), +// }, +// }, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// lower: string; +// usersToGroups: { +// group: { +// id: number; +// name: string; +// description: string | null; +// lower: string; +// }; +// }[]; +// }[] +// >(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(3); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(1); +// expect(response[2]?.usersToGroups.length).toEqual(2); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Dan', +// lower: 'dan', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 1, +// name: 'Group1', +// lower: 'group1', +// description: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Andrew', +// lower: 'andrew', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 2, +// name: 'Group2', +// lower: 'group2', +// description: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 3, +// name: 'Alex', +// lower: 'alex', +// verified: false, +// invitedBy: null, +// usersToGroups: [{ +// group: { +// id: 3, +// name: 'Group3', +// lower: 'group3', +// description: null, +// }, +// }, { +// group: { +// id: 2, +// name: 'Group2', +// lower: 'group2', +// description: null, +// }, +// }], +// }); +// }); + +// test.skip('Get groups with users + custom', async (t) => { +// const { singlestoreDb: db } = t; + +// await db.insert(usersTable).values([ +// { id: 1, name: 'Dan' }, +// { id: 2, name: 'Andrew' }, +// { id: 3, name: 'Alex' }, +// ]); + +// await db.insert(groupsTable).values([ +// { id: 1, name: 'Group1' }, +// { id: 2, name: 'Group2' }, +// { id: 3, name: 'Group3' }, +// ]); + +// await db.insert(usersToGroupsTable).values([ +// { userId: 1, groupId: 1 }, +// { userId: 2, groupId: 2 }, +// { userId: 3, groupId: 3 }, +// { userId: 3, groupId: 2 }, +// ]); + +// const response = await db.query.groupsTable.findMany({ +// extras: (table, { sql }) => ({ +// lower: sql`lower(${table.name})`.as('lower_name'), +// }), +// with: { +// usersToGroups: { +// columns: {}, +// with: { +// user: { +// extras: (table, { sql }) => ({ +// lower: sql`lower(${table.name})`.as('lower_name'), +// }), +// }, +// }, +// }, +// }, +// }); + +// expectTypeOf(response).toEqualTypeOf< +// { +// id: number; +// name: string; +// description: string | null; +// lower: string; +// usersToGroups: { +// user: { +// id: number; +// name: string; +// verified: boolean; +// invitedBy: number | null; +// lower: string; +// }; +// }[]; +// }[] +// >(); + +// response.sort((a, b) => (a.id > b.id) ? 1 : -1); + +// expect(response.length).toEqual(3); + +// expect(response[0]?.usersToGroups.length).toEqual(1); +// expect(response[1]?.usersToGroups.length).toEqual(2); +// expect(response[2]?.usersToGroups.length).toEqual(1); + +// expect(response).toContainEqual({ +// id: 1, +// name: 'Group1', +// lower: 'group1', +// description: null, +// usersToGroups: [{ +// user: { +// id: 1, +// name: 'Dan', +// lower: 'dan', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 2, +// name: 'Group2', +// lower: 'group2', +// description: null, +// usersToGroups: [{ +// user: { +// id: 2, +// name: 'Andrew', +// lower: 'andrew', +// verified: false, +// invitedBy: null, +// }, +// }, { +// user: { +// id: 3, +// name: 'Alex', +// lower: 'alex', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); + +// expect(response).toContainEqual({ +// id: 3, +// name: 'Group3', +// lower: 'group3', +// description: null, +// usersToGroups: [{ +// user: { +// id: 3, +// name: 'Alex', +// lower: 'alex', +// verified: false, +// invitedBy: null, +// }, +// }], +// }); +// }); + +// test('.toSQL()', () => { +// const query = db.query.usersTable.findFirst().toSQL(); + +// expect(query).toHaveProperty('sql', expect.any(String)); +// expect(query).toHaveProperty('params', expect.any(Array)); +// }); + +// // + custom + where + orderby + +// // + custom + where + orderby + limit + +// // + partial + +// // + partial(false) + +// // + partial + orderBy + where (all not selected) + +// /* +// One four-level relation users+posts+comments+coment_likes +// + users+users_to_groups+groups +// */ + +// /* +// Really hard case +// 1. users+posts+comments+coment_likes +// 2. users+users_to_groups+groups +// 3. users+users +// */ +// eslint-disable-next-line unicorn/no-empty-file diff --git a/integration-tests/tests/replicas/mysql.test.ts b/integration-tests/tests/replicas/mysql.test.ts index a7de02411..673a8da65 100644 --- a/integration-tests/tests/replicas/mysql.test.ts +++ b/integration-tests/tests/replicas/mysql.test.ts @@ -15,9 +15,9 @@ const users = mysqlTable('users', { describe('[select] read replicas mysql', () => { it('primary select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -35,9 +35,9 @@ describe('[select] read replicas mysql', () => { }); it('random replica select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -64,8 +64,8 @@ describe('[select] read replicas mysql', () => { }); it('single read replica select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -84,8 +84,8 @@ describe('[select] read replicas mysql', () => { }); it('single read replica select + primary select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -105,9 +105,9 @@ describe('[select] read replicas mysql', () => { }); it('always first read select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -134,9 +134,9 @@ describe('[select] read replicas mysql', () => { describe('[selectDistinct] read replicas mysql', () => { it('primary selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -153,9 +153,9 @@ describe('[selectDistinct] read replicas mysql', () => { }); it('random replica selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -181,8 +181,8 @@ describe('[selectDistinct] read replicas mysql', () => { }); it('single read replica selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -201,8 +201,8 @@ describe('[selectDistinct] read replicas mysql', () => { }); it('single read replica selectDistinct + primary selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -222,9 +222,9 @@ describe('[selectDistinct] read replicas mysql', () => { }); it('always first read selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -250,9 +250,9 @@ describe('[selectDistinct] read replicas mysql', () => { describe('[with] read replicas mysql', () => { it('primary with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -273,9 +273,9 @@ describe('[with] read replicas mysql', () => { }); it('random replica with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -299,8 +299,8 @@ describe('[with] read replicas mysql', () => { }); it('single read replica with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -317,8 +317,8 @@ describe('[with] read replicas mysql', () => { }); it('single read replica with + primary with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -336,9 +336,9 @@ describe('[with] read replicas mysql', () => { }); it('always first read with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -367,9 +367,9 @@ describe('[with] read replicas mysql', () => { describe('[update] replicas mysql', () => { it('primary update', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -402,9 +402,9 @@ describe('[update] replicas mysql', () => { describe('[delete] replicas mysql', () => { it('primary delete', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -438,9 +438,9 @@ describe('[delete] replicas mysql', () => { describe('[insert] replicas mysql', () => { it('primary insert', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -473,9 +473,9 @@ describe('[insert] replicas mysql', () => { describe('[execute] replicas mysql', () => { it('primary execute', async () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -515,9 +515,9 @@ describe('[execute] replicas mysql', () => { describe('[transaction] replicas mysql', () => { it('primary transaction', async () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -558,9 +558,9 @@ describe('[transaction] replicas mysql', () => { describe('[findFirst] read replicas mysql', () => { it('primary findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read2 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read2 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const db = withReplicas(primaryDb, [read1, read2]); @@ -578,9 +578,9 @@ describe('[findFirst] read replicas mysql', () => { }); it('random replica findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read2 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read2 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -607,8 +607,8 @@ describe('[findFirst] read replicas mysql', () => { }); it('single read replica findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const db = withReplicas(primaryDb, [read1]); @@ -625,8 +625,8 @@ describe('[findFirst] read replicas mysql', () => { }); it('single read replica findFirst + primary findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const db = withReplicas(primaryDb, [read1]); @@ -644,9 +644,9 @@ describe('[findFirst] read replicas mysql', () => { }); it('always first read findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read2 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read2 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -670,9 +670,9 @@ describe('[findFirst] read replicas mysql', () => { describe('[findMany] read replicas mysql', () => { it('primary findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read2 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read2 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const db = withReplicas(primaryDb, [read1, read2]); @@ -691,9 +691,9 @@ describe('[findMany] read replicas mysql', () => { }); it('random replica findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read2 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read2 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -724,8 +724,8 @@ describe('[findMany] read replicas mysql', () => { }); it('single read replica findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const db = withReplicas(primaryDb, [read1]); @@ -748,8 +748,8 @@ describe('[findMany] read replicas mysql', () => { }); it('single read replica findMany + primary findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const db = withReplicas(primaryDb, [read1]); @@ -774,9 +774,9 @@ describe('[findMany] read replicas mysql', () => { }); it('always first read findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read1 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); - const read2 = drizzle({} as any, { schema: { usersTable }, mode: 'default' }); + const primaryDb = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read1 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); + const read2 = drizzle.mock({ schema: { usersTable }, mode: 'default' }); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; diff --git a/integration-tests/tests/replicas/postgres.test.ts b/integration-tests/tests/replicas/postgres.test.ts index 6165ae413..0860aac6a 100644 --- a/integration-tests/tests/replicas/postgres.test.ts +++ b/integration-tests/tests/replicas/postgres.test.ts @@ -17,9 +17,9 @@ const users = pgTable('users', { describe('[select] read replicas postgres', () => { it('primary select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -37,9 +37,9 @@ describe('[select] read replicas postgres', () => { }); it('random replica select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -66,8 +66,8 @@ describe('[select] read replicas postgres', () => { }); it('single read replica select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -86,8 +86,8 @@ describe('[select] read replicas postgres', () => { }); it('single read replica select + primary select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -107,9 +107,9 @@ describe('[select] read replicas postgres', () => { }); it('always first read select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -136,9 +136,9 @@ describe('[select] read replicas postgres', () => { describe('[selectDistinct] read replicas postgres', () => { it('primary selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -155,9 +155,9 @@ describe('[selectDistinct] read replicas postgres', () => { }); it('random replica selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -183,8 +183,8 @@ describe('[selectDistinct] read replicas postgres', () => { }); it('single read replica selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -203,8 +203,8 @@ describe('[selectDistinct] read replicas postgres', () => { }); it('single read replica selectDistinct + primary selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -224,9 +224,9 @@ describe('[selectDistinct] read replicas postgres', () => { }); it('always first read selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -252,9 +252,9 @@ describe('[selectDistinct] read replicas postgres', () => { describe('[with] read replicas postgres', () => { it('primary with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -275,9 +275,9 @@ describe('[with] read replicas postgres', () => { }); it('random replica with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -301,8 +301,8 @@ describe('[with] read replicas postgres', () => { }); it('single read replica with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -319,8 +319,8 @@ describe('[with] read replicas postgres', () => { }); it('single read replica with + primary with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -338,9 +338,9 @@ describe('[with] read replicas postgres', () => { }); it('always first read with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -369,9 +369,9 @@ describe('[with] read replicas postgres', () => { describe('[update] replicas postgres', () => { it('primary update', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -404,9 +404,9 @@ describe('[update] replicas postgres', () => { describe('[delete] replicas postgres', () => { it('primary delete', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -440,9 +440,9 @@ describe('[delete] replicas postgres', () => { describe('[insert] replicas postgres', () => { it('primary insert', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -475,9 +475,9 @@ describe('[insert] replicas postgres', () => { describe('[execute] replicas postgres', () => { it('primary execute', async () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -517,9 +517,9 @@ describe('[execute] replicas postgres', () => { describe('[transaction] replicas postgres', () => { it('primary transaction', async () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -560,9 +560,9 @@ describe('[transaction] replicas postgres', () => { describe('[findFirst] read replicas postgres', () => { it('primary findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1, read2]); @@ -580,9 +580,9 @@ describe('[findFirst] read replicas postgres', () => { }); it('random replica findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -611,8 +611,8 @@ describe('[findFirst] read replicas postgres', () => { }); it('single read replica findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1]); @@ -629,8 +629,8 @@ describe('[findFirst] read replicas postgres', () => { }); it('single read replica findFirst + primary findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1]); @@ -648,9 +648,9 @@ describe('[findFirst] read replicas postgres', () => { }); it('always first read findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -674,9 +674,9 @@ describe('[findFirst] read replicas postgres', () => { describe('[findMany] read replicas postgres', () => { it('primary findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1, read2]); @@ -697,9 +697,9 @@ describe('[findMany] read replicas postgres', () => { }); it('random replica findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -734,8 +734,8 @@ describe('[findMany] read replicas postgres', () => { }); it('single read replica findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1]); @@ -762,8 +762,8 @@ describe('[findMany] read replicas postgres', () => { }); it('single read replica findMany + primary findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1]); @@ -792,9 +792,9 @@ describe('[findMany] read replicas postgres', () => { }); it('always first read findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; diff --git a/integration-tests/tests/replicas/singlestore.test.ts b/integration-tests/tests/replicas/singlestore.test.ts new file mode 100644 index 000000000..8ddad5b04 --- /dev/null +++ b/integration-tests/tests/replicas/singlestore.test.ts @@ -0,0 +1,814 @@ +import { sql } from 'drizzle-orm'; +import { drizzle } from 'drizzle-orm/singlestore'; +import { serial, singlestoreTable, withReplicas } from 'drizzle-orm/singlestore-core'; +import { describe, expect, it, vi } from 'vitest'; + +// const usersTable = singlestoreTable('users', { +// id: serial('id' as string).primaryKey(), +// name: text('name').notNull(), +// verified: boolean('verified').notNull().default(false), +// }); + +const users = singlestoreTable('users', { + id: serial('id' as string).primaryKey(), +}); + +describe('[select] read replicas singlestore', () => { + it('primary select', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2]); + + const spyPrimary = vi.spyOn(primaryDb, 'select'); + const spyRead1 = vi.spyOn(read1, 'select'); + const spyRead2 = vi.spyOn(read2, 'select'); + + const query = db.$primary.select().from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(query.toSQL().sql).toEqual('select `id` from `users`'); + + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + }); + + it('random replica select', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); + + const db = withReplicas(primaryDb, [read1, read2], () => { + return randomMockReplica(); + }); + + const spyPrimary = vi.spyOn(primaryDb, 'select'); + const spyRead1 = vi.spyOn(read1, 'select'); + const spyRead2 = vi.spyOn(read2, 'select'); + + const query1 = db.select({ count: sql`count(*)`.as('count') }).from(users).limit(1); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(0); + + expect(query1.toSQL().sql).toEqual('select count(*) as `count` from `users` limit ?'); + + const query2 = db.select().from(users); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select `id` from `users`'); + }); + + it('single read replica select', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1]); + + const spyPrimary = vi.spyOn(primaryDb, 'select'); + const spyRead1 = vi.spyOn(read1, 'select'); + + const query1 = db.select().from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select `id` from `users`'); + + const query2 = db.select().from(users); + expect(spyRead1).toHaveBeenCalledTimes(2); + expect(query2.toSQL().sql).toEqual('select `id` from `users`'); + }); + + it('single read replica select + primary select', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1]); + + const spyPrimary = vi.spyOn(primaryDb, 'select'); + const spyRead1 = vi.spyOn(read1, 'select'); + + const query1 = db.select({ id: users.id }).from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select `id` from `users`'); + + const query2 = db.$primary.select().from(users); + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select `id` from `users`'); + }); + + it('always first read select', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2], (replicas) => { + return replicas[0]!; + }); + + const spyPrimary = vi.spyOn(primaryDb, 'select'); + const spyRead1 = vi.spyOn(read1, 'select'); + const spyRead2 = vi.spyOn(read2, 'select'); + + const query1 = db.select().from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select `id` from `users`'); + + const query2 = db.select().from(users); + + expect(spyRead1).toHaveBeenCalledTimes(2); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('select `id` from `users`'); + }); +}); + +describe('[selectDistinct] read replicas singlestore', () => { + it('primary selectDistinct', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2]); + + const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); + const spyRead1 = vi.spyOn(read1, 'selectDistinct'); + const spyRead2 = vi.spyOn(read2, 'selectDistinct'); + + const query = db.$primary.selectDistinct().from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query.toSQL().sql).toEqual('select distinct `id` from `users`'); + }); + + it('random replica selectDistinct', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); + + const db = withReplicas(primaryDb, [read1, read2], () => { + return randomMockReplica(); + }); + + const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); + const spyRead1 = vi.spyOn(read1, 'selectDistinct'); + const spyRead2 = vi.spyOn(read2, 'selectDistinct'); + + const query1 = db.selectDistinct().from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select distinct `id` from `users`'); + + const query2 = db.selectDistinct().from(users); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select distinct `id` from `users`'); + }); + + it('single read replica selectDistinct', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1]); + + const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); + const spyRead1 = vi.spyOn(read1, 'selectDistinct'); + + const query1 = db.selectDistinct().from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select distinct `id` from `users`'); + + const query2 = db.selectDistinct().from(users); + expect(spyRead1).toHaveBeenCalledTimes(2); + expect(query2.toSQL().sql).toEqual('select distinct `id` from `users`'); + }); + + it('single read replica selectDistinct + primary selectDistinct', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1]); + + const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); + const spyRead1 = vi.spyOn(read1, 'selectDistinct'); + + const query1 = db.selectDistinct().from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query1.toSQL().sql).toEqual('select distinct `id` from `users`'); + + const query2 = db.$primary.selectDistinct().from(users); + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(query2.toSQL().sql).toEqual('select distinct `id` from `users`'); + }); + + it('always first read selectDistinct', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2], (replicas) => { + return replicas[0]!; + }); + + const spyPrimary = vi.spyOn(primaryDb, 'selectDistinct'); + const spyRead1 = vi.spyOn(read1, 'selectDistinct'); + const spyRead2 = vi.spyOn(read2, 'selectDistinct'); + + const query1 = db.selectDistinct().from(users); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('select distinct `id` from `users`'); + + const query2 = db.selectDistinct().from(users); + expect(spyRead1).toHaveBeenCalledTimes(2); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('select distinct `id` from `users`'); + }); +}); + +describe('[with] read replicas singlestore', () => { + it('primary with', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2]); + + const spyPrimary = vi.spyOn(primaryDb, 'with'); + const spyRead1 = vi.spyOn(read1, 'with'); + const spyRead2 = vi.spyOn(read2, 'with'); + const obj1 = {} as any; + const obj2 = {} as any; + const obj3 = {} as any; + const obj4 = {} as any; + + db.$primary.with(obj1, obj2, obj3, obj4); + + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(obj1, obj2, obj3, obj4); + }); + + it('random replica with', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); + + const db = withReplicas(primaryDb, [read1, read2], () => { + return randomMockReplica(); + }); + + const spyPrimary = vi.spyOn(primaryDb, 'with'); + const spyRead1 = vi.spyOn(read1, 'with'); + const spyRead2 = vi.spyOn(read2, 'with'); + + db.with(); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(0); + + db.with(); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(1); + }); + + it('single read replica with', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1]); + + const spyPrimary = vi.spyOn(primaryDb, 'with'); + const spyRead1 = vi.spyOn(read1, 'with'); + + db.with(); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + + db.with(); + expect(spyRead1).toHaveBeenCalledTimes(2); + }); + + it('single read replica with + primary with', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1]); + + const spyPrimary = vi.spyOn(primaryDb, 'with'); + const spyRead1 = vi.spyOn(read1, 'with'); + + db.with(); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + + db.$primary.with(); + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(1); + }); + + it('always first read with', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2], (replicas) => { + return replicas[0]!; + }); + + const spyPrimary = vi.spyOn(primaryDb, 'with'); + const spyRead1 = vi.spyOn(read1, 'with'); + const spyRead2 = vi.spyOn(read2, 'with'); + const obj1 = {} as any; + const obj2 = {} as any; + const obj3 = {} as any; + + db.with(obj1); + + expect(spyPrimary).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledTimes(1); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj1); + + db.with(obj2, obj3); + expect(spyRead1).toHaveBeenCalledTimes(2); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyRead1).toHaveBeenCalledWith(obj2, obj3); + }); +}); + +describe('[update] replicas singlestore', () => { + it('primary update', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2]); + + const spyPrimary = vi.spyOn(primaryDb, 'update'); + const spyRead1 = vi.spyOn(read1, 'update'); + const spyRead2 = vi.spyOn(read2, 'update'); + + const query1 = db.update(users).set({ id: 1 }); + + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query1.toSQL().sql).toEqual('update `users` set `id` = ?'); + + const query2 = db.update(users).set({ id: 1 }); + + expect(spyPrimary).toHaveBeenCalledTimes(2); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query2.toSQL().sql).toEqual('update `users` set `id` = ?'); + + const query3 = db.$primary.update(users).set({ id: 1 }); + + expect(spyPrimary).toHaveBeenCalledTimes(3); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(query3.toSQL().sql).toEqual('update `users` set `id` = ?'); + }); +}); + +describe('[delete] replicas singlestore', () => { + it('primary delete', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2]); + + const spyPrimary = vi.spyOn(primaryDb, 'delete'); + const spyRead1 = vi.spyOn(read1, 'delete'); + const spyRead2 = vi.spyOn(read2, 'delete'); + + const query1 = db.delete(users); + + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(users); + expect(query1.toSQL().sql).toEqual('delete from `users`'); + + const query2 = db.delete(users); + + expect(spyPrimary).toHaveBeenCalledTimes(2); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, users); + expect(query2.toSQL().sql).toEqual('delete from `users`'); + + db.$primary.delete({} as any); + + expect(spyPrimary).toHaveBeenCalledTimes(3); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + }); +}); + +describe('[insert] replicas singlestore', () => { + it('primary insert', () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2]); + + const spyPrimary = vi.spyOn(primaryDb, 'insert'); + const spyRead1 = vi.spyOn(read1, 'insert'); + const spyRead2 = vi.spyOn(read2, 'insert'); + + const query = db.insert(users).values({ id: 1 }); + + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(users); + expect(query.toSQL().sql).toEqual('insert into `users` (`id`) values (?)'); + + db.insert(users); + + expect(spyPrimary).toHaveBeenCalledTimes(2); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, users); + + db.$primary.insert({} as any); + + expect(spyPrimary).toHaveBeenCalledTimes(3); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + }); +}); + +describe('[execute] replicas singlestore', () => { + it('primary execute', async () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2]); + + const spyPrimary = vi.spyOn(primaryDb, 'execute'); + const spyRead1 = vi.spyOn(read1, 'execute'); + const spyRead2 = vi.spyOn(read2, 'execute'); + + expect(db.execute(sql``)).rejects.toThrow(); + + // try { + // db.execute(sql``); + // } catch { /* empty */ } + + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + + expect(db.execute(sql``)).rejects.toThrow(); + // try { + // db.execute(sql``); + // } catch { /* empty */ } + + expect(spyPrimary).toHaveBeenCalledTimes(2); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + + expect(db.execute(sql``)).rejects.toThrow(); + // try { + // db.execute(sql``); + // } catch { /* empty */ } + + expect(spyPrimary).toHaveBeenCalledTimes(3); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + }); +}); + +describe('[transaction] replicas singlestore', () => { + it('primary transaction', async () => { + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); + + const db = withReplicas(primaryDb, [read1, read2]); + + const spyPrimary = vi.spyOn(primaryDb, 'transaction'); + const spyRead1 = vi.spyOn(read1, 'transaction'); + const spyRead2 = vi.spyOn(read2, 'transaction'); + const txFn1 = async (tx: any) => { + tx.select().from({} as any); + }; + + expect(db.transaction(txFn1)).rejects.toThrow(); + + expect(spyPrimary).toHaveBeenCalledTimes(1); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenCalledWith(txFn1); + + const txFn2 = async (tx: any) => { + tx.select().from({} as any); + }; + + expect(db.transaction(txFn2)).rejects.toThrow(); + + expect(spyPrimary).toHaveBeenCalledTimes(2); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + expect(spyPrimary).toHaveBeenNthCalledWith(2, txFn2); + + expect(db.transaction(async (tx) => { + tx.select().from({} as any); + })).rejects.toThrow(); + + expect(spyPrimary).toHaveBeenCalledTimes(3); + expect(spyRead1).toHaveBeenCalledTimes(0); + expect(spyRead2).toHaveBeenCalledTimes(0); + }); +}); + +// We are waiting for SingleStore support for `json_array` function +// describe('[findFirst] read replicas singlestore', () => { +// // it('primary findFirst', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); +// // const read2 = drizzle.mock({ schema: { usersTable } }); + +// // const db = withReplicas(primaryDb, [read1, read2]); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); +// // const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); +// // const obj = {} as any; + +// // db.$primary.query.usersTable.findFirst(obj); + +// // expect(spyPrimary).toHaveBeenCalledTimes(1); +// // expect(spyRead1).toHaveBeenCalledTimes(0); +// // expect(spyRead2).toHaveBeenCalledTimes(0); +// // expect(spyPrimary).toHaveBeenCalledWith(obj); +// // }); + +// // it('random replica findFirst', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); +// // const read2 = drizzle.mock({ schema: { usersTable } }); + +// // const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); + +// // const db = withReplicas(primaryDb, [read1, read2], () => { +// // return randomMockReplica(); +// // }); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); +// // const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); +// // const par1 = {} as any; + +// // db.query.usersTable.findFirst(par1); + +// // expect(spyPrimary).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyRead2).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledWith(par1); + +// // const query = db.query.usersTable.findFirst(); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyRead2).toHaveBeenCalledTimes(1); +// // expect(query.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable` limit ?'); +// // }); + +// // We are waiting for SingleStore support for `json_array` function +// // it('single read replica findFirst', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); + +// // const db = withReplicas(primaryDb, [read1]); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); + +// // db.query.usersTable.findFirst(); + +// // expect(spyPrimary).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledTimes(1); + +// // db.query.usersTable.findFirst(); +// // expect(spyRead1).toHaveBeenCalledTimes(2); +// // }); + +// // We are waiting for SingleStore support for `json_array` function +// // it('single read replica findFirst + primary findFirst', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); + +// // const db = withReplicas(primaryDb, [read1]); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); + +// // db.query.usersTable.findFirst(); + +// // expect(spyPrimary).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledTimes(1); + +// // db.$primary.query.usersTable.findFirst(); +// // expect(spyPrimary).toHaveBeenCalledTimes(1); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // }); + +// // We are waiting for SingleStore support for `json_array` function +// // it('always first read findFirst', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); +// // const read2 = drizzle.mock({ schema: { usersTable } }); + +// // const db = withReplicas(primaryDb, [read1, read2], (replicas) => { +// // return replicas[0]!; +// // }); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findFirst'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findFirst'); +// // const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findFirst'); + +// // db.query.usersTable.findFirst(); + +// // expect(spyPrimary).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyRead2).toHaveBeenCalledTimes(0); + +// // db.query.usersTable.findFirst(); +// // expect(spyRead1).toHaveBeenCalledTimes(2); +// // expect(spyRead2).toHaveBeenCalledTimes(0); +// // }); +// }); + +// describe('[findMany] read replicas singlestore', () => { +// // We are waiting for SingleStore support for `json_array` function +// // it('primary findMany', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); +// // const read2 = drizzle.mock({ schema: { usersTable } }); + +// // const db = withReplicas(primaryDb, [read1, read2]); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); +// // const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); +// // const obj = {} as any; + +// // const query = db.$primary.query.usersTable.findMany(obj); + +// // expect(spyPrimary).toHaveBeenCalledTimes(1); +// // expect(spyRead1).toHaveBeenCalledTimes(0); +// // expect(spyRead2).toHaveBeenCalledTimes(0); +// // expect(spyPrimary).toHaveBeenCalledWith(obj); +// // expect(query.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); +// // }); + +// // We are waiting for SingleStore support for `json_array` function +// // it('random replica findMany', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); +// // const read2 = drizzle.mock({ schema: { usersTable } }); + +// // const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); + +// // const db = withReplicas(primaryDb, [read1, read2], () => { +// // return randomMockReplica(); +// // }); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); +// // const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); +// // const obj1 = {} as any; +// // const obj2 = {} as any; + +// // const query1 = db.query.usersTable.findMany(obj1); + +// // expect(spyPrimary).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyRead2).toHaveBeenCalledTimes(0); +// // expect(query1.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); +// // expect(spyRead1).toHaveBeenCalledWith(obj1); + +// // const query2 = db.query.usersTable.findMany(obj2); + +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyRead2).toHaveBeenCalledTimes(1); +// // expect(query2.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); +// // expect(spyRead2).toHaveBeenCalledWith(obj2); +// // }); + +// // We are waiting for SingleStore support for `json_array` function +// // it('single read replica findMany', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); + +// // const db = withReplicas(primaryDb, [read1]); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); +// // const obj1 = {} as any; +// // const obj2 = {} as any; + +// // const query1 = db.query.usersTable.findMany(obj1); + +// // expect(spyPrimary).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyRead1).toHaveBeenCalledWith(obj1); +// // expect(query1.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); + +// // const query2 = db.query.usersTable.findMany(obj2); +// // expect(spyRead1).toHaveBeenCalledTimes(2); +// // expect(spyRead1).toHaveBeenNthCalledWith(2, obj2); +// // expect(query2.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); +// // }); + +// // We are waiting for SingleStore support for `json_array` function +// // it('single read replica findMany + primary findMany', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); + +// // const db = withReplicas(primaryDb, [read1]); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); +// // const obj1 = {} as any; +// // const obj2 = {} as any; + +// // const query1 = db.query.usersTable.findMany(obj1); + +// // expect(spyPrimary).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyRead1).toHaveBeenCalledWith(obj1); +// // expect(query1.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); + +// // const query2 = db.$primary.query.usersTable.findMany(obj2); + +// // expect(spyPrimary).toHaveBeenCalledTimes(1); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyPrimary).toHaveBeenNthCalledWith(1, obj2); +// // expect(query2.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); +// // }); + +// // We are waiting for SingleStore support for `json_array` function +// // it('always first read findMany', () => { +// // const primaryDb = drizzle.mock({ schema: { usersTable } }); +// // const read1 = drizzle.mock({ schema: { usersTable } }); +// // const read2 = drizzle.mock({ schema: { usersTable } }); + +// // const db = withReplicas(primaryDb, [read1, read2], (replicas) => { +// // return replicas[0]!; +// // }); + +// // const spyPrimary = vi.spyOn(primaryDb['query']['usersTable'], 'findMany'); +// // const spyRead1 = vi.spyOn(read1['query']['usersTable'], 'findMany'); +// // const spyRead2 = vi.spyOn(read2['query']['usersTable'], 'findMany'); +// // const obj1 = {} as any; +// // const obj2 = {} as any; + +// // const query1 = db.query.usersTable.findMany(obj1); + +// // expect(spyPrimary).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledTimes(1); +// // expect(spyRead2).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenCalledWith(obj1); +// // expect(query1.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); + +// // const query2 = db.query.usersTable.findMany(obj2); +// // expect(spyRead1).toHaveBeenCalledTimes(2); +// // expect(spyRead2).toHaveBeenCalledTimes(0); +// // expect(spyRead1).toHaveBeenNthCalledWith(2, obj2); +// // expect(query2.toSQL().sql).toEqual('select `id`, `name`, `verified` from `users` `usersTable`'); +// // }); +// }); diff --git a/integration-tests/tests/replicas/sqlite.test.ts b/integration-tests/tests/replicas/sqlite.test.ts index 4093a6298..aab55bbfd 100644 --- a/integration-tests/tests/replicas/sqlite.test.ts +++ b/integration-tests/tests/replicas/sqlite.test.ts @@ -15,9 +15,9 @@ const users = sqliteTable('users', { describe('[select] read replicas sqlite', () => { it('primary select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -35,9 +35,9 @@ describe('[select] read replicas sqlite', () => { }); it('random replica select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -64,8 +64,8 @@ describe('[select] read replicas sqlite', () => { }); it('single read replica select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -84,8 +84,8 @@ describe('[select] read replicas sqlite', () => { }); it('single read replica select + primary select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -105,9 +105,9 @@ describe('[select] read replicas sqlite', () => { }); it('always first read select', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -134,9 +134,9 @@ describe('[select] read replicas sqlite', () => { describe('[selectDistinct] read replicas sqlite', () => { it('primary selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -153,9 +153,9 @@ describe('[selectDistinct] read replicas sqlite', () => { }); it('random replica selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -181,8 +181,8 @@ describe('[selectDistinct] read replicas sqlite', () => { }); it('single read replica selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -201,8 +201,8 @@ describe('[selectDistinct] read replicas sqlite', () => { }); it('single read replica selectDistinct + primary selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -222,9 +222,9 @@ describe('[selectDistinct] read replicas sqlite', () => { }); it('always first read selectDistinct', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -250,9 +250,9 @@ describe('[selectDistinct] read replicas sqlite', () => { describe('[with] read replicas sqlite', () => { it('primary with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -273,9 +273,9 @@ describe('[with] read replicas sqlite', () => { }); it('random replica with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -299,8 +299,8 @@ describe('[with] read replicas sqlite', () => { }); it('single read replica with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -317,8 +317,8 @@ describe('[with] read replicas sqlite', () => { }); it('single read replica with + primary with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); const db = withReplicas(primaryDb, [read1]); @@ -336,9 +336,9 @@ describe('[with] read replicas sqlite', () => { }); it('always first read with', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -367,9 +367,9 @@ describe('[with] read replicas sqlite', () => { describe('[update] replicas sqlite', () => { it('primary update', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -402,9 +402,9 @@ describe('[update] replicas sqlite', () => { describe('[delete] replicas sqlite', () => { it('primary delete', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -438,9 +438,9 @@ describe('[delete] replicas sqlite', () => { describe('[insert] replicas sqlite', () => { it('primary insert', () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -473,9 +473,9 @@ describe('[insert] replicas sqlite', () => { describe('[execute] replicas sqlite', () => { it('primary execute', async () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -511,9 +511,9 @@ describe('[execute] replicas sqlite', () => { describe('[transaction] replicas sqlite', () => { it('primary transaction', async () => { - const primaryDb = drizzle({} as any); - const read1 = drizzle({} as any); - const read2 = drizzle({} as any); + const primaryDb = drizzle.mock(); + const read1 = drizzle.mock(); + const read2 = drizzle.mock(); const db = withReplicas(primaryDb, [read1, read2]); @@ -554,9 +554,9 @@ describe('[transaction] replicas sqlite', () => { describe('[findFirst] read replicas sqlite', () => { it('primary findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1, read2]); @@ -574,9 +574,9 @@ describe('[findFirst] read replicas sqlite', () => { }); it('random replica findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -603,8 +603,8 @@ describe('[findFirst] read replicas sqlite', () => { }); it('single read replica findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1]); @@ -621,8 +621,8 @@ describe('[findFirst] read replicas sqlite', () => { }); it('single read replica findFirst + primary findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1]); @@ -640,9 +640,9 @@ describe('[findFirst] read replicas sqlite', () => { }); it('always first read findFirst', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; @@ -666,9 +666,9 @@ describe('[findFirst] read replicas sqlite', () => { describe('[findMany] read replicas sqlite', () => { it('primary findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1, read2]); @@ -687,9 +687,9 @@ describe('[findMany] read replicas sqlite', () => { }); it('random replica findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const randomMockReplica = vi.fn().mockReturnValueOnce(read1).mockReturnValueOnce(read2); @@ -720,8 +720,8 @@ describe('[findMany] read replicas sqlite', () => { }); it('single read replica findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1]); @@ -744,8 +744,8 @@ describe('[findMany] read replicas sqlite', () => { }); it('single read replica findMany + primary findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1]); @@ -770,9 +770,9 @@ describe('[findMany] read replicas sqlite', () => { }); it('always first read findMany', () => { - const primaryDb = drizzle({} as any, { schema: { usersTable } }); - const read1 = drizzle({} as any, { schema: { usersTable } }); - const read2 = drizzle({} as any, { schema: { usersTable } }); + const primaryDb = drizzle.mock({ schema: { usersTable } }); + const read1 = drizzle.mock({ schema: { usersTable } }); + const read2 = drizzle.mock({ schema: { usersTable } }); const db = withReplicas(primaryDb, [read1, read2], (replicas) => { return replicas[0]!; diff --git a/integration-tests/tests/seeder/mysql.test.ts b/integration-tests/tests/seeder/mysql.test.ts new file mode 100644 index 000000000..22530a2aa --- /dev/null +++ b/integration-tests/tests/seeder/mysql.test.ts @@ -0,0 +1,490 @@ +import Docker from 'dockerode'; +import { sql } from 'drizzle-orm'; +import type { MySql2Database } from 'drizzle-orm/mysql2'; +import { drizzle } from 'drizzle-orm/mysql2'; +import { reset, seed } from 'drizzle-seed'; +import getPort from 'get-port'; +import type { Connection } from 'mysql2/promise'; +import { createConnection } from 'mysql2/promise'; +import { v4 as uuid } from 'uuid'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import * as schema from './mysqlSchema.ts'; + +let mysqlContainer: Docker.Container; +let client: Connection; +let db: MySql2Database; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'mysql:8'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + mysqlContainer = await docker.createContainer({ + Image: image, + Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await mysqlContainer.start(); + + return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; +} + +const createNorthwindTables = async () => { + await db.execute( + sql` + CREATE TABLE \`customer\` ( + \`id\` varchar(256) NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text, + \`region\` text, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + \`fax\` text, + CONSTRAINT \`customer_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`order_detail\` ( + \`unit_price\` float NOT NULL, + \`quantity\` int NOT NULL, + \`discount\` float NOT NULL, + \`order_id\` int NOT NULL, + \`product_id\` int NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`employee\` ( + \`id\` int NOT NULL, + \`last_name\` text NOT NULL, + \`first_name\` text, + \`title\` text NOT NULL, + \`title_of_courtesy\` text NOT NULL, + \`birth_date\` timestamp NOT NULL, + \`hire_date\` timestamp NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`home_phone\` text NOT NULL, + \`extension\` int NOT NULL, + \`notes\` text NOT NULL, + \`reports_to\` int, + \`photo_path\` text, + CONSTRAINT \`employee_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`order\` ( + \`id\` int NOT NULL, + \`order_date\` timestamp NOT NULL, + \`required_date\` timestamp NOT NULL, + \`shipped_date\` timestamp, + \`ship_via\` int NOT NULL, + \`freight\` float NOT NULL, + \`ship_name\` text NOT NULL, + \`ship_city\` text NOT NULL, + \`ship_region\` text, + \`ship_postal_code\` text, + \`ship_country\` text NOT NULL, + \`customer_id\` varchar(256) NOT NULL, + \`employee_id\` int NOT NULL, + CONSTRAINT \`order_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`product\` ( + \`id\` int NOT NULL, + \`name\` text NOT NULL, + \`quantity_per_unit\` text NOT NULL, + \`unit_price\` float NOT NULL, + \`units_in_stock\` int NOT NULL, + \`units_on_order\` int NOT NULL, + \`reorder_level\` int NOT NULL, + \`discontinued\` int NOT NULL, + \`supplier_id\` int NOT NULL, + CONSTRAINT \`product_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`supplier\` ( + \`id\` int NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`region\` text, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + CONSTRAINT \`supplier_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + ALTER TABLE \`order_detail\` ADD CONSTRAINT \`order_detail_order_id_order_id_fk\` FOREIGN KEY (\`order_id\`) REFERENCES \`order\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`order_detail\` ADD CONSTRAINT \`order_detail_product_id_product_id_fk\` FOREIGN KEY (\`product_id\`) REFERENCES \`product\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`employee\` ADD CONSTRAINT \`employee_reports_to_employee_id_fk\` FOREIGN KEY (\`reports_to\`) REFERENCES \`employee\`(\`id\`) ON DELETE no action ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`order\` ADD CONSTRAINT \`order_customer_id_customer_id_fk\` FOREIGN KEY (\`customer_id\`) REFERENCES \`customer\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`order\` ADD CONSTRAINT \`order_employee_id_employee_id_fk\` FOREIGN KEY (\`employee_id\`) REFERENCES \`employee\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`product\` ADD CONSTRAINT \`product_supplier_id_supplier_id_fk\` FOREIGN KEY (\`supplier_id\`) REFERENCES \`supplier\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); +}; + +const createAllDataTypesTable = async () => { + await db.execute( + sql` + CREATE TABLE \`all_data_types\` ( + \`integer\` int, + \`tinyint\` tinyint, + \`smallint\` smallint, + \`mediumint\` mediumint, + \`bigint\` bigint, + \`bigint_number\` bigint, + \`real\` real, + \`decimal\` decimal, + \`double\` double, + \`float\` float, + \`serial\` serial AUTO_INCREMENT, + \`binary\` binary(255), + \`varbinary\` varbinary(256), + \`char\` char(255), + \`varchar\` varchar(256), + \`text\` text, + \`boolean\` boolean, + \`date_string\` date, + \`date\` date, + \`datetime\` datetime, + \`datetimeString\` datetime, + \`time\` time, + \`year\` year, + \`timestamp_date\` timestamp, + \`timestamp_string\` timestamp, + \`json\` json, + \`popularity\` enum('unknown','known','popular') + ); + `, + ); +}; + +const createAllGeneratorsTables = async () => { + await db.execute( + sql` + CREATE TABLE \`datetime_table\` ( + \`datetime\` datetime + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`year_table\` ( + \`year\` year + ); + `, + ); +}; + +beforeAll(async () => { + const connectionString = await createDockerDB(); + + const sleep = 1000; + let timeLeft = 40000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + db = drizzle(client); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); + throw lastError; + } + + await createNorthwindTables(); + await createAllDataTypesTable(); + await createAllGeneratorsTables(); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +test('basic seed test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); +}); + +test('seed with options.count:11 test', async () => { + await seed(db, schema, { count: 11 }); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(11); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) customers count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) all tables count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + details: { + count: 13, + }, + employees: { + count: 14, + }, + orders: { + count: 15, + }, + products: { + count: 16, + }, + suppliers: { + count: 17, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(13); + expect(employees.length).toBe(14); + expect(orders.length).toBe(15); + expect(products.length).toBe(16); + expect(suppliers.length).toBe(17); +}); + +test("redefine(refine) orders count using 'with' in customers", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test("sequential using of 'with'", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +// All data types test ------------------------------- +test('basic seed test for all mysql data types', async () => { + await seed(db, schema, { count: 1000 }); + + const allDataTypes = await db.select().from(schema.allDataTypes); + + // every value in each 10 rows does not equal undefined. + const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + + expect(predicate).toBe(true); +}); + +// All generators test------------------------------- +const count = 10000; + +test('datetime generator test', async () => { + await seed(db, { datetimeTable: schema.datetimeTable }).refine((funcs) => ({ + datetimeTable: { + count, + columns: { + datetime: funcs.datetime(), + }, + }, + })); + + const data = await db.select().from(schema.datetimeTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('year generator test', async () => { + await seed(db, { yearTable: schema.yearTable }).refine((funcs) => ({ + yearTable: { + count, + columns: { + year: funcs.year(), + }, + }, + })); + + const data = await db.select().from(schema.yearTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/integration-tests/tests/seeder/mysqlSchema.ts b/integration-tests/tests/seeder/mysqlSchema.ts new file mode 100644 index 000000000..2caa97fa0 --- /dev/null +++ b/integration-tests/tests/seeder/mysqlSchema.ts @@ -0,0 +1,167 @@ +import type { AnyMySqlColumn } from 'drizzle-orm/mysql-core'; +import { + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + mysqlEnum, + mysqlTable, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varbinary, + varchar, + year, +} from 'drizzle-orm/mysql-core'; + +export const customers = mysqlTable('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = mysqlTable( + 'employee', + { + id: int('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: int('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: int('reports_to').references((): AnyMySqlColumn => employees.id), + photoPath: text('photo_path'), + }, +); + +export const orders = mysqlTable('order', { + id: int('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: int('ship_via').notNull(), + freight: float('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: varchar('customer_id', { length: 256 }) + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: int('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = mysqlTable('supplier', { + id: int('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = mysqlTable('product', { + id: int('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: float('unit_price').notNull(), + unitsInStock: int('units_in_stock').notNull(), + unitsOnOrder: int('units_on_order').notNull(), + reorderLevel: int('reorder_level').notNull(), + discontinued: int('discontinued').notNull(), + + supplierId: int('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = mysqlTable('order_detail', { + unitPrice: float('unit_price').notNull(), + quantity: int('quantity').notNull(), + discount: float('discount').notNull(), + + orderId: int('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: int('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); + +// All data types table ------------------------------- +export const allDataTypes = mysqlTable('all_data_types', { + int: int('integer'), + tinyint: tinyint('tinyint'), + smallint: smallint('smallint'), + mediumint: mediumint('mediumint'), + biginteger: bigint('bigint', { mode: 'bigint' }), + bigintNumber: bigint('bigint_number', { mode: 'number' }), + real: real('real'), + decimal: decimal('decimal'), + double: double('double'), + float: float('float'), + serial: serial('serial'), + binary: binary('binary', { length: 255 }), + varbinary: varbinary('varbinary', { length: 256 }), + char: char('char', { length: 255 }), + varchar: varchar('varchar', { length: 256 }), + text: text('text'), + boolean: boolean('boolean'), + dateString: date('date_string', { mode: 'string' }), + date: date('date', { mode: 'date' }), + datetime: datetime('datetime', { mode: 'date' }), + datetimeString: datetime('datetimeString', { mode: 'string' }), + time: time('time'), + year: year('year'), + timestampDate: timestamp('timestamp_date', { mode: 'date' }), + timestampString: timestamp('timestamp_string', { mode: 'string' }), + json: json('json'), + mysqlEnum: mysqlEnum('popularity', ['unknown', 'known', 'popular']), +}); + +// All generators tables ------------------------------- +export const datetimeTable = mysqlTable('datetime_table', { + datetime: datetime('datetime'), +}); + +export const yearTable = mysqlTable('year_table', { + year: year('year'), +}); diff --git a/integration-tests/tests/seeder/pg.test.ts b/integration-tests/tests/seeder/pg.test.ts new file mode 100644 index 000000000..48109f9fa --- /dev/null +++ b/integration-tests/tests/seeder/pg.test.ts @@ -0,0 +1,2572 @@ +import { PGlite } from '@electric-sql/pglite'; +import { sql } from 'drizzle-orm'; +import type { PgliteDatabase } from 'drizzle-orm/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; +import { cities, countries, firstNames, lastNames, reset, seed } from 'drizzle-seed'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import * as schema from './pgSchema.ts'; + +let client: PGlite; +let db: PgliteDatabase; + +const createNorthwindTables = async () => { + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."customer" ( + "id" varchar(256) PRIMARY KEY NOT NULL, + "company_name" text NOT NULL, + "contact_name" text NOT NULL, + "contact_title" text NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "postal_code" text, + "region" text, + "country" text NOT NULL, + "phone" text NOT NULL, + "fax" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."order_detail" ( + "unit_price" numeric NOT NULL, + "quantity" integer NOT NULL, + "discount" numeric NOT NULL, + "order_id" integer NOT NULL, + "product_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."employee" ( + "id" integer PRIMARY KEY NOT NULL, + "last_name" text NOT NULL, + "first_name" text, + "title" text NOT NULL, + "title_of_courtesy" text NOT NULL, + "birth_date" timestamp NOT NULL, + "hire_date" timestamp NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "postal_code" text NOT NULL, + "country" text NOT NULL, + "home_phone" text NOT NULL, + "extension" integer NOT NULL, + "notes" text NOT NULL, + "reports_to" integer, + "photo_path" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."order" ( + "id" integer PRIMARY KEY NOT NULL, + "order_date" timestamp NOT NULL, + "required_date" timestamp NOT NULL, + "shipped_date" timestamp, + "ship_via" integer NOT NULL, + "freight" numeric NOT NULL, + "ship_name" text NOT NULL, + "ship_city" text NOT NULL, + "ship_region" text, + "ship_postal_code" text, + "ship_country" text NOT NULL, + "customer_id" text NOT NULL, + "employee_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."product" ( + "id" integer PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "quantity_per_unit" text NOT NULL, + "unit_price" numeric NOT NULL, + "units_in_stock" integer NOT NULL, + "units_on_order" integer NOT NULL, + "reorder_level" integer NOT NULL, + "discontinued" integer NOT NULL, + "supplier_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."supplier" ( + "id" integer PRIMARY KEY NOT NULL, + "company_name" text NOT NULL, + "contact_name" text NOT NULL, + "contact_title" text NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "region" text, + "postal_code" text NOT NULL, + "country" text NOT NULL, + "phone" text NOT NULL + ); + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."order_detail" ADD CONSTRAINT "order_detail_order_id_order_id_fk" FOREIGN KEY ("order_id") REFERENCES "seeder_lib_pg"."order"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."order_detail" ADD CONSTRAINT "order_detail_product_id_product_id_fk" FOREIGN KEY ("product_id") REFERENCES "seeder_lib_pg"."product"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."employee" ADD CONSTRAINT "employee_reports_to_employee_id_fk" FOREIGN KEY ("reports_to") REFERENCES "seeder_lib_pg"."employee"("id") ON DELETE no action ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."order" ADD CONSTRAINT "order_customer_id_customer_id_fk" FOREIGN KEY ("customer_id") REFERENCES "seeder_lib_pg"."customer"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."order" ADD CONSTRAINT "order_employee_id_employee_id_fk" FOREIGN KEY ("employee_id") REFERENCES "seeder_lib_pg"."employee"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + DO $$ BEGIN + ALTER TABLE "seeder_lib_pg"."product" ADD CONSTRAINT "product_supplier_id_supplier_id_fk" FOREIGN KEY ("supplier_id") REFERENCES "seeder_lib_pg"."supplier"("id") ON DELETE cascade ON UPDATE no action; + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); +}; + +const createAllDataTypesTable = async () => { + await db.execute( + sql` + DO $$ BEGIN + CREATE TYPE "seeder_lib_pg"."mood_enum" AS ENUM('sad', 'ok', 'happy'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."all_data_types" ( + "integer" integer, + "smallint" smallint, + "bigint" bigint, + "bigint_number" bigint, + "serial" serial, + "smallserial" smallserial, + "bigserial" bigserial, + "bigserial_number" bigserial, + "boolean" boolean, + "text" text, + "varchar" varchar(256), + "char" char(256), + "numeric" numeric, + "decimal" numeric, + "real" real, + "double_precision" double precision, + "json" json, + "jsonb" jsonb, + "time" time, + "timestamp_date" timestamp, + "timestamp_string" timestamp, + "date_string" date, + "date" date, + "interval" interval, + "point" "point", + "point_tuple" "point", + "line" "line", + "line_tuple" "line", + "mood_enum" "seeder_lib_pg"."mood_enum", + "uuid" "uuid" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."all_array_data_types" ( + "integer_array" integer[], + "smallint_array" smallint[], + "bigint_array" bigint[], + "bigint_number_array" bigint[], + "boolean_array" boolean[], + "text_array" text[], + "varchar_array" varchar(256)[], + "char_array" char(256)[], + "numeric_array" numeric[], + "decimal_array" numeric[], + "real_array" real[], + "double_precision_array" double precision[], + "json_array" json[], + "jsonb_array" jsonb[], + "time_array" time[], + "timestamp_date_array" timestamp[], + "timestamp_string_array" timestamp[], + "date_string_array" date[], + "date_array" date[], + "interval_array" interval[], + "point_array" "point"[], + "point_tuple_array" "point"[], + "line_array" "line"[], + "line_tuple_array" "line"[], + "mood_enum_array" "seeder_lib_pg"."mood_enum"[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."nd_arrays" ( + "integer_1d_array" integer[3], + "integer_2d_array" integer[3][4], + "integer_3d_array" integer[3][4][5], + "integer_4d_array" integer[3][4][5][6] + ); + `, + ); +}; + +const createAllGeneratorsTables = async () => { + await db.execute( + sql` + DO $$ BEGIN + CREATE TYPE "seeder_lib_pg"."enum" AS ENUM('sad', 'ok', 'happy'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_table" ( + "default_string" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_array_table" ( + "default_string" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."boolean_table" ( + "boolean" boolean + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."boolean_array_table" ( + "boolean" boolean[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_table" ( + "city" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_unique_table" ( + "city_unique" varchar(256), + CONSTRAINT "city_unique_table_city_unique_unique" UNIQUE("city_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_array_table" ( + "city" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_table" ( + "company_name" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_unique_table" ( + "company_name_unique" varchar(256), + CONSTRAINT "company_name_unique_table_company_name_unique_unique" UNIQUE("company_name_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_array_table" ( + "company_name" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_table" ( + "country" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_unique_table" ( + "country_unique" varchar(256), + CONSTRAINT "country_unique_table_country_unique_unique" UNIQUE("country_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_array_table" ( + "country" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."date_table" ( + "date" date + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."date_array_table" ( + "date" date[], + "date_string" date[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."email_table" ( + "email" varchar(256), + CONSTRAINT "email_table_email_unique" UNIQUE("email") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."email_array_table" ( + "email" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."enum_table" ( + "mood_enum" "seeder_lib_pg"."enum" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."first_name_table" ( + "first_name" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."first_name_unique_table" ( + "first_name_unique" varchar(256), + CONSTRAINT "first_name_unique_table_first_name_unique_unique" UNIQUE("first_name_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."first_name_array_table" ( + "first_name" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name__table" ( + "full_name_" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name_unique_table" ( + "full_name_unique" varchar(256), + CONSTRAINT "full_name_unique_table_full_name_unique_unique" UNIQUE("full_name_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name_array_table" ( + "full_name" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_primary_key_table" ( + "int_primary_key" integer, + CONSTRAINT "int_primary_key_table_int_primary_key_unique" UNIQUE("int_primary_key") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_table" ( + "int" integer + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_unique_table" ( + "int_unique" integer, + CONSTRAINT "int_unique_table_int_unique_unique" UNIQUE("int_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_array_table" ( + "int" integer[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_table" ( + "interval" interval + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_unique_table" ( + "interval_unique" interval, + CONSTRAINT "interval_unique_table_interval_unique_unique" UNIQUE("interval_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_array_table" ( + "interval" interval[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."job_title_table" ( + "job_title" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."job_title_array_table" ( + "job_title" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."json_table" ( + "json" json + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."json_array_table" ( + "json" json[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_table" ( + "last_name" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_unique_table" ( + "last_name_unique" varchar(256), + CONSTRAINT "last_name_unique_table_last_name_unique_unique" UNIQUE("last_name_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_array_table" ( + "last_name" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."line_table" ( + "line" "line" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."line_array_table" ( + "line" "line"[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."lorem_ipsum_table" ( + "lorem_ipsum" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."lorem_ipsum_array_table" ( + "lorem_ipsum" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_table" ( + "number" real + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_unique_table" ( + "number_unique" real, + CONSTRAINT "number_unique_table_number_unique_unique" UNIQUE("number_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_array_table" ( + "number" real[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."phone_number_table" ( + "phoneNumber" varchar(256), + "phone_number_template" varchar(256), + "phone_number_prefixes" varchar(256), + CONSTRAINT "phone_number_table_phoneNumber_unique" UNIQUE("phoneNumber"), + CONSTRAINT "phone_number_table_phone_number_template_unique" UNIQUE("phone_number_template"), + CONSTRAINT "phone_number_table_phone_number_prefixes_unique" UNIQUE("phone_number_prefixes") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."phone_number_array_table" ( + "phoneNumber" varchar(256)[], + "phone_number_template" varchar(256)[], + "phone_number_prefixes" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."point_table" ( + "point" "point" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."point_array_table" ( + "point" "point"[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_table" ( + "postcode" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_unique_table" ( + "postcode_unique" varchar(256), + CONSTRAINT "postcode_unique_table_postcode_unique_unique" UNIQUE("postcode_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_array_table" ( + "postcode" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."state_table" ( + "state" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."state_array_table" ( + "state" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_table" ( + "street_address" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_unique_table" ( + "street_address_unique" varchar(256), + CONSTRAINT "street_address_unique_table_street_address_unique_unique" UNIQUE("street_address_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_array_table" ( + "street_address" varchar(256)[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_table" ( + "string" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_unique_table" ( + "string_unique" varchar(256), + CONSTRAINT "string_unique_table_string_unique_unique" UNIQUE("string_unique") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_array_table" ( + "string" text[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."time_table" ( + "time" time + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."time_array_table" ( + "time" time[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."timestamp_table" ( + "timestamp" timestamp + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."timestamp_array_table" ( + "timestamp" timestamp[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_table" ( + "values_from_array_not_null" varchar(256) NOT NULL, + "values_from_array_weighted_not_null" varchar(256) NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_unique_table" ( + "values_from_array" varchar(256), + "values_from_array_not_null" varchar(256) NOT NULL, + "values_from_array_weighted" varchar(256), + "values_from_array_weighted_not_null" varchar(256) NOT NULL, + CONSTRAINT "values_from_array_unique_table_values_from_array_unique" UNIQUE("values_from_array"), + CONSTRAINT "values_from_array_unique_table_values_from_array_not_null_unique" UNIQUE("values_from_array_not_null"), + CONSTRAINT "values_from_array_unique_table_values_from_array_weighted_unique" UNIQUE("values_from_array_weighted"), + CONSTRAINT "values_from_array_unique_table_values_from_array_weighted_not_null_unique" UNIQUE("values_from_array_weighted_not_null") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_array_table" ( + "values_from_array" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."weighted_random_table" ( + "weighted_random" varchar(256) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."weighted_random_with_unique_gens_table" ( + "weighted_random_with_unique_gens" varchar(256), + CONSTRAINT "weighted_random_with_unique_gens_table_weighted_random_with_unique_gens_unique" UNIQUE("weighted_random_with_unique_gens") + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."identity_columns_table" ( + "id" integer GENERATED ALWAYS AS IDENTITY, + "id1" integer, + "name" text + ); + `, + ); +}; + +beforeAll(async () => { + client = new PGlite(); + + db = drizzle(client); + + await db.execute(sql`CREATE SCHEMA IF NOT EXISTS "seeder_lib_pg";`); + + await createNorthwindTables(); + await createAllDataTypesTable(); + await createAllGeneratorsTables(); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +afterAll(async () => { + await client.close(); +}); + +test('basic seed test', async () => { + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); +}); + +test('seed with options.count:11 test', async () => { + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(11); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) customers count', async () => { + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) all tables count', async () => { + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + details: { + count: 13, + }, + employees: { + count: 14, + }, + orders: { + count: 15, + }, + products: { + count: 16, + }, + suppliers: { + count: 17, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(13); + expect(employees.length).toBe(14); + expect(orders.length).toBe(15); + expect(products.length).toBe(16); + expect(suppliers.length).toBe(17); +}); + +test("redefine(refine) orders count using 'with' in customers", async () => { + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test("sequential using of 'with'", async () => { + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('seeding with identity columns', async () => { + await seed(db, { identityColumnsTable: schema.identityColumnsTable }); + + const result = await db.select().from(schema.identityColumnsTable); + + expect(result.length).toBe(10); +}); + +// All data types test ------------------------------- +test('basic seed test for all postgres data types', async () => { + await seed(db, { allDataTypes: schema.allDataTypes }, { count: 10000 }); + + const allDataTypes = await db.select().from(schema.allDataTypes); + // every value in each 10 rows does not equal undefined. + const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + + expect(predicate).toBe(true); +}); + +test('all array data types test', async () => { + await seed(db, { allArrayDataTypes: schema.allArrayDataTypes }, { count: 1000 }); + + const allArrayDataTypes = await db.select().from(schema.allArrayDataTypes); + // every value in each rows does not equal undefined. + const predicate = allArrayDataTypes.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val.length === 10) + ); + + expect(predicate).toBe(true); +}); + +test('nd arrays', async () => { + await seed(db, { ndArrays: schema.ndArrays }, { count: 1000 }); + + const ndArrays = await db.select().from(schema.ndArrays); + // every value in each rows does not equal undefined. + const predicate0 = ndArrays.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val.length !== 0) + ); + let predicate1 = true, predicate2 = true, predicate3 = true, predicate4 = true; + + for (const row of ndArrays) { + predicate1 = predicate1 && (row.integer1DArray?.length === 3); + + predicate2 = predicate2 && (row.integer2DArray?.length === 4) && (row.integer2DArray[0]?.length === 3); + + predicate3 = predicate3 && (row.integer3DArray?.length === 5) && (row.integer3DArray[0]?.length === 4) + && (row.integer3DArray[0][0]?.length === 3); + + predicate4 = predicate4 && (row.integer4DArray?.length === 6) && (row.integer4DArray[0]?.length === 5) + && (row.integer4DArray[0][0]?.length === 4) && (row.integer4DArray[0][0][0]?.length === 3); + } + + expect(predicate0 && predicate1 && predicate2 && predicate3 && predicate4).toBe(true); +}); + +// All generators test------------------------------- +const count = 1000; + +test('enum generator test', async () => { + await seed(db, { enumTable: schema.enumTable }).refine(() => ({ + enumTable: { + count, + }, + })); + + const data = await db.select().from(schema.enumTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('default generator test', async () => { + await seed(db, { defaultTable: schema.defaultTable }).refine((funcs) => ({ + defaultTable: { + count, + columns: { + defaultString: funcs.default({ defaultValue: 'default string' }), + }, + }, + })); + + const data = await db.select().from(schema.defaultTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('default array generator test', async () => { + await seed(db, { defaultTable: schema.defaultArrayTable }).refine((funcs) => ({ + defaultTable: { + count, + columns: { + defaultString: funcs.default({ defaultValue: 'default string', arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.defaultArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('valuesFromArray generator test', async () => { + await seed(db, { valuesFromArrayTable: schema.valuesFromArrayTable }).refine((funcs) => ({ + valuesFromArrayTable: { + count, + columns: { + valuesFromArrayNotNull: funcs.valuesFromArray({ values: lastNames }), + valuesFromArrayWeightedNotNull: funcs.valuesFromArray({ + values: [ + { values: lastNames, weight: 0.3 }, + { values: firstNames, weight: 0.7 }, + ], + }), + }, + }, + })); + + const data = await db.select().from(schema.valuesFromArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('valuesFromArray unique generator test', async () => { + // valuesFromArrayUniqueTable----------------------------------------------------------------------------------- + await seed(db, { valuesFromArrayUniqueTable: schema.valuesFromArrayUniqueTable }, { seed: 1 }).refine((funcs) => ({ + valuesFromArrayUniqueTable: { + count: 49998, + columns: { + valuesFromArray: funcs.valuesFromArray({ values: lastNames.slice(0, 20), isUnique: true }), + valuesFromArrayNotNull: funcs.valuesFromArray({ values: lastNames, isUnique: true }), + valuesFromArrayWeighted: funcs.valuesFromArray({ + values: [ + { values: lastNames.slice(0, 20000), weight: 0.3 }, + { values: lastNames.slice(20000), weight: 0.7 }, + ], + isUnique: true, + }), + valuesFromArrayWeightedNotNull: funcs.valuesFromArray({ + values: [ + { values: lastNames.slice(0, 14920), weight: 0.3 }, + { values: lastNames.slice(14920), weight: 0.7 }, + ], + isUnique: true, + }), + }, + }, + })); + + const data = await db.select().from(schema.valuesFromArrayUniqueTable); + // console.log(valuesFromArrayUniqueTableData); + const predicate = data.length !== 0 && data.every((row) => + row['valuesFromArrayWeightedNotNull'] !== null + && row['valuesFromArrayNotNull'] !== null + ); + expect(predicate).toBe(true); + + await expect( + seed(db, { valuesFromArrayUniqueTable: schema.valuesFromArrayUniqueTable }).refine((funcs) => ({ + valuesFromArrayUniqueTable: { + count: 49998, + columns: { + valuesFromArrayWeightedNotNull: funcs.valuesFromArray({ + values: [ + { values: lastNames.slice(0, 20000), weight: 0.3 }, + { values: lastNames.slice(20000), weight: 0.7 }, + ], + isUnique: true, + }), + }, + }, + })), + ).rejects.toThrow( + /^weighted values arrays is too small to generate values with specified probability for unique not null column\..+/, + ); + + await expect( + seed(db, { valuesFromArrayUniqueTable: schema.valuesFromArrayUniqueTable }).refine((funcs) => ({ + valuesFromArrayUniqueTable: { + count: 49998, + columns: { + valuesFromArrayNotNull: funcs.valuesFromArray({ + values: lastNames.slice(20), + isUnique: true, + }), + }, + }, + })), + ).rejects.toThrow('There are no enough values to fill unique column.'); + + await expect( + seed(db, { valuesFromArrayUniqueTable: schema.valuesFromArrayUniqueTable }, { seed: 1 }).refine((funcs) => ({ + valuesFromArrayUniqueTable: { + count: 49999, + columns: { + valuesFromArrayNotNull: funcs.valuesFromArray({ + values: lastNames, + isUnique: true, + }), + valuesFromArrayWeightedNotNull: funcs.valuesFromArray({ + values: [ + { values: lastNames.slice(0, 14854), weight: 0.3 }, + { values: lastNames.slice(14854), weight: 0.7 }, + ], + isUnique: true, + }), + }, + }, + })), + ).rejects.toThrow('There are no enough values to fill unique column.'); +}); + +test('valuesFromArray array generator test', async () => { + await seed(db, { valuesFromArrayTable: schema.valuesFromArrayArrayTable }).refine((funcs) => ({ + valuesFromArrayTable: { + count, + columns: { + valuesFromArray: funcs.valuesFromArray({ values: lastNames, arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.valuesFromArrayArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('intPrimaryKey generator test', async () => { + await seed(db, { intPrimaryKeyTable: schema.intPrimaryKeyTable }).refine((funcs) => ({ + intPrimaryKeyTable: { + count, + columns: { + intPrimaryKey: funcs.intPrimaryKey(), + }, + }, + })); + + const data = await db.select().from(schema.intPrimaryKeyTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('number generator test', async () => { + await seed(db, { numberTable: schema.numberTable }).refine((funcs) => ({ + numberTable: { + count, + columns: { + number: funcs.number(), + }, + }, + })); + + const data = await db.select().from(schema.numberTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('number unique generator test', async () => { + // numberUniqueTable----------------------------------------------------------------------------------- + await seed(db, { numberUniqueTable: schema.numberUniqueTable }).refine((funcs) => ({ + numberUniqueTable: { + count: 20070, + columns: { + numberUnique: funcs.number({ isUnique: true, minValue: -100.23, maxValue: 100.46 }), + }, + }, + })); + + const data = await db.select().from(schema.numberUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val >= -100.23 && val <= 100.46) + ); + expect(predicate).toBe(true); + + await expect( + seed(db, { numberUniqueTable: schema.numberUniqueTable }).refine((funcs) => ({ + numberUniqueTable: { + count: 20071, + columns: { + numberUnique: funcs.number({ isUnique: true, minValue: -100.23, maxValue: 100.46 }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique integers in given range(min, max), try to make range wider.'); +}); + +test('number array generator test', async () => { + await seed(db, { numberTable: schema.numberArrayTable }).refine((funcs) => ({ + numberTable: { + count, + columns: { + number: funcs.number({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.numberArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('int generator test', async () => { + await seed(db, { intTable: schema.intTable }).refine((funcs) => ({ + intTable: { + count, + columns: { + int: funcs.int(), + }, + }, + })); + + const data = await db.select().from(schema.intTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('int unique generator test', async () => { + // intUniqueTable----------------------------------------------------------------------------------- + await seed(db, { intUniqueTable: schema.intUniqueTable }).refine((funcs) => ({ + intUniqueTable: { + count: 201, + columns: { + intUnique: funcs.int({ isUnique: true, minValue: -100, maxValue: 100 }), + }, + }, + })); + + const data = await db.select().from(schema.intUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { intUniqueTable: schema.intUniqueTable }).refine((funcs) => ({ + intUniqueTable: { + count: 202, + columns: { + intUnique: funcs.int({ isUnique: true, minValue: -100, maxValue: 100 }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique integers in given range(min, max), try to make range wider.'); +}); + +test('int array generator test', async () => { + await seed(db, { intTable: schema.intArrayTable }).refine((funcs) => ({ + intTable: { + count, + columns: { + int: funcs.int({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.intArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('boolean generator test', async () => { + await seed(db, { booleanTable: schema.booleanTable }).refine((funcs) => ({ + booleanTable: { + count, + columns: { + boolean: funcs.boolean(), + }, + }, + })); + + const data = await db.select().from(schema.booleanTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('boolean array generator test', async () => { + await seed(db, { booleanTable: schema.booleanArrayTable }).refine((funcs) => ({ + booleanTable: { + count, + columns: { + boolean: funcs.boolean({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.booleanArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('date generator test', async () => { + await seed(db, { dateTable: schema.dateTable }).refine((funcs) => ({ + dateTable: { + count, + columns: { + date: funcs.date(), + }, + }, + })); + + const data = await db.select().from(schema.dateTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('date array generator test', async () => { + await seed(db, { dateTable: schema.dateArrayTable }).refine((funcs) => ({ + dateTable: { + count, + columns: { + date: funcs.date({ arraySize: 3 }), + dateString: funcs.date({ arraySize: 4 }), + }, + }, + })); + + const data = await db.select().from(schema.dateArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && [3, 4].includes(val.length)) + ); + expect(predicate).toBe(true); +}); + +test('time generator test', async () => { + await seed(db, { timeTable: schema.timeTable }).refine((funcs) => ({ + timeTable: { + count, + columns: { + time: funcs.time(), + }, + }, + })); + + const data = await db.select().from(schema.timeTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('time array generator test', async () => { + await seed(db, { timeTable: schema.timeArrayTable }).refine((funcs) => ({ + timeTable: { + count, + columns: { + time: funcs.time({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.timeArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('timestamp generator test', async () => { + await seed(db, { timestampTable: schema.timestampTable }).refine((funcs) => ({ + timestampTable: { + count, + columns: { + timestamp: funcs.timestamp(), + }, + }, + })); + + const data = await db.select().from(schema.timestampTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('timestamp array generator test', async () => { + await seed(db, { timestampTable: schema.timestampArrayTable }).refine((funcs) => ({ + timestampTable: { + count, + columns: { + timestamp: funcs.timestamp({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.timestampArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('json generator test', async () => { + await seed(db, { jsonTable: schema.jsonTable }).refine((funcs) => ({ + jsonTable: { + count, + columns: { + json: funcs.json(), + }, + }, + })); + + const data = await db.select().from(schema.jsonTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('json array generator test', async () => { + await seed(db, { jsonTable: schema.jsonArrayTable }).refine((funcs) => ({ + jsonTable: { + count, + columns: { + json: funcs.json({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.jsonArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('interval generator test', async () => { + await seed(db, { intervalTable: schema.intervalTable }).refine((funcs) => ({ + intervalTable: { + count, + columns: { + interval: funcs.interval(), + }, + }, + })); + + const data = await db.select().from(schema.intervalTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('interval unique generator test', async () => { + // intervalUniqueTable----------------------------------------------------------------------------------- + await seed(db, { intervalUniqueTable: schema.intervalUniqueTable }).refine((funcs) => ({ + intervalUniqueTable: { + count, + columns: { + intervalUnique: funcs.interval({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.intervalUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('interval array generator test', async () => { + await seed(db, { intervalTable: schema.intervalArrayTable }).refine((funcs) => ({ + intervalTable: { + count, + columns: { + interval: funcs.interval({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.intervalArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('string generator test', async () => { + await seed(db, { stringTable: schema.stringTable }).refine((funcs) => ({ + stringTable: { + count, + columns: { + string: funcs.string(), + }, + }, + })); + + const data = await db.select().from(schema.stringTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('string unique generator test', async () => { + await seed(db, { stringUniqueTable: schema.stringUniqueTable }).refine((funcs) => ({ + stringUniqueTable: { + count, + columns: { + stringUnique: funcs.string({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.stringUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('string array generator test', async () => { + await seed(db, { stringTable: schema.stringArrayTable }).refine((funcs) => ({ + stringTable: { + count, + columns: { + string: funcs.string({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.stringArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('email generator test', async () => { + await seed(db, { emailTable: schema.emailTable }).refine((funcs) => ({ + emailTable: { + count, + columns: { + email: funcs.email(), + }, + }, + })); + + const data = await db.select().from(schema.emailTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('email array generator test', async () => { + await seed(db, { emailTable: schema.emailArrayTable }).refine((funcs) => ({ + emailTable: { + count, + columns: { + email: funcs.email({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.emailArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('firstName generator test', async () => { + await seed(db, { firstNameTable: schema.firstNameTable }).refine((funcs) => ({ + firstNameTable: { + count, + columns: { + firstName: funcs.firstName(), + }, + }, + })); + + const data = await db.select().from(schema.firstNameTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('firstName unique generator test', async () => { + // firstNameUniqueTable----------------------------------------------------------------------------------- + await seed(db, { firstNameUniqueTable: schema.firstNameUniqueTable }).refine((funcs) => ({ + firstNameUniqueTable: { + count: 30274, + columns: { + firstNameUnique: funcs.firstName({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.firstNameUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { firstNameUniqueTable: schema.firstNameUniqueTable }, { count: 30275 }).refine((funcs) => ({ + firstNameUniqueTable: { + count: 30275, + columns: { + firstNameUnique: funcs.firstName({ isUnique: true }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique first names.'); +}); + +test('firstName array generator test', async () => { + await seed(db, { firstNameTable: schema.firstNameArrayTable }).refine((funcs) => ({ + firstNameTable: { + count, + columns: { + firstName: funcs.firstName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.firstNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('lastName generator test', async () => { + await seed(db, { lastNameTable: schema.lastNameTable }).refine((funcs) => ({ + lastNameTable: { + count, + columns: { + lastName: funcs.lastName(), + }, + }, + })); + + const data = await db.select().from(schema.lastNameTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('lastName unique generator test', async () => { + // lastNameUniqueTable----------------------------------------------------------------------------------- + await seed(db, { lastNameUniqueTable: schema.lastNameUniqueTable }).refine((funcs) => ({ + lastNameUniqueTable: { + count: 49998, + columns: { + lastNameUnique: funcs.lastName({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.lastNameUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { lastNameUniqueTable: schema.lastNameUniqueTable }).refine((funcs) => ({ + lastNameUniqueTable: { + count: 49999, + columns: { + lastNameUnique: funcs.lastName({ isUnique: true }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique last names.'); +}); + +test('lastName array generator test', async () => { + await seed(db, { lastNameTable: schema.lastNameArrayTable }).refine((funcs) => ({ + lastNameTable: { + count, + columns: { + lastName: funcs.lastName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.lastNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('fullName generator test', async () => { + await seed(db, { fullNameTable: schema.fullNameTable }).refine((funcs) => ({ + fullNameTable: { + count, + columns: { + fullName: funcs.fullName(), + }, + }, + })); + + const data = await db.select().from(schema.fullNameTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('fullName unique generator test', async () => { + // fullNameUniqueTable----------------------------------------------------------------------------------- + await seed(db, { fullNameUniqueTable: schema.fullNameUniqueTable }).refine((funcs) => ({ + fullNameUniqueTable: { + count, + columns: { + fullNameUnique: funcs.fullName({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.fullNameUniqueTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('fullName array generator test', async () => { + await seed(db, { fullNameTable: schema.fullNameArrayTable }).refine((funcs) => ({ + fullNameTable: { + count, + columns: { + fullName: funcs.fullName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.fullNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('country generator test', async () => { + await seed(db, { countryTable: schema.countryTable }).refine((funcs) => ({ + countryTable: { + count, + columns: { + country: funcs.country(), + }, + }, + })); + + const data = await db.select().from(schema.countryTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('country unique generator test', async () => { + // countryUniqueTable----------------------------------------------------------------------------------- + await seed(db, { countryUniqueTable: schema.countryUniqueTable }).refine((funcs) => ({ + countryUniqueTable: { + count: countries.length, + columns: { + countryUnique: funcs.country({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.countryUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { countryUniqueTable: schema.countryUniqueTable }).refine((funcs) => ({ + countryUniqueTable: { + count: countries.length + 1, + columns: { + countryUnique: funcs.country({ isUnique: true }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique countries.'); +}); + +test('country array generator test', async () => { + await seed(db, { countryTable: schema.countryArrayTable }).refine((funcs) => ({ + countryTable: { + count, + columns: { + country: funcs.country({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.countryArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('city generator test', async () => { + await seed(db, { cityTable: schema.cityTable }).refine((funcs) => ({ + cityTable: { + count, + columns: { + city: funcs.city(), + }, + }, + })); + + const data = await db.select().from(schema.cityTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('city unique generator test', async () => { + // cityUniqueTable----------------------------------------------------------------------------------- + await reset(db, { cityUniqueTable: schema.cityUniqueTable }); + await seed(db, { cityUniqueTable: schema.cityUniqueTable }).refine((funcs) => ({ + cityUniqueTable: { + count: cities.length, + columns: { + cityUnique: funcs.city({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.cityUniqueTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { cityUniqueTable: schema.cityUniqueTable }).refine((funcs) => ({ + cityUniqueTable: { + count: cities.length + 1, + columns: { + cityUnique: funcs.city({ isUnique: true }), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique cities.'); +}); + +test('city array generator test', async () => { + await seed(db, { cityTable: schema.cityArrayTable }).refine((funcs) => ({ + cityTable: { + count, + columns: { + city: funcs.city({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.cityArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('streetAddress generator test', async () => { + await seed(db, { streetAddressTable: schema.streetAddressTable }).refine((funcs) => ({ + streetAddressTable: { + count, + columns: { + streetAddress: funcs.streetAddress(), + }, + }, + })); + + const data = await db.select().from(schema.streetAddressTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('streetAddress unique generator test', async () => { + await seed(db, { streetAddressUniqueTable: schema.streetAddressUniqueTable }).refine((funcs) => ({ + streetAddressUniqueTable: { + count, + columns: { + streetAddressUnique: funcs.streetAddress({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.streetAddressUniqueTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('streetAddress array generator test', async () => { + await seed(db, { streetAddressTable: schema.streetAddressArrayTable }).refine((funcs) => ({ + streetAddressTable: { + count, + columns: { + streetAddress: funcs.streetAddress({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.streetAddressArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('jobTitle generator test', async () => { + await seed(db, { jobTitleTable: schema.jobTitleTable }).refine((funcs) => ({ + jobTitleTable: { + count, + columns: { + jobTitle: funcs.jobTitle(), + }, + }, + })); + + const data = await db.select().from(schema.jobTitleTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('jobTitle array generator test', async () => { + await seed(db, { jobTitleTable: schema.jobTitleArrayTable }).refine((funcs) => ({ + jobTitleTable: { + count, + columns: { + jobTitle: funcs.jobTitle({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.jobTitleArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('postcode generator test', async () => { + await seed(db, { postcodeTable: schema.postcodeTable }).refine((funcs) => ({ + postcodeTable: { + count, + columns: { + postcode: funcs.postcode(), + }, + }, + })); + + const data = await db.select().from(schema.postcodeTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('postcode unique generator test', async () => { + await seed(db, { postcodeUniqueTable: schema.postcodeUniqueTable }).refine((funcs) => ({ + postcodeUniqueTable: { + count, + columns: { + postcodeUnique: funcs.postcode({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.postcodeUniqueTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('postcode array generator test', async () => { + await seed(db, { postcodeTable: schema.postcodeArrayTable }).refine((funcs) => ({ + postcodeTable: { + count, + columns: { + postcode: funcs.postcode({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.postcodeArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('state generator test', async () => { + await seed(db, { stateTable: schema.stateTable }).refine((funcs) => ({ + stateTable: { + count, + columns: { + state: funcs.state(), + }, + }, + })); + + const data = await db.select().from(schema.stateTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('state array generator test', async () => { + await seed(db, { stateTable: schema.stateArrayTable }).refine((funcs) => ({ + stateTable: { + count, + columns: { + state: funcs.state({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.stateArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('companyName generator test', async () => { + await seed(db, { companyNameTable: schema.companyNameTable }).refine((funcs) => ({ + companyNameTable: { + count, + columns: { + companyName: funcs.companyName(), + }, + }, + })); + + const data = await db.select().from(schema.companyNameTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('companyName unique generator test', async () => { + await seed(db, { companyNameUniqueTable: schema.companyNameUniqueTable }).refine((funcs) => ({ + companyNameUniqueTable: { + count, + columns: { + companyNameUnique: funcs.companyName({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.companyNameUniqueTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('companyName array generator test', async () => { + await seed(db, { companyNameTable: schema.companyNameArrayTable }).refine((funcs) => ({ + companyNameTable: { + count, + columns: { + companyName: funcs.companyName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.companyNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('loremIpsum generator test', async () => { + await seed(db, { loremIpsumTable: schema.loremIpsumTable }).refine((funcs) => ({ + loremIpsumTable: { + count, + columns: { + loremIpsum: funcs.loremIpsum(), + }, + }, + })); + + const data = await db.select().from(schema.loremIpsumTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('loremIpsum array generator test', async () => { + await seed(db, { loremIpsumTable: schema.loremIpsumArrayTable }).refine((funcs) => ({ + loremIpsumTable: { + count, + columns: { + loremIpsum: funcs.loremIpsum({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.loremIpsumArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('point generator test', async () => { + await seed(db, { pointTable: schema.pointTable }).refine((funcs) => ({ + pointTable: { + count, + columns: { + point: funcs.point(), + }, + }, + })); + + const data = await db.select().from(schema.pointTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('point unique generator test', async () => { + await reset(db, { pointTable: schema.pointTable }); + await seed(db, { pointTable: schema.pointTable }).refine((funcs) => ({ + pointTable: { + count, + columns: { + point: funcs.point({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.pointTable); + // every value in each row does not equal undefined. + let predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + // using Set because PGlite does not support unique point + const pointStrsSet = new Set(data.map((row) => row.point!.map(String).join(','))); + predicate = pointStrsSet.size === data.length; + expect(predicate).toBe(true); +}); + +test('point array generator test', async () => { + await seed(db, { pointTable: schema.pointArrayTable }).refine((funcs) => ({ + pointTable: { + count, + columns: { + point: funcs.point({ arraySize: 2 }), + }, + }, + })); + + const data = await db.select().from(schema.pointArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 2)); + expect(predicate).toBe(true); +}); + +test('line generator test', async () => { + await seed(db, { lineTable: schema.lineTable }).refine((funcs) => ({ + lineTable: { + count, + columns: { + line: funcs.line(), + }, + }, + })); + + const data = await db.select().from(schema.lineTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('line unique generator test', async () => { + await reset(db, { lineTable: schema.lineTable }); + await seed(db, { lineTable: schema.lineTable }).refine((funcs) => ({ + lineTable: { + count, + columns: { + line: funcs.line({ isUnique: true }), + }, + }, + })); + + const data = await db.select().from(schema.lineTable); + // every value in each row does not equal undefined. + let predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + // using Set because PGlite does not support unique point + const lineStrsSet = new Set(data.map((row) => row.line!.map(String).join(','))); + predicate = lineStrsSet.size === data.length; + expect(predicate).toBe(true); +}); + +test('line array generator test', async () => { + await seed(db, { lineTable: schema.lineArrayTable }).refine((funcs) => ({ + lineTable: { + count, + columns: { + line: funcs.line({ arraySize: 2 }), + }, + }, + })); + + const data = await db.select().from(schema.lineArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 2)); + expect(predicate).toBe(true); +}); + +test('phoneNumber generator test', async () => { + await seed(db, { phoneNumberTable: schema.phoneNumberTable }).refine((funcs) => ({ + phoneNumberTable: { + count, + columns: { + phoneNumber: funcs.phoneNumber(), + phoneNumberPrefixes: funcs.phoneNumber({ + prefixes: ['+380 99', '+380 67', '+1'], + generatedDigitsNumbers: [7, 7, 10], + }), + phoneNumberTemplate: funcs.phoneNumber({ template: '+380 ## ## ### ##' }), + }, + }, + })); + + const data = await db.select().from(schema.phoneNumberTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('phoneNumber array generator test', async () => { + await seed(db, { phoneNumberTable: schema.phoneNumberArrayTable }).refine((funcs) => ({ + phoneNumberTable: { + count, + columns: { + phoneNumber: funcs.phoneNumber({ arraySize: 3 }), + phoneNumberPrefixes: funcs.phoneNumber({ + prefixes: ['+380 99', '+380 67', '+1'], + generatedDigitsNumbers: [7, 7, 10], + arraySize: 4, + }), + phoneNumberTemplate: funcs.phoneNumber({ + template: '+380 ## ## ### ##', + arraySize: 5, + }), + }, + }, + })); + + const data = await db.select().from(schema.phoneNumberArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && [3, 4, 5].includes(val.length)) + ); + expect(predicate).toBe(true); +}); + +test('weightedRandom generator test', async () => { + await seed(db, { weightedRandomTable: schema.weightedRandomTable }).refine((funcs) => ({ + weightedRandomTable: { + count, + columns: { + weightedRandom: funcs.weightedRandom([ + { value: funcs.default({ defaultValue: 'default value' }), weight: 0.3 }, + { value: funcs.loremIpsum(), weight: 0.7 }, + ]), + }, + }, + })); + + const data = await db.select().from(schema.weightedRandomTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('weightedRandom with unique gens generator test', async () => { + await seed(db, { weightedRandomWithUniqueGensTable: schema.weightedRandomWithUniqueGensTable }).refine((funcs) => ({ + weightedRandomWithUniqueGensTable: { + count: 10000, + columns: { + weightedRandomWithUniqueGens: funcs.weightedRandom([ + { weight: 0.3, value: funcs.email() }, + { weight: 0.7, value: funcs.firstName({ isUnique: true }) }, + ]), + }, + }, + })); + + const data = await db.select().from(schema.weightedRandomWithUniqueGensTable); + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + await expect( + seed(db, { weightedRandomWithUniqueGensTable: schema.weightedRandomWithUniqueGensTable }).refine((funcs) => ({ + weightedRandomWithUniqueGensTable: { + count: 40000, + columns: { + weightedRandomWithUniqueGens: funcs.weightedRandom([ + { weight: 0.1, value: funcs.email() }, + { weight: 0.9, value: funcs.firstName({ isUnique: true }) }, + ]), + }, + }, + })), + ).rejects.toThrow('count exceeds max number of unique first names.'); + + await expect( + seed(db, { weightedRandomWithUniqueGensTable: schema.weightedRandomWithUniqueGensTable }).refine((funcs) => ({ + weightedRandomWithUniqueGensTable: { + count: 10000, + columns: { + weightedRandomWithUniqueGens: funcs.weightedRandom([ + { weight: 0.2, value: funcs.email() }, + { weight: 0.9, value: funcs.firstName({ isUnique: true }) }, + ]), + }, + }, + })), + ).rejects.toThrow( + 'The weights for the Weighted Random feature must add up to exactly 1. Please review your weights to ensure they total 1 before proceeding', + ); +}); diff --git a/integration-tests/tests/seeder/pgSchema.ts b/integration-tests/tests/seeder/pgSchema.ts new file mode 100644 index 000000000..ee0fceff5 --- /dev/null +++ b/integration-tests/tests/seeder/pgSchema.ts @@ -0,0 +1,502 @@ +import type { AnyPgColumn } from 'drizzle-orm/pg-core'; +import { + bigint, + bigserial, + boolean, + char, + date, + decimal, + doublePrecision, + integer, + interval, + json, + jsonb, + line, + numeric, + pgEnum, + pgSchema, + point, + real, + serial, + smallint, + smallserial, + text, + time, + timestamp, + uuid, + varchar, +} from 'drizzle-orm/pg-core'; + +export const schema = pgSchema('seeder_lib_pg'); + +export const customers = schema.table('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = schema.table( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to').references((): AnyPgColumn => employees.id), + photoPath: text('photo_path'), + }, +); + +export const orders = schema.table('order', { + id: integer('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id') + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: integer('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = schema.table('supplier', { + id: integer('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = schema.table('product', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = schema.table('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: integer('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); + +// All data types table ------------------------------- +export const moodEnum = pgEnum('mood_enum', ['sad', 'ok', 'happy']); + +export const allDataTypes = schema.table('all_data_types', { + integer: integer('integer'), + smallint: smallint('smallint'), + biginteger: bigint('bigint', { mode: 'bigint' }), + bigintNumber: bigint('bigint_number', { mode: 'number' }), + serial: serial('serial'), + smallserial: smallserial('smallserial'), + bigserial: bigserial('bigserial', { mode: 'bigint' }), + bigserialNumber: bigserial('bigserial_number', { mode: 'number' }), + boolean: boolean('boolean'), + text: text('text'), + varchar: varchar('varchar', { length: 256 }), + char: char('char', { length: 256 }), + numeric: numeric('numeric'), + decimal: decimal('decimal'), + real: real('real'), + doublePrecision: doublePrecision('double_precision'), + json: json('json'), + jsonb: jsonb('jsonb'), + time: time('time'), + timestampDate: timestamp('timestamp_date', { mode: 'date' }), + timestampString: timestamp('timestamp_string', { mode: 'string' }), + dateString: date('date_string', { mode: 'string' }), + date: date('date', { mode: 'date' }), + interval: interval('interval'), + point: point('point', { mode: 'xy' }), + pointTuple: point('point_tuple', { mode: 'tuple' }), + line: line('line', { mode: 'abc' }), + lineTuple: line('line_tuple', { mode: 'tuple' }), + moodEnum: moodEnum('mood_enum'), + uuid: uuid('uuid'), +}); + +export const allArrayDataTypes = schema.table('all_array_data_types', { + integerArray: integer('integer_array').array(), + smallintArray: smallint('smallint_array').array(), + bigintegerArray: bigint('bigint_array', { mode: 'bigint' }).array(), + bigintNumberArray: bigint('bigint_number_array', { mode: 'number' }).array(), + booleanArray: boolean('boolean_array').array(), + textArray: text('text_array').array(), + varcharArray: varchar('varchar_array', { length: 256 }).array(), + charArray: char('char_array', { length: 256 }).array(), + numericArray: numeric('numeric_array').array(), + decimalArray: decimal('decimal_array').array(), + realArray: real('real_array').array(), + doublePrecisionArray: doublePrecision('double_precision_array').array(), + jsonArray: json('json_array').array(), + jsonbArray: jsonb('jsonb_array').array(), + timeArray: time('time_array').array(), + timestampDateArray: timestamp('timestamp_date_array', { mode: 'date' }).array(), + timestampStringArray: timestamp('timestamp_string_array', { mode: 'string' }).array(), + dateStringArray: date('date_string_array', { mode: 'string' }).array(), + dateArray: date('date_array', { mode: 'date' }).array(), + intervalArray: interval('interval_array').array(), + pointArray: point('point_array', { mode: 'xy' }).array(), + pointTupleArray: point('point_tuple_array', { mode: 'tuple' }).array(), + lineArray: line('line_array', { mode: 'abc' }).array(), + lineTupleArray: line('line_tuple_array', { mode: 'tuple' }).array(), + moodEnumArray: moodEnum('mood_enum_array').array(), +}); + +export const ndArrays = schema.table('nd_arrays', { + integer1DArray: integer('integer_1d_array').array(3), + integer2DArray: integer('integer_2d_array').array(3).array(4), + integer3DArray: integer('integer_3d_array').array(3).array(4).array(5), + integer4DArray: integer('integer_4d_array').array(3).array(4).array(5).array(6), +}); + +// All generators tables ------------------------------- +export const enumTable = schema.table('enum_table', { + mood: moodEnum('mood_enum'), +}); + +export const defaultTable = schema.table('default_table', { + defaultString: text('default_string'), +}); + +export const defaultArrayTable = schema.table('default_array_table', { + defaultString: text('default_string').array(), +}); + +export const valuesFromArrayTable = schema.table('values_from_array_table', { + valuesFromArrayNotNull: varchar('values_from_array_not_null', { length: 256 }).notNull(), + valuesFromArrayWeightedNotNull: varchar('values_from_array_weighted_not_null', { length: 256 }).notNull(), +}); + +export const valuesFromArrayUniqueTable = schema.table('values_from_array_unique_table', { + valuesFromArray: varchar('values_from_array', { length: 256 }).unique(), + valuesFromArrayNotNull: varchar('values_from_array_not_null', { length: 256 }).unique().notNull(), + valuesFromArrayWeighted: varchar('values_from_array_weighted', { length: 256 }).unique(), + valuesFromArrayWeightedNotNull: varchar('values_from_array_weighted_not_null', { length: 256 }).unique().notNull(), +}); + +export const valuesFromArrayArrayTable = schema.table('values_from_array_array_table', { + valuesFromArray: varchar('values_from_array', { length: 256 }).array(), +}); + +export const intPrimaryKeyTable = schema.table('int_primary_key_table', { + intPrimaryKey: integer('int_primary_key').unique(), +}); + +export const numberTable = schema.table('number_table', { + number: real('number'), +}); + +export const numberUniqueTable = schema.table('number_unique_table', { + numberUnique: real('number_unique').unique(), +}); + +export const numberArrayTable = schema.table('number_array_table', { + number: real('number').array(), +}); + +export const intTable = schema.table('int_table', { + int: integer('int'), +}); + +export const intUniqueTable = schema.table('int_unique_table', { + intUnique: integer('int_unique').unique(), +}); + +export const intArrayTable = schema.table('int_array_table', { + int: integer('int').array(), +}); + +export const booleanTable = schema.table('boolean_table', { + boolean: boolean('boolean'), +}); + +export const booleanArrayTable = schema.table('boolean_array_table', { + boolean: boolean('boolean').array(), +}); + +export const dateTable = schema.table('date_table', { + date: date('date'), +}); + +// TODO: add tests for data type with different modes +export const dateArrayTable = schema.table('date_array_table', { + date: date('date', { mode: 'date' }).array(), + dateString: date('date_string', { mode: 'string' }).array(), +}); + +export const timeTable = schema.table('time_table', { + time: time('time'), +}); + +export const timeArrayTable = schema.table('time_array_table', { + time: time('time').array(), +}); + +export const timestampTable = schema.table('timestamp_table', { + timestamp: timestamp('timestamp'), +}); + +export const timestampArrayTable = schema.table('timestamp_array_table', { + timestamp: timestamp('timestamp').array(), +}); + +export const jsonTable = schema.table('json_table', { + json: json('json'), +}); + +export const jsonArrayTable = schema.table('json_array_table', { + json: json('json').array(), +}); + +export const intervalTable = schema.table('interval_table', { + interval: interval('interval'), +}); + +export const intervalUniqueTable = schema.table('interval_unique_table', { + intervalUnique: interval('interval_unique').unique(), +}); + +export const intervalArrayTable = schema.table('interval_array_table', { + interval: interval('interval').array(), +}); + +export const stringTable = schema.table('string_table', { + string: text('string'), +}); + +export const stringUniqueTable = schema.table('string_unique_table', { + stringUnique: varchar('string_unique', { length: 256 }).unique(), +}); + +export const stringArrayTable = schema.table('string_array_table', { + string: text('string').array(), +}); + +export const emailTable = schema.table('email_table', { + email: varchar('email', { length: 256 }).unique(), +}); + +export const emailArrayTable = schema.table('email_array_table', { + email: varchar('email', { length: 256 }).array(), +}); + +export const firstNameTable = schema.table('first_name_table', { + firstName: varchar('first_name', { length: 256 }), +}); + +export const firstNameUniqueTable = schema.table('first_name_unique_table', { + firstNameUnique: varchar('first_name_unique', { length: 256 }).unique(), +}); + +export const firstNameArrayTable = schema.table('first_name_array_table', { + firstName: varchar('first_name', { length: 256 }).array(), +}); + +export const lastNameTable = schema.table('last_name_table', { + lastName: varchar('last_name', { length: 256 }), +}); + +export const lastNameUniqueTable = schema.table('last_name_unique_table', { + lastNameUnique: varchar('last_name_unique', { length: 256 }).unique(), +}); + +export const lastNameArrayTable = schema.table('last_name_array_table', { + lastName: varchar('last_name', { length: 256 }).array(), +}); + +export const fullNameTable = schema.table('full_name__table', { + fullName: varchar('full_name_', { length: 256 }), +}); + +export const fullNameUniqueTable = schema.table('full_name_unique_table', { + fullNameUnique: varchar('full_name_unique', { length: 256 }).unique(), +}); + +export const fullNameArrayTable = schema.table('full_name_array_table', { + fullName: varchar('full_name', { length: 256 }).array(), +}); + +export const countryTable = schema.table('country_table', { + country: varchar('country', { length: 256 }), +}); + +export const countryUniqueTable = schema.table('country_unique_table', { + countryUnique: varchar('country_unique', { length: 256 }).unique(), +}); + +export const countryArrayTable = schema.table('country_array_table', { + country: varchar('country', { length: 256 }).array(), +}); + +export const cityTable = schema.table('city_table', { + city: varchar('city', { length: 256 }), +}); + +export const cityUniqueTable = schema.table('city_unique_table', { + cityUnique: varchar('city_unique', { length: 256 }).unique(), +}); + +export const cityArrayTable = schema.table('city_array_table', { + city: varchar('city', { length: 256 }).array(), +}); + +export const streetAddressTable = schema.table('street_address_table', { + streetAddress: varchar('street_address', { length: 256 }), +}); + +export const streetAddressUniqueTable = schema.table('street_address_unique_table', { + streetAddressUnique: varchar('street_address_unique', { length: 256 }).unique(), +}); + +export const streetAddressArrayTable = schema.table('street_address_array_table', { + streetAddress: varchar('street_address', { length: 256 }).array(), +}); + +export const jobTitleTable = schema.table('job_title_table', { + jobTitle: text('job_title'), +}); + +export const jobTitleArrayTable = schema.table('job_title_array_table', { + jobTitle: text('job_title').array(), +}); + +export const postcodeTable = schema.table('postcode_table', { + postcode: varchar('postcode', { length: 256 }), +}); + +export const postcodeUniqueTable = schema.table('postcode_unique_table', { + postcodeUnique: varchar('postcode_unique', { length: 256 }).unique(), +}); + +export const postcodeArrayTable = schema.table('postcode_array_table', { + postcode: varchar('postcode', { length: 256 }).array(), +}); + +export const stateTable = schema.table('state_table', { + state: text('state'), +}); + +export const stateArrayTable = schema.table('state_array_table', { + state: text('state').array(), +}); + +export const companyNameTable = schema.table('company_name_table', { + companyName: text('company_name'), +}); + +export const companyNameUniqueTable = schema.table('company_name_unique_table', { + companyNameUnique: varchar('company_name_unique', { length: 256 }).unique(), +}); + +export const companyNameArrayTable = schema.table('company_name_array_table', { + companyName: text('company_name').array(), +}); + +export const loremIpsumTable = schema.table('lorem_ipsum_table', { + loremIpsum: text('lorem_ipsum'), +}); + +export const loremIpsumArrayTable = schema.table('lorem_ipsum_array_table', { + loremIpsum: text('lorem_ipsum').array(), +}); + +export const pointTable = schema.table('point_table', { + point: point('point'), +}); + +export const pointArrayTable = schema.table('point_array_table', { + point: point('point').array(), +}); + +export const lineTable = schema.table('line_table', { + line: line('line'), +}); + +export const lineArrayTable = schema.table('line_array_table', { + line: line('line').array(), +}); + +// export const pointUniqueTable = schema.table("point_unique_table", { +// pointUnique: point("point_unique").unique(), +// }); + +// export const lineUniqueTable = schema.table("line_unique_table", { +// lineUnique: line("line_unique").unique(), +// }); + +export const phoneNumberTable = schema.table('phone_number_table', { + phoneNumber: varchar('phoneNumber', { length: 256 }).unique(), + phoneNumberTemplate: varchar('phone_number_template', { length: 256 }).unique(), + phoneNumberPrefixes: varchar('phone_number_prefixes', { length: 256 }).unique(), +}); + +export const phoneNumberArrayTable = schema.table('phone_number_array_table', { + phoneNumber: varchar('phoneNumber', { length: 256 }).array(), + phoneNumberTemplate: varchar('phone_number_template', { length: 256 }).array(), + phoneNumberPrefixes: varchar('phone_number_prefixes', { length: 256 }).array(), +}); + +export const weightedRandomTable = schema.table('weighted_random_table', { + weightedRandom: varchar('weighted_random', { length: 256 }), +}); + +export const weightedRandomWithUniqueGensTable = schema.table('weighted_random_with_unique_gens_table', { + weightedRandomWithUniqueGens: varchar('weighted_random_with_unique_gens', { length: 256 }).unique(), +}); + +export const identityColumnsTable = schema.table('identity_columns_table', { + id: integer('id').generatedAlwaysAsIdentity(), + id1: integer('id1'), + name: text('name'), +}); diff --git a/integration-tests/tests/seeder/sqlite.test.ts b/integration-tests/tests/seeder/sqlite.test.ts new file mode 100644 index 000000000..f9d124401 --- /dev/null +++ b/integration-tests/tests/seeder/sqlite.test.ts @@ -0,0 +1,322 @@ +import BetterSqlite3 from 'better-sqlite3'; +import { sql } from 'drizzle-orm'; +import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { reset, seed } from 'drizzle-seed'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import * as schema from './sqliteSchema.ts'; + +let client: BetterSqlite3.Database; +let db: BetterSQLite3Database; + +beforeAll(async () => { + client = new BetterSqlite3(':memory:'); + + db = drizzle(client); + + db.run( + sql.raw(` + CREATE TABLE \`customer\` ( + \`id\` text PRIMARY KEY NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text, + \`region\` text, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + \`fax\` text +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`order_detail\` ( + \`unit_price\` numeric NOT NULL, + \`quantity\` integer NOT NULL, + \`discount\` numeric NOT NULL, + \`order_id\` integer NOT NULL, + \`product_id\` integer NOT NULL, + FOREIGN KEY (\`order_id\`) REFERENCES \`order\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`product_id\`) REFERENCES \`product\`(\`id\`) ON UPDATE no action ON DELETE cascade +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`employee\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`last_name\` text NOT NULL, + \`first_name\` text, + \`title\` text NOT NULL, + \`title_of_courtesy\` text NOT NULL, + \`birth_date\` integer NOT NULL, + \`hire_date\` integer NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`home_phone\` text NOT NULL, + \`extension\` integer NOT NULL, + \`notes\` text NOT NULL, + \`reports_to\` integer, + \`photo_path\` text, + FOREIGN KEY (\`reports_to\`) REFERENCES \`employee\`(\`id\`) ON UPDATE no action ON DELETE no action +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`order\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`order_date\` integer NOT NULL, + \`required_date\` integer NOT NULL, + \`shipped_date\` integer, + \`ship_via\` integer NOT NULL, + \`freight\` numeric NOT NULL, + \`ship_name\` text NOT NULL, + \`ship_city\` text NOT NULL, + \`ship_region\` text, + \`ship_postal_code\` text, + \`ship_country\` text NOT NULL, + \`customer_id\` text NOT NULL, + \`employee_id\` integer NOT NULL, + FOREIGN KEY (\`customer_id\`) REFERENCES \`customer\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`employee_id\`) REFERENCES \`employee\`(\`id\`) ON UPDATE no action ON DELETE cascade +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`product\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`name\` text NOT NULL, + \`quantity_per_unit\` text NOT NULL, + \`unit_price\` numeric NOT NULL, + \`units_in_stock\` integer NOT NULL, + \`units_on_order\` integer NOT NULL, + \`reorder_level\` integer NOT NULL, + \`discontinued\` integer NOT NULL, + \`supplier_id\` integer NOT NULL, + FOREIGN KEY (\`supplier_id\`) REFERENCES \`supplier\`(\`id\`) ON UPDATE no action ON DELETE cascade +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`supplier\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`region\` text, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`phone\` text NOT NULL +); + `), + ); + + // All data types test ------------------------------- + db.run( + sql.raw(` + CREATE TABLE \`all_data_types\` ( + \`integer_number\` integer, + \`integer_boolean\` integer, + \`integer_timestamp\` integer, + \`integer_timestampms\` integer, + \`real\` real, + \`text\` text, + \`text_json\` text, + \`blob_bigint\` blob, + \`blob_buffer\` blob, + \`blob_json\` blob, + \`numeric\` numeric +); + `), + ); +}); + +afterAll(async () => { + client.close(); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +test('basic seed test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); +}); + +test('seed with options.count:11 test', async () => { + await seed(db, schema, { count: 11 }); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(11); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) customers count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) all tables count', async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + details: { + count: 13, + }, + employees: { + count: 14, + }, + orders: { + count: 15, + }, + products: { + count: 16, + }, + suppliers: { + count: 17, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(13); + expect(employees.length).toBe(14); + expect(orders.length).toBe(15); + expect(products.length).toBe(16); + expect(suppliers.length).toBe(17); +}); + +test("redefine(refine) orders count using 'with' in customers", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test("sequential using of 'with'", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +// All data types test ------------------------------- +test('basic seed test for all sqlite data types', async () => { + // migrate(db, { migrationsFolder: path.join(__dirname, "sqliteMigrations") }); + + await seed(db, { allDataTypes: schema.allDataTypes }, { count: 10000 }); + + const allDataTypes = await db.select().from(schema.allDataTypes); + // every value in each 10 rows does not equal undefined. + const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + + expect(predicate).toBe(true); +}); diff --git a/integration-tests/tests/seeder/sqliteSchema.ts b/integration-tests/tests/seeder/sqliteSchema.ts new file mode 100644 index 000000000..338833659 --- /dev/null +++ b/integration-tests/tests/seeder/sqliteSchema.ts @@ -0,0 +1,122 @@ +import { blob, foreignKey, integer, numeric, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const customers = sqliteTable('customer', { + id: text('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = sqliteTable( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: integer('birth_date', { mode: 'timestamp' }).notNull(), + hireDate: integer('hire_date', { mode: 'timestamp' }).notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to'), + photoPath: text('photo_path'), + }, + (table) => ({ + reportsToFk: foreignKey(() => ({ + columns: [table.reportsTo], + foreignColumns: [table.id], + })), + }), +); + +export const orders = sqliteTable('order', { + id: integer('id').primaryKey(), + orderDate: integer('order_date', { mode: 'timestamp' }).notNull(), + requiredDate: integer('required_date', { mode: 'timestamp' }).notNull(), + shippedDate: integer('shipped_date', { mode: 'timestamp' }), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id') + .notNull() + .references(() => customers.id, { onDelete: 'cascade' }), + + employeeId: integer('employee_id') + .notNull() + .references(() => employees.id, { onDelete: 'cascade' }), +}); + +export const suppliers = sqliteTable('supplier', { + id: integer('id').primaryKey({ autoIncrement: true }), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = sqliteTable('product', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id') + .notNull() + .references(() => suppliers.id, { onDelete: 'cascade' }), +}); + +export const details = sqliteTable('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id') + .notNull() + .references(() => orders.id, { onDelete: 'cascade' }), + + productId: integer('product_id') + .notNull() + .references(() => products.id, { onDelete: 'cascade' }), +}); + +// All data types table ------------------------------- +export const allDataTypes = sqliteTable('all_data_types', { + integerNumber: integer('integer_number', { mode: 'number' }), + integerBoolean: integer('integer_boolean', { mode: 'boolean' }), + integerTimestamp: integer('integer_timestamp', { mode: 'timestamp' }), + integerTimestampms: integer('integer_timestampms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { mode: 'text' }), + textJson: text('text_json', { mode: 'json' }), + blobBigint: blob('blob_bigint', { mode: 'bigint' }), + blobBuffer: blob('blob_buffer', { mode: 'buffer' }), + blobJson: blob('blob_json', { mode: 'json' }), + numeric: numeric('numeric'), +}); diff --git a/integration-tests/tests/singlestore/singlestore-common.ts b/integration-tests/tests/singlestore/singlestore-common.ts new file mode 100644 index 000000000..b8fe39608 --- /dev/null +++ b/integration-tests/tests/singlestore/singlestore-common.ts @@ -0,0 +1,3524 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import 'dotenv/config'; +import Docker from 'dockerode'; +import { + and, + asc, + avg, + avgDistinct, + count, + countDistinct, + eq, + exists, + getTableColumns, + gt, + gte, + inArray, + lt, + max, + min, + Name, + notInArray, + placeholder, + sql, + sum, + sumDistinct, + TransactionRollbackError, +} from 'drizzle-orm'; +import type { SingleStoreDatabase } from 'drizzle-orm/singlestore-core'; +import { + alias, + bigint, + boolean, + date, + datetime, + decimal, + except, + getTableConfig, + index, + int, + intersect, + json, + mediumint, + primaryKey, + serial, + singlestoreEnum, + singlestoreSchema, + singlestoreTable, + singlestoreTableCreator, + /* singlestoreView, */ + smallint, + text, + time, + timestamp, + tinyint, + union, + unionAll, + unique, + uniqueIndex, + uniqueKeyName, + varchar, + vector, + year, +} from 'drizzle-orm/singlestore-core'; +import { dotProduct, euclideanDistance } from 'drizzle-orm/singlestore-core/expressions'; +import { migrate } from 'drizzle-orm/singlestore/migrator'; +import getPort from 'get-port'; +import { v4 as uuid } from 'uuid'; +import { afterAll, beforeEach, describe, expect, expectTypeOf, test } from 'vitest'; +import { Expect, toLocalDate } from '~/utils.ts'; +import type { Equal } from '~/utils.ts'; + +type TestSingleStoreDB = SingleStoreDatabase; + +declare module 'vitest' { + interface TestContext { + singlestore: { + db: TestSingleStoreDB; + }; + } +} + +const ENABLE_LOGGING = false; + +const usersTable = singlestoreTable('userstest', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + verified: boolean('verified').notNull().default(false), + jsonb: json('jsonb').$type(), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +const users2Table = singlestoreTable('users2', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id'), +}); + +const citiesTable = singlestoreTable('cities', { + id: serial('id').primaryKey(), + name: text('name').notNull(), +}); + +const usersOnUpdate = singlestoreTable('users_on_update', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + updateCounter: int('update_counter').default(sql`1`).$onUpdateFn(() => sql`update_counter + 1`), + updatedAt: datetime('updated_at', { mode: 'date' }).$onUpdateFn(() => new Date()), + alwaysNull: text('always_null').$type().$onUpdateFn(() => null), // need to add $type because $onUpdate add a default value +}); + +const datesTable = singlestoreTable('datestable', { + date: date('date'), + dateAsString: date('date_as_string', { mode: 'string' }), + time: time('time'), + datetime: datetime('datetime'), + datetimeAsString: datetime('datetime_as_string', { mode: 'string' }), + timestamp: timestamp('timestamp'), + timestampAsString: timestamp('timestamp_as_string', { mode: 'string' }), + year: year('year'), +}); + +const coursesTable = singlestoreTable('courses', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + categoryId: int('category_id'), +}); + +const courseCategoriesTable = singlestoreTable('course_categories', { + id: serial('id').primaryKey(), + name: text('name').notNull(), +}); + +const orders = singlestoreTable('orders', { + id: serial('id').primaryKey(), + region: text('region').notNull(), + product: text('product').notNull().$default(() => 'random_string'), + amount: int('amount').notNull(), + quantity: int('quantity').notNull(), +}); + +const usersMigratorTable = singlestoreTable('users12', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + email: text('email').notNull(), +}, (table) => { + return { + name: uniqueIndex('').on(table.name).using('btree'), + }; +}); + +// To test aggregate functions +const aggregateTable = singlestoreTable('aggregate_table', { + id: serial('id').notNull(), + name: text('name').notNull(), + a: int('a'), + b: int('b'), + c: int('c'), + nullOnly: int('null_only'), +}); + +const vectorSearchTable = singlestoreTable('vector_search', { + id: serial('id').notNull(), + text: text('text').notNull(), + embedding: vector('embedding', { dimensions: 10 }), +}); + +// To test another schema and multischema +const mySchema = singlestoreSchema(`mySchema`); + +const usersMySchemaTable = mySchema.table('userstest', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + verified: boolean('verified').notNull().default(false), + jsonb: json('jsonb').$type(), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +const users2MySchemaTable = mySchema.table('users2', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id'), +}); + +const citiesMySchemaTable = mySchema.table('cities', { + id: serial('id').primaryKey(), + name: text('name').notNull(), +}); + +let singlestoreContainer: Docker.Container; +export async function createDockerDB(): Promise<{ connectionString: string; container: Docker.Container }> { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'ghcr.io/singlestore-labs/singlestoredb-dev:latest'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err))) + ); + + singlestoreContainer = await docker.createContainer({ + Image: image, + Env: ['ROOT_PASSWORD=singlestore'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await singlestoreContainer.start(); + await new Promise((resolve) => setTimeout(resolve, 4000)); + + return { + connectionString: `singlestore://root:singlestore@localhost:${port}/`, + container: singlestoreContainer, + }; +} + +export function tests(driver?: string) { + describe('common', () => { + afterAll(async () => { + await singlestoreContainer?.stop().catch(console.error); + }); + + beforeEach(async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`drop table if exists userstest`); + await db.execute(sql`drop table if exists users2`); + await db.execute(sql`drop table if exists cities`); + + await db.execute(sql`drop schema if exists \`mySchema\``); + await db.execute(sql`create schema if not exists \`mySchema\``); + + await db.execute( + sql` + create table userstest ( + id serial primary key, + name text not null, + verified boolean not null default false, + jsonb json, + created_at timestamp not null default now() + ) + `, + ); + + await db.execute( + sql` + create table users2 ( + id serial primary key, + name text not null, + city_id int + ) + `, + ); + + await db.execute( + sql` + create table cities ( + id serial primary key, + name text not null + ) + `, + ); + + // mySchema + await db.execute( + sql` + create table \`mySchema\`.\`userstest\` ( + \`id\` serial primary key, + \`name\` text not null, + \`verified\` boolean not null default false, + \`jsonb\` json, + \`created_at\` timestamp not null default now() + ) + `, + ); + + await db.execute( + sql` + create table \`mySchema\`.\`cities\` ( + \`id\` serial primary key, + \`name\` text not null + ) + `, + ); + + await db.execute( + sql` + create table \`mySchema\`.\`users2\` ( + \`id\` serial primary key, + \`name\` text not null, + \`city_id\` int + ) + `, + ); + }); + + async function setupReturningFunctionsTest(db: SingleStoreDatabase) { + await db.execute(sql`drop table if exists \`users_default_fn\``); + await db.execute( + sql` + create table \`users_default_fn\` ( + \`id\` varchar(256) primary key, + \`name\` text not null + ); + `, + ); + } + + async function setupSetOperationTest(db: TestSingleStoreDB) { + await db.execute(sql`drop table if exists \`users2\``); + await db.execute(sql`drop table if exists \`cities\``); + await db.execute( + sql` + create table \`users2\` ( + \`id\` serial primary key, + \`name\` text not null, + \`city_id\` int + ) + `, + ); + + await db.execute( + sql` + create table \`cities\` ( + \`id\` serial primary key, + \`name\` text not null + ) + `, + ); + + await db.insert(citiesTable).values([ + { id: 1, name: 'New York' }, + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + ]); + + await db.insert(users2Table).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 2 }, + { id: 3, name: 'Jack', cityId: 3 }, + { id: 4, name: 'Peter', cityId: 3 }, + { id: 5, name: 'Ben', cityId: 2 }, + { id: 6, name: 'Jill', cityId: 1 }, + { id: 7, name: 'Mary', cityId: 2 }, + { id: 8, name: 'Sally', cityId: 1 }, + ]); + } + + async function setupAggregateFunctionsTest(db: TestSingleStoreDB) { + await db.execute(sql`drop table if exists \`aggregate_table\``); + await db.execute( + sql` + create table \`aggregate_table\` ( + \`id\` integer primary key auto_increment not null, + \`name\` text not null, + \`a\` integer, + \`b\` integer, + \`c\` integer, + \`null_only\` integer + ); + `, + ); + await db.insert(aggregateTable).values([ + { id: 1, name: 'value 1', a: 5, b: 10, c: 20 }, + { id: 2, name: 'value 1', a: 5, b: 20, c: 30 }, + { id: 3, name: 'value 2', a: 10, b: 50, c: 60 }, + { id: 4, name: 'value 3', a: 20, b: 20, c: null }, + { id: 5, name: 'value 4', a: null, b: 90, c: 120 }, + { id: 6, name: 'value 5', a: 80, b: 10, c: null }, + { id: 7, name: 'value 6', a: null, b: null, c: 150 }, + ]); + } + + async function setupVectorSearchTest(db: TestSingleStoreDB) { + await db.execute(sql`drop table if exists \`vector_search\``); + await db.execute( + sql` + create table \`vector_search\` ( + \`id\` integer primary key auto_increment not null, + \`text\` text not null, + \`embedding\` vector(10) not null + ) + `, + ); + await db.insert(vectorSearchTable).values([ + { + id: 1, + text: 'I like dogs', + embedding: [0.6119, 0.1395, 0.2921, 0.3664, 0.4561, 0.7852, 0.1997, 0.5142, 0.5924, 0.0465], + }, + { + id: 2, + text: 'I like cats', + embedding: [0.6075, 0.1705, 0.0651, 0.9489, 0.9656, 0.8084, 0.3046, 0.0977, 0.6842, 0.4402], + }, + ]); + } + + test('table config: unsigned ints', async () => { + const unsignedInts = singlestoreTable('cities1', { + bigint: bigint('bigint', { mode: 'number', unsigned: true }), + int: int('int', { unsigned: true }), + smallint: smallint('smallint', { unsigned: true }), + mediumint: mediumint('mediumint', { unsigned: true }), + tinyint: tinyint('tinyint', { unsigned: true }), + }); + + const tableConfig = getTableConfig(unsignedInts); + + const bigintColumn = tableConfig.columns.find((c) => c.name === 'bigint')!; + const intColumn = tableConfig.columns.find((c) => c.name === 'int')!; + const smallintColumn = tableConfig.columns.find((c) => c.name === 'smallint')!; + const mediumintColumn = tableConfig.columns.find((c) => c.name === 'mediumint')!; + const tinyintColumn = tableConfig.columns.find((c) => c.name === 'tinyint')!; + + expect(bigintColumn.getSQLType()).toBe('bigint unsigned'); + expect(intColumn.getSQLType()).toBe('int unsigned'); + expect(smallintColumn.getSQLType()).toBe('smallint unsigned'); + expect(mediumintColumn.getSQLType()).toBe('mediumint unsigned'); + expect(tinyintColumn.getSQLType()).toBe('tinyint unsigned'); + }); + + test('table config: signed ints', async () => { + const unsignedInts = singlestoreTable('cities1', { + bigint: bigint('bigint', { mode: 'number' }), + int: int('int'), + smallint: smallint('smallint'), + mediumint: mediumint('mediumint'), + tinyint: tinyint('tinyint'), + }); + + const tableConfig = getTableConfig(unsignedInts); + + const bigintColumn = tableConfig.columns.find((c) => c.name === 'bigint')!; + const intColumn = tableConfig.columns.find((c) => c.name === 'int')!; + const smallintColumn = tableConfig.columns.find((c) => c.name === 'smallint')!; + const mediumintColumn = tableConfig.columns.find((c) => c.name === 'mediumint')!; + const tinyintColumn = tableConfig.columns.find((c) => c.name === 'tinyint')!; + + expect(bigintColumn.getSQLType()).toBe('bigint'); + expect(intColumn.getSQLType()).toBe('int'); + expect(smallintColumn.getSQLType()).toBe('smallint'); + expect(mediumintColumn.getSQLType()).toBe('mediumint'); + expect(tinyintColumn.getSQLType()).toBe('tinyint'); + }); + + test('table config: primary keys name', async () => { + const table = singlestoreTable('cities', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + state: text('state'), + }, (t) => ({ + f: primaryKey({ columns: [t.id, t.name], name: 'custom_pk' }), + })); + + const tableConfig = getTableConfig(table); + + expect(tableConfig.primaryKeys).toHaveLength(1); + expect(tableConfig.primaryKeys[0]!.getName()).toBe('custom_pk'); + }); + + test('table configs: unique third param', async () => { + const cities1Table = singlestoreTable('cities1', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + state: text('state'), + }, (t) => ({ + f: unique('custom_name').on(t.name, t.state), + f1: unique('custom_name1').on(t.name, t.state), + })); + + const tableConfig = getTableConfig(cities1Table); + + expect(tableConfig.uniqueConstraints).toHaveLength(2); + + expect(tableConfig.uniqueConstraints[0]?.name).toBe('custom_name'); + expect(tableConfig.uniqueConstraints[0]?.columns.map((t) => t.name)).toEqual(['name', 'state']); + + expect(tableConfig.uniqueConstraints[1]?.name).toBe('custom_name1'); + expect(tableConfig.uniqueConstraints[1]?.columns.map((t) => t.name)).toEqual(['name', 'state']); + }); + + test('table configs: unique in column', async () => { + const cities1Table = singlestoreTable('cities1', { + id: serial('id').primaryKey(), + name: text('name').notNull().unique(), + state: text('state').unique('custom'), + field: text('field').unique('custom_field'), + }); + + const tableConfig = getTableConfig(cities1Table); + + const columnName = tableConfig.columns.find((it) => it.name === 'name'); + expect(columnName?.uniqueName).toBe(uniqueKeyName(cities1Table, [columnName!.name])); + expect(columnName?.isUnique).toBeTruthy(); + + const columnState = tableConfig.columns.find((it) => it.name === 'state'); + expect(columnState?.uniqueName).toBe('custom'); + expect(columnState?.isUnique).toBeTruthy(); + + const columnField = tableConfig.columns.find((it) => it.name === 'field'); + expect(columnField?.uniqueName).toBe('custom_field'); + expect(columnField?.isUnique).toBeTruthy(); + }); + + test('select all fields', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const result = await db.select().from(usersTable); + + expect(result[0]!.createdAt).toBeInstanceOf(Date); + // not timezone based timestamp, thats why it should not work here + // t.assert(Math.abs(result[0]!.createdAt.getTime() - now) < 2000); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); + }); + + test('select sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.select({ + name: sql`upper(${usersTable.name})`, + }).from(usersTable); + + expect(users).toEqual([{ name: 'JOHN' }]); + }); + + test('select typed sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.select({ + name: sql`upper(${usersTable.name})`, + }).from(usersTable); + + expect(users).toEqual([{ name: 'JOHN' }]); + }); + + test('select with empty array in inArray', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + const result = await db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable) + .where(inArray(usersTable.id, [])) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([]); + }); + + test('select with empty array in notInArray', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + const result = await db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable) + .where(notInArray(usersTable.id, [])) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'JOHN' }, { name: 'JANE' }, { name: 'JANE' }]); + }); + + test('select distinct', async (ctx) => { + const { db } = ctx.singlestore; + + const usersDistinctTable = singlestoreTable('users_distinct', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${usersDistinctTable}`); + await db.execute(sql`create table ${usersDistinctTable} (id int, name text)`); + + await db.insert(usersDistinctTable).values([ + { id: 1, name: 'John' }, + { id: 1, name: 'John' }, + { id: 2, name: 'John' }, + { id: 1, name: 'Jane' }, + ]); + const users = await db.selectDistinct().from(usersDistinctTable).orderBy( + usersDistinctTable.id, + usersDistinctTable.name, + ); + + await db.execute(sql`drop table ${usersDistinctTable}`); + + expect(users).toEqual([{ id: 1, name: 'Jane' }, { id: 1, name: 'John' }, { id: 2, name: 'John' }]); + }); + + test('insert returning sql', async (ctx) => { + const { db } = ctx.singlestore; + + const [result, _] = await db.insert(usersTable).values({ id: 1, name: 'John' }); + + expect(result.insertId).toBe(1); + }); + + test('delete returning sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(users[0].affectedRows).toBe(1); + }); + + test('update returning sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + expect(users[0].changedRows).toBe(1); + }); + + test('update with returning all fields', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const updatedUsers = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + const users = await db.select().from(usersTable).where(eq(usersTable.id, 1)); + + expect(updatedUsers[0].changedRows).toBe(1); + + expect(users[0]!.createdAt).toBeInstanceOf(Date); + // not timezone based timestamp, thats why it should not work here + // t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 2000); + expect(users).toEqual([{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]); + }); + + test('update with returning partial', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const updatedUsers = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + const users = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(updatedUsers[0].changedRows).toBe(1); + + expect(users).toEqual([{ id: 1, name: 'Jane' }]); + }); + + test('delete with returning all fields', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const deletedUser = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(deletedUser[0].affectedRows).toBe(1); + }); + + test('delete with returning partial', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const deletedUser = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(deletedUser[0].affectedRows).toBe(1); + }); + + test('insert + select', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const result = await db.select().from(usersTable); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); + + await db.insert(usersTable).values({ id: 2, name: 'Jane' }); + const result2 = await db.select().from(usersTable).orderBy(asc(usersTable.id)); + expect(result2).toEqual([ + { id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt }, + { id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt }, + ]); + }); + + test('json insert', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John', jsonb: ['foo', 'bar'] }); + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + jsonb: usersTable.jsonb, + }).from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'John', jsonb: ['foo', 'bar'] }]); + }); + + test('insert with overridden default values', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John', verified: true }); + const result = await db.select().from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'John', verified: true, jsonb: null, createdAt: result[0]!.createdAt }]); + }); + + test('insert many', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([ + { id: 1, name: 'John' }, + { id: 2, name: 'Bruce', jsonb: ['foo', 'bar'] }, + { id: 3, name: 'Jane' }, + { id: 4, name: 'Austin', verified: true }, + ]); + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + jsonb: usersTable.jsonb, + verified: usersTable.verified, + }).from(usersTable) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([ + { id: 1, name: 'John', jsonb: null, verified: false }, + { id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false }, + { id: 3, name: 'Jane', jsonb: null, verified: false }, + { id: 4, name: 'Austin', jsonb: null, verified: true }, + ]); + }); + + test('insert many with returning', async (ctx) => { + const { db } = ctx.singlestore; + + const result = await db.insert(usersTable).values([ + { name: 'John' }, + { name: 'Bruce', jsonb: ['foo', 'bar'] }, + { name: 'Jane' }, + { name: 'Austin', verified: true }, + ]); + + expect(result[0].affectedRows).toBe(4); + }); + + test('select with group by as field', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.name) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]); + }); + + test('select with exists', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const user = alias(usersTable, 'user'); + const result = await db.select({ name: usersTable.name }).from(usersTable).where( + exists( + db.select({ one: sql`1` }).from(user).where(and(eq(usersTable.name, 'John'), eq(user.id, usersTable.id))), + ), + ) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }]); + }); + + test('select with group by as sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(sql`${usersTable.name}`) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]); + }); + + test('$default function', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`orders\``); + await db.execute( + sql` + create table \`orders\` ( + \`id\` serial primary key, + \`region\` text not null, + \`product\` text not null, + \`amount\` int not null, + \`quantity\` int not null + ) + `, + ); + + await db.insert(orders).values({ id: 1, region: 'Ukraine', amount: 1, quantity: 1 }); + const selectedOrder = await db.select().from(orders); + + expect(selectedOrder).toEqual([{ + id: 1, + amount: 1, + quantity: 1, + region: 'Ukraine', + product: 'random_string', + }]); + }); + + test('$default with empty array', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`s_orders\``); + await db.execute( + sql` + create table \`s_orders\` ( + \`id\` serial primary key, + \`region\` text default 'Ukraine', + \`product\` text not null + ) + `, + ); + + const users = singlestoreTable('s_orders', { + id: serial('id').primaryKey(), + region: text('region').default('Ukraine'), + product: text('product').$defaultFn(() => 'random_string'), + }); + + await db.insert(users).values({ id: 1 }); + const selectedOrder = await db.select().from(users); + + expect(selectedOrder).toEqual([{ + id: 1, + region: 'Ukraine', + product: 'random_string', + }]); + }); + + test('select with group by as sql + column', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(sql`${usersTable.name}`, usersTable.id) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + }); + + test('select with group by as column + sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, sql`${usersTable.name}`) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + }); + + test('select with group by complex query', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, sql`${usersTable.name}`) + .orderBy(asc(usersTable.name)) + .limit(1); + + expect(result).toEqual([{ name: 'Jane' }]); + }); + + test('build query', async (ctx) => { + const { db } = ctx.singlestore; + + const query = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, usersTable.name) + .toSQL(); + + expect(query).toEqual({ + sql: `select \`id\`, \`name\` from \`userstest\` group by \`userstest\`.\`id\`, \`userstest\`.\`name\``, + params: [], + }); + }); + + test('Query check: Insert all defaults in 1 row', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').default('Dan'), + state: text('state'), + }); + + const query = db + .insert(users) + .values({}) + .toSQL(); + + expect(query).toEqual({ + sql: 'insert into `users` (`id`, `name`, `state`) values (default, default, default)', + params: [], + }); + }); + + test('Query check: Insert all defaults in multiple rows', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').default('Dan'), + state: text('state').default('UA'), + }); + + const query = db + .insert(users) + .values([{}, {}]) + .toSQL(); + + expect(query).toEqual({ + sql: + 'insert into `users` (`id`, `name`, `state`) values (default, default, default), (default, default, default)', + params: [], + }); + }); + + test('Insert all defaults in 1 row', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('empty_insert_single', { + id: serial('id').primaryKey(), + name: text('name').default('Dan'), + state: text('state'), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table ${users} (id serial primary key, name text default 'Dan', state text)`, + ); + + await db.insert(users).values({ id: 1 }); + + const res = await db.select().from(users); + + expect(res).toEqual([{ id: 1, name: 'Dan', state: null }]); + }); + + test('Insert all defaults in multiple rows', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('empty_insert_multiple', { + id: serial('id').primaryKey(), + name: text('name').default('Dan'), + state: text('state'), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table ${users} (id serial primary key, name text default 'Dan', state text)`, + ); + + await db.insert(users).values([{ id: 1 }, { id: 2 }]); + + const res = await db.select().from(users).orderBy(asc(users.id)); + + expect(res).toEqual([{ id: 1, name: 'Dan', state: null }, { id: 2, name: 'Dan', state: null }]); + }); + + test('build query insert with onDuplicate', async (ctx) => { + const { db } = ctx.singlestore; + + const query = db.insert(usersTable) + .values({ id: 1, name: 'John', jsonb: ['foo', 'bar'] }) + .onDuplicateKeyUpdate({ set: { id: 1, name: 'John1' } }) + .toSQL(); + + expect(query).toEqual({ + sql: + 'insert into `userstest` (`id`, `name`, `verified`, `jsonb`, `created_at`) values (?, ?, default, ?, default) on duplicate key update `id` = ?, `name` = ?', + params: [1, 'John', '["foo","bar"]', 1, 'John1'], + }); + }); + + test('insert with onDuplicate', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }); + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }) + .onDuplicateKeyUpdate({ set: { name: 'John1' } }); + + const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(res).toEqual([{ id: 1, name: 'John1' }]); + }); + + test('insert conflict', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }); + + await expect((async () => { + db.insert(usersTable).values({ id: 1, name: 'John1' }); + })()).resolves.not.toThrowError(); + }); + + test('insert conflict with ignore', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }); + + await db.insert(usersTable) + .ignore() + .values({ id: 1, name: 'John1' }); + + const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(res).toEqual([{ id: 1, name: 'John' }]); + }); + + test('insert sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: sql`${'John'}` }); + const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable); + expect(result).toEqual([{ id: 1, name: 'John' }]); + }); + + test('partial join with alias', async (ctx) => { + const { db } = ctx.singlestore; + const customerAlias = alias(usersTable, 'customer'); + + await db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select({ + user: { + id: usersTable.id, + name: usersTable.name, + }, + customer: { + id: customerAlias.id, + name: customerAlias.name, + }, + }).from(usersTable) + .leftJoin(customerAlias, eq(customerAlias.id, 11)) + .where(eq(usersTable.id, 10)) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ + user: { id: 10, name: 'Ivan' }, + customer: { id: 11, name: 'Hans' }, + }]); + }); + + test('full join with alias', async (ctx) => { + const { db } = ctx.singlestore; + + const singlestoreTable = singlestoreTableCreator((name) => `prefixed_${name}`); + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial primary key, name text not null)`); + + const customers = alias(users, 'customer'); + + await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select().from(users) + .leftJoin(customers, eq(customers.id, 11)) + .where(eq(users.id, 10)) + .orderBy(asc(users.id)); + + expect(result).toEqual([{ + users: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, + }]); + + await db.execute(sql`drop table ${users}`); + }); + + test('select from alias', async (ctx) => { + const { db } = ctx.singlestore; + + const singlestoreTable = singlestoreTableCreator((name) => `prefixed_${name}`); + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial primary key, name text not null)`); + + const user = alias(users, 'user'); + const customers = alias(users, 'customer'); + + await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select() + .from(user) + .leftJoin(customers, eq(customers.id, 11)) + .where(eq(user.id, 10)) + .orderBy(asc(user.id)); + + expect(result).toEqual([{ + user: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, + }]); + + await db.execute(sql`drop table ${users}`); + }); + + test('insert with spaces', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: sql`'Jo h n'` }); + const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'Jo h n' }]); + }); + + test('prepared statement', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const statement = db.select({ + id: usersTable.id, + name: usersTable.name, + }).from(usersTable) + .prepare(); + const result = await statement.execute(); + + expect(result).toEqual([{ id: 1, name: 'John' }]); + }); + + test('insert: placeholders on columns with encoder', async (ctx) => { + const { db } = ctx.singlestore; + + const date = new Date('2024-08-07T15:30:00Z'); + + const statement = db.insert(usersTable).values({ + id: 1, + name: 'John', + createdAt: sql.placeholder('createdAt'), + }).prepare(); + + await statement.execute({ createdAt: date }); + + const result = await db + .select({ + id: usersTable.id, + createdAt: usersTable.createdAt, + }) + .from(usersTable); + + expect(result).toEqual([ + { id: 1, createdAt: date }, + ]); + }); + + test('prepared statement reuse', async (ctx) => { + const { db } = ctx.singlestore; + + const stmt = db.insert(usersTable).values({ + verified: true, + id: placeholder('id'), + name: placeholder('name'), + }).prepare(); + + for (let i = 0; i < 10; i++) { + await stmt.execute({ id: i + 1, name: `John ${i}` }); + } + + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + verified: usersTable.verified, + }).from(usersTable) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([ + { id: 1, name: 'John 0', verified: true }, + { id: 2, name: 'John 1', verified: true }, + { id: 3, name: 'John 2', verified: true }, + { id: 4, name: 'John 3', verified: true }, + { id: 5, name: 'John 4', verified: true }, + { id: 6, name: 'John 5', verified: true }, + { id: 7, name: 'John 6', verified: true }, + { id: 8, name: 'John 7', verified: true }, + { id: 9, name: 'John 8', verified: true }, + { id: 10, name: 'John 9', verified: true }, + ]); + }); + + test('prepared statement with placeholder in .where', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const stmt = db.select({ + id: usersTable.id, + name: usersTable.name, + }).from(usersTable) + .where(eq(usersTable.id, placeholder('id'))) + .prepare(); + const result = await stmt.execute({ id: 1 }); + + expect(result).toEqual([{ id: 1, name: 'John' }]); + }); + + test('migrator', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists cities_migration`); + await db.execute(sql`drop table if exists users_migration`); + await db.execute(sql`drop table if exists users12`); + await db.execute(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/singlestore' }); + + await db.insert(usersMigratorTable).values({ id: 1, name: 'John', email: 'email' }); + + const result = await db.select().from(usersMigratorTable); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.execute(sql`drop table cities_migration`); + await db.execute(sql`drop table users_migration`); + await db.execute(sql`drop table users12`); + await db.execute(sql`drop table __drizzle_migrations`); + }); + + test('insert via db.execute + select via db.execute', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute( + sql`insert into ${usersTable} (${new Name(usersTable.id.name)},${new Name( + usersTable.name.name, + )}) values (1,${'John'})`, + ); + + const result = await db.execute<{ id: number; name: string }>(sql`select id, name from ${usersTable}`); + expect(result[0]).toEqual([{ id: 1, name: 'John' }]); + }); + + test('insert via db.execute w/ query builder', async (ctx) => { + const { db } = ctx.singlestore; + + const inserted = await db.execute( + db.insert(usersTable).values({ id: 1, name: 'John' }), + ); + expect(inserted[0].affectedRows).toBe(1); + }); + + test('insert + select all possible dates', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`datestable\``); + await db.execute( + sql` + create table \`datestable\` ( + \`date\` date, + \`date_as_string\` date, + \`time\` time, + \`datetime\` datetime, + \`datetime_as_string\` datetime, + \`timestamp\` timestamp(6), + \`timestamp_as_string\` timestamp(6), + \`year\` year + ) + `, + ); + + const date = new Date('2022-11-11'); + const dateWithMilliseconds = new Date('2022-11-11 12:12:12.123'); + + await db.insert(datesTable).values({ + date: date, + dateAsString: '2022-11-11', + time: '12:12:12', + datetime: date, + year: 22, + datetimeAsString: '2022-11-11 12:12:12', + timestamp: dateWithMilliseconds, + timestampAsString: '2022-11-11 12:12:12.123', + }); + + const res = await db.select().from(datesTable); + + expect(res[0]?.date).toBeInstanceOf(Date); + expect(res[0]?.datetime).toBeInstanceOf(Date); + expect(typeof res[0]?.dateAsString).toBe('string'); + expect(typeof res[0]?.datetimeAsString).toBe('string'); + + expect(res).toEqual([{ + date: toLocalDate(new Date('2022-11-11')), + dateAsString: '2022-11-11', + time: '12:12:12', + datetime: new Date('2022-11-11'), + year: 2022, + datetimeAsString: '2022-11-11 12:12:12', + timestamp: new Date('2022-11-11 12:12:12.123'), + timestampAsString: '2022-11-11 12:12:12.123000', + }]); + + await db.execute(sql`drop table if exists \`datestable\``); + }); + + const tableWithEnums = singlestoreTable('enums_test_case', { + id: serial('id').primaryKey(), + enum1: singlestoreEnum('enum1', ['a', 'b', 'c']).notNull(), + enum2: singlestoreEnum('enum2', ['a', 'b', 'c']).default('a'), + enum3: singlestoreEnum('enum3', ['a', 'b', 'c']).notNull().default('b'), + }); + + test('SingleStore enum test case #1', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`enums_test_case\``); + + await db.execute(sql` + create table \`enums_test_case\` ( + \`id\` serial primary key, + \`enum1\` ENUM('a', 'b', 'c') not null, + \`enum2\` ENUM('a', 'b', 'c') default 'a', + \`enum3\` ENUM('a', 'b', 'c') not null default 'b' + ) + `); + + await db.insert(tableWithEnums).values([ + { id: 1, enum1: 'a', enum2: 'b', enum3: 'c' }, + { id: 2, enum1: 'a', enum3: 'c' }, + { id: 3, enum1: 'a' }, + ]); + + const res = await db.select().from(tableWithEnums).orderBy(asc(tableWithEnums.id)); + + await db.execute(sql`drop table \`enums_test_case\``); + + expect(res).toEqual([ + { id: 1, enum1: 'a', enum2: 'b', enum3: 'c' }, + { id: 2, enum1: 'a', enum2: 'a', enum3: 'c' }, + { id: 3, enum1: 'a', enum2: 'a', enum3: 'b' }, + ]); + }); + + test('left join (flat object fields)', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(citiesTable) + .values([{ id: 1, name: 'Paris' }, { id: 2, name: 'London' }]); + + await db.insert(users2Table).values([{ id: 1, name: 'John', cityId: 1 }, { id: 2, name: 'Jane' }]); + + const res = await db.select({ + userId: users2Table.id, + userName: users2Table.name, + cityId: citiesTable.id, + cityName: citiesTable.name, + }).from(users2Table) + .leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id)) + .orderBy(users2Table.id); + + expect(res).toEqual([ + { userId: 1, userName: 'John', cityId: 1, cityName: 'Paris' }, + { userId: 2, userName: 'Jane', cityId: null, cityName: null }, + ]); + }); + + test('left join (grouped fields)', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(citiesTable) + .values([{ id: 1, name: 'Paris' }, { id: 2, name: 'London' }]); + + await db.insert(users2Table).values([{ id: 1, name: 'John', cityId: 1 }, { id: 2, name: 'Jane' }]); + + const res = await db.select({ + id: users2Table.id, + user: { + name: users2Table.name, + nameUpper: sql`upper(${users2Table.name})`, + }, + city: { + id: citiesTable.id, + name: citiesTable.name, + nameUpper: sql`upper(${citiesTable.name})`, + }, + }).from(users2Table) + .leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id)) + .orderBy(asc(users2Table.id)); + + expect(res).toEqual([ + { + id: 1, + user: { name: 'John', nameUpper: 'JOHN' }, + city: { id: 1, name: 'Paris', nameUpper: 'PARIS' }, + }, + { + id: 2, + user: { name: 'Jane', nameUpper: 'JANE' }, + city: null, + }, + ]); + }); + + test('left join (all fields)', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(citiesTable) + .values([{ id: 1, name: 'Paris' }, { id: 2, name: 'London' }]); + + await db.insert(users2Table).values([{ id: 1, name: 'John', cityId: 1 }, { id: 2, name: 'Jane' }]); + + const res = await db.select().from(users2Table) + .leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id)) + .orderBy(asc(users2Table.id)); + + expect(res).toEqual([ + { + users2: { + id: 1, + name: 'John', + cityId: 1, + }, + cities: { + id: 1, + name: 'Paris', + }, + }, + { + users2: { + id: 2, + name: 'Jane', + cityId: null, + }, + cities: null, + }, + ]); + }); + + test('join subquery', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`courses\``); + await db.execute(sql`drop table if exists \`course_categories\``); + + await db.execute( + sql` + create table \`course_categories\` ( + \`id\` serial primary key, + \`name\` text not null + ) + `, + ); + + await db.execute( + sql` + create table \`courses\` ( + \`id\` serial primary key, + \`name\` text not null, + \`category_id\` int + ) + `, + ); + + await db.insert(courseCategoriesTable).values([ + { id: 1, name: 'Category 1' }, + { id: 2, name: 'Category 2' }, + { id: 3, name: 'Category 3' }, + { id: 4, name: 'Category 4' }, + ]); + + await db.insert(coursesTable).values([ + { id: 1, name: 'Development', categoryId: 2 }, + { id: 2, name: 'IT & Software', categoryId: 3 }, + { id: 3, name: 'Marketing', categoryId: 4 }, + { id: 4, name: 'Design', categoryId: 1 }, + ]); + + const sq2 = db + .select({ + categoryId: courseCategoriesTable.id, + category: courseCategoriesTable.name, + total: sql`count(${courseCategoriesTable.id})`, + }) + .from(courseCategoriesTable) + .groupBy(courseCategoriesTable.id, courseCategoriesTable.name) + .as('sq2'); + + const res = await db + .select({ + courseName: coursesTable.name, + categoryId: sq2.categoryId, + }) + .from(coursesTable) + .leftJoin(sq2, eq(coursesTable.categoryId, sq2.categoryId)) + .orderBy(coursesTable.name); + + expect(res).toEqual([ + { courseName: 'Design', categoryId: 1 }, + { courseName: 'Development', categoryId: 2 }, + { courseName: 'IT & Software', categoryId: 3 }, + { courseName: 'Marketing', categoryId: 4 }, + ]); + + await db.execute(sql`drop table if exists \`courses\``); + await db.execute(sql`drop table if exists \`course_categories\``); + }); + + test('with ... select', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`orders\``); + await db.execute( + sql` + create table \`orders\` ( + \`id\` serial primary key, + \`region\` text not null, + \`product\` text not null, + \`amount\` int not null, + \`quantity\` int not null + ) + `, + ); + + await db.insert(orders).values([ + { region: 'Europe', product: 'A', amount: 10, quantity: 1 }, + { region: 'Europe', product: 'A', amount: 20, quantity: 2 }, + { region: 'Europe', product: 'B', amount: 20, quantity: 2 }, + { region: 'Europe', product: 'B', amount: 30, quantity: 3 }, + { region: 'US', product: 'A', amount: 30, quantity: 3 }, + { region: 'US', product: 'A', amount: 40, quantity: 4 }, + { region: 'US', product: 'B', amount: 40, quantity: 4 }, + { region: 'US', product: 'B', amount: 50, quantity: 5 }, + ]); + + const regionalSales = db + .$with('regional_sales') + .as( + db + .select({ + region: orders.region, + totalSales: sql`sum(${orders.amount})`.as('total_sales'), + }) + .from(orders) + .groupBy(orders.region), + ); + + const topRegions = db + .$with('top_regions') + .as( + db + .select({ + region: regionalSales.region, + }) + .from(regionalSales) + .where( + gt( + regionalSales.totalSales, + db.select({ sales: sql`sum(${regionalSales.totalSales})/10` }).from(regionalSales), + ), + ), + ); + + const result = await db + .with(regionalSales, topRegions) + .select({ + region: orders.region, + product: orders.product, + productUnits: sql`cast(sum(${orders.quantity}) as unsigned)`, + productSales: sql`cast(sum(${orders.amount}) as unsigned)`, + }) + .from(orders) + .where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions))) + .groupBy(orders.region, orders.product) + .orderBy(orders.region, orders.product); + + expect(result).toEqual([ + { + region: 'Europe', + product: 'A', + productUnits: 3, + productSales: 30, + }, + { + region: 'Europe', + product: 'B', + productUnits: 5, + productSales: 50, + }, + { + region: 'US', + product: 'A', + productUnits: 7, + productSales: 70, + }, + { + region: 'US', + product: 'B', + productUnits: 9, + productSales: 90, + }, + ]); + }); + + test('with ... update', async (ctx) => { + const { db } = ctx.singlestore; + + const products = singlestoreTable('products', { + id: serial('id').primaryKey(), + price: decimal('price', { + precision: 15, + scale: 2, + }).notNull(), + cheap: boolean('cheap').notNull().default(false), + }); + + await db.execute(sql`drop table if exists ${products}`); + await db.execute(sql` + create table ${products} ( + id serial primary key, + price decimal(15, 2) not null, + cheap boolean not null default false + ) + `); + + await db.insert(products).values([ + { id: 1, price: '10.99' }, + { id: 2, price: '25.85' }, + { id: 3, price: '32.99' }, + { id: 4, price: '2.50' }, + { id: 5, price: '4.59' }, + ]); + + const averagePrice = db + .$with('average_price') + .as( + db + .select({ + value: sql`avg(${products.price})`.as('value'), + }) + .from(products), + ); + + await db + .with(averagePrice) + .update(products) + .set({ + cheap: true, + }) + .where(lt(products.price, sql`(select * from ${averagePrice})`)); + + const result = await db + .select({ + id: products.id, + }) + .from(products) + .where(eq(products.cheap, true)) + .orderBy(asc(products.id)); + + expect(result).toEqual([ + { id: 1 }, + { id: 4 }, + { id: 5 }, + ]); + }); + + test('with ... delete', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`orders\``); + await db.execute( + sql` + create table \`orders\` ( + \`id\` serial primary key, + \`region\` text not null, + \`product\` text not null, + \`amount\` int not null, + \`quantity\` int not null + ) + `, + ); + + await db.insert(orders).values([ + { id: 1, region: 'Europe', product: 'A', amount: 10, quantity: 1 }, + { id: 2, region: 'Europe', product: 'A', amount: 20, quantity: 2 }, + { id: 3, region: 'Europe', product: 'B', amount: 20, quantity: 2 }, + { id: 4, region: 'Europe', product: 'B', amount: 30, quantity: 3 }, + { id: 5, region: 'US', product: 'A', amount: 30, quantity: 3 }, + { id: 6, region: 'US', product: 'A', amount: 40, quantity: 4 }, + { id: 7, region: 'US', product: 'B', amount: 40, quantity: 4 }, + { id: 8, region: 'US', product: 'B', amount: 50, quantity: 5 }, + ]); + + const averageAmount = db + .$with('average_amount') + .as( + db + .select({ + value: sql`avg(${orders.amount})`.as('value'), + }) + .from(orders), + ); + + await db + .with(averageAmount) + .delete(orders) + .where(gt(orders.amount, sql`(select * from ${averageAmount})`)); + + const result = await db + .select({ + id: orders.id, + }) + .from(orders) + .orderBy(asc(orders.id)); + + expect(result).toEqual([ + { id: 1 }, + { id: 2 }, + { id: 3 }, + { id: 4 }, + { id: 5 }, + ]); + }); + + test('select from subquery sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(users2Table).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]); + + const sq = db + .select({ name: sql`concat(${users2Table.name}, " modified")`.as('name') }) + .from(users2Table) + .orderBy(asc(users2Table.id)) + .as('sq'); + + const res = await db.select({ name: sq.name }).from(sq); + + expect(res).toEqual([{ name: 'John modified' }, { name: 'Jane modified' }]); + }); + + test('select a field without joining its table', (ctx) => { + const { db } = ctx.singlestore; + + expect(() => db.select({ name: users2Table.name }).from(usersTable).prepare()).toThrowError(); + }); + + test('select all fields from subquery without alias', (ctx) => { + const { db } = ctx.singlestore; + + const sq = db.$with('sq').as(db.select({ name: sql`upper(${users2Table.name})` }).from(users2Table)); + + expect(() => db.select().from(sq).prepare()).toThrowError(); + }); + + test('select count()', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }]); + + const res = await db.select({ count: sql`count(*)` }).from(usersTable); + + expect(res).toEqual([{ count: 2 }]); + }); + + test('select for ...', (ctx) => { + const { db } = ctx.singlestore; + + { + const query = db.select().from(users2Table).for('update').toSQL(); + expect(query.sql).toMatch(/ for update$/); + } + { + const query = db.select().from(users2Table).for('share', { skipLocked: true }).toSQL(); + expect(query.sql).toMatch(/ for share skip locked$/); + } + { + const query = db.select().from(users2Table).for('update', { noWait: true }).toSQL(); + expect(query.sql).toMatch(/ for update no wait$/); + } + }); + + test('having', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(citiesTable).values([{ id: 1, name: 'London' }, { id: 2, name: 'Paris' }, { + id: 3, + name: 'New York', + }]); + + await db.insert(users2Table).values([{ id: 1, name: 'John', cityId: 1 }, { id: 2, name: 'Jane', cityId: 1 }, { + id: 3, + name: 'Jack', + cityId: 2, + }]); + + const result = await db + .select({ + id: citiesTable.id, + name: sql`upper(${citiesTable.name})`.as('upper_name'), + usersCount: sql`count(${users2Table.id})`.as('users_count'), + }) + .from(citiesTable) + .leftJoin(users2Table, eq(users2Table.cityId, citiesTable.id)) + .where(({ name }) => sql`length(${name}) >= 3`) + .groupBy(citiesTable.id) + .having(({ usersCount }) => sql`${usersCount} > 0`) + .orderBy(({ name }) => name); + + expect(result).toEqual([ + { + id: 1, + name: 'LONDON', + usersCount: 2, + }, + { + id: 2, + name: 'PARIS', + usersCount: 1, + }, + ]); + }); + + // TODO: Unskip when views are supported + /* test.skip('view', async (ctx) => { + const { db } = ctx.singlestore; + + const newYorkers1 = singlestoreView('new_yorkers') + .as((qb) => qb.select().from(users2Table).where(eq(users2Table.cityId, 1))); + + const newYorkers2 = singlestoreView('new_yorkers', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }).as(sql`select * from ${users2Table} where ${eq(users2Table.cityId, 1)}`); + + const newYorkers3 = singlestoreView('new_yorkers', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }).existing(); + + await db.execute(sql`create view new_yorkers as ${getViewConfig(newYorkers1).query}`); + + await db.insert(citiesTable).values([{ id: 1, name: 'New York' }, { id: 2, name: 'Paris' }]); + + await db.insert(users2Table).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + { id: 3, name: 'Jack', cityId: 2 }, + ]); + + { + const result = await db.select().from(newYorkers1).orderBy(asc(newYorkers1.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select().from(newYorkers2).orderBy(asc(newYorkers2.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select().from(newYorkers3).orderBy(asc(newYorkers3.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select({ name: newYorkers1.name }).from(newYorkers1).orderBy(asc(newYorkers1.id)); + expect(result).toEqual([ + { name: 'John' }, + { name: 'Jane' }, + ]); + } + + await db.execute(sql`drop view ${newYorkers1}`); + }); */ + + test('select from raw sql', async (ctx) => { + const { db } = ctx.singlestore; + + const result = await db.select({ + id: sql`id`, + name: sql`name`, + }).from(sql`(select 1 as id, 'John' as name) as users`); + + Expect>; + + expect(result).toEqual([ + { id: 1, name: 'John' }, + ]); + }); + + test('select from raw sql with joins', async (ctx) => { + const { db } = ctx.singlestore; + + const result = await db + .select({ + id: sql`users.id`, + name: sql`users.name`, + userCity: sql`users.city`, + cityName: sql`cities.name`, + }) + .from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`) + .leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, sql`cities.id = users.id`); + + Expect>; + + expect(result).toEqual([ + { id: 1, name: 'John', userCity: 'New York', cityName: 'Paris' }, + ]); + }); + + test('join on aliased sql from select', async (ctx) => { + const { db } = ctx.singlestore; + + const result = await db + .select({ + userId: sql`users.id`.as('userId'), + name: sql`users.name`, + userCity: sql`users.city`, + cityId: sql`cities.id`.as('cityId'), + cityName: sql`cities.name`, + }) + .from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`) + .leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, (cols) => eq(cols.cityId, cols.userId)); + + Expect< + Equal<{ userId: number; name: string; userCity: string; cityId: number; cityName: string }[], typeof result> + >; + + expect(result).toEqual([ + { userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }, + ]); + }); + + test('join on aliased sql from with clause', async (ctx) => { + const { db } = ctx.singlestore; + + const users = db.$with('users').as( + db.select({ + id: sql`id`.as('userId'), + name: sql`name`.as('userName'), + city: sql`city`.as('city'), + }).from( + sql`(select 1 as id, 'John' as name, 'New York' as city) as users`, + ), + ); + + const cities = db.$with('cities').as( + db.select({ + id: sql`id`.as('cityId'), + name: sql`name`.as('cityName'), + }).from( + sql`(select 1 as id, 'Paris' as name) as cities`, + ), + ); + + const result = await db + .with(users, cities) + .select({ + userId: users.id, + name: users.name, + userCity: users.city, + cityId: cities.id, + cityName: cities.name, + }) + .from(users) + .leftJoin(cities, (cols) => eq(cols.cityId, cols.userId)); + + Expect< + Equal<{ userId: number; name: string; userCity: string; cityId: number; cityName: string }[], typeof result> + >; + + expect(result).toEqual([ + { userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }, + ]); + }); + + test('prefixed table', async (ctx) => { + const { db } = ctx.singlestore; + + const singlestoreTable = singlestoreTableCreator((name) => `myprefix_${name}`); + + const users = singlestoreTable('test_prefixed_table_with_unique_name', { + id: int('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table myprefix_test_prefixed_table_with_unique_name (id int not null primary key, name text not null)`, + ); + + await db.insert(users).values({ id: 1, name: 'John' }); + + const result = await db.select().from(users); + + expect(result).toEqual([{ id: 1, name: 'John' }]); + + await db.execute(sql`drop table ${users}`); + }); + + test('orderBy with aliased column', (ctx) => { + const { db } = ctx.singlestore; + + const query = db.select({ + test: sql`something`.as('test'), + }).from(users2Table).orderBy((fields) => fields.test).toSQL(); + + expect(query.sql).toBe('select something as `test` from `users2` order by `test`'); + }); + + test('timestamp timezone', async (ctx) => { + const { db } = ctx.singlestore; + + const date = new Date(Date.parse('2020-01-01T12:34:56+07:00')); + + await db.insert(usersTable).values({ id: 1, name: 'With default times' }); + await db.insert(usersTable).values({ + id: 2, + name: 'Without default times', + createdAt: date, + }); + const users = await db.select().from(usersTable).orderBy(asc(usersTable.id)); + + // check that the timestamps are set correctly for default times + expect(Math.abs(users[0]!.createdAt.getTime() - Date.now())).toBeLessThan(2000); + + // check that the timestamps are set correctly for non default times + expect(Math.abs(users[1]!.createdAt.getTime() - date.getTime())).toBeLessThan(2000); + }); + + test('transaction', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users_transactions', { + id: serial('id').primaryKey(), + balance: int('balance').notNull(), + }); + const products = singlestoreTable('products_transactions', { + id: serial('id').primaryKey(), + price: int('price').notNull(), + stock: int('stock').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`drop table if exists ${products}`); + + await db.execute(sql`create table users_transactions (id serial not null primary key, balance int not null)`); + await db.execute( + sql`create table products_transactions (id serial not null primary key, price int not null, stock int not null)`, + ); + + const [{ insertId: userId }] = await db.insert(users).values({ id: 1, balance: 100 }); + const user = await db.select().from(users).where(eq(users.id, userId)).then((rows) => rows[0]!); + const [{ insertId: productId }] = await db.insert(products).values({ id: 1, price: 10, stock: 10 }); + const product = await db.select().from(products).where(eq(products.id, productId)).then((rows) => rows[0]!); + + await db.transaction(async (tx) => { + await tx.update(users).set({ balance: user.balance - product.price }).where(eq(users.id, user.id)); + await tx.update(products).set({ stock: product.stock - 1 }).where(eq(products.id, product.id)); + }); + + const result = await db.select().from(users); + + expect(result).toEqual([{ id: 1, balance: 90 }]); + + await db.execute(sql`drop table ${users}`); + await db.execute(sql`drop table ${products}`); + }); + + test('transaction rollback', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users_transactions_rollback', { + id: serial('id').primaryKey(), + balance: int('balance').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table users_transactions_rollback (id serial not null primary key, balance int not null)`, + ); + + await expect((async () => { + await db.transaction(async (tx) => { + await tx.insert(users).values({ balance: 100 }); + tx.rollback(); + }); + })()).rejects.toThrowError(TransactionRollbackError); + + const result = await db.select().from(users); + + expect(result).toEqual([]); + + await db.execute(sql`drop table ${users}`); + }); + + test('join subquery with join', async (ctx) => { + const { db } = ctx.singlestore; + + const internalStaff = singlestoreTable('internal_staff', { + userId: int('user_id').notNull(), + }); + + const customUser = singlestoreTable('custom_user', { + id: int('id').notNull(), + }); + + const ticket = singlestoreTable('ticket', { + staffId: int('staff_id').notNull(), + }); + + await db.execute(sql`drop table if exists ${internalStaff}`); + await db.execute(sql`drop table if exists ${customUser}`); + await db.execute(sql`drop table if exists ${ticket}`); + + await db.execute(sql`create table internal_staff (user_id integer not null)`); + await db.execute(sql`create table custom_user (id integer not null)`); + await db.execute(sql`create table ticket (staff_id integer not null)`); + + await db.insert(internalStaff).values({ userId: 1 }); + await db.insert(customUser).values({ id: 1 }); + await db.insert(ticket).values({ staffId: 1 }); + + const subq = db + .select() + .from(internalStaff) + .leftJoin(customUser, eq(internalStaff.userId, customUser.id)) + .as('internal_staff'); + + const mainQuery = await db + .select() + .from(ticket) + .leftJoin(subq, eq(subq.internal_staff.userId, ticket.staffId)); + + expect(mainQuery).toEqual([{ + ticket: { staffId: 1 }, + internal_staff: { + internal_staff: { userId: 1 }, + custom_user: { id: 1 }, + }, + }]); + + await db.execute(sql`drop table ${internalStaff}`); + await db.execute(sql`drop table ${customUser}`); + await db.execute(sql`drop table ${ticket}`); + }); + + // TODO: Unskip when views are supported + /* test.skip('subquery with view', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users_subquery_view', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }); + + const newYorkers = singlestoreView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`drop view if exists ${newYorkers}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, name text not null, city_id integer not null)`, + ); + await db.execute(sql`create view ${newYorkers} as select * from ${users} where city_id = 1`); + + await db.insert(users).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 2 }, + { id: 3, name: 'Jack', cityId: 1 }, + { id: 4, name: 'Jill', cityId: 2 }, + ]); + + const sq = db.$with('sq').as(db.select().from(newYorkers)); + const result = await db.with(sq).select().from(sq).orderBy(asc(sq.id)); + + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 3, name: 'Jack', cityId: 1 }, + ]); + + await db.execute(sql`drop view ${newYorkers}`); + await db.execute(sql`drop table ${users}`); + }); */ + + // TODO: Unskip when views are supported + /* test.skip('join view as subquery', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users_join_view', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }); + + const newYorkers = singlestoreView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`drop view if exists ${newYorkers}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, name text not null, city_id integer not null)`, + ); + await db.execute(sql`create view ${newYorkers} as select * from ${users} where city_id = 1`); + + await db.insert(users).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 2 }, + { id: 3, name: 'Jack', cityId: 1 }, + { id: 4, name: 'Jill', cityId: 2 }, + ]); + + const sq = db.select().from(newYorkers).as('new_yorkers_sq'); + + const result = await db.select().from(users).leftJoin(sq, eq(users.id, sq.id)).orderBy(asc(users.id)); + + expect(result).toEqual([ + { + users_join_view: { id: 1, name: 'John', cityId: 1 }, + new_yorkers_sq: { id: 1, name: 'John', cityId: 1 }, + }, + { + users_join_view: { id: 2, name: 'Jane', cityId: 2 }, + new_yorkers_sq: null, + }, + { + users_join_view: { id: 3, name: 'Jack', cityId: 1 }, + new_yorkers_sq: { id: 3, name: 'Jack', cityId: 1 }, + }, + { + users_join_view: { id: 4, name: 'Jill', cityId: 2 }, + new_yorkers_sq: null, + }, + ]); + + await db.execute(sql`drop view ${newYorkers}`); + await db.execute(sql`drop table ${users}`); + }); */ + + test('select iterator', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users_iterator', { + id: serial('id').primaryKey(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial not null primary key)`); + + await db.insert(users).values([{ id: 1 }, { id: 2 }, { id: 3 }]); + + const iter = db.select().from(users) + .orderBy(asc(users.id)) + .iterator(); + + const result: typeof users.$inferSelect[] = []; + + for await (const row of iter) { + result.push(row); + } + + expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); + }); + + test('select iterator w/ prepared statement', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users_iterator', { + id: serial('id').primaryKey(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial not null primary key)`); + + await db.insert(users).values([{ id: 1 }, { id: 2 }, { id: 3 }]); + + const prepared = db.select().from(users) + .orderBy(asc(users.id)) + .prepare(); + const iter = prepared.iterator(); + const result: typeof users.$inferSelect[] = []; + + for await (const row of iter) { + result.push(row); + } + + expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); + }); + + test('insert undefined', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name'), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, name text)`, + ); + + await expect((async () => { + await db.insert(users).values({ name: undefined }); + })()).resolves.not.toThrowError(); + + await db.execute(sql`drop table ${users}`); + }); + + test('update undefined', async (ctx) => { + const { db } = ctx.singlestore; + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name'), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, name text)`, + ); + + await expect((async () => { + await db.update(users).set({ name: undefined }); + })()).rejects.toThrowError(); + + await expect((async () => { + await db.update(users).set({ id: 1, name: undefined }); + })()).resolves.not.toThrowError(); + + await db.execute(sql`drop table ${users}`); + }); + + test('utc config for datetime', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`datestable\``); + await db.execute( + sql` + create table \`datestable\` ( + \`datetime_utc\` datetime(6), + \`datetime\` datetime(6) + ) + `, + ); + const datesTable = singlestoreTable('datestable', { + datetimeUTC: datetime('datetime_utc', { mode: 'date' }), + datetime: datetime('datetime'), + }); + + const dateObj = new Date('2022-11-11'); + const dateUtc = new Date('2022-11-11T12:12:12.122Z'); + + await db.insert(datesTable).values({ + datetimeUTC: dateUtc, + datetime: dateObj, + }); + + const res = await db.select().from(datesTable); + + const [rawSelect] = await db.execute(sql`select \`datetime_utc\` from \`datestable\``); + const selectedRow = (rawSelect as unknown as [{ datetime_utc: string }])[0]; + + expect(selectedRow.datetime_utc).toBe('2022-11-11 12:12:12.122000'); + expect(new Date(selectedRow.datetime_utc.replace(' ', 'T') + 'Z')).toEqual(dateUtc); + + expect(res[0]?.datetime).toBeInstanceOf(Date); + expect(res[0]?.datetimeUTC).toBeInstanceOf(Date); + + expect(res).toEqual([{ + datetimeUTC: dateUtc, + datetime: new Date('2022-11-11'), + }]); + + await db.execute(sql`drop table if exists \`datestable\``); + }); + + // TODO (https://memsql.atlassian.net/browse/MCDB-63261) allow chaining limit and orderby in subquery + test('set operations (union) from query builder with subquery', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + const citiesQuery = db + .select({ + id: citiesTable.id, + name: citiesTable.name, + orderCol: sql`0`.as('orderCol'), + }) + .from(citiesTable); + + const usersQuery = db + .select({ + id: users2Table.id, + name: users2Table.name, + orderCol: sql`1`.as('orderCol'), + }) + .from(users2Table); + + const unionQuery = db + .select({ + id: sql`id`, + name: sql`name`, + }) + .from( + citiesQuery.union(usersQuery).as('combined'), + ) + .orderBy(sql`orderCol`, sql`id`) + .limit(8); + + const result = await unionQuery; + + expect(result).toHaveLength(8); + + expect(result).toEqual([ + { id: 1, name: 'New York' }, + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + { id: 1, name: 'John' }, + { id: 2, name: 'Jane' }, + { id: 3, name: 'Jack' }, + { id: 4, name: 'Peter' }, + { id: 5, name: 'Ben' }, + ]); + + // union should throw if selected fields are not in the same order + await expect((async () => { + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).union( + db + .select({ name: users2Table.name, id: users2Table.id }) + .from(users2Table), + ); + })()).rejects.toThrowError(); + }); + + test('set operations (union) as function', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const result = await union( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ); + + expect(result).toHaveLength(2); + + expect(result).toEqual([ + { id: 1, name: 'New York' }, + { id: 1, name: 'John' }, + ]); + + await expect((async () => { + union( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + db + .select({ name: users2Table.name, id: users2Table.id }) + .from(users2Table).where(eq(users2Table.id, 1)), + ); + })()).rejects.toThrowError(); + }); + + test('set operations (union all) from query builder', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const sq = db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).orderBy(asc(sql`id`)).limit(2).unionAll( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).orderBy(asc(sql`id`)).limit(2), + ).as('sq'); + + const result = await db.select().from(sq).orderBy(asc(sql`id`)).limit(3); + + expect(result).toHaveLength(3); + + expect(result).toEqual([ + { id: 1, name: 'New York' }, + { id: 1, name: 'New York' }, + { id: 2, name: 'London' }, + ]); + + await expect((async () => { + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).limit(2).unionAll( + db + .select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable).limit(2), + ).orderBy(asc(sql`id`)); + })()).rejects.toThrowError(); + }); + + test('set operations (union all) as function', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const sq = unionAll( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).as('sq'); + + const result = await db.select().from(sq).limit(1); + + expect(result).toHaveLength(1); + + expect(result).toEqual([ + { id: 1, name: 'New York' }, + ]); + + await expect((async () => { + unionAll( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + db + .select({ name: users2Table.name, id: users2Table.id }) + .from(users2Table).where(eq(users2Table.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).limit(1); + })()).rejects.toThrowError(); + }); + + test('set operations (intersect) from query builder', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const sq = db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).intersect( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(gt(citiesTable.id, 1)), + ) + .as('sq'); + + const result = await db.select().from(sq).orderBy(asc(sql`id`)); + + expect(result).toHaveLength(2); + + expect(result).toEqual([ + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + ]); + + await expect((async () => { + db + .select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable).intersect( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(gt(citiesTable.id, 1)), + ); + })()).rejects.toThrowError(); + }); + + test('set operations (intersect) as function', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const sq = await intersect( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).as('sq'); + + const result = await db.select().from(sq).limit(1); + + expect(result).toHaveLength(0); + + expect(result).toEqual([]); + + await expect((async () => { + intersect( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + db + .select({ name: users2Table.name, id: users2Table.id }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).limit(1); + })()).rejects.toThrowError(); + }); + + test('set operations (except) from query builder', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const result = await db + .select() + .from(citiesTable).except( + db + .select() + .from(citiesTable).where(gt(citiesTable.id, 1)), + ); + + expect(result).toHaveLength(1); + + expect(result).toEqual([ + { id: 1, name: 'New York' }, + ]); + }); + + test('set operations (except) as function', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const sq = await except( + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable), + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).as('sq'); + + const result = await db.select().from(sq).limit(3); + + expect(result).toHaveLength(2); + + expect(result).toEqual([ + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + ]); + + await expect((async () => { + except( + db + .select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable), + db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).limit(3); + })()).rejects.toThrowError(); + }); + + test('define constraints as array', async (ctx) => { + const { db } = ctx.singlestore; + + const table = singlestoreTable('name', { + id: int(), + }, (t) => [ + index('name').on(t.id), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + + test('define constraints as array inside third param', async (ctx) => { + const { db } = ctx.singlestore; + + const table = singlestoreTable('name', { + id: int(), + }, (t) => [ + [index('name').on(t.id), primaryKey({ columns: [t.id], name: 'custom' })], + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + + test.skip('set operations (mixed) from query builder', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const sq1 = unionAll( + db + .select() + .from(citiesTable).where(gt(citiesTable.id, 1)), + db.select().from(citiesTable).where(eq(citiesTable.id, 2)), + ).as('sq1'); + + const sq2 = await db.select().from(sq1).orderBy(asc(sql`id`)).as('sq2'); + + const sq3 = await db.select().from(sq2).limit(1).offset(1).as('sq3'); + + const result = await db + .select() + .from(citiesTable) + .except( + db + .select() + .from(sq3), + ); + + expect(result).toHaveLength(2); + + expect(result).toEqual([ + { id: 3, name: 'Tampa' }, + { id: 1, name: 'New York' }, + ]); + + await expect((async () => { + db + .select() + .from(citiesTable).except( + ({ unionAll }) => + unionAll( + db + .select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable).where(gt(citiesTable.id, 1)), + db.select().from(citiesTable).where(eq(citiesTable.id, 2)), + ), + ); + })()).rejects.toThrowError(); + }); + + test('set operations (mixed all) as function with subquery', async (ctx) => { + const { db } = ctx.singlestore; + + await setupSetOperationTest(db); + + const sq1 = except( + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(gte(users2Table.id, 5)), + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 7)), + ).as('sq1'); + + const sq2 = await db.select().from(sq1).orderBy(asc(sql`id`)).as('sq2'); + + const sq3 = await db.select().from(sq2).limit(1).as('sq3'); + + const result = await union( + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + db.select().from(sq3), + db + .select().from(citiesTable).where(gt(citiesTable.id, 1)), + ); + + expect(result).toHaveLength(4); + + // multiple results possible as a result of the filters >= 5 and ==7 because singlestore doesn't guarantee order + // dynamically validate results + const hasValidEntry = (entry: { id: number; name: string }) => { + if (entry.id === 1) return entry.name === 'John'; + if (entry.id > 1 && entry.id < 5) return entry.name === 'Tampa' || entry.name === 'London'; + if (entry.id >= 5 && entry.id !== 7) return true; // Accept any entry with id >= 5 and not 7 + return false; + }; + + for (const entry of result) { + expect(hasValidEntry(entry)).toBe(true); + } + + await expect((async () => { + union( + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + except( + db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(gte(users2Table.id, 5)), + db + .select({ name: users2Table.name, id: users2Table.id }) + .from(users2Table).where(eq(users2Table.id, 7)), + ).limit(1), + db + .select().from(citiesTable).where(gt(citiesTable.id, 1)), + ); + })()).rejects.toThrowError(); + }); + + test('aggregate function: count', async (ctx) => { + const { db } = ctx.singlestore; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: count() }).from(table); + const result2 = await db.select({ value: count(table.a) }).from(table); + const result3 = await db.select({ value: countDistinct(table.name) }).from(table); + + expect(result1[0]?.value).toBe(7); + expect(result2[0]?.value).toBe(5); + expect(result3[0]?.value).toBe(6); + }); + + test('aggregate function: avg', async (ctx) => { + const { db } = ctx.singlestore; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: avg(table.b) }).from(table); + const result2 = await db.select({ value: avg(table.nullOnly) }).from(table); + const result3 = await db.select({ value: avgDistinct(table.b) }).from(table); + + expect(result1[0]?.value).toBe('33.3333'); + expect(result2[0]?.value).toBe(null); + expect(result3[0]?.value).toBe('42.5000'); + }); + + test('aggregate function: sum', async (ctx) => { + const { db } = ctx.singlestore; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: sum(table.b) }).from(table); + const result2 = await db.select({ value: sum(table.nullOnly) }).from(table); + const result3 = await db.select({ value: sumDistinct(table.b) }).from(table); + + expect(result1[0]?.value).toBe('200'); + expect(result2[0]?.value).toBe(null); + expect(result3[0]?.value).toBe('170'); + }); + + test('aggregate function: max', async (ctx) => { + const { db } = ctx.singlestore; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: max(table.b) }).from(table); + const result2 = await db.select({ value: max(table.nullOnly) }).from(table); + + expect(result1[0]?.value).toBe(90); + expect(result2[0]?.value).toBe(null); + }); + + test('aggregate function: min', async (ctx) => { + const { db } = ctx.singlestore; + const table = aggregateTable; + await setupAggregateFunctionsTest(db); + + const result1 = await db.select({ value: min(table.b) }).from(table); + const result2 = await db.select({ value: min(table.nullOnly) }).from(table); + + expect(result1[0]?.value).toBe(10); + expect(result2[0]?.value).toBe(null); + }); + + test('simple vector search', async (ctx) => { + const { db } = ctx.singlestore; + const table = vectorSearchTable; + const embedding = [0.42, 0.93, 0.88, 0.57, 0.32, 0.64, 0.76, 0.52, 0.19, 0.81]; // ChatGPT's 10 dimension embedding for "dogs are cool" not sure how accurate but it works + await setupVectorSearchTest(db); + + const withRankEuclidean = db.select({ + id: table.id, + text: table.text, + rank: sql`row_number() over (order by ${euclideanDistance(table.embedding, embedding)})`.as('rank'), + }).from(table).as('with_rank'); + const withRankDotProduct = db.select({ + id: table.id, + text: table.text, + rank: sql`row_number() over (order by ${dotProduct(table.embedding, embedding)})`.as('rank'), + }).from(table).as('with_rank'); + const result1 = await db.select({ id: withRankEuclidean.id, text: withRankEuclidean.text }).from( + withRankEuclidean, + ).where(eq(withRankEuclidean.rank, 1)); + const result2 = await db.select({ id: withRankDotProduct.id, text: withRankDotProduct.text }).from( + withRankDotProduct, + ).where(eq(withRankDotProduct.rank, 1)); + + expect(result1.length).toEqual(1); + expect(result1[0]).toEqual({ id: 1, text: 'I like dogs' }); + + expect(result2.length).toEqual(1); + expect(result2[0]).toEqual({ id: 1, text: 'I like dogs' }); + }); + + test('test $onUpdateFn and $onUpdate works as $default', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists ${usersOnUpdate}`); + + await db.execute( + sql` + create table ${usersOnUpdate} ( + id serial not null primary key, + name text not null, + update_counter integer default 1 not null, + updated_at datetime(6), + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { id: 1, name: 'John' }, + { id: 2, name: 'Jane' }, + { id: 3, name: 'Jack' }, + { id: 4, name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'John', id: 1, updateCounter: 1, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: 1, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 750; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } + }); + + test('test $onUpdateFn and $onUpdate works updating', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists ${usersOnUpdate}`); + + await db.execute( + sql` + create table ${usersOnUpdate} ( + id serial not null primary key, + name text not null, + update_counter integer default 1 not null, + updated_at datetime(6), + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { id: 1, name: 'John', alwaysNull: 'this will will be null after updating' }, + { id: 2, name: 'Jane' }, + { id: 3, name: 'Jack' }, + { id: 4, name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + const initial = await db.select({ id: usersOnUpdate.id, updatedAt: usersOnUpdate.updatedAt }).from(usersOnUpdate); + + await db.update(usersOnUpdate).set({ name: 'Angel' }).where(eq(usersOnUpdate.id, 1)); + + const justDates = await db.select({ id: usersOnUpdate.id, updatedAt: usersOnUpdate.updatedAt }).from( + usersOnUpdate, + ); + + const response = await db.select().from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { id: 1, name: 'Angel', updateCounter: 2, updatedAt: expect.any(Date), alwaysNull: null }, + { id: 2, name: 'Jane', updateCounter: 1, updatedAt: expect.any(Date), alwaysNull: null }, + { id: 3, name: 'Jack', updateCounter: 1, updatedAt: expect.any(Date), alwaysNull: null }, + { id: 4, name: 'Jill', updateCounter: 1, updatedAt: expect.any(Date), alwaysNull: null }, + ]); + + const initialRecord = initial.find((record) => record.id === 1); + const updatedRecord = justDates.find((record) => record.id === 1); + + expect(initialRecord?.updatedAt?.valueOf()).not.toBe(updatedRecord?.updatedAt?.valueOf()); + + const msDelay = 1000; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } + }); + + // mySchema tests + test('mySchema :: select all fields', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersMySchemaTable).values({ id: 1, name: 'John' }); + const result = await db.select().from(usersMySchemaTable); + + expect(result[0]!.createdAt).toBeInstanceOf(Date); + // not timezone based timestamp, thats why it should not work here + // t.assert(Math.abs(result[0]!.createdAt.getTime() - now) < 2000); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); + }); + + test('mySchema :: select sql', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values({ name: 'John' }); + const users = await db.select({ + name: sql`upper(${usersMySchemaTable.name})`, + }).from(usersMySchemaTable); + + expect(users).toEqual([{ name: 'JOHN' }]); + }); + + test('mySchema :: select typed sql', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values({ name: 'John' }); + const users = await db.select({ + name: sql`upper(${usersMySchemaTable.name})`, + }).from(usersMySchemaTable); + + expect(users).toEqual([{ name: 'JOHN' }]); + }); + + test('mySchema :: select distinct', async (ctx) => { + const { db } = ctx.singlestore; + + const usersDistinctTable = singlestoreTable('users_distinct', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${usersDistinctTable}`); + await db.execute(sql`create table ${usersDistinctTable} (id int, name text)`); + + await db.insert(usersDistinctTable).values([ + { id: 1, name: 'John' }, + { id: 1, name: 'John' }, + { id: 2, name: 'John' }, + { id: 1, name: 'Jane' }, + ]); + const users = await db.selectDistinct().from(usersDistinctTable).orderBy( + usersDistinctTable.id, + usersDistinctTable.name, + ); + + await db.execute(sql`drop table ${usersDistinctTable}`); + + expect(users).toEqual([{ id: 1, name: 'Jane' }, { id: 1, name: 'John' }, { id: 2, name: 'John' }]); + }); + + test('mySchema :: insert returning sql', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + const [result, _] = await db.insert(usersMySchemaTable).values({ id: 1, name: 'John' }); + + expect(result.insertId).toBe(1); + }); + + test('mySchema :: delete returning sql', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values({ name: 'John' }); + const users = await db.delete(usersMySchemaTable).where(eq(usersMySchemaTable.name, 'John')); + + expect(users[0].affectedRows).toBe(1); + }); + + test('mySchema :: update with returning partial', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values({ id: 1, name: 'John' }); + const updatedUsers = await db.update(usersMySchemaTable).set({ name: 'Jane' }).where( + eq(usersMySchemaTable.name, 'John'), + ); + + const users = await db.select({ id: usersMySchemaTable.id, name: usersMySchemaTable.name }).from( + usersMySchemaTable, + ) + .where( + eq(usersMySchemaTable.id, 1), + ); + + expect(updatedUsers[0].changedRows).toBe(1); + + expect(users).toEqual([{ id: 1, name: 'Jane' }]); + }); + + test('mySchema :: delete with returning all fields', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersMySchemaTable).values({ name: 'John' }); + const deletedUser = await db.delete(usersMySchemaTable).where(eq(usersMySchemaTable.name, 'John')); + + expect(deletedUser[0].affectedRows).toBe(1); + }); + + test('mySchema :: insert + select', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values({ id: 1, name: 'John' }); + const result = await db.select().from(usersMySchemaTable); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); + + await db.insert(usersMySchemaTable).values({ id: 2, name: 'Jane' }); + const result2 = await db.select().from(usersMySchemaTable).orderBy(asc(usersMySchemaTable.id)); + expect(result2).toEqual([ + { id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt }, + { id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt }, + ]); + }); + + test('mySchema :: insert with overridden default values', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values({ id: 1, name: 'John', verified: true }); + const result = await db.select().from(usersMySchemaTable); + + expect(result).toEqual([{ id: 1, name: 'John', verified: true, jsonb: null, createdAt: result[0]!.createdAt }]); + }); + + test('mySchema :: insert many', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values([ + { id: 1, name: 'John' }, + { id: 2, name: 'Bruce', jsonb: ['foo', 'bar'] }, + { id: 3, name: 'Jane' }, + { id: 4, name: 'Austin', verified: true }, + ]); + const result = await db.select({ + id: usersMySchemaTable.id, + name: usersMySchemaTable.name, + jsonb: usersMySchemaTable.jsonb, + verified: usersMySchemaTable.verified, + }).from(usersMySchemaTable) + .orderBy(asc(usersMySchemaTable.id)); + + expect(result).toEqual([ + { id: 1, name: 'John', jsonb: null, verified: false }, + { id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false }, + { id: 3, name: 'Jane', jsonb: null, verified: false }, + { id: 4, name: 'Austin', jsonb: null, verified: true }, + ]); + }); + + test('mySchema :: select with group by as field', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { + id: 3, + name: 'Jane', + }]); + + const result = await db.select({ name: usersMySchemaTable.name }).from(usersMySchemaTable) + .groupBy(usersMySchemaTable.name) + .orderBy(asc(usersMySchemaTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]); + }); + + test('mySchema :: select with group by as column + sql', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { + id: 3, + name: 'Jane', + }]); + + const result = await db.select({ name: usersMySchemaTable.name }).from(usersMySchemaTable) + .groupBy(usersMySchemaTable.id, sql`${usersMySchemaTable.name}`) + .orderBy(asc(usersMySchemaTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + }); + + test('mySchema :: build query', async (ctx) => { + const { db } = ctx.singlestore; + + const query = db.select({ id: usersMySchemaTable.id, name: usersMySchemaTable.name }).from(usersMySchemaTable) + .groupBy(usersMySchemaTable.id, usersMySchemaTable.name) + .toSQL(); + + expect(query).toEqual({ + sql: + `select \`id\`, \`name\` from \`mySchema\`.\`userstest\` group by \`mySchema\`.\`userstest\`.\`id\`, \`mySchema\`.\`userstest\`.\`name\``, + params: [], + }); + }); + + test('mySchema :: insert with spaces', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values({ id: 1, name: sql`'Jo h n'` }); + const result = await db.select({ id: usersMySchemaTable.id, name: usersMySchemaTable.name }).from( + usersMySchemaTable, + ); + + expect(result).toEqual([{ id: 1, name: 'Jo h n' }]); + }); + + test('mySchema :: prepared statement with placeholder in .where', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.insert(usersMySchemaTable).values({ id: 1, name: 'John' }); + const stmt = db.select({ + id: usersMySchemaTable.id, + name: usersMySchemaTable.name, + }).from(usersMySchemaTable) + .where(eq(usersMySchemaTable.id, sql.placeholder('id'))) + .prepare(); + const result = await stmt.execute({ id: 1 }); + + expect(result).toEqual([{ id: 1, name: 'John' }]); + }); + + test('mySchema :: select from tables with same name from different schema using alias', async (ctx) => { + const { db } = ctx.singlestore; + await db.execute(sql`truncate table \`mySchema\`.\`userstest\``); + + await db.execute(sql`drop table if exists \`userstest\``); + await db.execute( + sql` + create table \`userstest\` ( + \`id\` serial primary key, + \`name\` text not null, + \`verified\` boolean not null default false, + \`jsonb\` json, + \`created_at\` timestamp not null default now() + ) + `, + ); + + await db.insert(usersMySchemaTable).values({ id: 10, name: 'Ivan' }); + await db.insert(usersTable).values({ id: 11, name: 'Hans' }); + + const customerAlias = alias(usersTable, 'customer'); + + const result = await db + .select().from(usersMySchemaTable) + .leftJoin(customerAlias, eq(customerAlias.id, 11)) + .where(eq(usersMySchemaTable.id, 10)); + + expect(result).toEqual([{ + userstest: { + id: 10, + name: 'Ivan', + verified: false, + jsonb: null, + createdAt: result[0]!.userstest.createdAt, + }, + customer: { + id: 11, + name: 'Hans', + verified: false, + jsonb: null, + createdAt: result[0]!.customer!.createdAt, + }, + }]); + }); + + test('insert $returningId: serial as id', async (ctx) => { + const { db } = ctx.singlestore; + + const result = await db.insert(usersTable).values({ id: 1, name: 'John' }).$returningId(); + + expectTypeOf(result).toEqualTypeOf<{ + id: number; + }[]>(); + + expect(result).toStrictEqual([{ id: 1 }]); + }); + + test('insert $returningId: serial as id, batch insert', async (ctx) => { + const { db } = ctx.singlestore; + + const result = await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'John1' }]) + .$returningId(); + + expectTypeOf(result).toEqualTypeOf<{ + id: number; + }[]>(); + + // singlestore auto increments when batch inserting, so the ids increment by one + expect(result).toStrictEqual([{ id: 2 }, { id: 3 }]); + }); + + test('insert $returningId: $default as primary key', async (ctx) => { + const { db } = ctx.singlestore; + + const uniqueKeys = ['ao865jf3mcmkfkk8o5ri495z', 'dyqs529eom0iczo2efxzbcut']; + let iterator = 0; + + const usersTableDefFn = singlestoreTable('users_default_fn', { + customId: varchar('id', { length: 256 }).primaryKey().$defaultFn(() => { + const value = uniqueKeys[iterator]!; + iterator++; + return value; + }), + name: text('name').notNull(), + }); + + await setupReturningFunctionsTest(db); + + const result = await db.insert(usersTableDefFn).values([{ name: 'John' }, { name: 'John1' }]) + // ^? + .$returningId(); + + expectTypeOf(result).toEqualTypeOf<{ + customId: string; + }[]>(); + + expect(result).toStrictEqual([{ customId: 'ao865jf3mcmkfkk8o5ri495z' }, { + customId: 'dyqs529eom0iczo2efxzbcut', + }]); + }); + + test('insert $returningId: $default as primary key with value', async (ctx) => { + const { db } = ctx.singlestore; + + const uniqueKeys = ['ao865jf3mcmkfkk8o5ri495z', 'dyqs529eom0iczo2efxzbcut']; + let iterator = 0; + + const usersTableDefFn = singlestoreTable('users_default_fn', { + customId: varchar('id', { length: 256 }).primaryKey().$defaultFn(() => { + const value = uniqueKeys[iterator]!; + iterator++; + return value; + }), + name: text('name').notNull(), + }); + + await setupReturningFunctionsTest(db); + + const result = await db.insert(usersTableDefFn).values([{ name: 'John', customId: 'test' }, { name: 'John1' }]) + // ^? + .$returningId(); + + expectTypeOf(result).toEqualTypeOf<{ + customId: string; + }[]>(); + + expect(result).toStrictEqual([{ customId: 'test' }, { customId: 'ao865jf3mcmkfkk8o5ri495z' }]); + }); + + // TODO: Unkip this test when views are supported + /* test.skip('mySchema :: view', async (ctx) => { + const { db } = ctx.singlestore; + + const newYorkers1 = mySchema.view('new_yorkers') + .as((qb) => qb.select().from(users2MySchemaTable).where(eq(users2MySchemaTable.cityId, 1))); + + const newYorkers2 = mySchema.view('new_yorkers', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }).as(sql`select * from ${users2MySchemaTable} where ${eq(users2MySchemaTable.cityId, 1)}`); + + const newYorkers3 = mySchema.view('new_yorkers', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }).existing(); + + await db.execute(sql`create view ${newYorkers1} as ${getViewConfig(newYorkers1).query}`); + + await db.insert(citiesMySchemaTable).values([{ id: 1, name: 'New York' }, { id: 2, name: 'Paris' }]); + + await db.insert(users2MySchemaTable).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + { id: 3, name: 'Jack', cityId: 2 }, + ]); + + { + const result = await db.select().from(newYorkers1).orderBy(asc(newYorkers1.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select().from(newYorkers2).orderBy(asc(newYorkers2.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select().from(newYorkers3).orderBy(asc(newYorkers3.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select({ name: newYorkers1.name }).from(newYorkers1).orderBy(asc(newYorkers1.id)); + expect(result).toEqual([ + { name: 'John' }, + { name: 'Jane' }, + ]); + } + + await db.execute(sql`drop view ${newYorkers1}`); + }); */ + + test('limit 0', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db + .select() + .from(usersTable) + .limit(0); + + expect(users).toEqual([]); + }); + + test('limit -1', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db + .select() + .from(usersTable) + .limit(-1); + + expect(users.length).toBeGreaterThan(0); + }); + }); +} diff --git a/integration-tests/tests/singlestore/singlestore-custom.test.ts b/integration-tests/tests/singlestore/singlestore-custom.test.ts new file mode 100644 index 000000000..c599df436 --- /dev/null +++ b/integration-tests/tests/singlestore/singlestore-custom.test.ts @@ -0,0 +1,827 @@ +import retry from 'async-retry'; +import type Docker from 'dockerode'; +import { asc, eq, Name, placeholder, sql } from 'drizzle-orm'; +import type { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore'; +import { drizzle } from 'drizzle-orm/singlestore'; +import { + alias, + binary, + customType, + date, + datetime, + serial, + singlestoreEnum, + singlestoreTable, + singlestoreTableCreator, + text, + time, + varchar, + year, +} from 'drizzle-orm/singlestore-core'; +import { migrate } from 'drizzle-orm/singlestore/migrator'; +import * as mysql2 from 'mysql2/promise'; +import { v4 as uuid } from 'uuid'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { toLocalDate } from '~/utils'; +import { createDockerDB } from './singlestore-common'; + +const ENABLE_LOGGING = false; + +let db: SingleStoreDriverDatabase; +let client: mysql2.Connection; +let container: Docker.Container | undefined; + +beforeAll(async () => { + let connectionString; + if (process.env['SINGLESTORE_CONNECTION_STRING']) { + connectionString = process.env['SINGLESTORE_CONNECTION_STRING']; + } else { + const { connectionString: conStr, container: contrainerObj } = await createDockerDB(); + connectionString = conStr; + container = contrainerObj; + } + client = await retry(async () => { + client = await mysql2.createConnection(connectionString); + await client.connect(); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.end(); + }, + }); + await client.query(`CREATE DATABASE IF NOT EXISTS drizzle;`); + await client.changeUser({ database: 'drizzle' }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + await client?.end(); + await container?.stop().catch(console.error); +}); + +beforeEach((ctx) => { + ctx.singlestore = { + db, + }; +}); + +const customSerial = customType<{ data: number; notNull: true; default: true }>({ + dataType() { + return 'serial'; + }, +}); + +const customText = customType<{ data: string }>({ + dataType() { + return 'text'; + }, +}); + +const customBoolean = customType<{ data: boolean }>({ + dataType() { + return 'boolean'; + }, + fromDriver(value) { + if (typeof value === 'boolean') { + return value; + } + return value === 1; + }, +}); + +const customJson = (name: string) => + customType<{ data: TData; driverData: string }>({ + dataType() { + return 'json'; + }, + toDriver(value: TData): string { + return JSON.stringify(value); + }, + })(name); + +const customTimestamp = customType< + { data: Date; driverData: string; config: { fsp: number } } +>({ + dataType(config) { + const precision = config?.fsp === undefined ? '' : ` (${config.fsp})`; + return `timestamp${precision}`; + }, + fromDriver(value: string): Date { + return new Date(value); + }, +}); + +const customBinary = customType<{ data: string; driverData: Buffer; config: { length: number } }>({ + dataType(config) { + return config?.length === undefined + ? `binary` + : `binary(${config.length})`; + }, + + toDriver(value) { + return sql`UNHEX(${value})`; + }, + + fromDriver(value) { + return value.toString('hex'); + }, +}); + +const usersTable = singlestoreTable('userstest', { + id: customSerial('id').primaryKey(), + name: customText('name').notNull(), + verified: customBoolean('verified').notNull().default(false), + jsonb: customJson('jsonb'), + createdAt: customTimestamp('created_at').notNull().default(sql`now()`), +}); + +const datesTable = singlestoreTable('datestable', { + date: date('date'), + dateAsString: date('date_as_string', { mode: 'string' }), + time: time('time'), + datetime: datetime('datetime'), + datetimeAsString: datetime('datetime_as_string', { mode: 'string' }), + year: year('year'), +}); + +export const testTable = singlestoreTable('test_table', { + id: customBinary('id', { length: 16 }).primaryKey(), + sqlId: binary('sql_id', { length: 16 }), + rawId: varchar('raw_id', { length: 64 }), +}); + +const usersMigratorTable = singlestoreTable('users12', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + email: text('email').notNull(), +}); + +beforeEach(async () => { + await db.execute(sql`drop table if exists \`userstest\``); + await db.execute(sql`drop table if exists \`datestable\``); + await db.execute(sql`drop table if exists \`test_table\``); + // await ctx.db.execute(sql`create schema public`); + await db.execute( + sql` + create table \`userstest\` ( + \`id\` serial primary key, + \`name\` text not null, + \`verified\` boolean not null default false, + \`jsonb\` json, + \`created_at\` timestamp not null default now() + ) + `, + ); + + await db.execute( + sql` + create table \`datestable\` ( + \`date\` date, + \`date_as_string\` date, + \`time\` time, + \`datetime\` datetime, + \`datetime_as_string\` datetime, + \`year\` year + ) + `, + ); + + await db.execute( + sql` + create table \`test_table\` ( + \`id\` binary(16) primary key, + \`sql_id\` binary(16), + \`raw_id\` varchar(64) + ) + `, + ); +}); + +test('select all fields', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const result = await db.select().from(usersTable); + + expect(result[0]!.createdAt).toBeInstanceOf(Date); + // not timezone based timestamp, thats why it should not work here + // t.assert(Math.abs(result[0]!.createdAt.getTime() - now) < 2000); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); +}); + +test('select sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.select({ + name: sql`upper(${usersTable.name})`, + }).from(usersTable); + + expect(users).toEqual([{ name: 'JOHN' }]); +}); + +test('select typed sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.select({ + name: sql`upper(${usersTable.name})`, + }).from(usersTable); + + expect(users).toEqual([{ name: 'JOHN' }]); +}); + +test('insert returning sql', async (ctx) => { + const { db } = ctx.singlestore; + + const [result, _] = await db.insert(usersTable).values({ id: 1, name: 'John' }); + + expect(result.insertId).toBe(1); +}); + +test('delete returning sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(users[0].affectedRows).toBe(1); +}); + +test('update returning sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + expect(users[0].changedRows).toBe(1); +}); + +test('update with returning all fields', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const updatedUsers = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + const users = await db.select().from(usersTable).where(eq(usersTable.id, 1)); + + expect(updatedUsers[0].changedRows).toBe(1); + + expect(users[0]!.createdAt).toBeInstanceOf(Date); + // not timezone based timestamp, thats why it should not work here + // t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 2000); + expect(users).toEqual([{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]); +}); + +test('update with returning partial', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const updatedUsers = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + const users = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(updatedUsers[0].changedRows).toBe(1); + + expect(users).toEqual([{ id: 1, name: 'Jane' }]); +}); + +test('delete with returning all fields', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const deletedUser = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(deletedUser[0].affectedRows).toBe(1); +}); + +test('delete with returning partial', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ name: 'John' }); + const deletedUser = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(deletedUser[0].affectedRows).toBe(1); +}); + +test('insert + select', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const result = await db.select().from(usersTable); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); + + await db.insert(usersTable).values({ id: 2, name: 'Jane' }); + const result2 = await db.select().from(usersTable).orderBy(asc(usersTable.id)); + expect(result2).toEqual([ + { id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt }, + { id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt }, + ]); +}); + +test('json insert', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John', jsonb: ['foo', 'bar'] }); + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + jsonb: usersTable.jsonb, + }).from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'John', jsonb: ['foo', 'bar'] }]); +}); + +test('insert with overridden default values', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John', verified: true }); + const result = await db.select().from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'John', verified: true, jsonb: null, createdAt: result[0]!.createdAt }]); +}); + +test('insert many', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([ + { id: 1, name: 'John' }, + { id: 2, name: 'Bruce', jsonb: ['foo', 'bar'] }, + { id: 3, name: 'Jane' }, + { id: 4, name: 'Austin', verified: true }, + ]); + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + jsonb: usersTable.jsonb, + verified: usersTable.verified, + }).from(usersTable).orderBy(asc(usersTable.id)); + + expect(result).toEqual([ + { id: 1, name: 'John', jsonb: null, verified: false }, + { id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false }, + { id: 3, name: 'Jane', jsonb: null, verified: false }, + { id: 4, name: 'Austin', jsonb: null, verified: true }, + ]); +}); + +test('insert many with returning', async (ctx) => { + const { db } = ctx.singlestore; + + const result = await db.insert(usersTable).values([ + { name: 'John' }, + { name: 'Bruce', jsonb: ['foo', 'bar'] }, + { name: 'Jane' }, + { name: 'Austin', verified: true }, + ]); + + expect(result[0].affectedRows).toBe(4); +}); + +test('select with group by as field', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.name).orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]); +}); + +test('select with group by as sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(sql`${usersTable.name}`).orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]); +}); + +test('select with group by as sql + column', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(sql`${usersTable.name}`, usersTable.id).orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); +}); + +test('select with group by as column + sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, sql`${usersTable.name}`).orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); +}); + +test('select with group by complex query', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, sql`${usersTable.name}`) + .orderBy(asc(usersTable.name)) + .limit(1); + + expect(result).toEqual([{ name: 'Jane' }]); +}); + +test('build query', async (ctx) => { + const { db } = ctx.singlestore; + + const query = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, usersTable.name) + .toSQL(); + + expect(query).toEqual({ + sql: `select \`id\`, \`name\` from \`userstest\` group by \`userstest\`.\`id\`, \`userstest\`.\`name\``, + params: [], + }); +}); + +test('build query insert with onDuplicate', async (ctx) => { + const { db } = ctx.singlestore; + + const query = db.insert(usersTable) + .values({ name: 'John', jsonb: ['foo', 'bar'] }) + .onDuplicateKeyUpdate({ set: { name: 'John1' } }) + .toSQL(); + + expect(query).toEqual({ + sql: + 'insert into `userstest` (`id`, `name`, `verified`, `jsonb`, `created_at`) values (default, ?, default, ?, default) on duplicate key update `name` = ?', + params: ['John', '["foo","bar"]', 'John1'], + }); +}); + +test('insert with onDuplicate', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }); + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }) + .onDuplicateKeyUpdate({ set: { name: 'John1' } }); + + const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(res).toEqual([{ id: 1, name: 'John1' }]); +}); + +test('insert conflict', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }); + + await expect((async () => { + db.insert(usersTable).values({ id: 1, name: 'John1' }); + })()).resolves.not.toThrowError(); +}); + +test('insert conflict with ignore', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }); + + await db.insert(usersTable) + .ignore() + .values({ id: 1, name: 'John1' }); + + const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(res).toEqual([{ id: 1, name: 'John' }]); +}); + +test('insert sql', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: sql`${'John'}` }); + const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable); + expect(result).toEqual([{ id: 1, name: 'John' }]); +}); + +test('partial join with alias', async (ctx) => { + const { db } = ctx.singlestore; + const customerAlias = alias(usersTable, 'customer'); + + await db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select({ + user: { + id: usersTable.id, + name: usersTable.name, + }, + customer: { + id: customerAlias.id, + name: customerAlias.name, + }, + }).from(usersTable) + .leftJoin(customerAlias, eq(customerAlias.id, 11)) + .where(eq(usersTable.id, 10)) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ + user: { id: 10, name: 'Ivan' }, + customer: { id: 11, name: 'Hans' }, + }]); +}); + +test('full join with alias', async (ctx) => { + const { db } = ctx.singlestore; + + const singlestoreTable = singlestoreTableCreator((name) => `prefixed_${name}`); + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial primary key, name text not null)`); + + const customers = alias(users, 'customer'); + + await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select().from(users) + .leftJoin(customers, eq(customers.id, 11)) + .where(eq(users.id, 10)) + .orderBy(asc(users.id)); + + expect(result).toEqual([{ + users: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, + }]); + + await db.execute(sql`drop table ${users}`); +}); + +test('select from alias', async (ctx) => { + const { db } = ctx.singlestore; + + const singlestoreTable = singlestoreTableCreator((name) => `prefixed_${name}`); + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial primary key, name text not null)`); + + const user = alias(users, 'user'); + const customers = alias(users, 'customer'); + + await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select() + .from(user) + .leftJoin(customers, eq(customers.id, 11)) + .where(eq(user.id, 10)) + .orderBy(asc(user.id)); + + expect(result).toEqual([{ + user: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, + }]); + + await db.execute(sql`drop table ${users}`); +}); + +test('insert with spaces', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: sql`'Jo h n'` }); + const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'Jo h n' }]); +}); + +test('prepared statement', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const statement = db.select({ + id: usersTable.id, + name: usersTable.name, + }).from(usersTable) + .prepare(); + const result = await statement.execute(); + + expect(result).toEqual([{ id: 1, name: 'John' }]); +}); + +test('prepared statement reuse', async (ctx) => { + const { db } = ctx.singlestore; + + const stmt = db.insert(usersTable).values({ + id: placeholder('id'), + verified: true, + name: placeholder('name'), + }).prepare(); + + for (let i = 0; i < 10; i++) { + await stmt.execute({ id: i + 1, name: `John ${i}` }); + } + + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + verified: usersTable.verified, + }).from(usersTable).orderBy(asc(usersTable.id)); + + expect(result).toEqual([ + { id: 1, name: 'John 0', verified: true }, + { id: 2, name: 'John 1', verified: true }, + { id: 3, name: 'John 2', verified: true }, + { id: 4, name: 'John 3', verified: true }, + { id: 5, name: 'John 4', verified: true }, + { id: 6, name: 'John 5', verified: true }, + { id: 7, name: 'John 6', verified: true }, + { id: 8, name: 'John 7', verified: true }, + { id: 9, name: 'John 8', verified: true }, + { id: 10, name: 'John 9', verified: true }, + ]); +}); + +test('prepared statement with placeholder in .where', async (ctx) => { + const { db } = ctx.singlestore; + + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const stmt = db.select({ + id: usersTable.id, + name: usersTable.name, + }).from(usersTable) + .where(eq(usersTable.id, placeholder('id'))) + .prepare(); + const result = await stmt.execute({ id: 1 }); + + expect(result).toEqual([{ id: 1, name: 'John' }]); +}); + +test('migrator', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists cities_migration`); + await db.execute(sql`drop table if exists users_migration`); + await db.execute(sql`drop table if exists users12`); + await db.execute(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/singlestore' }); + + await db.insert(usersMigratorTable).values({ id: 1, name: 'John', email: 'email' }); + + const result = await db.select().from(usersMigratorTable); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.execute(sql`drop table cities_migration`); + await db.execute(sql`drop table users_migration`); + await db.execute(sql`drop table users12`); + await db.execute(sql`drop table __drizzle_migrations`); +}); + +test('insert via db.execute + select via db.execute', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute( + sql`insert into ${usersTable} (${new Name(usersTable.id.name)}, ${new Name( + usersTable.name.name, + )}) values (1,${'John'})`, + ); + + const result = await db.execute<{ id: number; name: string }>(sql`select id, name from ${usersTable}`); + expect(result[0]).toEqual([{ id: 1, name: 'John' }]); +}); + +test('insert via db.execute w/ query builder', async (ctx) => { + const { db } = ctx.singlestore; + + const inserted = await db.execute( + db.insert(usersTable).values({ name: 'John' }), + ); + expect(inserted[0].affectedRows).toBe(1); +}); + +test('insert + select all possible dates', async (ctx) => { + const { db } = ctx.singlestore; + + const date = new Date('2022-11-11'); + + await db.insert(datesTable).values({ + date: date, + dateAsString: '2022-11-11', + time: '12:12:12', + datetime: date, + year: 22, + datetimeAsString: '2022-11-11 12:12:12', + }); + + const res = await db.select().from(datesTable); + + expect(res[0]?.date).toBeInstanceOf(Date); + expect(res[0]?.datetime).toBeInstanceOf(Date); + expect(res[0]?.dateAsString).toBeTypeOf('string'); + expect(res[0]?.datetimeAsString).toBeTypeOf('string'); + + expect(res).toEqual([{ + date: toLocalDate(new Date('2022-11-11')), + dateAsString: '2022-11-11', + time: '12:12:12', + datetime: new Date('2022-11-11'), + year: 2022, + datetimeAsString: '2022-11-11 12:12:12', + }]); +}); + +const tableWithEnums = singlestoreTable('enums_test_case', { + id: serial('id').primaryKey(), + enum1: singlestoreEnum('enum1', ['a', 'b', 'c']).notNull(), + enum2: singlestoreEnum('enum2', ['a', 'b', 'c']).default('a'), + enum3: singlestoreEnum('enum3', ['a', 'b', 'c']).notNull().default('b'), +}); + +test('SingleStore enum test case #1', async (ctx) => { + const { db } = ctx.singlestore; + + await db.execute(sql`drop table if exists \`enums_test_case\``); + + await db.execute(sql` + create table \`enums_test_case\` ( + \`id\` serial primary key, + \`enum1\` ENUM('a', 'b', 'c') not null, + \`enum2\` ENUM('a', 'b', 'c') default 'a', + \`enum3\` ENUM('a', 'b', 'c') not null default 'b' + ) + `); + + await db.insert(tableWithEnums).values([ + { id: 1, enum1: 'a', enum2: 'b', enum3: 'c' }, + { id: 2, enum1: 'a', enum3: 'c' }, + { id: 3, enum1: 'a' }, + ]); + + const res = await db.select().from(tableWithEnums).orderBy(asc(tableWithEnums.id)); + + await db.execute(sql`drop table \`enums_test_case\``); + + expect(res).toEqual([ + { id: 1, enum1: 'a', enum2: 'b', enum3: 'c' }, + { id: 2, enum1: 'a', enum2: 'a', enum3: 'c' }, + { id: 3, enum1: 'a', enum2: 'a', enum3: 'b' }, + ]); +}); + +test('custom binary', async (ctx) => { + const { db } = ctx.singlestore; + + const id = uuid().replace(/-/g, ''); + await db.insert(testTable).values({ + id, + sqlId: sql`UNHEX(${id})`, + rawId: id, + }); + + const res = await db.select().from(testTable); + + expect(res).toEqual([{ + id, + sqlId: Buffer.from(id, 'hex'), + rawId: id, + }]); +}); diff --git a/integration-tests/tests/singlestore/singlestore-prefixed.test.ts b/integration-tests/tests/singlestore/singlestore-prefixed.test.ts new file mode 100644 index 000000000..6f29d31a2 --- /dev/null +++ b/integration-tests/tests/singlestore/singlestore-prefixed.test.ts @@ -0,0 +1,1574 @@ +import retry from 'async-retry'; +import type Docker from 'dockerode'; +import type { Equal } from 'drizzle-orm'; +import { asc, eq, getTableName, gt, inArray, Name, sql, TransactionRollbackError } from 'drizzle-orm'; +import type { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore'; +import { drizzle } from 'drizzle-orm/singlestore'; +import { + alias, + boolean, + date, + datetime, + int, + json, + serial, + singlestoreEnum, + singlestoreTable as singlestoreTableRaw, + singlestoreTableCreator, + /* singlestoreView, */ + text, + time, + timestamp, + uniqueIndex, + year, +} from 'drizzle-orm/singlestore-core'; +import { migrate } from 'drizzle-orm/singlestore/migrator'; +import * as mysql2 from 'mysql2/promise'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { Expect, toLocalDate } from '~/utils'; +import { createDockerDB } from './singlestore-common'; + +const ENABLE_LOGGING = false; + +let db: SingleStoreDriverDatabase; +let client: mysql2.Connection; +let container: Docker.Container | undefined; + +beforeAll(async () => { + let connectionString; + if (process.env['SINGLESTORE_CONNECTION_STRING']) { + connectionString = process.env['SINGLESTORE_CONNECTION_STRING']; + } else { + const { connectionString: conStr, container: contrainerObj } = await createDockerDB(); + connectionString = conStr; + container = contrainerObj; + } + client = await retry(async () => { + client = await mysql2.createConnection(connectionString); + await client.connect(); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.end(); + }, + }); + + await client.query(`CREATE DATABASE IF NOT EXISTS drizzle;`); + await client.changeUser({ database: 'drizzle' }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + await client?.end(); + await container?.stop().catch(console.error); +}); + +const tablePrefix = 'drizzle_tests_'; + +const singlestoreTable = singlestoreTableCreator((name) => `${tablePrefix}${name}`); +const usersTable = singlestoreTable('userstest', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + verified: boolean('verified').notNull().default(false), + jsonb: json('jsonb').$type(), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + +const users2Table = singlestoreTable('users2', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id'), +}); + +const citiesTable = singlestoreTable('cities', { + id: serial('id').primaryKey(), + name: text('name').notNull(), +}); + +beforeEach(async () => { + await db.execute(sql`drop table if exists ${usersTable}`); + await db.execute(sql`drop table if exists ${users2Table}`); + await db.execute(sql`drop table if exists ${citiesTable}`); + + await db.execute( + sql` + create table ${usersTable} ( + \`id\` serial primary key, + \`name\` text not null, + \`verified\` boolean not null default false, + \`jsonb\` json, + \`created_at\` timestamp not null default now() + ) + `, + ); + + await db.execute( + sql` + create table ${users2Table} ( + \`id\` serial primary key, + \`name\` text not null, + \`city_id\` int + ) + `, + ); + + await db.execute( + sql` + create table ${citiesTable} ( + \`id\` serial primary key, + \`name\` text not null + ) + `, + ); +}); + +test('select all fields', async () => { + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const result = await db.select().from(usersTable); + + expect(result[0]!.createdAt).toBeInstanceOf(Date); + // not timezone based timestamp, thats why it should not work here + // t.assert(Math.abs(result[0]!.createdAt.getTime() - now) < 2000); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); +}); + +test('select sql', async () => { + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.select({ + name: sql`upper(${usersTable.name})`, + }).from(usersTable); + + expect(users).toEqual([{ name: 'JOHN' }]); +}); + +test('select typed sql', async () => { + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.select({ + name: sql`upper(${usersTable.name})`, + }).from(usersTable); + + expect(users).toEqual([{ name: 'JOHN' }]); +}); + +test('select distinct', async () => { + const usersDistinctTable = singlestoreTable('users_distinct', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${usersDistinctTable}`); + await db.execute(sql`create table ${usersDistinctTable} (id int, name text)`); + + await db.insert(usersDistinctTable).values([ + { id: 1, name: 'John' }, + { id: 1, name: 'John' }, + { id: 2, name: 'John' }, + { id: 1, name: 'Jane' }, + ]); + const users = await db.selectDistinct().from(usersDistinctTable).orderBy( + usersDistinctTable.id, + usersDistinctTable.name, + ); + + await db.execute(sql`drop table ${usersDistinctTable}`); + + expect(users).toEqual([{ id: 1, name: 'Jane' }, { id: 1, name: 'John' }, { id: 2, name: 'John' }]); +}); + +test('insert returning sql', async () => { + const [result, _] = await db.insert(usersTable).values({ id: 1, name: 'John' }); + + expect(result.insertId).toBe(1); +}); + +test('delete returning sql', async () => { + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(users[0].affectedRows).toBe(1); +}); + +test('update returning sql', async () => { + await db.insert(usersTable).values({ name: 'John' }); + const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + expect(users[0].changedRows).toBe(1); +}); + +test('update with returning all fields', async () => { + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const updatedUsers = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + const users = await db.select().from(usersTable).where(eq(usersTable.id, 1)); + + expect(updatedUsers[0].changedRows).toBe(1); + + expect(users[0]!.createdAt).toBeInstanceOf(Date); + // not timezone based timestamp, thats why it should not work here + // t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 2000); + expect(users).toEqual([{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]); +}); + +test('update with returning partial', async () => { + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const updatedUsers = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')); + + const users = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(updatedUsers[0].changedRows).toBe(1); + + expect(users).toEqual([{ id: 1, name: 'Jane' }]); +}); + +test('delete with returning all fields', async () => { + await db.insert(usersTable).values({ name: 'John' }); + const deletedUser = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(deletedUser[0].affectedRows).toBe(1); +}); + +test('delete with returning partial', async () => { + await db.insert(usersTable).values({ name: 'John' }); + const deletedUser = await db.delete(usersTable).where(eq(usersTable.name, 'John')); + + expect(deletedUser[0].affectedRows).toBe(1); +}); + +test('insert + select', async () => { + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const result = await db.select().from(usersTable); + expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]); + + await db.insert(usersTable).values({ id: 2, name: 'Jane' }); + const result2 = await db.select().from(usersTable).orderBy(asc(usersTable.id)); + expect(result2).toEqual([ + { id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt }, + { id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt }, + ]); +}); + +test('json insert', async () => { + await db.insert(usersTable).values({ id: 1, name: 'John', jsonb: ['foo', 'bar'] }); + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + jsonb: usersTable.jsonb, + }).from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'John', jsonb: ['foo', 'bar'] }]); +}); + +test('insert with overridden default values', async () => { + await db.insert(usersTable).values({ id: 1, name: 'John', verified: true }); + const result = await db.select().from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'John', verified: true, jsonb: null, createdAt: result[0]!.createdAt }]); +}); + +test('insert many', async () => { + await db.insert(usersTable).values([ + { id: 1, name: 'John' }, + { id: 2, name: 'Bruce', jsonb: ['foo', 'bar'] }, + { id: 3, name: 'Jane' }, + { id: 4, name: 'Austin', verified: true }, + ]); + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + jsonb: usersTable.jsonb, + verified: usersTable.verified, + }).from(usersTable) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([ + { id: 1, name: 'John', jsonb: null, verified: false }, + { id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false }, + { id: 3, name: 'Jane', jsonb: null, verified: false }, + { id: 4, name: 'Austin', jsonb: null, verified: true }, + ]); +}); + +test('insert many with returning', async () => { + const result = await db.insert(usersTable).values([ + { name: 'John' }, + { name: 'Bruce', jsonb: ['foo', 'bar'] }, + { name: 'Jane' }, + { name: 'Austin', verified: true }, + ]); + + expect(result[0].affectedRows).toBe(4); +}); + +test('select with group by as field', async () => { + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.name) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]); +}); + +test('select with group by as sql', async () => { + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(sql`${usersTable.name}`) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]); +}); + +test('select with group by as sql + column', async () => { + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(sql`${usersTable.name}`, usersTable.id) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); +}); + +test('select with group by as column + sql', async () => { + await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }, { id: 3, name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, sql`${usersTable.name}`) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); +}); + +test('select with group by complex query', async () => { + await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + + const result = await db.select({ name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, sql`${usersTable.name}`) + .orderBy(asc(usersTable.name)) + .limit(1); + + expect(result).toEqual([{ name: 'Jane' }]); +}); + +test('build query', async () => { + const query = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable) + .groupBy(usersTable.id, usersTable.name) + .toSQL(); + + expect(query).toEqual({ + sql: `select \`id\`, \`name\` from \`${getTableName(usersTable)}\` group by \`${ + getTableName(usersTable) + }\`.\`id\`, \`${getTableName(usersTable)}\`.\`name\``, + params: [], + }); +}); + +test('build query insert with onDuplicate', async () => { + const query = db.insert(usersTable) + .values({ name: 'John', jsonb: ['foo', 'bar'] }) + .onDuplicateKeyUpdate({ set: { name: 'John1' } }) + .toSQL(); + + expect(query).toEqual({ + sql: `insert into \`${ + getTableName(usersTable) + }\` (\`id\`, \`name\`, \`verified\`, \`jsonb\`, \`created_at\`) values (default, ?, default, ?, default) on duplicate key update \`name\` = ?`, + params: ['John', '["foo","bar"]', 'John1'], + }); +}); + +test('insert with onDuplicate', async () => { + await db.insert(usersTable) + .values({ id: 1, name: 'John' }); + + await db.insert(usersTable) + .values({ id: 1, name: 'John' }) + .onDuplicateKeyUpdate({ set: { name: 'John1' } }); + + const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(res).toEqual([{ id: 1, name: 'John1' }]); +}); + +test('insert conflict', async () => { + await db.insert(usersTable) + .values({ name: 'John' }); + + await expect((async () => { + db.insert(usersTable).values({ id: 1, name: 'John1' }); + })()).resolves.not.toThrowError(); +}); + +test('insert conflict with ignore', async () => { + await db.insert(usersTable) + .values({ id: 1, name: 'John' }); + + await db.insert(usersTable) + .ignore() + .values({ id: 1, name: 'John1' }); + + const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ); + + expect(res).toEqual([{ id: 1, name: 'John' }]); +}); + +test('insert sql', async () => { + await db.insert(usersTable).values({ id: 1, name: sql`${'John'}` }); + const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable); + expect(result).toEqual([{ id: 1, name: 'John' }]); +}); + +test('partial join with alias', async () => { + const customerAlias = alias(usersTable, 'customer'); + + await db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select({ + user: { + id: usersTable.id, + name: usersTable.name, + }, + customer: { + id: customerAlias.id, + name: customerAlias.name, + }, + }).from(usersTable) + .leftJoin(customerAlias, eq(customerAlias.id, 11)) + .where(eq(usersTable.id, 10)) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([{ + user: { id: 10, name: 'Ivan' }, + customer: { id: 11, name: 'Hans' }, + }]); +}); + +test('full join with alias', async () => { + const singlestoreTable = singlestoreTableCreator((name) => `prefixed_${name}`); + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial primary key, name text not null)`); + + const customers = alias(users, 'customer'); + + await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select().from(users) + .leftJoin(customers, eq(customers.id, 11)) + .where(eq(users.id, 10)) + .orderBy(asc(users.id)); + + expect(result).toEqual([{ + users: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, + }]); + + await db.execute(sql`drop table ${users}`); +}); + +test('select from alias', async () => { + const singlestoreTable = singlestoreTableCreator((name) => `prefixed_${name}`); + + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial primary key, name text not null)`); + + const user = alias(users, 'user'); + const customers = alias(users, 'customer'); + + await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]); + const result = await db + .select() + .from(user) + .leftJoin(customers, eq(customers.id, 11)) + .where(eq(user.id, 10)) + .orderBy(asc(user.id)); + + expect(result).toEqual([{ + user: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, + }]); + + await db.execute(sql`drop table ${users}`); +}); + +test('insert with spaces', async () => { + await db.insert(usersTable).values({ id: 1, name: sql`'Jo h n'` }); + const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable); + + expect(result).toEqual([{ id: 1, name: 'Jo h n' }]); +}); + +test('prepared statement', async () => { + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const statement = db.select({ + id: usersTable.id, + name: usersTable.name, + }).from(usersTable) + .prepare(); + const result = await statement.execute(); + + expect(result).toEqual([{ id: 1, name: 'John' }]); +}); + +test('prepared statement reuse', async () => { + const stmt = db.insert(usersTable).values({ + verified: true, + id: sql.placeholder('id'), + name: sql.placeholder('name'), + }).prepare(); + + for (let i = 0; i < 10; i++) { + await stmt.execute({ id: i + 1, name: `John ${i}` }); + } + + const result = await db.select({ + id: usersTable.id, + name: usersTable.name, + verified: usersTable.verified, + }).from(usersTable) + .orderBy(asc(usersTable.id)); + + expect(result).toEqual([ + { id: 1, name: 'John 0', verified: true }, + { id: 2, name: 'John 1', verified: true }, + { id: 3, name: 'John 2', verified: true }, + { id: 4, name: 'John 3', verified: true }, + { id: 5, name: 'John 4', verified: true }, + { id: 6, name: 'John 5', verified: true }, + { id: 7, name: 'John 6', verified: true }, + { id: 8, name: 'John 7', verified: true }, + { id: 9, name: 'John 8', verified: true }, + { id: 10, name: 'John 9', verified: true }, + ]); +}); + +test('prepared statement with placeholder in .where', async () => { + await db.insert(usersTable).values({ id: 1, name: 'John' }); + const stmt = db.select({ + id: usersTable.id, + name: usersTable.name, + }).from(usersTable) + .where(eq(usersTable.id, sql.placeholder('id'))) + .prepare(); + const result = await stmt.execute({ id: 1 }); + + expect(result).toEqual([{ id: 1, name: 'John' }]); +}); + +test('migrator', async () => { + const usersMigratorTable = singlestoreTableRaw('users12', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + email: text('email').notNull(), + }, (table) => { + return { + name: uniqueIndex('').on(table.name).using('btree'), + }; + }); + + await db.execute(sql.raw(`drop table if exists cities_migration`)); + await db.execute(sql.raw(`drop table if exists users_migration`)); + await db.execute(sql.raw(`drop table if exists users12`)); + await db.execute(sql.raw(`drop table if exists __drizzle_migrations`)); + + await migrate(db, { migrationsFolder: './drizzle2/singlestore' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + + const result = await db.select().from(usersMigratorTable); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.execute(sql.raw(`drop table cities_migration`)); + await db.execute(sql.raw(`drop table users_migration`)); + await db.execute(sql.raw(`drop table users12`)); + await db.execute(sql.raw(`drop table __drizzle_migrations`)); +}); + +test('insert via db.execute + select via db.execute', async () => { + await db.execute( + sql`insert into ${usersTable} (${new Name(usersTable.id.name)}, ${new Name( + usersTable.name.name, + )}) values (1, ${'John'})`, + ); + + const result = await db.execute<{ id: number; name: string }>(sql`select id, name from ${usersTable}`); + expect(result[0]).toEqual([{ id: 1, name: 'John' }]); +}); + +test('insert via db.execute w/ query builder', async () => { + const inserted = await db.execute( + db.insert(usersTable).values({ name: 'John' }), + ); + expect(inserted[0].affectedRows).toBe(1); +}); + +test('insert + select all possible dates', async () => { + const datesTable = singlestoreTable('datestable', { + date: date('date'), + dateAsString: date('date_as_string', { mode: 'string' }), + time: time('time'), + datetime: datetime('datetime'), + datetimeAsString: datetime('datetime_as_string', { mode: 'string' }), + year: year('year'), + }); + + await db.execute(sql`drop table if exists ${datesTable}`); + await db.execute( + sql` + create table ${datesTable} ( + \`date\` date, + \`date_as_string\` date, + \`time\` time, + \`datetime\` datetime, + \`datetime_as_string\` datetime, + \`year\` year + ) + `, + ); + + const d = new Date('2022-11-11'); + + await db.insert(datesTable).values({ + date: d, + dateAsString: '2022-11-11', + time: '12:12:12', + datetime: d, + year: 22, + datetimeAsString: '2022-11-11 12:12:12', + }); + + const res = await db.select().from(datesTable); + + expect(res[0]?.date).toBeInstanceOf(Date); + expect(res[0]?.datetime).toBeInstanceOf(Date); + expect(typeof res[0]?.dateAsString).toBe('string'); + expect(typeof res[0]?.datetimeAsString).toBe('string'); + + expect(res).toEqual([{ + date: toLocalDate(new Date('2022-11-11')), + dateAsString: '2022-11-11', + time: '12:12:12', + datetime: new Date('2022-11-11'), + year: 2022, + datetimeAsString: '2022-11-11 12:12:12', + }]); + + await db.execute(sql`drop table ${datesTable}`); +}); + +test('SingleStore enum test case #1', async () => { + const tableWithEnums = singlestoreTable('enums_test_case', { + id: serial('id').primaryKey(), + enum1: singlestoreEnum('enum1', ['a', 'b', 'c']).notNull(), + enum2: singlestoreEnum('enum2', ['a', 'b', 'c']).default('a'), + enum3: singlestoreEnum('enum3', ['a', 'b', 'c']).notNull().default('b'), + }); + + await db.execute(sql`drop table if exists ${tableWithEnums}`); + + await db.execute(sql` + create table ${tableWithEnums} ( + \`id\` serial primary key, + \`enum1\` ENUM('a', 'b', 'c') not null, + \`enum2\` ENUM('a', 'b', 'c') default 'a', + \`enum3\` ENUM('a', 'b', 'c') not null default 'b' + ) + `); + + await db.insert(tableWithEnums).values([ + { id: 1, enum1: 'a', enum2: 'b', enum3: 'c' }, + { id: 2, enum1: 'a', enum3: 'c' }, + { id: 3, enum1: 'a' }, + ]); + + const res = await db.select().from(tableWithEnums).orderBy(asc(tableWithEnums.id)); + + await db.execute(sql`drop table ${tableWithEnums}`); + + expect(res).toEqual([ + { id: 1, enum1: 'a', enum2: 'b', enum3: 'c' }, + { id: 2, enum1: 'a', enum2: 'a', enum3: 'c' }, + { id: 3, enum1: 'a', enum2: 'a', enum3: 'b' }, + ]); +}); + +test('left join (flat object fields)', async () => { + await db.insert(citiesTable) + .values([{ id: 1, name: 'Paris' }, { id: 2, name: 'London' }]); + + await db.insert(users2Table).values([{ id: 1, name: 'John', cityId: 1 }, { id: 2, name: 'Jane' }]); + + const res = await db.select({ + userId: users2Table.id, + userName: users2Table.name, + cityId: citiesTable.id, + cityName: citiesTable.name, + }).from(users2Table) + .leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id)) + .orderBy(asc(users2Table.id)); + + expect(res).toEqual([ + { userId: 1, userName: 'John', cityId: 1, cityName: 'Paris' }, + { userId: 2, userName: 'Jane', cityId: null, cityName: null }, + ]); +}); + +test('left join (grouped fields)', async () => { + await db.insert(citiesTable) + .values([{ id: 1, name: 'Paris' }, { id: 2, name: 'London' }]); + + await db.insert(users2Table).values([{ id: 1, name: 'John', cityId: 1 }, { id: 2, name: 'Jane' }]); + + const res = await db.select({ + id: users2Table.id, + user: { + name: users2Table.name, + nameUpper: sql`upper(${users2Table.name})`, + }, + city: { + id: citiesTable.id, + name: citiesTable.name, + nameUpper: sql`upper(${citiesTable.name})`, + }, + }).from(users2Table) + .leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id)) + .orderBy(asc(users2Table.id)); + + expect(res).toEqual([ + { + id: 1, + user: { name: 'John', nameUpper: 'JOHN' }, + city: { id: 1, name: 'Paris', nameUpper: 'PARIS' }, + }, + { + id: 2, + user: { name: 'Jane', nameUpper: 'JANE' }, + city: null, + }, + ]); +}); + +test('left join (all fields)', async () => { + await db.insert(citiesTable) + .values([{ id: 1, name: 'Paris' }, { id: 2, name: 'London' }]); + + await db.insert(users2Table).values([{ id: 1, name: 'John', cityId: 1 }, { id: 2, name: 'Jane' }]); + + const res = await db.select().from(users2Table) + .leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id)) + .orderBy(asc(users2Table.id)); + + expect(res).toEqual([ + { + users2: { + id: 1, + name: 'John', + cityId: 1, + }, + cities: { + id: 1, + name: 'Paris', + }, + }, + { + users2: { + id: 2, + name: 'Jane', + cityId: null, + }, + cities: null, + }, + ]); +}); + +test('join subquery', async () => { + const coursesTable = singlestoreTable('courses', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + categoryId: int('category_id'), + }); + + const courseCategoriesTable = singlestoreTable('course_categories', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${coursesTable}`); + await db.execute(sql`drop table if exists ${courseCategoriesTable}`); + + await db.execute( + sql` + create table ${courseCategoriesTable} ( + \`id\` serial primary key, + \`name\` text not null + ) + `, + ); + + await db.execute( + sql` + create table ${coursesTable} ( + \`id\` serial primary key, + \`name\` text not null, + \`category_id\` int + ) + `, + ); + + await db.insert(courseCategoriesTable).values([ + { id: 1, name: 'Category 1' }, + { id: 2, name: 'Category 2' }, + { id: 3, name: 'Category 3' }, + { id: 4, name: 'Category 4' }, + ]); + + await db.insert(coursesTable).values([ + { id: 1, name: 'Development', categoryId: 2 }, + { id: 2, name: 'IT & Software', categoryId: 3 }, + { id: 3, name: 'Marketing', categoryId: 4 }, + { id: 4, name: 'Design', categoryId: 1 }, + ]); + + const sq2 = db + .select({ + categoryId: courseCategoriesTable.id, + category: courseCategoriesTable.name, + total: sql`count(${courseCategoriesTable.id})`, + }) + .from(courseCategoriesTable) + .groupBy(courseCategoriesTable.id, courseCategoriesTable.name) + .orderBy(courseCategoriesTable.id) + .as('sq2'); + + const res = await db + .select({ + courseName: coursesTable.name, + categoryId: sq2.categoryId, + }) + .from(coursesTable) + .leftJoin(sq2, eq(coursesTable.categoryId, sq2.categoryId)) + .orderBy(coursesTable.name); + + await db.execute(sql`drop table ${coursesTable}`); + await db.execute(sql`drop table ${courseCategoriesTable}`); + + expect(res).toEqual([ + { courseName: 'Design', categoryId: 1 }, + { courseName: 'Development', categoryId: 2 }, + { courseName: 'IT & Software', categoryId: 3 }, + { courseName: 'Marketing', categoryId: 4 }, + ]); +}); + +test('with ... select', async () => { + const orders = singlestoreTable('orders', { + id: serial('id').primaryKey(), + region: text('region').notNull(), + product: text('product').notNull(), + amount: int('amount').notNull(), + quantity: int('quantity').notNull(), + }); + + await db.execute(sql`drop table if exists ${orders}`); + await db.execute( + sql` + create table ${orders} ( + \`id\` serial primary key, + \`region\` text not null, + \`product\` text not null, + \`amount\` int not null, + \`quantity\` int not null + ) + `, + ); + + await db.insert(orders).values([ + { region: 'Europe', product: 'A', amount: 10, quantity: 1 }, + { region: 'Europe', product: 'A', amount: 20, quantity: 2 }, + { region: 'Europe', product: 'B', amount: 20, quantity: 2 }, + { region: 'Europe', product: 'B', amount: 30, quantity: 3 }, + { region: 'US', product: 'A', amount: 30, quantity: 3 }, + { region: 'US', product: 'A', amount: 40, quantity: 4 }, + { region: 'US', product: 'B', amount: 40, quantity: 4 }, + { region: 'US', product: 'B', amount: 50, quantity: 5 }, + ]); + + const regionalSales = db + .$with('regional_sales') + .as( + db + .select({ + region: orders.region, + totalSales: sql`sum(${orders.amount})`.as('total_sales'), + }) + .from(orders) + .groupBy(orders.region), + ); + + const topRegions = db + .$with('top_regions') + .as( + db + .select({ + region: regionalSales.region, + }) + .from(regionalSales) + .where( + gt( + regionalSales.totalSales, + db.select({ sales: sql`sum(${regionalSales.totalSales})/10` }).from(regionalSales), + ), + ), + ); + + const result = await db + .with(regionalSales, topRegions) + .select({ + region: orders.region, + product: orders.product, + productUnits: sql`cast(sum(${orders.quantity}) as unsigned)`, + productSales: sql`cast(sum(${orders.amount}) as unsigned)`, + }) + .from(orders) + .where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions))) + .groupBy(orders.region, orders.product) + .orderBy(orders.region, orders.product); + + await db.execute(sql`drop table ${orders}`); + + expect(result).toEqual([ + { + region: 'Europe', + product: 'A', + productUnits: 3, + productSales: 30, + }, + { + region: 'Europe', + product: 'B', + productUnits: 5, + productSales: 50, + }, + { + region: 'US', + product: 'A', + productUnits: 7, + productSales: 70, + }, + { + region: 'US', + product: 'B', + productUnits: 9, + productSales: 90, + }, + ]); +}); + +test('select from subquery sql', async () => { + await db.insert(users2Table).values([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]); + + const sq = db + .select({ name: sql`concat(${users2Table.name}, " modified")`.as('name') }) + .from(users2Table) + .orderBy(asc(users2Table.id)) + .as('sq'); + + const res = await db.select({ name: sq.name }).from(sq); + + expect(res).toEqual([{ name: 'John modified' }, { name: 'Jane modified' }]); +}); + +test('select a field without joining its table', () => { + expect(() => db.select({ name: users2Table.name }).from(usersTable).prepare()).toThrowError(); +}); + +test('select all fields from subquery without alias', () => { + const sq = db.$with('sq').as(db.select({ name: sql`upper(${users2Table.name})` }).from(users2Table)); + + expect(() => db.select().from(sq).prepare()).toThrowError(); +}); + +test('select count()', async () => { + await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }]); + + const res = await db.select({ count: sql`count(*)` }).from(usersTable); + + expect(res).toEqual([{ count: 2 }]); +}); + +test('select for ...', () => { + { + const query = db.select().from(users2Table).for('update').toSQL(); + expect(query.sql).toMatch(/ for update$/); + } + { + const query = db.select().from(users2Table).for('share', { skipLocked: true }).toSQL(); + expect(query.sql).toMatch(/ for share skip locked$/); + } + { + const query = db.select().from(users2Table).for('update', { noWait: true }).toSQL(); + expect(query.sql).toMatch(/ for update no wait$/); + } +}); + +test('having', async () => { + await db.insert(citiesTable).values([{ id: 1, name: 'London' }, { id: 2, name: 'Paris' }, { + id: 3, + name: 'New York', + }]); + + await db.insert(users2Table).values([{ id: 1, name: 'John', cityId: 1 }, { id: 2, name: 'Jane', cityId: 1 }, { + id: 3, + name: 'Jack', + cityId: 2, + }]); + + const result = await db + .select({ + id: citiesTable.id, + name: sql`upper(${citiesTable.name})`.as('upper_name'), + usersCount: sql`count(${users2Table.id})`.as('users_count'), + }) + .from(citiesTable) + .leftJoin(users2Table, eq(users2Table.cityId, citiesTable.id)) + .where(({ name }) => sql`length(${name}) >= 3`) + .groupBy(citiesTable.id) + .having(({ usersCount }) => sql`${usersCount} > 0`) + .orderBy(({ name }) => name); + + expect(result).toEqual([ + { + id: 1, + name: 'LONDON', + usersCount: 2, + }, + { + id: 2, + name: 'PARIS', + usersCount: 1, + }, + ]); +}); + +// TODO: Unskip when views are supported +/* test.skip('view', async () => { + const newYorkers1 = singlestoreView('new_yorkers') + .as((qb) => qb.select().from(users2Table).where(eq(users2Table.cityId, 1))); + + const newYorkers2 = singlestoreView('new_yorkers', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }).as(sql`select * from ${users2Table} where ${eq(users2Table.cityId, 1)}`); + + const newYorkers3 = singlestoreView('new_yorkers', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }).existing(); + + await db.execute(sql`create view new_yorkers as ${getViewConfig(newYorkers1).query}`); + + await db.insert(citiesTable).values([{ name: 'New York' }, { name: 'Paris' }]); + + await db.insert(users2Table).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + { id: 3, name: 'Jack', cityId: 2 }, + ]); + + { + const result = await db.select().from(newYorkers1).orderBy(asc(newYorkers1.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select().from(newYorkers2).orderBy(asc(newYorkers2.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select().from(newYorkers3).orderBy(asc(newYorkers3.id)); + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = await db.select({ name: newYorkers1.name }).from(newYorkers1).orderBy(asc(newYorkers1.id)); + expect(result).toEqual([ + { name: 'John' }, + { name: 'Jane' }, + ]); + } + + await db.execute(sql`drop view ${newYorkers1}`); +}); */ + +test('select from raw sql', async () => { + const result = await db.select({ + id: sql`id`, + name: sql`name`, + }).from(sql`(select 1 as id, 'John' as name) as users`); + + Expect>; + + expect(result).toEqual([ + { id: 1, name: 'John' }, + ]); +}); + +test('select from raw sql with joins', async () => { + const result = await db + .select({ + id: sql`users.id`, + name: sql`users.name`, + userCity: sql`users.city`, + cityName: sql`cities.name`, + }) + .from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`) + .leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, sql`cities.id = users.id`); + + Expect>; + + expect(result).toEqual([ + { id: 1, name: 'John', userCity: 'New York', cityName: 'Paris' }, + ]); +}); + +test('join on aliased sql from select', async () => { + const result = await db + .select({ + userId: sql`users.id`.as('userId'), + name: sql`users.name`, + userCity: sql`users.city`, + cityId: sql`cities.id`.as('cityId'), + cityName: sql`cities.name`, + }) + .from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`) + .leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, (cols) => eq(cols.cityId, cols.userId)); + + Expect>; + + expect(result).toEqual([ + { userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }, + ]); +}); + +test('join on aliased sql from with clause', async () => { + const users = db.$with('users').as( + db.select({ + id: sql`id`.as('userId'), + name: sql`name`.as('userName'), + city: sql`city`.as('city'), + }).from( + sql`(select 1 as id, 'John' as name, 'New York' as city) as users`, + ), + ); + + const cities = db.$with('cities').as( + db.select({ + id: sql`id`.as('cityId'), + name: sql`name`.as('cityName'), + }).from( + sql`(select 1 as id, 'Paris' as name) as cities`, + ), + ); + + const result = await db + .with(users, cities) + .select({ + userId: users.id, + name: users.name, + userCity: users.city, + cityId: cities.id, + cityName: cities.name, + }) + .from(users) + .leftJoin(cities, (cols) => eq(cols.cityId, cols.userId)); + + Expect>; + + expect(result).toEqual([ + { userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }, + ]); +}); + +test('prefixed table', async () => { + const singlestoreTable = singlestoreTableCreator((name) => `myprefix_${name}`); + + const users = singlestoreTable('test_prefixed_table_with_unique_name', { + id: int('id').primaryKey(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table myprefix_test_prefixed_table_with_unique_name (id int not null primary key, name text not null)`, + ); + + await db.insert(users).values({ id: 1, name: 'John' }); + + const result = await db.select().from(users); + + expect(result).toEqual([{ id: 1, name: 'John' }]); + + await db.execute(sql`drop table ${users}`); +}); + +test('orderBy with aliased column', () => { + const query = db.select({ + test: sql`something`.as('test'), + }).from(users2Table).orderBy((fields) => fields.test).toSQL(); + + expect(query.sql).toBe(`select something as \`test\` from \`${getTableName(users2Table)}\` order by \`test\``); +}); + +test('timestamp timezone', async () => { + const date = new Date(Date.parse('2020-01-01T12:34:56+07:00')); + + await db.insert(usersTable).values({ id: 1, name: 'With default times' }); + await db.insert(usersTable).values({ + id: 2, + name: 'Without default times', + createdAt: date, + }); + const users = await db.select().from(usersTable).orderBy(asc(usersTable.id)); + + // check that the timestamps are set correctly for default times + expect(Math.abs(users[0]!.createdAt.getTime() - Date.now())).toBeLessThan(2000); + + // check that the timestamps are set correctly for non default times + expect(Math.abs(users[1]!.createdAt.getTime() - date.getTime())).toBeLessThan(2000); +}); + +test('transaction', async () => { + const users = singlestoreTable('users_transactions', { + id: serial('id').primaryKey(), + balance: int('balance').notNull(), + }); + const products = singlestoreTable('products_transactions', { + id: serial('id').primaryKey(), + price: int('price').notNull(), + stock: int('stock').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`drop table if exists ${products}`); + + await db.execute(sql`create table ${users} (id serial not null primary key, balance int not null)`); + await db.execute( + sql`create table ${products} (id serial not null primary key, price int not null, stock int not null)`, + ); + + const [{ insertId: userId }] = await db.insert(users).values({ id: 1, balance: 100 }); + const user = await db.select().from(users).where(eq(users.id, userId)).then((rows) => rows[0]!); + const [{ insertId: productId }] = await db.insert(products).values({ id: 1, price: 10, stock: 10 }); + const product = await db.select().from(products).where(eq(products.id, productId)).then((rows) => rows[0]!); + + await db.transaction(async (tx) => { + await tx.update(users).set({ balance: user.balance - product.price }).where(eq(users.id, user.id)); + await tx.update(products).set({ stock: product.stock - 1 }).where(eq(products.id, product.id)); + }); + + const result = await db.select().from(users); + + await db.execute(sql`drop table ${users}`); + await db.execute(sql`drop table ${products}`); + + expect(result).toEqual([{ id: 1, balance: 90 }]); +}); + +test('transaction rollback', async () => { + const users = singlestoreTable('users_transactions_rollback', { + id: serial('id').primaryKey(), + balance: int('balance').notNull(), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, balance int not null)`, + ); + + await expect((async () => { + await db.transaction(async (tx) => { + await tx.insert(users).values({ balance: 100 }); + tx.rollback(); + }); + })()).rejects.toThrowError(TransactionRollbackError); + + const result = await db.select().from(users); + + await db.execute(sql`drop table ${users}`); + + expect(result).toEqual([]); +}); + +test('join subquery with join', async () => { + const internalStaff = singlestoreTable('internal_staff', { + userId: int('user_id').notNull(), + }); + + const customUser = singlestoreTable('custom_user', { + id: int('id').notNull(), + }); + + const ticket = singlestoreTable('ticket', { + staffId: int('staff_id').notNull(), + }); + + await db.execute(sql`drop table if exists ${internalStaff}`); + await db.execute(sql`drop table if exists ${customUser}`); + await db.execute(sql`drop table if exists ${ticket}`); + + await db.execute(sql`create table ${internalStaff} (user_id integer not null)`); + await db.execute(sql`create table ${customUser} (id integer not null)`); + await db.execute(sql`create table ${ticket} (staff_id integer not null)`); + + await db.insert(internalStaff).values({ userId: 1 }); + await db.insert(customUser).values({ id: 1 }); + await db.insert(ticket).values({ staffId: 1 }); + + const subq = db + .select() + .from(internalStaff) + .leftJoin(customUser, eq(internalStaff.userId, customUser.id)) + .as('internal_staff'); + + const mainQuery = await db + .select() + .from(ticket) + .leftJoin(subq, eq(subq.internal_staff.userId, ticket.staffId)); + + await db.execute(sql`drop table ${internalStaff}`); + await db.execute(sql`drop table ${customUser}`); + await db.execute(sql`drop table ${ticket}`); + + expect(mainQuery).toEqual([{ + ticket: { staffId: 1 }, + internal_staff: { + internal_staff: { userId: 1 }, + custom_user: { id: 1 }, + }, + }]); +}); + +// TODO: Unskip when views are supported +/* test.skip('subquery with view', async () => { + const users = singlestoreTable('users_subquery_view', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }); + + const newYorkers = singlestoreView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`drop view if exists ${newYorkers}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, name text not null, city_id integer not null)`, + ); + await db.execute(sql`create view ${newYorkers} as select * from ${users} where city_id = 1`); + + await db.insert(users).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 2 }, + { id: 3, name: 'Jack', cityId: 1 }, + { id: 4, name: 'Jill', cityId: 2 }, + ]); + + const sq = db.$with('sq').as(db.select().from(newYorkers)); + const result = await db.with(sq).select().from(sq).orderBy(asc(sq.id)); + + await db.execute(sql`drop view ${newYorkers}`); + await db.execute(sql`drop table ${users}`); + + expect(result).toEqual([ + { id: 1, name: 'John', cityId: 1 }, + { id: 3, name: 'Jack', cityId: 1 }, + ]); +}); */ + +// TODO: Unskip when views are supported +/* test.skip('join view as subquery', async () => { + const users = singlestoreTable('users_join_view', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + cityId: int('city_id').notNull(), + }); + + const newYorkers = singlestoreView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`drop view if exists ${newYorkers}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, name text not null, city_id integer not null)`, + ); + await db.execute(sql`create view ${newYorkers} as select * from ${users} where city_id = 1`); + + await db.insert(users).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 2 }, + { id: 3, name: 'Jack', cityId: 1 }, + { id: 4, name: 'Jill', cityId: 2 }, + ]); + + const sq = db.select().from(newYorkers).as('new_yorkers_sq'); + + const result = await db.select().from(users).leftJoin(sq, eq(users.id, sq.id)).orderBy(asc(users.id)); + + expect(result).toEqual([ + { + users_join_view: { id: 1, name: 'John', cityId: 1 }, + new_yorkers_sq: { id: 1, name: 'John', cityId: 1 }, + }, + { + users_join_view: { id: 2, name: 'Jane', cityId: 2 }, + new_yorkers_sq: null, + }, + { + users_join_view: { id: 3, name: 'Jack', cityId: 1 }, + new_yorkers_sq: { id: 3, name: 'Jack', cityId: 1 }, + }, + { + users_join_view: { id: 4, name: 'Jill', cityId: 2 }, + new_yorkers_sq: null, + }, + ]); + + await db.execute(sql`drop view ${newYorkers}`); + await db.execute(sql`drop table ${users}`); +}); */ + +test('select iterator', async () => { + const users = singlestoreTable('users_iterator', { + id: serial('id').primaryKey(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial not null primary key)`); + + await db.insert(users).values([{ id: 1 }, { id: 2 }, { id: 3 }]); + + const iter = db.select().from(users) + .orderBy(asc(users.id)) + .iterator(); + + const result: typeof users.$inferSelect[] = []; + + for await (const row of iter) { + result.push(row); + } + + expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); +}); + +test('select iterator w/ prepared statement', async () => { + const users = singlestoreTable('users_iterator', { + id: serial('id').primaryKey(), + }); + + await db.execute(sql`drop table if exists ${users}`); + await db.execute(sql`create table ${users} (id serial not null primary key)`); + + await db.insert(users).values([{ id: 1 }, { id: 2 }, { id: 3 }]); + + const prepared = db.select().from(users) + .orderBy(asc(users.id)) + .prepare(); + const iter = prepared.iterator(); + const result: typeof users.$inferSelect[] = []; + + for await (const row of iter) { + result.push(row); + } + + expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); +}); + +test('insert undefined', async () => { + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name'), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, name text)`, + ); + + await expect((async () => { + await db.insert(users).values({ name: undefined }); + })()).resolves.not.toThrowError(); + + await db.execute(sql`drop table ${users}`); +}); + +test('update undefined', async () => { + const users = singlestoreTable('users', { + id: serial('id').primaryKey(), + name: text('name'), + }); + + await db.execute(sql`drop table if exists ${users}`); + + await db.execute( + sql`create table ${users} (id serial not null primary key, name text)`, + ); + + await expect((async () => { + await db.update(users).set({ name: undefined }); + })()).rejects.toThrowError(); + + await expect((async () => { + await db.update(users).set({ id: 1, name: undefined }); + })()).resolves.not.toThrowError(); + + await db.execute(sql`drop table ${users}`); +}); diff --git a/integration-tests/tests/singlestore/singlestore-proxy.test.ts b/integration-tests/tests/singlestore/singlestore-proxy.test.ts new file mode 100644 index 000000000..51dc48a4a --- /dev/null +++ b/integration-tests/tests/singlestore/singlestore-proxy.test.ts @@ -0,0 +1,140 @@ +import retry from 'async-retry'; +import type { SingleStoreRemoteDatabase } from 'drizzle-orm/singlestore-proxy'; +import { drizzle as proxyDrizzle } from 'drizzle-orm/singlestore-proxy'; +import * as mysql2 from 'mysql2/promise'; +import { afterAll, beforeAll, beforeEach } from 'vitest'; +import { skipTests } from '~/common'; +import { createDockerDB, tests } from './singlestore-common'; + +const ENABLE_LOGGING = false; + +// eslint-disable-next-line drizzle-internal/require-entity-kind +class ServerSimulator { + constructor(private db: mysql2.Connection) {} + + async query(sql: string, params: any[], method: 'all' | 'execute') { + if (method === 'all') { + try { + const result = await this.db.query({ + sql, + values: params, + rowsAsArray: true, + typeCast: function(field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }); + + return { data: result[0] as any }; + } catch (e: any) { + return { error: e }; + } + } else if (method === 'execute') { + try { + const result = await this.db.query({ + sql, + values: params, + typeCast: function(field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }); + + return { data: result as any }; + } catch (e: any) { + return { error: e }; + } + } else { + return { error: 'Unknown method value' }; + } + } + + async migrations(queries: string[]) { + await this.db.query('START TRANSACTION'); + try { + for (const query of queries) { + await this.db.query(query); + } + await this.db.query('COMMIT'); + } catch (e) { + await this.db.query('ROLLBACK'); + throw e; + } + + return {}; + } +} + +let db: SingleStoreRemoteDatabase; +let client: mysql2.Connection; +let serverSimulator: ServerSimulator; + +beforeAll(async () => { + let connectionString; + if (process.env['SINGLESTORE_CONNECTION_STRING']) { + connectionString = process.env['SINGLESTORE_CONNECTION_STRING']; + } else { + const { connectionString: conStr } = await createDockerDB(); + connectionString = conStr; + } + client = await retry(async () => { + client = await mysql2.createConnection(connectionString); + await client.connect(); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.end(); + }, + }); + + await client.query(`CREATE DATABASE IF NOT EXISTS drizzle;`); + await client.changeUser({ database: 'drizzle' }); + + serverSimulator = new ServerSimulator(client); + db = proxyDrizzle(async (sql, params, method) => { + try { + const response = await serverSimulator.query(sql, params, method); + + if (response.error !== undefined) { + throw response.error; + } + + return { rows: response.data }; + } catch (e: any) { + console.error('Error from singlestore proxy server:', e.message); + throw e; + } + }, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + await client?.end(); +}); + +beforeEach((ctx) => { + ctx.singlestore = { + db, + }; +}); + +skipTests([ + 'select iterator w/ prepared statement', + 'select iterator', + 'nested transaction rollback', + 'nested transaction', + 'transaction rollback', + 'transaction', + 'transaction with options (set isolationLevel)', + 'migrator', +]); + +tests(); diff --git a/integration-tests/tests/singlestore/singlestore.test.ts b/integration-tests/tests/singlestore/singlestore.test.ts new file mode 100644 index 000000000..bfb1ee5b7 --- /dev/null +++ b/integration-tests/tests/singlestore/singlestore.test.ts @@ -0,0 +1,51 @@ +import retry from 'async-retry'; +import { drizzle } from 'drizzle-orm/singlestore'; +import type { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore'; +import * as mysql2 from 'mysql2/promise'; +import { afterAll, beforeAll, beforeEach } from 'vitest'; +import { createDockerDB, tests } from './singlestore-common'; + +const ENABLE_LOGGING = false; + +let db: SingleStoreDriverDatabase; +let client: mysql2.Connection; + +beforeAll(async () => { + let connectionString; + if (process.env['SINGLESTORE_CONNECTION_STRING']) { + connectionString = process.env['SINGLESTORE_CONNECTION_STRING']; + } else { + const { connectionString: conStr } = await createDockerDB(); + connectionString = conStr; + } + client = await retry(async () => { + client = await mysql2.createConnection(connectionString); + await client.connect(); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.end(); + }, + }); + + await client.query(`CREATE DATABASE IF NOT EXISTS drizzle;`); + await client.changeUser({ database: 'drizzle' }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + await client?.end(); +}); + +beforeEach((ctx) => { + ctx.singlestore = { + db, + }; +}); + +tests(); diff --git a/integration-tests/tests/sqlite/d1-batch.test.ts b/integration-tests/tests/sqlite/d1-batch.test.ts index 7ca1dff0f..dba22cd4d 100644 --- a/integration-tests/tests/sqlite/d1-batch.test.ts +++ b/integration-tests/tests/sqlite/d1-batch.test.ts @@ -1,3 +1,4 @@ +/// import 'dotenv/config'; import { D1Database, D1DatabaseAPI } from '@miniflare/d1'; import { createSQLiteDB } from '@miniflare/shared'; @@ -139,7 +140,7 @@ let db: DrizzleD1Database; beforeAll(async () => { const sqliteDb = await createSQLiteDB(':memory:'); const d1db = new D1Database(new D1DatabaseAPI(sqliteDb)); - db = drizzle(d1db as any, { logger: ENABLE_LOGGING, schema }); + db = drizzle(d1db, { logger: ENABLE_LOGGING, schema }); }); beforeEach(async () => { diff --git a/integration-tests/tests/sqlite/d1.test.ts b/integration-tests/tests/sqlite/d1.test.ts index 20e9e9d14..8c0863c5c 100644 --- a/integration-tests/tests/sqlite/d1.test.ts +++ b/integration-tests/tests/sqlite/d1.test.ts @@ -16,7 +16,7 @@ let db: DrizzleD1Database; beforeAll(async () => { const sqliteDb = await createSQLiteDB(':memory:'); const d1db = new D1Database(new D1DatabaseAPI(sqliteDb)); - db = drizzle(d1db as any, { logger: ENABLE_LOGGING }); + db = drizzle(d1db, { logger: ENABLE_LOGGING }); }); beforeEach((ctx) => { diff --git a/integration-tests/tests/sqlite/durable-objects/drizzle/0000_cuddly_black_bolt.sql b/integration-tests/tests/sqlite/durable-objects/drizzle/0000_cuddly_black_bolt.sql new file mode 100644 index 000000000..deff0ea9a --- /dev/null +++ b/integration-tests/tests/sqlite/durable-objects/drizzle/0000_cuddly_black_bolt.sql @@ -0,0 +1,11 @@ +CREATE TABLE `another_users` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `email` text NOT NULL +); +--> statement-breakpoint +CREATE TABLE `users12` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `email` text NOT NULL +); diff --git a/integration-tests/tests/sqlite/durable-objects/drizzle/meta/0000_snapshot.json b/integration-tests/tests/sqlite/durable-objects/drizzle/meta/0000_snapshot.json new file mode 100644 index 000000000..9c151ecc9 --- /dev/null +++ b/integration-tests/tests/sqlite/durable-objects/drizzle/meta/0000_snapshot.json @@ -0,0 +1,80 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "66be869a-d55d-4790-a382-de654dff1506", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "another_users": { + "name": "another_users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users12": { + "name": "users12", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/integration-tests/tests/sqlite/durable-objects/drizzle/meta/_journal.json b/integration-tests/tests/sqlite/durable-objects/drizzle/meta/_journal.json new file mode 100644 index 000000000..ab94199ee --- /dev/null +++ b/integration-tests/tests/sqlite/durable-objects/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1732696446109, + "tag": "0000_cuddly_black_bolt", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/integration-tests/tests/sqlite/durable-objects/drizzle/migrations.js b/integration-tests/tests/sqlite/durable-objects/drizzle/migrations.js new file mode 100644 index 000000000..c6f2a6ada --- /dev/null +++ b/integration-tests/tests/sqlite/durable-objects/drizzle/migrations.js @@ -0,0 +1,9 @@ +import m0000 from './0000_cuddly_black_bolt.sql'; +import journal from './meta/_journal.json'; + +export default { + journal, + migrations: { + m0000, + }, +}; diff --git a/integration-tests/tests/sqlite/durable-objects/index.ts b/integration-tests/tests/sqlite/durable-objects/index.ts new file mode 100644 index 000000000..b67534d4e --- /dev/null +++ b/integration-tests/tests/sqlite/durable-objects/index.ts @@ -0,0 +1,3619 @@ +/// + +import { expect } from 'chai'; +import { DurableObject } from 'cloudflare:workers'; +import { + and, + asc, + avg, + avgDistinct, + count, + countDistinct, + eq, + exists, + getTableColumns, + gt, + gte, + inArray, + lt, + max, + min, + Name, + notInArray, + sql, + sum, + sumDistinct, +} from 'drizzle-orm'; +import { drizzle, type DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite'; +import { migrate } from 'drizzle-orm/durable-sqlite/migrator'; +import { + alias, + type BaseSQLiteDatabase, + blob, + except, + getViewConfig, + int, + integer, + intersect, + numeric, + primaryKey, + sqliteTable, + sqliteTableCreator, + sqliteView, + text, + union, + unionAll, +} from 'drizzle-orm/sqlite-core'; +import { type Equal, Expect } from '~/utils'; +import migrations from './drizzle/migrations'; + +export const usersTable = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + verified: integer('verified', { mode: 'boolean' }).notNull().default(false), + json: blob('json', { mode: 'json' }).$type(), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`strftime('%s', 'now')`), +}); + +export const usersOnUpdate = sqliteTable('users_on_update', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + updateCounter: integer('update_counter').default(sql`1`).$onUpdateFn(() => sql`update_counter + 1`), + updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).$onUpdate(() => new Date()), + alwaysNull: text('always_null').$type().$onUpdate(() => null), +}); + +export const users2Table = sqliteTable('users2', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + cityId: integer('city_id').references(() => citiesTable.id), +}); + +export const citiesTable = sqliteTable('cities', { + id: integer('id').primaryKey(), + name: text('name').notNull(), +}); + +export const coursesTable = sqliteTable('courses', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + categoryId: integer('category_id').references(() => courseCategoriesTable.id), +}); + +export const courseCategoriesTable = sqliteTable('course_categories', { + id: integer('id').primaryKey(), + name: text('name').notNull(), +}); + +export const orders = sqliteTable('orders', { + id: integer('id').primaryKey(), + region: text('region').notNull(), + product: text('product').notNull().$default(() => 'random_string'), + amount: integer('amount').notNull(), + quantity: integer('quantity').notNull(), +}); + +export const usersMigratorTable = sqliteTable('users12', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + email: text('email').notNull(), +}); + +export const anotherUsersMigratorTable = sqliteTable('another_users', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + email: text('email').notNull(), +}); + +export const pkExampleTable = sqliteTable('pk_example', { + id: integer('id').notNull(), + name: text('name').notNull(), + email: text('email').notNull(), +}, (table) => ({ + compositePk: primaryKey({ columns: [table.id, table.name] }), +})); + +export const bigIntExample = sqliteTable('big_int_example', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + bigInt: blob('big_int', { mode: 'bigint' }).notNull(), +}); + +// To test aggregate functions +export const aggregateTable = sqliteTable('aggregate_table', { + id: integer('id').primaryKey({ autoIncrement: true }).notNull(), + name: text('name').notNull(), + a: integer('a'), + b: integer('b'), + c: integer('c'), + nullOnly: integer('null_only'), +}); + +async function setupSetOperationTest(db: BaseSQLiteDatabase) { + await db.run(sql`drop table if exists users2`); + await db.run(sql`drop table if exists cities`); + await db.run(sql` + create table \`cities\` ( + id integer primary key, + name text not null + ) + `); + + await db.run(sql` + create table \`users2\` ( + id integer primary key, + name text not null, + city_id integer references ${citiesTable}(${sql.identifier(citiesTable.id.name)}) + ) + `); + + await db.insert(citiesTable).values([ + { id: 1, name: 'New York' }, + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + ]); + + await db.insert(users2Table).values([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 2 }, + { id: 3, name: 'Jack', cityId: 3 }, + { id: 4, name: 'Peter', cityId: 3 }, + { id: 5, name: 'Ben', cityId: 2 }, + { id: 6, name: 'Jill', cityId: 1 }, + { id: 7, name: 'Mary', cityId: 2 }, + { id: 8, name: 'Sally', cityId: 1 }, + ]); +} + +async function setupAggregateFunctionsTest(db: BaseSQLiteDatabase) { + await db.run(sql`drop table if exists "aggregate_table"`); + await db.run( + sql` + create table "aggregate_table" ( + "id" integer primary key autoincrement not null, + "name" text not null, + "a" integer, + "b" integer, + "c" integer, + "null_only" integer + ); + `, + ); + await db.insert(aggregateTable).values([ + { name: 'value 1', a: 5, b: 10, c: 20 }, + { name: 'value 1', a: 5, b: 20, c: 30 }, + { name: 'value 2', a: 10, b: 50, c: 60 }, + { name: 'value 3', a: 20, b: 20, c: null }, + { name: 'value 4', a: null, b: 90, c: 120 }, + { name: 'value 5', a: 80, b: 10, c: null }, + { name: 'value 6', a: null, b: null, c: 150 }, + ]); +} + +// eslint-disable-next-line drizzle-internal/require-entity-kind +export class MyDurableObject extends DurableObject { + storage: DurableObjectStorage; + db: DrizzleSqliteDODatabase; + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + this.storage = ctx.storage; + this.db = drizzle(this.storage, { logger: false }); + } + + async migrate1(): Promise { + try { + this.db.run(sql`drop table if exists another_users`); + this.db.run(sql`drop table if exists users12`); + this.db.run(sql`drop table if exists __drizzle_migrations`); + + migrate(this.db, migrations); + + this.db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = this.db.select().from(usersMigratorTable).all(); + + this.db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = this.db.select().from(anotherUsersMigratorTable).all(); + + expect(result).deep.equal([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).deep.equal([{ id: 1, name: 'John', email: 'email' }]); + + this.db.run(sql`drop table another_users`); + this.db.run(sql`drop table users12`); + this.db.run(sql`drop table __drizzle_migrations`); + } catch { + throw new Error('migrate1 has broken'); + } + } + + async beforeEach(): Promise { + this.db.run(sql`drop table if exists ${usersTable}`); + this.db.run(sql`drop table if exists ${users2Table}`); + this.db.run(sql`drop table if exists ${citiesTable}`); + this.db.run(sql`drop table if exists ${coursesTable}`); + this.db.run(sql`drop table if exists ${courseCategoriesTable}`); + this.db.run(sql`drop table if exists ${orders}`); + this.db.run(sql`drop table if exists ${bigIntExample}`); + this.db.run(sql`drop table if exists ${pkExampleTable}`); + this.db.run(sql`drop table if exists user_notifications_insert_into`); + this.db.run(sql`drop table if exists users_insert_into`); + this.db.run(sql`drop table if exists notifications_insert_into`); + + this.db.run(sql` + create table ${usersTable} ( + id integer primary key, + name text not null, + verified integer not null default 0, + json blob, + created_at integer not null default (strftime('%s', 'now')) + ) + `); + + this.db.run(sql` + create table ${citiesTable} ( + id integer primary key, + name text not null + ) + `); + this.db.run(sql` + create table ${courseCategoriesTable} ( + id integer primary key, + name text not null + ) + `); + + this.db.run(sql` + create table ${users2Table} ( + id integer primary key, + name text not null, + city_id integer references ${citiesTable}(${sql.identifier(citiesTable.id.name)}) + ) + `); + this.db.run(sql` + create table ${coursesTable} ( + id integer primary key, + name text not null, + category_id integer references ${courseCategoriesTable}(${sql.identifier(courseCategoriesTable.id.name)}) + ) + `); + this.db.run(sql` + create table ${orders} ( + id integer primary key, + region text not null, + product text not null, + amount integer not null, + quantity integer not null + ) + `); + this.db.run(sql` + create table ${pkExampleTable} ( + id integer not null, + name text not null, + email text not null, + primary key (id, name) + ) + `); + this.db.run(sql` + create table ${bigIntExample} ( + id integer primary key, + name text not null, + big_int blob not null + ) + `); + } + + async insertBigIntValues(): Promise { + try { + await this.beforeEach(); + + this.db + .insert(bigIntExample) + .values({ name: 'one', bigInt: BigInt('0') }) + .run(); + this.db + .insert(bigIntExample) + .values({ name: 'two', bigInt: BigInt('127') }) + .run(); + this.db + .insert(bigIntExample) + .values({ name: 'three', bigInt: BigInt('32767') }) + .run(); + this.db + .insert(bigIntExample) + .values({ name: 'four', bigInt: BigInt('1234567890') }) + .run(); + this.db + .insert(bigIntExample) + .values({ name: 'five', bigInt: BigInt('12345678900987654321') }) + .run(); + + const result = this.db.select().from(bigIntExample).all(); + expect(result).deep.equal([ + { id: 1, name: 'one', bigInt: BigInt('0') }, + { id: 2, name: 'two', bigInt: BigInt('127') }, + { id: 3, name: 'three', bigInt: BigInt('32767') }, + { id: 4, name: 'four', bigInt: BigInt('1234567890') }, + { id: 5, name: 'five', bigInt: BigInt('12345678900987654321') }, + ]); + } catch (error: any) { + console.log(error); + throw new Error('insertBigIntValues has broken'); + } + } + async selectAllFields(): Promise { + try { + await this.beforeEach(); + const now = Date.now(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const result = this.db.select().from(usersTable).all(); + expect(result[0]!.createdAt).instanceOf(Date); + expect(Math.abs(result[0]!.createdAt.getTime() - now)).lessThan(5000); + expect(result).deep.equal([{ + id: 1, + name: 'John', + verified: false, + json: null, + createdAt: result[0]!.createdAt, + }]); + } catch { + throw new Error('selectAllFields has broken'); + } + } + + async selectPartial(): Promise { + try { + await this.beforeEach(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const result = this.db.select({ name: usersTable.name }).from(usersTable).all(); + + expect(result).deep.equal([{ name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectPartial error`); + } + } + + async selectSql(): Promise { + try { + await this.beforeEach(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const users = this.db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable) + .all(); + + expect(users).deep.equal([{ name: 'JOHN' }]); + } catch { + throw new Error('selectSql has broken'); + } + } + + async selectTypedSql(): Promise { + try { + await this.beforeEach(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const users = this.db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable) + .all(); + + expect(users).deep.equal([{ name: 'JOHN' }]); + } catch { + throw new Error('selectTypedSql has broken'); + } + } + + async selectWithEmptyArrayInInArray(): Promise { + try { + await this.beforeEach(); + + await this.db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + const result = await this.db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable) + .where(inArray(usersTable.id, [])); + + expect(result).deep.equal([]); + } catch (error: any) { + console.error(error); + throw new Error('selectWithEmptyArrayInInArray has broken'); + } + } + + async selectWithEmptyArrayInNotInArray(): Promise { + try { + await this.beforeEach(); + + await this.db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + const result = await this.db + .select({ + name: sql`upper(${usersTable.name})`, + }) + .from(usersTable) + .where(notInArray(usersTable.id, [])); + + expect(result).deep.equal([{ name: 'JOHN' }, { name: 'JANE' }, { name: 'JANE' }]); + } catch (error: any) { + console.error(error); + throw new Error('selectWithEmptyArrayInNotInArray has broken'); + } + } + + async selectDistinct(): Promise { + try { + await this.beforeEach(); + + const usersDistinctTable = sqliteTable('users_distinct', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${usersDistinctTable}`); + this.db.run(sql`create table ${usersDistinctTable} (id integer, name text)`); + + this.db + .insert(usersDistinctTable) + .values([ + { id: 1, name: 'John' }, + { id: 1, name: 'John' }, + { id: 2, name: 'John' }, + { id: 1, name: 'Jane' }, + ]) + .run(); + const users = this.db.selectDistinct().from(usersDistinctTable).orderBy( + usersDistinctTable.id, + usersDistinctTable.name, + ).all(); + + this.db.run(sql`drop table ${usersDistinctTable}`); + + expect(users).deep.equal([ + { id: 1, name: 'Jane' }, + { id: 1, name: 'John' }, + { id: 2, name: 'John' }, + ]); + } catch (error: any) { + console.error(error); + throw new Error('selectDistinct has broken'); + } + } + + async returingSql(): Promise { + try { + await this.beforeEach(); + + const users = this.db + .insert(usersTable) + .values({ name: 'John' }) + .returning({ + name: sql`upper(${usersTable.name})`, + }) + .all(); + + expect(users).deep.equal([{ name: 'JOHN' }]); + } catch (error: any) { + console.error(error); + throw new Error('returingSql has broken'); + } + } + + async $defaultFunction(): Promise { + try { + await this.beforeEach(); + + await this.db.insert(orders).values({ id: 1, region: 'Ukraine', amount: 1, quantity: 1 }); + const selectedOrder = await this.db.select().from(orders); + + expect(selectedOrder).deep.equal([ + { + id: 1, + amount: 1, + quantity: 1, + region: 'Ukraine', + product: 'random_string', + }, + ]); + } catch (error: any) { + console.error(error); + throw new Error('defaultFunction has broken'); + } + } + + async deleteReturningSql(): Promise { + try { + await this.beforeEach(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const users = this.db + .delete(usersTable) + .where(eq(usersTable.name, 'John')) + .returning({ + name: sql`upper(${usersTable.name})`, + }) + .all(); + + expect(users).deep.equal([{ name: 'JOHN' }]); + } catch (error: any) { + console.error(error); + throw new Error('deleteReturningSql has broken'); + } + } + + async queryCheckInsertSingleEmptyRow(): Promise { + try { + await this.beforeEach(); + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name').default('Dan'), + state: text('state'), + }); + + const query = this.db.insert(users).values({}).toSQL(); + + expect(query).deep.equal({ + sql: 'insert into "users" ("id", "name", "state") values (null, ?, null)', + params: ['Dan'], + }); + } catch (error: any) { + console.error(error); + throw new Error('queryCheckInsertSingleEmptyRow has broken'); + } + } + + async queryCheckInsertMultipleEmptyRow(): Promise { + try { + await this.beforeEach(); + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name').default('Dan'), + state: text('state'), + }); + + const query = this.db.insert(users).values([{}, {}]).toSQL(); + + expect(query).deep.equal({ + sql: 'insert into "users" ("id", "name", "state") values (null, ?, null), (null, ?, null)', + params: ['Dan', 'Dan'], + }); + } catch (error: any) { + console.error(error); + throw new Error('queryCheckInsertMultipleEmptyRow has broken'); + } + } + + async insertAllDefaultsIn1Row(): Promise { + try { + await this.beforeEach(); + + const users = sqliteTable('empty_insert_single', { + id: integer('id').primaryKey(), + name: text('name').default('Dan'), + state: text('state'), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run(sql`create table ${users} (id integer primary key, name text default 'Dan', state text)`); + + this.db.insert(users).values({}).run(); + + const res = this.db.select().from(users).all(); + + expect(res).deep.equal([{ id: 1, name: 'Dan', state: null }]); + } catch (error: any) { + console.error(error); + throw new Error('insertAllDefaultsIn1Row has broken'); + } + } + + async insertAllDefaultsInMultipleRows(): Promise { + try { + await this.beforeEach(); + + const users = sqliteTable('empty_insert_multiple', { + id: integer('id').primaryKey(), + name: text('name').default('Dan'), + state: text('state'), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run(sql`create table ${users} (id integer primary key, name text default 'Dan', state text)`); + + this.db.insert(users).values([{}, {}]).run(); + + const res = this.db.select().from(users).all(); + + expect(res).deep.equal([ + { id: 1, name: 'Dan', state: null }, + { id: 2, name: 'Dan', state: null }, + ]); + } catch (error: any) { + console.error(error); + throw new Error('insertAllDefaultsInMultipleRows has broken'); + } + } + + async updateReturningSql(): Promise { + try { + await this.beforeEach(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const users = this.db + .update(usersTable) + .set({ name: 'Jane' }) + .where(eq(usersTable.name, 'John')) + .returning({ + name: sql`upper(${usersTable.name})`, + }) + .all(); + + expect(users).deep.equal([{ name: 'JANE' }]); + } catch (error: any) { + console.error(error); + throw new Error('updateReturningSql has broken'); + } + } + + async insertWithAutoIncrement(): Promise { + try { + await this.beforeEach(); + + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'George' }, { name: 'Austin' }]) + .run(); + const result = this.db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).all(); + + expect(result).deep.equal([ + { id: 1, name: 'John' }, + { id: 2, name: 'Jane' }, + { id: 3, name: 'George' }, + { id: 4, name: 'Austin' }, + ]); + } catch (error: any) { + console.error(error); + throw new Error('insertWithAutoIncrement has broken'); + } + } + + async insertDataWithDefaultValues(): Promise { + try { + await this.beforeEach(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const result = this.db.select().from(usersTable).all(); + + expect(result).deep.equal([{ + id: 1, + name: 'John', + verified: false, + json: null, + createdAt: result[0]!.createdAt, + }]); + } catch (error: any) { + console.error(error); + throw new Error('insertDataWithDefaultValues has broken'); + } + } + + async insertDataWithOverridenDefaultValues(): Promise { + try { + await this.beforeEach(); + + this.db.insert(usersTable).values({ name: 'John', verified: true }).run(); + const result = this.db.select().from(usersTable).all(); + + expect(result).deep.equal([{ id: 1, name: 'John', verified: true, json: null, createdAt: result[0]!.createdAt }]); + } catch (error: any) { + console.error(error); + throw new Error('insertDataWithOverridenDefaultValues has broken'); + } + } + + async updateWithReturningFields(): Promise { + try { + await this.beforeEach(); + const now = Date.now(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const users = this.db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning() + .all(); + + expect(users[0]!.createdAt).instanceOf(Date); + expect(Math.abs(users[0]!.createdAt.getTime() - now)).lessThan(5000); + expect(users).deep.equal([{ id: 1, name: 'Jane', verified: false, json: null, createdAt: users[0]!.createdAt }]); + } catch (error: any) { + console.error(error); + throw new Error('updateWithReturningFields has broken'); + } + } + + async updateWithReturningPartial(): Promise { + try { + await this.beforeEach(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const users = this.db + .update(usersTable) + .set({ name: 'Jane' }) + .where(eq(usersTable.name, 'John')) + .returning({ + id: usersTable.id, + name: usersTable.name, + }) + .all(); + + expect(users).deep.equal([{ id: 1, name: 'Jane' }]); + } catch (error: any) { + console.error(error); + throw new Error('updateWithReturningFields has broken'); + } + } + + async updateWithReturningAllFields(): Promise { + try { + await this.beforeEach(); + + const now = Date.now(); + + this.db.insert(usersTable).values({ name: 'John' }).run(); + const users = this.db.delete(usersTable).where(eq(usersTable.name, 'John')).returning().all(); + + expect(users[0]!.createdAt).instanceOf(Date); + expect(Math.abs(users[0]!.createdAt.getTime() - now)).lessThan(5000); + expect(users).deep.equal([{ id: 1, name: 'John', verified: false, json: null, createdAt: users[0]!.createdAt }]); + } catch (error: any) { + console.error(error); + throw new Error('updateWithReturningFields has broken'); + } + } + + async deleteWithReturningPartial(): Promise { + try { + await this.beforeEach(); + this.db.insert(usersTable).values({ name: 'John' }).run(); + const users = this.db + .delete(usersTable) + .where(eq(usersTable.name, 'John')) + .returning({ + id: usersTable.id, + name: usersTable.name, + }) + .all(); + + expect(users).deep.equal([{ id: 1, name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`deleteWithReturningPartial error`); + } + } + + async insertAndSelect(): Promise { + try { + await this.beforeEach(); + this.db.insert(usersTable).values({ name: 'John' }).run(); + const result = this.db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).all(); + + expect(result).deep.equal([{ id: 1, name: 'John' }]); + + this.db.insert(usersTable).values({ name: 'Jane' }).run(); + const result2 = this.db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).all(); + + expect(result2).deep.equal([ + { id: 1, name: 'John' }, + { id: 2, name: 'Jane' }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`insertAndSelect error`); + } + } + + async jsonInsert(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values({ name: 'John', json: ['foo', 'bar'] }) + .run(); + const result = this.db + .select({ + id: usersTable.id, + name: usersTable.name, + json: usersTable.json, + }) + .from(usersTable) + .all(); + + expect(result).deep.equal([{ id: 1, name: 'John', json: ['foo', 'bar'] }]); + } catch (error: any) { + console.error(error); + throw new Error(`jsonInsert error`); + } + } + + async insertMany(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Bruce', json: ['foo', 'bar'] }, { name: 'Jane' }, { + name: 'Austin', + verified: true, + }]) + .run(); + const result = this.db + .select({ + id: usersTable.id, + name: usersTable.name, + json: usersTable.json, + verified: usersTable.verified, + }) + .from(usersTable) + .all(); + + expect(result).deep.equal([ + { id: 1, name: 'John', json: null, verified: false }, + { id: 2, name: 'Bruce', json: ['foo', 'bar'], verified: false }, + { id: 3, name: 'Jane', json: null, verified: false }, + { id: 4, name: 'Austin', json: null, verified: true }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`insertMany error`); + } + } + + async insertManyWithReturning(): Promise { + try { + await this.beforeEach(); + const result = this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Bruce', json: ['foo', 'bar'] }, { name: 'Jane' }, { + name: 'Austin', + verified: true, + }]) + .returning({ + id: usersTable.id, + name: usersTable.name, + json: usersTable.json, + verified: usersTable.verified, + }) + .all(); + + expect(result).deep.equal([ + { id: 1, name: 'John', json: null, verified: false }, + { id: 2, name: 'Bruce', json: ['foo', 'bar'], verified: false }, + { id: 3, name: 'Jane', json: null, verified: false }, + { id: 4, name: 'Austin', json: null, verified: true }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`insertManyWithReturning error`); + } + } + + async partialJoinWithAlias(): Promise { + try { + await this.beforeEach(); + const customerAlias = alias(usersTable, 'customer'); + + await this.db.insert(usersTable).values([ + { id: 10, name: 'Ivan' }, + { id: 11, name: 'Hans' }, + ]); + + const result = await this.db + .select({ + user: { + id: usersTable.id, + name: usersTable.name, + }, + customer: { + id: customerAlias.id, + name: customerAlias.name, + }, + }) + .from(usersTable) + .leftJoin(customerAlias, eq(customerAlias.id, 11)) + .where(eq(usersTable.id, 10)); + + expect(result).deep.equal([ + { + user: { id: 10, name: 'Ivan' }, + customer: { id: 11, name: 'Hans' }, + }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`partialJoinWithAlias error`); + } + } + + async fullJoinWithAlias(): Promise { + try { + await this.beforeEach(); + const sqliteTable = sqliteTableCreator((name) => `prefixed_${name}`); + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${users}`); + this.db.run(sql`create table ${users} (id integer primary key, name text not null)`); + + const customers = alias(users, 'customer'); + + this.db + .insert(users) + .values([ + { id: 10, name: 'Ivan' }, + { id: 11, name: 'Hans' }, + ]) + .run(); + const result = this.db.select().from(users).leftJoin(customers, eq(customers.id, 11)).where(eq(users.id, 10)) + .all(); + + expect(result).deep.equal([ + { + users: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, + }, + ]); + + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`fullJoinWithAlias error`); + } + } + + async selectFromAlias(): Promise { + try { + await this.beforeEach(); + const sqliteTable = sqliteTableCreator((name) => `prefixed_${name}`); + + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${users}`); + this.db.run(sql`create table ${users} (id integer primary key, name text not null)`); + + const user = alias(users, 'user'); + const customers = alias(users, 'customer'); + + this.db + .insert(users) + .values([ + { id: 10, name: 'Ivan' }, + { id: 11, name: 'Hans' }, + ]) + .run(); + const result = this.db.select().from(user).leftJoin(customers, eq(customers.id, 11)).where(eq(user.id, 10)).all(); + + expect(result).deep.equal([ + { + user: { + id: 10, + name: 'Ivan', + }, + customer: { + id: 11, + name: 'Hans', + }, + }, + ]); + + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`selectFromAlias error`); + } + } + + async insertWithSpaces(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values({ name: sql`'Jo h n'` }) + .run(); + const result = await this.db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).all(); + + expect(result).deep.equal([{ id: 1, name: 'Jo h n' }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertWithSpaces error`); + } + } + + async preparedStatement(): Promise { + try { + await this.beforeEach(); + this.db.insert(usersTable).values({ name: 'John' }).run(); + const statement = this.db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).prepare(); + const result = statement.all(); + + expect(result).deep.equal([{ id: 1, name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`preparedStatement error`); + } + } + + async preparedStatementReuse(): Promise { + try { + await this.beforeEach(); + const stmt = this.db + .insert(usersTable) + .values({ name: sql.placeholder('name') }) + .prepare(); + + for (let i = 0; i < 10; i++) { + stmt.run({ name: `John ${i}` }); + } + + const result = this.db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .all(); + + expect(result).deep.equal([ + { id: 1, name: 'John 0' }, + { id: 2, name: 'John 1' }, + { id: 3, name: 'John 2' }, + { id: 4, name: 'John 3' }, + { id: 5, name: 'John 4' }, + { id: 6, name: 'John 5' }, + { id: 7, name: 'John 6' }, + { id: 8, name: 'John 7' }, + { id: 9, name: 'John 8' }, + { id: 10, name: 'John 9' }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`preparedStatementReuse error`); + } + } + + async insertPlaceholdersOnColumnsWithEncoder(): Promise { + try { + await this.beforeEach(); + const stmt = this.db + .insert(usersTable) + .values({ + name: 'John', + verified: sql.placeholder('verified'), + }) + .prepare(); + + stmt.run({ verified: true }); + stmt.run({ verified: false }); + + const result = this.db + .select({ + id: usersTable.id, + verified: usersTable.verified, + }) + .from(usersTable) + .all(); + + expect(result).deep.equal([ + { id: 1, verified: true }, + { id: 2, verified: false }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`insertPlaceholdersOnColumnsWithEncoder error`); + } + } + + async preparedStatementWithPlaceholderInWhere(): Promise { + try { + await this.beforeEach(); + this.db.insert(usersTable).values({ name: 'John' }).run(); + const stmt = this.db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .where(eq(usersTable.id, sql.placeholder('id'))) + .prepare(); + const result = stmt.all({ id: 1 }); + + expect(result).deep.equal([{ id: 1, name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`preparedStatementWithPlaceholderInWhere error`); + } + } + + async preparedStatementWithPlaceholderInLimit(): Promise { + try { + await this.beforeEach(); + this.db.insert(usersTable).values({ name: 'John' }).run(); + const stmt = this.db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .where(eq(usersTable.id, sql.placeholder('id'))) + .limit(sql.placeholder('limit')) + .prepare(); + + const result = await stmt.all({ id: 1, limit: 1 }); + + expect(result).deep.equal([{ id: 1, name: 'John' }]); + expect(result).length(1); + } catch (error: any) { + console.error(error); + throw new Error(`preparedStatementWithPlaceholderInLimit error`); + } + } + + async preparedStatementWithPlaceholderInOffset(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'John1' }]) + .run(); + const stmt = this.db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .prepare(); + + const result = stmt.all({ limit: 1, offset: 1 }); + + expect(result).deep.equal([{ id: 2, name: 'John1' }]); + } catch (error: any) { + console.error(error); + throw new Error(`preparedStatementWithPlaceholderInOffset error`); + } + } + + async preparedStatementBuiltUsing$dynamic(): Promise { + try { + await this.beforeEach(); + function withLimitOffset(qb: any) { + return qb.limit(sql.placeholder('limit')).offset(sql.placeholder('offset')); + } + + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'John1' }]) + .run(); + const stmt = this.db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .$dynamic(); + withLimitOffset(stmt).prepare('stmt_limit'); + + const result = await stmt.all({ limit: 1, offset: 1 }); + + expect(result).deep.equal([{ id: 2, name: 'John1' }]); + expect(result).length(1); + } catch (error: any) { + console.error(error); + throw new Error(`preparedStatementBuiltUsing error`); + } + } + + async selectWithGroupByAsField(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]) + .run(); + + const result = this.db.select({ name: usersTable.name }).from(usersTable).groupBy(usersTable.name).all(); + + expect(result).deep.equal([{ name: 'Jane' }, { name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectWithGroupByAsField error`); + } + } + + async selectWithExists(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]) + .run(); + + const user = alias(usersTable, 'user'); + const result = this.db + .select({ name: usersTable.name }) + .from(usersTable) + .where( + exists( + this.db + .select({ one: sql`1` }) + .from(user) + .where(and(eq(usersTable.name, 'John'), eq(user.id, usersTable.id))), + ), + ) + .all(); + + expect(result).deep.equal([{ name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectWithExists error`); + } + } + + async selectWithGroupByAsSql(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]) + .run(); + + const result = this.db + .select({ name: usersTable.name }) + .from(usersTable) + .groupBy(sql`${usersTable.name}`) + .all(); + + expect(result).deep.equal([{ name: 'Jane' }, { name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectWithGroupByAsSql error`); + } + } + + async selectWithGroupByAsSqlPlusColumn(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]) + .run(); + + const result = this.db + .select({ name: usersTable.name }) + .from(usersTable) + .groupBy(sql`${usersTable.name}`, usersTable.id) + .all(); + + expect(result).deep.equal([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectWithGroupByAsSqlPlusColumn error`); + } + } + + async selectWithGroupByAsColumnPlusSql(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]) + .run(); + + const result = this.db + .select({ name: usersTable.name }) + .from(usersTable) + .groupBy(usersTable.id, sql`${usersTable.name}`) + .all(); + + expect(result).deep.equal([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectWithGroupByAsColumnPlusSql error`); + } + } + + async selectWithGroupByComplexQuery(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]) + .run(); + + const result = this.db + .select({ name: usersTable.name }) + .from(usersTable) + .groupBy(usersTable.id, sql`${usersTable.name}`) + .orderBy(asc(usersTable.name)) + .limit(1) + .all(); + + expect(result).deep.equal([{ name: 'Jane' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectWithGroupByComplexQuery error`); + } + } + + async buildQuery(): Promise { + try { + await this.beforeEach(); + const query = this.db + .select({ id: usersTable.id, name: usersTable.name }) + .from(usersTable) + .groupBy(usersTable.id, usersTable.name) + .toSQL(); + + expect(query).deep.equal({ + sql: 'select "id", "name" from "users" group by "users"."id", "users"."name"', + params: [], + }); + } catch (error: any) { + console.error(error); + throw new Error(`buildQuery error`); + } + } + + async insertViaDbRunPlusSelectViaDbAll(): Promise { + try { + await this.beforeEach(); + this.db.run(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${'John'})`); + + const result = this.db.all<{ id: number; name: string }>(sql`select id, name from "users"`); + expect(result).deep.equal([{ id: 1, name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertViaDbRunPlusSelectViaDbAll error`); + } + } + + async insertViaDbGet(): Promise { + try { + await this.beforeEach(); + const inserted = this.db.get<{ id: number; name: string }>( + sql`insert into ${usersTable} (${new Name( + usersTable.name.name, + )}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`, + ); + expect(inserted).deep.equal({ id: 1, name: 'John' }); + } catch (error: any) { + console.error(error); + throw new Error(`insertViaDbGet error`); + } + } + + async insertViaDbRunPlusSelectViaDbGet(): Promise { + try { + await this.beforeEach(); + this.db.run(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${'John'})`); + + const result = this.db.get<{ id: number; name: string }>( + sql`select ${usersTable.id}, ${usersTable.name} from ${usersTable}`, + ); + expect(result).deep.equal({ id: 1, name: 'John' }); + } catch (error: any) { + console.error(error); + throw new Error(`insertViaDbRunPlusSelectViaDbGet error`); + } + } + + async insertViaDbGetQueryBuilder(): Promise { + try { + await this.beforeEach(); + const inserted = this.db.get>( + this.db.insert(usersTable).values({ name: 'John' }).returning({ id: usersTable.id, name: usersTable.name }), + ); + expect(inserted).deep.equal({ id: 1, name: 'John' }); + } catch (error: any) { + console.error(error); + throw new Error(`insertViaDbGetQueryBuilder error`); + } + } + + async joinSubquery(): Promise { + try { + await this.beforeEach(); + this.db + .insert(courseCategoriesTable) + .values([{ name: 'Category 1' }, { name: 'Category 2' }, { name: 'Category 3' }, { name: 'Category 4' }]) + .run(); + + this.db + .insert(coursesTable) + .values([ + { name: 'Development', categoryId: 2 }, + { name: 'IT & Software', categoryId: 3 }, + { name: 'Marketing', categoryId: 4 }, + { name: 'Design', categoryId: 1 }, + ]) + .run(); + + const sq2 = this.db + .select({ + categoryId: courseCategoriesTable.id, + category: courseCategoriesTable.name, + total: sql`count(${courseCategoriesTable.id})`, + }) + .from(courseCategoriesTable) + .groupBy(courseCategoriesTable.id, courseCategoriesTable.name) + .as('sq2'); + + const res = await this.db + .select({ + courseName: coursesTable.name, + categoryId: sq2.categoryId, + }) + .from(coursesTable) + .leftJoin(sq2, eq(coursesTable.categoryId, sq2.categoryId)) + .orderBy(coursesTable.name) + .all(); + + expect(res).deep.equal([ + { courseName: 'Design', categoryId: 1 }, + { courseName: 'Development', categoryId: 2 }, + { courseName: 'IT & Software', categoryId: 3 }, + { courseName: 'Marketing', categoryId: 4 }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`joinSubquery error`); + } + } + + async withSelect(): Promise { + try { + await this.beforeEach(); + this.db + .insert(orders) + .values([ + { region: 'Europe', product: 'A', amount: 10, quantity: 1 }, + { region: 'Europe', product: 'A', amount: 20, quantity: 2 }, + { region: 'Europe', product: 'B', amount: 20, quantity: 2 }, + { region: 'Europe', product: 'B', amount: 30, quantity: 3 }, + { region: 'US', product: 'A', amount: 30, quantity: 3 }, + { region: 'US', product: 'A', amount: 40, quantity: 4 }, + { region: 'US', product: 'B', amount: 40, quantity: 4 }, + { region: 'US', product: 'B', amount: 50, quantity: 5 }, + ]) + .run(); + + const regionalSales = this.db.$with('regional_sales').as( + this.db + .select({ + region: orders.region, + totalSales: sql`sum(${orders.amount})`.as('total_sales'), + }) + .from(orders) + .groupBy(orders.region), + ); + + const topRegions = this.db.$with('top_regions').as( + this.db + .select({ + region: regionalSales.region, + }) + .from(regionalSales) + .where( + gt( + regionalSales.totalSales, + this.db.select({ sales: sql`sum(${regionalSales.totalSales})/10` }).from(regionalSales), + ), + ), + ); + + const result = this.db + .with(regionalSales, topRegions) + .select({ + region: orders.region, + product: orders.product, + productUnits: sql`cast(sum(${orders.quantity}) as int)`, + productSales: sql`cast(sum(${orders.amount}) as int)`, + }) + .from(orders) + .where(inArray(orders.region, this.db.select({ region: topRegions.region }).from(topRegions))) + .groupBy(orders.region, orders.product) + .orderBy(orders.region, orders.product) + .all(); + + expect(result).deep.equal([ + { + region: 'Europe', + product: 'A', + productUnits: 3, + productSales: 30, + }, + { + region: 'Europe', + product: 'B', + productUnits: 5, + productSales: 50, + }, + { + region: 'US', + product: 'A', + productUnits: 7, + productSales: 70, + }, + { + region: 'US', + product: 'B', + productUnits: 9, + productSales: 90, + }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`withSelect error`); + } + } + + async withUpdate(): Promise { + try { + await this.beforeEach(); + const products = sqliteTable('products', { + id: integer('id').primaryKey(), + price: numeric('price').notNull(), + cheap: integer('cheap', { mode: 'boolean' }).notNull().default(false), + }); + + this.db.run(sql`drop table if exists ${products}`); + this.db.run(sql` + create table ${products} ( + id integer primary key, + price numeric not null, + cheap integer not null default 0 + ) + `); + + await this.db + .insert(products) + .values([{ price: '10.99' }, { price: '25.85' }, { price: '32.99' }, { price: '2.50' }, { price: '4.59' }]); + + const averagePrice = this.db.$with('average_price').as( + this.db + .select({ + value: sql`avg(${products.price})`.as('value'), + }) + .from(products), + ); + + const result = await this.db + .with(averagePrice) + .update(products) + .set({ + cheap: true, + }) + .where(lt(products.price, sql`(select * from ${averagePrice})`)) + .returning({ + id: products.id, + }); + + expect(result).deep.equal([{ id: 1 }, { id: 4 }, { id: 5 }]); + } catch (error: any) { + console.error(error); + throw new Error(`withUpdate error`); + } + } + + async withInsert(): Promise { + try { + await this.beforeEach(); + const users = sqliteTable('users', { + username: text('username').notNull(), + admin: integer('admin', { mode: 'boolean' }).notNull(), + }); + + this.db.run(sql`drop table if exists ${users}`); + this.db.run(sql`create table ${users} (username text not null, admin integer not null default 0)`); + + const userCount = this.db.$with('user_count').as( + this.db + .select({ + value: sql`count(*)`.as('value'), + }) + .from(users), + ); + + const result = await this.db + .with(userCount) + .insert(users) + .values([{ username: 'user1', admin: sql`((select * from ${userCount}) = 0)` }]) + .returning({ + admin: users.admin, + }); + + expect(result).deep.equal([{ admin: true }]); + } catch (error: any) { + console.error(error); + throw new Error(`withInsert error`); + } + } + + async withDelete(): Promise { + try { + await this.beforeEach(); + await this.db.insert(orders).values([ + { region: 'Europe', product: 'A', amount: 10, quantity: 1 }, + { region: 'Europe', product: 'A', amount: 20, quantity: 2 }, + { region: 'Europe', product: 'B', amount: 20, quantity: 2 }, + { region: 'Europe', product: 'B', amount: 30, quantity: 3 }, + { region: 'US', product: 'A', amount: 30, quantity: 3 }, + { region: 'US', product: 'A', amount: 40, quantity: 4 }, + { region: 'US', product: 'B', amount: 40, quantity: 4 }, + { region: 'US', product: 'B', amount: 50, quantity: 5 }, + ]); + + const averageAmount = this.db.$with('average_amount').as( + this.db + .select({ + value: sql`avg(${orders.amount})`.as('value'), + }) + .from(orders), + ); + + const result = this.db + .with(averageAmount) + .delete(orders) + .where(gt(orders.amount, sql`(select * from ${averageAmount})`)) + .returning({ + id: orders.id, + }) + .all(); + + expect(result).deep.equal([{ id: 6 }, { id: 7 }, { id: 8 }]); + } catch (error: any) { + console.error(error); + throw new Error(`withDelete error`); + } + } + + async selectFromSubquerySql(): Promise { + try { + await this.beforeEach(); + this.db + .insert(users2Table) + .values([{ name: 'John' }, { name: 'Jane' }]) + .run(); + + const sq = this.db + .select({ name: sql`${users2Table.name} || ' modified'`.as('name') }) + .from(users2Table) + .as('sq'); + + const res = this.db.select({ name: sq.name }).from(sq).all(); + + expect(res).deep.equal([{ name: 'John modified' }, { name: 'Jane modified' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectFromSubquerySql error`); + } + } + + async selectAFieldWithoutJoiningItsTable(): Promise { + try { + await this.beforeEach(); + expect(() => this.db.select({ name: users2Table.name }).from(usersTable).prepare()).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`selectAFieldWithoutJoiningItsTable error`); + } + } + + async selectAllFieldsFromSubqueryWithoutAlias(): Promise { + try { + const sq = this.db.$with('sq').as( + this.db.select({ name: sql`upper(${users2Table.name})` }).from(users2Table), + ); + + expect(() => this.db.select().from(sq).prepare()).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`selectAllFieldsFromSubqueryWithoutAlias error`); + } + } + + async selectCount(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ name: 'John' }, { name: 'Jane' }]) + .run(); + + const res = this.db + .select({ count: sql`count(*)` }) + .from(usersTable) + .all(); + + expect(res).deep.equal([{ count: 2 }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectCount error`); + } + } + + async having(): Promise { + try { + await this.beforeEach(); + this.db + .insert(citiesTable) + .values([{ name: 'London' }, { name: 'Paris' }, { name: 'New York' }]) + .run(); + + this.db + .insert(users2Table) + .values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 1 }, + { name: 'Jack', cityId: 2 }, + ]) + .run(); + + const result = this.db + .select({ + id: citiesTable.id, + name: sql`upper(${citiesTable.name})`.as('upper_name'), + usersCount: sql`count(${users2Table.id})`.as('users_count'), + }) + .from(citiesTable) + .leftJoin(users2Table, eq(users2Table.cityId, citiesTable.id)) + .where(({ name }) => sql`length(${name}) >= 3`) + .groupBy(citiesTable.id) + .having(({ usersCount }) => sql`${usersCount} > 0`) + .orderBy(({ name }) => name) + .all(); + + expect(result).deep.equal([ + { + id: 1, + name: 'LONDON', + usersCount: 2, + }, + { + id: 2, + name: 'PARIS', + usersCount: 1, + }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`having error`); + } + } + + async view(): Promise { + try { + await this.beforeEach(); + const newYorkers1 = sqliteView('new_yorkers').as((qb) => + qb.select().from(users2Table).where(eq(users2Table.cityId, 1)) + ); + + const newYorkers2 = sqliteView('new_yorkers', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + cityId: integer('city_id').notNull(), + }).as(sql`select * from ${users2Table} where ${eq(users2Table.cityId, 1)}`); + + const newYorkers3 = sqliteView('new_yorkers', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + cityId: integer('city_id').notNull(), + }).existing(); + + this.db.run(sql`create view if not exists new_yorkers as ${getViewConfig(newYorkers1).query}`); + + this.db + .insert(citiesTable) + .values([{ name: 'New York' }, { name: 'Paris' }]) + .run(); + + this.db + .insert(users2Table) + .values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 1 }, + { name: 'Jack', cityId: 2 }, + ]) + .run(); + + { + const result = this.db.select().from(newYorkers1).all(); + expect(result).deep.equal([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = this.db.select().from(newYorkers2).all(); + expect(result).deep.equal([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = this.db.select().from(newYorkers3).all(); + expect(result).deep.equal([ + { id: 1, name: 'John', cityId: 1 }, + { id: 2, name: 'Jane', cityId: 1 }, + ]); + } + + { + const result = this.db.select({ name: newYorkers1.name }).from(newYorkers1).all(); + expect(result).deep.equal([{ name: 'John' }, { name: 'Jane' }]); + } + + this.db.run(sql`drop view ${newYorkers1}`); + } catch (error: any) { + console.error(error); + throw new Error(`view error`); + } + } + + async insertNullTimestamp(): Promise { + try { + await this.beforeEach(); + const test = sqliteTable('test', { + t: integer('t', { mode: 'timestamp' }), + }); + + this.db.run(sql`create table ${test} (t timestamp)`); + + this.db.insert(test).values({ t: null }).run(); + const res = await this.db.select().from(test).all(); + expect(res).deep.equal([{ t: null }]); + + this.db.run(sql`drop table ${test}`); + } catch (error: any) { + console.error(error); + throw new Error(`insertNullTimestamp error`); + } + } + + async selectFromRawSql(): Promise { + try { + const result = this.db + .select({ + id: sql`id`, + name: sql`name`, + }) + .from(sql`(select 1 as id, 'John' as name) as users`) + .all(); + + Expect>; + + expect(result).deep.equal([{ id: 1, name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectFromRawSql error`); + } + } + + async selectFromRawSqlWithJoins(): Promise { + try { + await this.beforeEach(); + const result = this.db + .select({ + id: sql`users.id`, + name: sql`users.name`.as('userName'), + userCity: sql`users.city`, + cityName: sql`cities.name`.as('cityName'), + }) + .from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`) + .leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, sql`cities.id = users.id`) + .all(); + + Expect>; + + expect(result).deep.equal([{ id: 1, name: 'John', userCity: 'New York', cityName: 'Paris' }]); + } catch (error: any) { + console.error(error); + throw new Error(`selectFromRawSqlWithJoins error`); + } + } + + async joinOnAliasedSqlFromSelect(): Promise { + try { + await this.beforeEach(); + const result = this.db + .select({ + userId: sql`users.id`.as('userId'), + name: sql`users.name`.as('userName'), + userCity: sql`users.city`, + cityId: sql`cities.id`.as('cityId'), + cityName: sql`cities.name`.as('cityName'), + }) + .from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`) + .leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, (cols) => eq(cols.cityId, cols.userId)) + .all(); + + Expect< + Equal<{ userId: number; name: string; userCity: string; cityId: number; cityName: string }[], typeof result> + >; + + expect(result).deep.equal([{ userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }]); + } catch (error: any) { + console.error(error); + throw new Error(`joinOnAliasedSqlFromSelect error`); + } + } + + async joinOnAliasedSqlFromWithClause(): Promise { + try { + await this.beforeEach(); + const users = this.db.$with('users').as( + this.db + .select({ + id: sql`id`.as('userId'), + name: sql`name`.as('userName'), + city: sql`city`.as('city'), + }) + .from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`), + ); + + const cities = this.db.$with('cities').as( + this.db + .select({ + id: sql`id`.as('cityId'), + name: sql`name`.as('cityName'), + }) + .from(sql`(select 1 as id, 'Paris' as name) as cities`), + ); + + const result = this.db + .with(users, cities) + .select({ + userId: users.id, + name: users.name, + userCity: users.city, + cityId: cities.id, + cityName: cities.name, + }) + .from(users) + .leftJoin(cities, (cols) => eq(cols.cityId, cols.userId)) + .all(); + + Expect< + Equal<{ userId: number; name: string; userCity: string; cityId: number; cityName: string }[], typeof result> + >; + + expect(result).deep.equal([{ userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }]); + } catch (error: any) { + console.error(error); + throw new Error(`joinOnAliasedSqlFromWithClause error`); + } + } + + async prefixedTable(): Promise { + try { + await this.beforeEach(); + const sqliteTable = sqliteTableCreator((name) => `myprefix_${name}`); + + const users = sqliteTable('test_prefixed_table_with_unique_name', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run( + sql`create table myprefix_test_prefixed_table_with_unique_name (id integer not null primary key, name text not null)`, + ); + + this.db.insert(users).values({ id: 1, name: 'John' }).run(); + + const result = this.db.select().from(users).all(); + + expect(result).deep.equal([{ id: 1, name: 'John' }]); + + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`prefixedTable error`); + } + } + + async orderByWithAliasedColumn(): Promise { + try { + await this.beforeEach(); + const query = this.db + .select({ + test: sql`something`.as('test'), + }) + .from(users2Table) + .orderBy((fields) => fields.test) + .toSQL(); + + expect(query.sql).equal('select something as "test" from "users2" order by "test"'); + } catch (error: any) { + console.error(error); + throw new Error(`orderByWithAliasedColumn error`); + } + } + + async transaction(): Promise { + try { + await this.beforeEach(); + const users = sqliteTable('users_transactions', { + id: integer('id').primaryKey(), + balance: integer('balance').notNull(), + }); + const products = sqliteTable('products_transactions', { + id: integer('id').primaryKey(), + price: integer('price').notNull(), + stock: integer('stock').notNull(), + }); + + this.db.run(sql`drop table if exists ${users}`); + this.db.run(sql`drop table if exists ${products}`); + + this.db.run(sql`create table users_transactions (id integer not null primary key, balance integer not null)`); + this.db.run( + sql`create table products_transactions (id integer not null primary key, price integer not null, stock integer not null)`, + ); + + const user = this.db.insert(users).values({ balance: 100 }).returning().get(); + const product = this.db.insert(products).values({ price: 10, stock: 10 }).returning().get(); + + this.db.transaction(async (tx) => { + tx.update(users) + .set({ balance: user.balance - product.price }) + .where(eq(users.id, user.id)) + .run(); + tx.update(products) + .set({ stock: product.stock - 1 }) + .where(eq(products.id, product.id)) + .run(); + }); + + const result = this.db.select().from(users).all(); + + expect(result).deep.equal([{ id: 1, balance: 90 }]); + + this.db.run(sql`drop table ${users}`); + this.db.run(sql`drop table ${products}`); + } catch (error: any) { + console.error(error); + throw new Error(`transaction error`); + } + } + + // async transactionRollback(): Promise{ + // const users = sqliteTable('users_transactions_rollback', { + // id: integer('id').primaryKey(), + // balance: integer('balance').notNull(), + // }); + + // this.db.run(sql`drop table if exists ${users}`); + + // this.db.run( + // sql`create table users_transactions_rollback (id integer not null primary key, balance integer not null)`, + // ); + // await expect(async () => { + // this.db.transaction(async (tx) => { + // tx.insert(users).values({ balance: 100 }).run(); + // tx.rollback(); + // }); + // }).re(TransactionRollbackError); + + // const result = await db.select().from(users).all(); + + // expect(result).toEqual([]); + + // await db.run(sql`drop table ${users}`); + // }; + + async nestedTransaction(): Promise { + try { + await this.beforeEach(); + const users = sqliteTable('users_nested_transactions', { + id: integer('id').primaryKey(), + balance: integer('balance').notNull(), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run( + sql`create table users_nested_transactions (id integer not null primary key, balance integer not null)`, + ); + + this.db.transaction((tx) => { + tx.insert(users).values({ balance: 100 }).run(); + + tx.transaction((tx) => { + tx.update(users).set({ balance: 200 }).run(); + }); + }); + + const result = this.db.select().from(users).all(); + + expect(result).deep.equal([{ id: 1, balance: 200 }]); + + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`nestedTransaction error`); + } + } + + // async nestedTransactionRollback(): Promise{ + // const users = sqliteTable('users_nested_transactions_rollback', { + // id: integer('id').primaryKey(), + // balance: integer('balance').notNull(), + // }); + + // this.db.run(sql`drop table if exists ${users}`); + + // this.db.run( + // sql`create table users_nested_transactions_rollback (id integer not null primary key, balance integer not null)`, + // ); + + // this.db.transaction((tx) => { + // this.tx.insert(users).values({ balance: 100 }).run(); + + // expect(async () => { + // await tx.transaction(async (tx) => { + // await tx.update(users).set({ balance: 200 }).run(); + // tx.rollback(); + // }); + // }).rejects.toThrowError(TransactionRollbackError); + // }); + + // const result = await db.select().from(users).all(); + + // expect(result).toEqual([{ id: 1, balance: 100 }]); + + // await db.run(sql`drop table ${users}`); + // }; + + async joinSubqueryWithJoin(): Promise { + try { + await this.beforeEach(); + const internalStaff = sqliteTable('internal_staff', { + userId: integer('user_id').notNull(), + }); + + const customUser = sqliteTable('custom_user', { + id: integer('id').notNull(), + }); + + const ticket = sqliteTable('ticket', { + staffId: integer('staff_id').notNull(), + }); + + this.db.run(sql`drop table if exists ${internalStaff}`); + this.db.run(sql`drop table if exists ${customUser}`); + this.db.run(sql`drop table if exists ${ticket}`); + + this.db.run(sql`create table internal_staff (user_id integer not null)`); + this.db.run(sql`create table custom_user (id integer not null)`); + this.db.run(sql`create table ticket (staff_id integer not null)`); + + this.db.insert(internalStaff).values({ userId: 1 }).run(); + this.db.insert(customUser).values({ id: 1 }).run(); + this.db.insert(ticket).values({ staffId: 1 }).run(); + + const subq = this.db.select().from(internalStaff).leftJoin(customUser, eq(internalStaff.userId, customUser.id)) + .as('internal_staff'); + + const mainQuery = this.db.select().from(ticket).leftJoin(subq, eq(subq.internal_staff.userId, ticket.staffId)) + .all(); + + expect(mainQuery).deep.equal([ + { + ticket: { staffId: 1 }, + internal_staff: { + internal_staff: { userId: 1 }, + custom_user: { id: 1 }, + }, + }, + ]); + + this.db.run(sql`drop table ${internalStaff}`); + this.db.run(sql`drop table ${customUser}`); + this.db.run(sql`drop table ${ticket}`); + } catch (error: any) { + console.error(error); + throw new Error(`joinSubqueryWithJoin error`); + } + } + + async joinViewAsSubquery(): Promise { + try { + await this.beforeEach(); + const users = sqliteTable('users_join_view', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + cityId: integer('city_id').notNull(), + }); + + const newYorkers = sqliteView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1))); + + this.db.run(sql`drop table if exists ${users}`); + this.db.run(sql`drop view if exists ${newYorkers}`); + + this.db.run( + sql`create table ${users} (id integer not null primary key, name text not null, city_id integer not null)`, + ); + this.db.run(sql`create view if not exists ${newYorkers} as ${getViewConfig(newYorkers).query}`); + + this.db + .insert(users) + .values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 2 }, + { name: 'Jack', cityId: 1 }, + { name: 'Jill', cityId: 2 }, + ]) + .run(); + + const sq = this.db.select().from(newYorkers).as('new_yorkers_sq'); + + const result = await this.db.select().from(users).leftJoin(sq, eq(users.id, sq.id)).all(); + + expect(result).deep.equal([ + { + users_join_view: { id: 1, name: 'John', cityId: 1 }, + new_yorkers_sq: { id: 1, name: 'John', cityId: 1 }, + }, + { + users_join_view: { id: 2, name: 'Jane', cityId: 2 }, + new_yorkers_sq: null, + }, + { + users_join_view: { id: 3, name: 'Jack', cityId: 1 }, + new_yorkers_sq: { id: 3, name: 'Jack', cityId: 1 }, + }, + { + users_join_view: { id: 4, name: 'Jill', cityId: 2 }, + new_yorkers_sq: null, + }, + ]); + + this.db.run(sql`drop view ${newYorkers}`); + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`joinViewAsSubquery error`); + } + } + + async insertWithOnConflictDoNothing(): Promise { + try { + await this.beforeEach(); + this.db.insert(usersTable).values({ id: 1, name: 'John' }).run(); + + this.db.insert(usersTable).values({ id: 1, name: 'John' }).onConflictDoNothing().run(); + + const res = this.db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ).all(); + + expect(res).deep.equal([{ id: 1, name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertWithOnConflictDoNothing error`); + } + } + + async insertWithOnConflictDoNothinUsingCompositePk(): Promise { + try { + await this.beforeEach(); + this.db.insert(pkExampleTable).values({ id: 1, name: 'John', email: 'john@example.com' }).run(); + + this.db.insert(pkExampleTable).values({ id: 1, name: 'John', email: 'john1@example.com' }).onConflictDoNothing() + .run(); + + const res = await this.db + .select({ id: pkExampleTable.id, name: pkExampleTable.name, email: pkExampleTable.email }) + .from(pkExampleTable) + .where(eq(pkExampleTable.id, 1)) + .all(); + + expect(res).deep.equal([{ id: 1, name: 'John', email: 'john@example.com' }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertWithOnConflictDoNothinUsingCompositePk error`); + } + } + + async insertWithOnConflictDoNothingUsingTarget(): Promise { + try { + this.db.insert(usersTable).values({ id: 1, name: 'John' }).run(); + + this.db.insert(usersTable).values({ id: 1, name: 'John' }).onConflictDoNothing({ target: usersTable.id }).run(); + + const res = this.db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ).all(); + + expect(res).deep.equal([{ id: 1, name: 'John' }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertWithOnConflictDoNothingUsingTarget error`); + } + } + + async insertWithOnConflictDoNothingUsingCompositePkAsTarget(): Promise { + try { + await this.beforeEach(); + this.db.insert(pkExampleTable).values({ id: 1, name: 'John', email: 'john@example.com' }).run(); + + this.db + .insert(pkExampleTable) + .values({ id: 1, name: 'John', email: 'john1@example.com' }) + .onConflictDoNothing({ target: [pkExampleTable.id, pkExampleTable.name] }) + .run(); + + const res = this.db + .select({ id: pkExampleTable.id, name: pkExampleTable.name, email: pkExampleTable.email }) + .from(pkExampleTable) + .where(eq(pkExampleTable.id, 1)) + .all(); + + expect(res).deep.equal([{ id: 1, name: 'John', email: 'john@example.com' }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertWithOnConflictDoNothingUsingCompositePkAsTarget error`); + } + } + + async insertWithOnConflictDoUpdate(): Promise { + try { + await this.beforeEach(); + this.db.insert(usersTable).values({ id: 1, name: 'John' }).run(); + + this.db + .insert(usersTable) + .values({ id: 1, name: 'John' }) + .onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } }) + .run(); + + const res = this.db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where( + eq(usersTable.id, 1), + ).all(); + + expect(res).deep.equal([{ id: 1, name: 'John1' }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertWithOnConflictDoUpdate error`); + } + } + + async insertWithOnConflictDoUpdateWhere(): Promise { + try { + await this.beforeEach(); + this.db + .insert(usersTable) + .values([{ id: 1, name: 'John', verified: false }]) + .run(); + + this.db + .insert(usersTable) + .values({ id: 1, name: 'John1', verified: true }) + .onConflictDoUpdate({ + target: usersTable.id, + set: { name: 'John1', verified: true }, + where: eq(usersTable.verified, false), + }) + .run(); + + const res = this.db + .select({ id: usersTable.id, name: usersTable.name, verified: usersTable.verified }) + .from(usersTable) + .where(eq(usersTable.id, 1)) + .all(); + + expect(res).deep.equal([{ id: 1, name: 'John1', verified: true }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertWithOnConflictDoUpdateWhere error`); + } + } + + async insertWithOnConflictDoUpdateUsingCompositePk(): Promise { + try { + await this.beforeEach(); + this.db.insert(pkExampleTable).values({ id: 1, name: 'John', email: 'john@example.com' }).run(); + + this.db + .insert(pkExampleTable) + .values({ id: 1, name: 'John', email: 'john@example.com' }) + .onConflictDoUpdate({ target: [pkExampleTable.id, pkExampleTable.name], set: { email: 'john1@example.com' } }) + .run(); + + const res = this.db + .select({ id: pkExampleTable.id, name: pkExampleTable.name, email: pkExampleTable.email }) + .from(pkExampleTable) + .where(eq(pkExampleTable.id, 1)) + .all(); + + expect(res).deep.equal([{ id: 1, name: 'John', email: 'john1@example.com' }]); + } catch (error: any) { + console.error(error); + throw new Error(`insertWithOnConflictDoUpdateUsingCompositePk error`); + } + } + + async insertUndefined(): Promise { + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + expect((() => { + this.db.insert(users).values({ name: undefined }).run(); + })()).not.throw(); + + this.db.run(sql`drop table ${users}`); + } + + async updateUndefined(): Promise { + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run( + sql`create table ${users} (id integer primary key, name text)`, + ); + + expect((() => { + this.db.update(users).set({ name: undefined }).run(); + })()).throw(); + expect((() => { + this.db.update(users).set({ id: 1, name: undefined }).run(); + })()).not.throw(); + + this.db.run(sql`drop table ${users}`); + } + + async apiCRUD(): Promise { + try { + await this.beforeEach(); + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run(sql`create table ${users} (id integer primary key, name text)`); + + this.db.insert(users).values({ id: 1, name: 'John' }).run(); + + const res = this.db.select().from(users).all(); + + expect(res).deep.equal([{ id: 1, name: 'John' }]); + + this.db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).all(); + + const res1 = this.db.select().from(users).all(); + + expect(res1).deep.equal([{ id: 1, name: 'John1' }]); + + this.db.delete(users).where(eq(users.id, 1)).run(); + + const res2 = this.db.select().from(users).all(); + + expect(res2).deep.equal([]); + + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`apiCRUD error`); + } + } + + async apiInsertPlusSelectPreparePlusAsyncExecute(): Promise { + try { + await this.beforeEach(); + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run(sql`create table ${users} (id integer primary key, name text)`); + + const insertStmt = this.db.insert(users).values({ id: 1, name: 'John' }).prepare(); + insertStmt.execute().sync(); + + const selectStmt = this.db.select().from(users).prepare(); + const res = selectStmt.execute().sync(); + + expect(res).deep.equal([{ id: 1, name: 'John' }]); + + const updateStmt = this.db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + updateStmt.execute().sync(); + + const res1 = selectStmt.execute().sync(); + + expect(res1).deep.equal([{ id: 1, name: 'John1' }]); + + const deleteStmt = this.db.delete(users).where(eq(users.id, 1)).prepare(); + deleteStmt.execute().sync(); + + const res2 = selectStmt.execute().sync(); + + expect(res2).deep.equal([]); + + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`apiInsertPlusSelectPreparePlusAsyncExecute error`); + } + } + + async apiInsertSelectPreparePlusSyncExecute(): Promise { + try { + await this.beforeEach(); + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run(sql`create table ${users} (id integer primary key, name text)`); + + const insertStmt = this.db.insert(users).values({ id: 1, name: 'John' }).prepare(); + insertStmt.execute().sync(); + + const selectStmt = this.db.select().from(users).prepare(); + const res = selectStmt.execute().sync(); + + expect(res).deep.equal([{ id: 1, name: 'John' }]); + + const updateStmt = this.db.update(users).set({ name: 'John1' }).where(eq(users.id, 1)).prepare(); + updateStmt.execute().sync(); + + const res1 = selectStmt.execute().sync(); + + expect(res1).deep.equal([{ id: 1, name: 'John1' }]); + + const deleteStmt = this.db.delete(users).where(eq(users.id, 1)).prepare(); + deleteStmt.execute().sync(); + + const res2 = selectStmt.execute().sync(); + + expect(res2).deep.equal([]); + + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`apiInsertSelectPreparePlusSyncExecute error`); + } + } + + async selectPlusGetForEmptyResult(): Promise { + try { + await this.beforeEach(); + const users = sqliteTable('users', { + id: integer('id').primaryKey(), + name: text('name'), + }); + + this.db.run(sql`drop table if exists ${users}`); + + this.db.run(sql`create table ${users} (id integer primary key, name text)`); + + const res = this.db.select().from(users).where(eq(users.id, 1)).get(); + + expect(res).eq(undefined); + + this.db.run(sql`drop table ${users}`); + } catch (error: any) { + console.error(error); + throw new Error(`selectPlusGetForEmptyResult error`); + } + } + + async setOperationsUnionFromQueryBuilderWithSubquery(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const sq = this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable) + .union(this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table)) + .orderBy(asc(sql`name`)) + .as('sq'); + + const result = await this.db.select().from(sq).limit(5).offset(5); + + expect(result).length(5); + + expect(result).deep.equal([ + { id: 2, name: 'London' }, + { id: 7, name: 'Mary' }, + { id: 1, name: 'New York' }, + { id: 4, name: 'Peter' }, + { id: 8, name: 'Sally' }, + ]); + + expect(() => { + this.db + .select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable).union( + this.db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table), + ).orderBy(asc(sql`name`)).all(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsUnionFromQueryBuilderWithSubquery error`); + } + } + + async setOperationsUnionAsFunction(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const result = union( + this.db.select({ id: citiesTable.id, name: citiesTable.name }).from(citiesTable).where(eq(citiesTable.id, 1)), + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)), + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)), + ).orderBy(asc(sql`name`)).all(); + + expect(result).length(2); + + expect(result).deep.equal([ + { id: 1, name: 'John' }, + { id: 1, name: 'New York' }, + ]); + + expect(() => { + union( + this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + this.db + .select({ name: users2Table.name, id: users2Table.id }) + .from(users2Table).where(eq(users2Table.id, 1)), + this.db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).orderBy(asc(sql`name`)).run(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsUnionAsFunction error`); + } + } + + async setOperationsUnionAllFromQueryBuilder(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const result = this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable) + .unionAll(this.db.select({ id: citiesTable.id, name: citiesTable.name }).from(citiesTable)) + .orderBy(asc(citiesTable.id)) + .limit(5) + .offset(1).all(); + + expect(result).length(5); + + expect(result).deep.equal([ + { id: 1, name: 'New York' }, + { id: 2, name: 'London' }, + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + { id: 3, name: 'Tampa' }, + ]); + + expect(() => { + this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).unionAll( + this.db + .select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable), + ).orderBy(asc(citiesTable.id)).limit(5).offset(1).run(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsUnionAllFromQueryBuilder error`); + } + } + + async setOperationsUnionAllAsFunction(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const result = unionAll( + this.db.select({ id: citiesTable.id, name: citiesTable.name }).from(citiesTable).where(eq(citiesTable.id, 1)), + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)), + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)), + ).all(); + + expect(result).length(3); + + expect(result).deep.equal([ + { id: 1, name: 'New York' }, + { id: 1, name: 'John' }, + { id: 1, name: 'John' }, + ]); + + expect(() => { + unionAll( + this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + this.db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + this.db + .select({ name: users2Table.name, id: users2Table.id }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).run(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsUnionAllAsFunction error`); + } + } + + async setOperationsIntersectFromQueryBuilder(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const result = this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable) + .intersect( + this.db.select({ id: citiesTable.id, name: citiesTable.name }).from(citiesTable).where(gt(citiesTable.id, 1)), + ) + .orderBy(asc(sql`name`)).all(); + + expect(result).length(2); + + expect(result).deep.equal([ + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + ]); + + expect(() => { + this.db + .select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable).intersect( + this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(gt(citiesTable.id, 1)), + ).orderBy(asc(sql`name`)).run(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsIntersectFromQueryBuilder error`); + } + } + + async setOperationsIntersectAsFunction(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const result = intersect( + this.db.select({ id: citiesTable.id, name: citiesTable.name }).from(citiesTable).where(eq(citiesTable.id, 1)), + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)), + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)), + ).all(); + + expect(result).length(0); + + expect(result).deep.equal([]); + + expect(() => { + intersect( + this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + this.db + .select({ name: users2Table.name, id: users2Table.id }) + .from(users2Table).where(eq(users2Table.id, 1)), + this.db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).run(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsIntersectAsFunction error`); + } + } + + async setOperationsExceptFromQueryBuilder(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const result = this.db + .select() + .from(citiesTable) + .except(this.db.select().from(citiesTable).where(gt(citiesTable.id, 1))).all(); + + expect(result).length(1); + + expect(result).deep.equal([{ id: 1, name: 'New York' }]); + + expect(() => { + this.db + .select() + .from(citiesTable).except( + this.db + .select({ name: users2Table.name, id: users2Table.id }) + .from(citiesTable).where(gt(citiesTable.id, 1)), + ); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsExceptFromQueryBuilder error`); + } + } + + async setOperationsExceptAsFunction(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const result = except( + this.db.select({ id: citiesTable.id, name: citiesTable.name }).from(citiesTable), + this.db.select({ id: citiesTable.id, name: citiesTable.name }).from(citiesTable).where(eq(citiesTable.id, 1)), + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)), + ).orderBy(asc(sql`id`)).all(); + + expect(result).length(2); + + expect(result).deep.equal([ + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + ]); + expect(() => { + except( + this.db + .select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable), + this.db + .select({ id: citiesTable.id, name: citiesTable.name }) + .from(citiesTable).where(eq(citiesTable.id, 1)), + this.db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + ).orderBy(asc(sql`id`)).run(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsExceptAsFunction error`); + } + } + + async setOperationsMixedFromQueryBuilder(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const result = this.db + .select() + .from(citiesTable) + .except(({ unionAll }) => + unionAll( + this.db.select().from(citiesTable).where(gt(citiesTable.id, 1)), + this.db.select().from(citiesTable).where(eq(citiesTable.id, 2)), + ) + ).all(); + + expect(result).length(2); + + expect(result).deep.equal([ + { id: 1, name: 'New York' }, + { id: 2, name: 'London' }, + ]); + + expect(() => { + this.db + .select() + .from(citiesTable).except( + ({ unionAll }) => + unionAll( + this.db + .select() + .from(citiesTable).where(gt(citiesTable.id, 1)), + this.db.select({ name: citiesTable.name, id: citiesTable.id }) + .from(citiesTable).where(eq(citiesTable.id, 2)), + ), + ).run(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsMixedFromQueryBuilder error`); + } + } + + async setOperationsMixedAllAsFunctionWithSubquery(): Promise { + try { + await this.beforeEach(); + await setupSetOperationTest(this.db); + + const sq = union( + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)), + except( + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where( + gte(users2Table.id, 5), + ), + this.db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 7)), + ), + this.db.select().from(citiesTable).where(gt(citiesTable.id, 1)), + ) + .orderBy(asc(sql`id`)) + .as('sq'); + + const result = await this.db.select().from(sq).limit(4).offset(1); + + expect(result).length(4); + + expect(result).deep.equal([ + { id: 2, name: 'London' }, + { id: 3, name: 'Tampa' }, + { id: 5, name: 'Ben' }, + { id: 6, name: 'Jill' }, + ]); + + expect(() => { + union( + this.db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 1)), + except( + this.db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(gte(users2Table.id, 5)), + this.db + .select({ id: users2Table.id, name: users2Table.name }) + .from(users2Table).where(eq(users2Table.id, 7)), + ), + this.db + .select({ name: users2Table.name, id: users2Table.id }) + .from(citiesTable).where(gt(citiesTable.id, 1)), + ).orderBy(asc(sql`id`)).run(); + }).throw(); + } catch (error: any) { + console.error(error); + throw new Error(`setOperationsMixedAllAsFunctionWithSubquery error`); + } + } + + async aggregateFunctionCount(): Promise { + try { + await this.beforeEach(); + const table = aggregateTable; + await setupAggregateFunctionsTest(this.db); + + const result1 = await this.db.select({ value: count() }).from(table); + const result2 = await this.db.select({ value: count(table.a) }).from(table); + const result3 = await this.db.select({ value: countDistinct(table.name) }).from(table); + + expect(result1[0]?.value).eq(7); + expect(result2[0]?.value).eq(5); + expect(result3[0]?.value).eq(6); + } catch (error: any) { + console.error(error); + throw new Error(`aggregateFunctionCount error`); + } + } + + async aggregatFunctionAvg(): Promise { + try { + await this.beforeEach(); + const table = aggregateTable; + await setupAggregateFunctionsTest(this.db); + + const result1 = await this.db.select({ value: avg(table.a) }).from(table); + const result2 = await this.db.select({ value: avg(table.nullOnly) }).from(table); + const result3 = await this.db.select({ value: avgDistinct(table.b) }).from(table); + + expect(result1[0]?.value).eq('24'); + expect(result2[0]?.value).eq(null); + expect(result3[0]?.value).eq('42.5'); + } catch (error: any) { + console.error(error); + throw new Error(`aggregatFunctionAvg error`); + } + } + + async aggregateFunctionSum(): Promise { + try { + await this.beforeEach(); + const table = aggregateTable; + await setupAggregateFunctionsTest(this.db); + + const result1 = await this.db.select({ value: sum(table.b) }).from(table); + const result2 = await this.db.select({ value: sum(table.nullOnly) }).from(table); + const result3 = await this.db.select({ value: sumDistinct(table.b) }).from(table); + + expect(result1[0]?.value).eq('200'); + expect(result2[0]?.value).eq(null); + expect(result3[0]?.value).eq('170'); + } catch (error: any) { + console.error(error); + throw new Error(`aggregateFunctionSum error`); + } + } + + async aggregateFunctionMax(): Promise { + try { + await this.beforeEach(); + const table = aggregateTable; + await setupAggregateFunctionsTest(this.db); + + const result1 = await this.db.select({ value: max(table.b) }).from(table); + const result2 = await this.db.select({ value: max(table.nullOnly) }).from(table); + + expect(result1[0]?.value).eq(90); + expect(result2[0]?.value).eq(null); + } catch (error: any) { + console.error(error); + throw new Error(`aggregateFunctionMax error`); + } + } + + async aggregateFunctionMin(): Promise { + try { + await this.beforeEach(); + const table = aggregateTable; + await setupAggregateFunctionsTest(this.db); + + const result1 = await this.db.select({ value: min(table.b) }).from(table); + const result2 = await this.db.select({ value: min(table.nullOnly) }).from(table); + + expect(result1[0]?.value).eq(10); + expect(result2[0]?.value).eq(null); + } catch (error: any) { + console.error(error); + throw new Error(`aggregateFunctionMin error`); + } + } + + async test$onUpdateFnAnd$onUpdateWorksAs$default(): Promise { + try { + await this.beforeEach(); + this.db.run(sql`drop table if exists ${usersOnUpdate}`); + + this.db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1 not null, + updated_at integer, + always_null text + ) + `, + ); + + this.db + .insert(usersOnUpdate) + .values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jack' }, { name: 'Jill' }]) + .run(); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + const justDates = await this.db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await this.db + .select({ ...rest }) + .from(usersOnUpdate) + .orderBy(asc(usersOnUpdate.id)); + + expect(response).deep.equal([ + { name: 'John', id: 1, updateCounter: 1, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: 1, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 250; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).greaterThan(Date.now() - msDelay); + } + } catch (error: any) { + console.error(error); + throw new Error(`test$onUpdateFnAnd$onUpdateWorksAs$default error`); + } + } + + async test$onUpdateFnAnd$onUpdateWorksUpdating(): Promise { + try { + await this.beforeEach(); + this.db.run(sql`drop table if exists ${usersOnUpdate}`); + + this.db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1, + updated_at integer, + always_null text + ) + `, + ); + + await this.db + .insert(usersOnUpdate) + .values([{ name: 'John', alwaysNull: 'this will be null after updating' }, { name: 'Jane' }, { name: 'Jack' }, { + name: 'Jill', + }]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + await this.db.update(usersOnUpdate).set({ name: 'Angel' }).where(eq(usersOnUpdate.id, 1)); + await this.db.update(usersOnUpdate).set({ updateCounter: null }).where(eq(usersOnUpdate.id, 2)); + + const justDates = await this.db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await this.db + .select({ ...rest }) + .from(usersOnUpdate) + .orderBy(asc(usersOnUpdate.id)); + + expect(response).deep.equal([ + { name: 'Angel', id: 1, updateCounter: 2, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: null, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 250; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).greaterThan(Date.now() - msDelay); + } + } catch (error: any) { + console.error(error); + throw new Error(`test$onUpdateFnAnd$onUpdateWorksUpdating error`); + } + } + + async $countSeparate(): Promise { + try { + await this.beforeEach(); + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${countTestTable}`); + this.db.run(sql`create table ${countTestTable} (id int, name text)`); + + await this.db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await this.db.$count(countTestTable); + + this.db.run(sql`drop table ${countTestTable}`); + + expect(count).eq(4); + } catch (error: any) { + console.error(error); + throw new Error(`$countSeparate error`); + } + } + + async $countEmbedded(): Promise { + try { + await this.beforeEach(); + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${countTestTable}`); + this.db.run(sql`create table ${countTestTable} (id int, name text)`); + + await this.db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await this.db + .select({ + count: this.db.$count(countTestTable), + }) + .from(countTestTable); + + this.db.run(sql`drop table ${countTestTable}`); + + expect(count).deep.equal([{ count: 4 }, { count: 4 }, { count: 4 }, { count: 4 }]); + } catch (error: any) { + console.error(error); + throw new Error(`$countEmbedded error`); + } + } + + async $countSeparateReuse(): Promise { + try { + await this.beforeEach(); + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${countTestTable}`); + this.db.run(sql`create table ${countTestTable} (id int, name text)`); + + await this.db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = this.db.$count(countTestTable); + + const count1 = await count; + + await this.db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await this.db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + this.db.run(sql`drop table ${countTestTable}`); + + expect(count1).eq(4); + expect(count2).eq(5); + expect(count3).eq(6); + } catch (error: any) { + console.error(error); + throw new Error(`$countSeparateReuse error`); + } + } + + async $countEmbeddedReuse(): Promise { + try { + await this.beforeEach(); + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${countTestTable}`); + this.db.run(sql`create table ${countTestTable} (id int, name text)`); + + await this.db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = this.db + .select({ + count: this.db.$count(countTestTable), + }) + .from(countTestTable); + + const count1 = await count; + + await this.db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await this.db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + this.db.run(sql`drop table ${countTestTable}`); + + expect(count1).deep.equal([{ count: 4 }, { count: 4 }, { count: 4 }, { count: 4 }]); + expect(count2).deep.equal([{ count: 5 }, { count: 5 }, { count: 5 }, { count: 5 }, { count: 5 }]); + expect(count3).deep.equal([{ count: 6 }, { count: 6 }, { count: 6 }, { count: 6 }, { count: 6 }, { count: 6 }]); + } catch (error: any) { + console.error(error); + throw new Error(`$countEmbeddedReuse error`); + } + } + + async $countSeparateWithFilters(): Promise { + try { + await this.beforeEach(); + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${countTestTable}`); + this.db.run(sql`create table ${countTestTable} (id int, name text)`); + + await this.db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await this.db.$count(countTestTable, gt(countTestTable.id, 1)); + + this.db.run(sql`drop table ${countTestTable}`); + + expect(count).deep.equal(3); + } catch (error: any) { + console.error(error); + throw new Error(`$countSeparateWithFilters error`); + } + } + + async $countEmbeddedWithFilters(): Promise { + try { + await this.beforeEach(); + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + this.db.run(sql`drop table if exists ${countTestTable}`); + this.db.run(sql`create table ${countTestTable} (id int, name text)`); + + await this.db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await this.db + .select({ + count: this.db.$count(countTestTable, gt(countTestTable.id, 1)), + }) + .from(countTestTable); + + await this.db.run(sql`drop table ${countTestTable}`); + + expect(count).deep.equal([{ count: 3 }, { count: 3 }, { count: 3 }, { count: 3 }]); + } catch (error: any) { + console.error(error); + throw new Error(`$countEmbeddedWithFilters error`); + } + } + + async updateWithLimitAndOrderBy(): Promise { + try { + await this.beforeEach(); + await this.db.insert(usersTable).values([ + { name: 'Barry', verified: false }, + { name: 'Alan', verified: false }, + { name: 'Carl', verified: false }, + ]); + + await this.db.update(usersTable).set({ verified: true }).limit(2).orderBy(asc(usersTable.name)); + + const result = await this.db + .select({ name: usersTable.name, verified: usersTable.verified }) + .from(usersTable) + .orderBy(asc(usersTable.name)); + + expect(result).deep.equal([ + { name: 'Alan', verified: true }, + { name: 'Barry', verified: true }, + { name: 'Carl', verified: false }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`updateWithLimitAndOrderBy error`); + } + } + + async deleteWithLimitAndOrderBy(): Promise { + try { + await this.beforeEach(); + await this.db.insert(usersTable).values([ + { name: 'Barry', verified: false }, + { name: 'Alan', verified: false }, + { name: 'Carl', verified: false }, + ]); + + await this.db.delete(usersTable).where(eq(usersTable.verified, false)).limit(1).orderBy(asc(usersTable.name)); + + const result = await this.db + .select({ name: usersTable.name, verified: usersTable.verified }) + .from(usersTable) + .orderBy(asc(usersTable.name)); + expect(result).deep.equal([ + { name: 'Barry', verified: false }, + { name: 'Carl', verified: false }, + ]); + } catch (error: any) { + console.error(error); + throw new Error(`deleteWithLimitAndOrderBy error`); + } + } +} + +export default { + /** + * This is the standard fetch handler for a Cloudflare Worker + * + * @param request - The request submitted to the Worker from the client + * @param env - The interface to reference bindings declared in wrangler.toml + * @param ctx - The execution context of the Worker + * @returns The response to be sent back to the client + */ + async fetch(request, env): Promise { + try { + const id: DurableObjectId = env.MY_DURABLE_OBJECT.idFromName('durable-object'); + const stub = env.MY_DURABLE_OBJECT.get(id); + + await stub.migrate1(); + + await stub.insertBigIntValues(); + + await stub.selectAllFields(); + await stub.selectPartial(); + await stub.selectSql(); + await stub.selectTypedSql(); + await stub.selectWithEmptyArrayInInArray(); + await stub.selectWithEmptyArrayInNotInArray(); + await stub.selectDistinct(); + await stub.returingSql(); + await stub.$defaultFunction(); + await stub.deleteReturningSql(); + await stub.queryCheckInsertSingleEmptyRow(); + await stub.queryCheckInsertMultipleEmptyRow(); + await stub.insertAllDefaultsIn1Row(); + await stub.insertAllDefaultsInMultipleRows(); + await stub.updateReturningSql(); + await stub.insertWithAutoIncrement(); + await stub.insertDataWithDefaultValues(); + await stub.insertDataWithOverridenDefaultValues(); + await stub.updateWithReturningFields(); + await stub.updateWithReturningPartial(); + await stub.updateWithReturningAllFields(); + await stub.deleteWithReturningPartial(); + await stub.insertAndSelect(); + + await stub.jsonInsert(); + await stub.insertMany(); + await stub.insertManyWithReturning(); + + await stub.partialJoinWithAlias(); + await stub.fullJoinWithAlias(); + await stub.selectFromAlias(); + await stub.insertWithSpaces(); + await stub.preparedStatement(); + await stub.preparedStatementReuse(); + await stub.insertPlaceholdersOnColumnsWithEncoder(); + await stub.preparedStatementWithPlaceholderInWhere(); + await stub.preparedStatementWithPlaceholderInLimit(); + await stub.preparedStatementWithPlaceholderInOffset(); + await stub.preparedStatementBuiltUsing$dynamic(); + + await stub.selectWithGroupByAsField(); + await stub.selectWithExists(); + await stub.selectWithGroupByAsSql(); + await stub.selectWithGroupByAsSqlPlusColumn(); + await stub.selectWithGroupByAsColumnPlusSql(); + await stub.selectWithGroupByComplexQuery(); + await stub.buildQuery(); + await stub.insertViaDbRunPlusSelectViaDbAll(); + await stub.insertViaDbGet(); + await stub.insertViaDbRunPlusSelectViaDbGet(); + await stub.insertViaDbGetQueryBuilder(); + await stub.joinSubquery(); + await stub.withSelect(); + await stub.withUpdate(); + await stub.withInsert(); + + await stub.withDelete(); + await stub.selectFromSubquerySql(); + await stub.selectAFieldWithoutJoiningItsTable(); + await stub.selectCount(); + await stub.having(); + await stub.insertNullTimestamp(); + await stub.selectFromRawSql(); + await stub.selectFromRawSqlWithJoins(); + await stub.joinOnAliasedSqlFromSelect(); + await stub.joinOnAliasedSqlFromWithClause(); + await stub.prefixedTable(); + await stub.orderByWithAliasedColumn(); + await stub.transaction(); + await stub.nestedTransaction(); + await stub.joinSubqueryWithJoin(); + await stub.joinViewAsSubquery(); + await stub.insertWithOnConflictDoNothing(); + await stub.insertWithOnConflictDoNothinUsingCompositePk(); + await stub.insertWithOnConflictDoNothingUsingTarget(); + await stub.insertWithOnConflictDoNothingUsingCompositePkAsTarget(); + await stub.insertWithOnConflictDoUpdate(); + await stub.insertWithOnConflictDoUpdateWhere(); + await stub.insertWithOnConflictDoUpdateUsingCompositePk(); + await stub.apiCRUD(); + await stub.apiInsertPlusSelectPreparePlusAsyncExecute(); + await stub.apiInsertSelectPreparePlusSyncExecute(); + await stub.selectPlusGetForEmptyResult(); + await stub.setOperationsUnionFromQueryBuilderWithSubquery(); + await stub.setOperationsUnionAsFunction(); + await stub.setOperationsUnionAllFromQueryBuilder(); + await stub.setOperationsUnionAllAsFunction(); + await stub.setOperationsIntersectFromQueryBuilder(); + await stub.setOperationsIntersectAsFunction(); + await stub.setOperationsExceptFromQueryBuilder(); + await stub.setOperationsExceptAsFunction(); + await stub.setOperationsMixedFromQueryBuilder(); + await stub.setOperationsMixedAllAsFunctionWithSubquery(); + await stub.aggregateFunctionCount(); + await stub.aggregatFunctionAvg(); + await stub.aggregateFunctionSum(); + await stub.aggregateFunctionMax(); + await stub.aggregateFunctionMin(); + await stub.test$onUpdateFnAnd$onUpdateWorksAs$default(); + await stub.test$onUpdateFnAnd$onUpdateWorksUpdating(); + await stub.$countSeparate(); + await stub.$countEmbedded(); + await stub.$countEmbeddedReuse(); + await stub.$countSeparateWithFilters(); + await stub.$countEmbeddedWithFilters(); + await stub.updateWithLimitAndOrderBy(); + await stub.deleteWithLimitAndOrderBy(); + await stub.updateUndefined(); + await stub.insertUndefined(); + + return new Response(); + } catch (error: any) { + return new Response(error.message); + } + }, +} satisfies ExportedHandler; diff --git a/integration-tests/tests/sqlite/durable-objects/worker-configuration.d.ts b/integration-tests/tests/sqlite/durable-objects/worker-configuration.d.ts new file mode 100644 index 000000000..cab87a7b0 --- /dev/null +++ b/integration-tests/tests/sqlite/durable-objects/worker-configuration.d.ts @@ -0,0 +1,5 @@ +// Generated by Wrangler by running `wrangler types` + +interface Env { + MY_DURABLE_OBJECT: DurableObjectNamespace; +} diff --git a/integration-tests/tests/sqlite/durable-objects/wrangler.toml b/integration-tests/tests/sqlite/durable-objects/wrangler.toml new file mode 100644 index 000000000..7e3ea736c --- /dev/null +++ b/integration-tests/tests/sqlite/durable-objects/wrangler.toml @@ -0,0 +1,25 @@ +#:schema node_modules/wrangler/config-schema.json +name = "sqlite-durable-objects" +main = "index.ts" +compatibility_date = "2024-11-12" +compatibility_flags = [ "nodejs_compat" ] + +# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model. +# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps. +# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects +[[durable_objects.bindings]] +name = "MY_DURABLE_OBJECT" +class_name = "MyDurableObject" + +# Durable Object migrations. +# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#migrations +[[migrations]] +tag = "v1" +new_sqlite_classes = ["MyDurableObject"] + +[[rules]] +type = "Text" +globs = ["**/*.sql"] +fallthrough = true + + diff --git a/integration-tests/tests/sqlite/libsql-http.test.ts b/integration-tests/tests/sqlite/libsql-http.test.ts new file mode 100644 index 000000000..576d8d48a --- /dev/null +++ b/integration-tests/tests/sqlite/libsql-http.test.ts @@ -0,0 +1,186 @@ +import { type Client, createClient } from '@libsql/client/http'; +import retry from 'async-retry'; +import { asc, eq, getTableColumns, sql } from 'drizzle-orm'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; +import { drizzle } from 'drizzle-orm/libsql/http'; +import { migrate } from 'drizzle-orm/libsql/migrator'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { anotherUsersMigratorTable, tests, usersMigratorTable, usersOnUpdate } from './sqlite-common'; + +const ENABLE_LOGGING = false; + +let db: LibSQLDatabase; +let client: Client; + +beforeAll(async () => { + const url = process.env['LIBSQL_REMOTE_URL']; + const authToken = process.env['LIBSQL_REMOTE_TOKEN']; + if (!url) { + throw new Error('LIBSQL_REMOTE_URL is not set'); + } + client = await retry(async () => { + client = createClient({ url, authToken }); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.close(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + client?.close(); +}); + +beforeEach((ctx) => { + ctx.sqlite = { + db, + }; +}); + +test('migrator', async () => { + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = await db.select().from(usersMigratorTable).all(); + + await db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = await db.select().from(anotherUsersMigratorTable).all(); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table __drizzle_migrations`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists ${sql.identifier(customTable)}`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite', migrationsTable: customTable }); + + // test if the custom migrations table was created + const res = await db.all(sql`select * from ${sql.identifier(customTable)};`); + expect(res.length > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table ${sql.identifier(customTable)}`); +}); + +test('test $onUpdateFn and $onUpdate works as $default', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists ${usersOnUpdate}`); + + await db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1 not null, + updated_at integer, + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { name: 'John' }, + { name: 'Jane' }, + { name: 'Jack' }, + { name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'John', id: 1, updateCounter: 1, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: 1, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 1750; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } +}); + +test('test $onUpdateFn and $onUpdate works updating', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists ${usersOnUpdate}`); + + await db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1, + updated_at integer, + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { name: 'John', alwaysNull: 'this will be null after updating' }, + { name: 'Jane' }, + { name: 'Jack' }, + { name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + await db.update(usersOnUpdate).set({ name: 'Angel' }).where(eq(usersOnUpdate.id, 1)); + await db.update(usersOnUpdate).set({ updateCounter: null }).where(eq(usersOnUpdate.id, 2)); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'Angel', id: 1, updateCounter: 2, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: null, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 1750; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } +}); + +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', + 'test $onUpdateFn and $onUpdate works as $default', + 'test $onUpdateFn and $onUpdate works updating', +]); + +tests(); diff --git a/integration-tests/tests/sqlite/libsql-node.test.ts b/integration-tests/tests/sqlite/libsql-node.test.ts new file mode 100644 index 000000000..bfe2c1574 --- /dev/null +++ b/integration-tests/tests/sqlite/libsql-node.test.ts @@ -0,0 +1,97 @@ +import { type Client, createClient } from '@libsql/client/node'; +import retry from 'async-retry'; +import { sql } from 'drizzle-orm'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; +import { migrate } from 'drizzle-orm/libsql/migrator'; +import { drizzle } from 'drizzle-orm/libsql/node'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { anotherUsersMigratorTable, tests, usersMigratorTable } from './sqlite-common'; + +const ENABLE_LOGGING = false; + +let db: LibSQLDatabase; +let client: Client; + +beforeAll(async () => { + const url = process.env['LIBSQL_URL']; + const authToken = process.env['LIBSQL_AUTH_TOKEN']; + if (!url) { + throw new Error('LIBSQL_URL is not set'); + } + client = await retry(async () => { + client = createClient({ url, authToken }); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.close(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + client?.close(); +}); + +beforeEach((ctx) => { + ctx.sqlite = { + db, + }; +}); + +test('migrator', async () => { + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = await db.select().from(usersMigratorTable).all(); + + await db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = await db.select().from(anotherUsersMigratorTable).all(); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table __drizzle_migrations`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists ${sql.identifier(customTable)}`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite', migrationsTable: customTable }); + + // test if the custom migrations table was created + const res = await db.all(sql`select * from ${sql.identifier(customTable)};`); + expect(res.length > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table ${sql.identifier(customTable)}`); +}); + +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', +]); + +tests(); diff --git a/integration-tests/tests/sqlite/libsql-sqlite3.test.ts b/integration-tests/tests/sqlite/libsql-sqlite3.test.ts new file mode 100644 index 000000000..8d8419f17 --- /dev/null +++ b/integration-tests/tests/sqlite/libsql-sqlite3.test.ts @@ -0,0 +1,97 @@ +import { type Client, createClient } from '@libsql/client/sqlite3'; +import retry from 'async-retry'; +import { sql } from 'drizzle-orm'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; +import { migrate } from 'drizzle-orm/libsql/migrator'; +import { drizzle } from 'drizzle-orm/libsql/sqlite3'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { anotherUsersMigratorTable, tests, usersMigratorTable } from './sqlite-common'; + +const ENABLE_LOGGING = false; + +let db: LibSQLDatabase; +let client: Client; + +beforeAll(async () => { + const url = ':memory:'; + client = await retry(async () => { + client = createClient({ url }); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.close(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + client?.close(); +}); + +beforeEach((ctx) => { + ctx.sqlite = { + db, + }; +}); + +test('migrator', async () => { + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = await db.select().from(usersMigratorTable).all(); + + await db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = await db.select().from(anotherUsersMigratorTable).all(); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table __drizzle_migrations`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists ${sql.identifier(customTable)}`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite', migrationsTable: customTable }); + + // test if the custom migrations table was created + const res = await db.all(sql`select * from ${sql.identifier(customTable)};`); + expect(res.length > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table ${sql.identifier(customTable)}`); +}); + +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', + 'transaction', + 'transaction rollback', + 'nested transaction', + 'nested transaction rollback', +]); + +tests(); diff --git a/integration-tests/tests/sqlite/libsql-ws.test.ts b/integration-tests/tests/sqlite/libsql-ws.test.ts new file mode 100644 index 000000000..86810a36f --- /dev/null +++ b/integration-tests/tests/sqlite/libsql-ws.test.ts @@ -0,0 +1,188 @@ +import { type Client, createClient } from '@libsql/client/ws'; +import retry from 'async-retry'; +import { asc, eq, getTableColumns, sql } from 'drizzle-orm'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; +import { migrate } from 'drizzle-orm/libsql/migrator'; +import { drizzle } from 'drizzle-orm/libsql/ws'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { anotherUsersMigratorTable, tests, usersMigratorTable, usersOnUpdate } from './sqlite-common'; + +const ENABLE_LOGGING = false; + +let db: LibSQLDatabase; +let client: Client; + +beforeAll(async () => { + const url = process.env['LIBSQL_REMOTE_URL']; + const authToken = process.env['LIBSQL_REMOTE_TOKEN']; + if (!url) { + throw new Error('LIBSQL_REMOTE_URL is not set'); + } + client = await retry(async () => { + client = createClient({ url, authToken }); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.close(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + client?.close(); +}); + +beforeEach((ctx) => { + ctx.sqlite = { + db, + }; +}); + +test('migrator', async () => { + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = await db.select().from(usersMigratorTable).all(); + + await db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = await db.select().from(anotherUsersMigratorTable).all(); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table __drizzle_migrations`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists ${sql.identifier(customTable)}`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite', migrationsTable: customTable }); + + // test if the custom migrations table was created + const res = await db.all(sql`select * from ${sql.identifier(customTable)};`); + expect(res.length > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table ${sql.identifier(customTable)}`); +}); + +test('test $onUpdateFn and $onUpdate works as $default', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists ${usersOnUpdate}`); + + await db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1 not null, + updated_at integer, + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { name: 'John' }, + { name: 'Jane' }, + { name: 'Jack' }, + { name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'John', id: 1, updateCounter: 1, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: 1, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 1250; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } +}); + +test('test $onUpdateFn and $onUpdate works updating', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists ${usersOnUpdate}`); + + await db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1, + updated_at integer, + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { name: 'John', alwaysNull: 'this will be null after updating' }, + { name: 'Jane' }, + { name: 'Jack' }, + { name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + await db.update(usersOnUpdate).set({ name: 'Angel' }).where(eq(usersOnUpdate.id, 1)); + await db.update(usersOnUpdate).set({ updateCounter: null }).where(eq(usersOnUpdate.id, 2)); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'Angel', id: 1, updateCounter: 2, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: null, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 1250; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } +}); + +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', + 'join view as subquery', + 'test $onUpdateFn and $onUpdate works as $default', + 'test $onUpdateFn and $onUpdate works updating', + 'prepared statement reuse', +]); + +tests(); diff --git a/integration-tests/tests/sqlite/libsql.test.ts b/integration-tests/tests/sqlite/libsql.test.ts index 71d3b289e..b99d7e9bf 100644 --- a/integration-tests/tests/sqlite/libsql.test.ts +++ b/integration-tests/tests/sqlite/libsql.test.ts @@ -4,6 +4,7 @@ import { sql } from 'drizzle-orm'; import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql'; import { migrate } from 'drizzle-orm/libsql/migrator'; import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; import { randomString } from '~/utils'; import { anotherUsersMigratorTable, tests, usersMigratorTable } from './sqlite-common'; @@ -87,4 +88,9 @@ test('migrator : migrate with custom table', async () => { await db.run(sql`drop table ${sql.identifier(customTable)}`); }); +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', +]); + tests(); diff --git a/integration-tests/tests/sqlite/sql-js.test.ts b/integration-tests/tests/sqlite/sql-js.test.ts index ec3d7b583..4c733835f 100644 --- a/integration-tests/tests/sqlite/sql-js.test.ts +++ b/integration-tests/tests/sqlite/sql-js.test.ts @@ -58,5 +58,7 @@ skipTests([ */ 'transaction rollback', 'nested transaction rollback', + 'delete with limit and order by', + 'update with limit and order by', ]); tests(); diff --git a/integration-tests/tests/sqlite/sqlite-common.ts b/integration-tests/tests/sqlite/sqlite-common.ts index 49c609941..c6d67cee3 100644 --- a/integration-tests/tests/sqlite/sqlite-common.ts +++ b/integration-tests/tests/sqlite/sqlite-common.ts @@ -29,6 +29,7 @@ import { foreignKey, getTableConfig, getViewConfig, + index, int, integer, intersect, @@ -63,7 +64,7 @@ export const usersTable = sqliteTable('users', { createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`strftime('%s', 'now')`), }); -const usersOnUpdate = sqliteTable('users_on_update', { +export const usersOnUpdate = sqliteTable('users_on_update', { id: integer('id').primaryKey({ autoIncrement: true }), name: text('name').notNull(), updateCounter: integer('update_counter').default(sql`1`).$onUpdateFn(() => sql`update_counter + 1`), @@ -153,6 +154,9 @@ export function tests() { await db.run(sql`drop table if exists ${orders}`); await db.run(sql`drop table if exists ${bigIntExample}`); await db.run(sql`drop table if exists ${pkExampleTable}`); + await db.run(sql`drop table if exists user_notifications_insert_into`); + await db.run(sql`drop table if exists users_insert_into`); + await db.run(sql`drop table if exists notifications_insert_into`); await db.run(sql` create table ${usersTable} ( @@ -844,10 +848,7 @@ export function tests() { test('prepared statement reuse', async (ctx) => { const { db } = ctx.sqlite; - const stmt = db.insert(usersTable).values({ - verified: true, - name: sql.placeholder('name'), - }).prepare(); + const stmt = db.insert(usersTable).values({ name: sql.placeholder('name') }).prepare(); for (let i = 0; i < 10; i++) { await stmt.run({ name: `John ${i}` }); @@ -856,20 +857,41 @@ export function tests() { const result = await db.select({ id: usersTable.id, name: usersTable.name, + }).from(usersTable).all(); + + expect(result).toEqual([ + { id: 1, name: 'John 0' }, + { id: 2, name: 'John 1' }, + { id: 3, name: 'John 2' }, + { id: 4, name: 'John 3' }, + { id: 5, name: 'John 4' }, + { id: 6, name: 'John 5' }, + { id: 7, name: 'John 6' }, + { id: 8, name: 'John 7' }, + { id: 9, name: 'John 8' }, + { id: 10, name: 'John 9' }, + ]); + }); + + test('insert: placeholders on columns with encoder', async (ctx) => { + const { db } = ctx.sqlite; + + const stmt = db.insert(usersTable).values({ + name: 'John', + verified: sql.placeholder('verified'), + }).prepare(); + + await stmt.run({ verified: true }); + await stmt.run({ verified: false }); + + const result = await db.select({ + id: usersTable.id, verified: usersTable.verified, }).from(usersTable).all(); expect(result).toEqual([ - { id: 1, name: 'John 0', verified: true }, - { id: 2, name: 'John 1', verified: true }, - { id: 3, name: 'John 2', verified: true }, - { id: 4, name: 'John 3', verified: true }, - { id: 5, name: 'John 4', verified: true }, - { id: 6, name: 'John 5', verified: true }, - { id: 7, name: 'John 6', verified: true }, - { id: 8, name: 'John 7', verified: true }, - { id: 9, name: 'John 8', verified: true }, - { id: 10, name: 'John 9', verified: true }, + { id: 1, verified: true }, + { id: 2, verified: false }, ]); }); @@ -888,6 +910,68 @@ export function tests() { expect(result).toEqual([{ id: 1, name: 'John' }]); }); + test('prepared statement with placeholder in .limit', async (ctx) => { + const { db } = ctx.sqlite; + + await db.insert(usersTable).values({ name: 'John' }).run(); + const stmt = db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .where(eq(usersTable.id, sql.placeholder('id'))) + .limit(sql.placeholder('limit')) + .prepare(); + + const result = await stmt.all({ id: 1, limit: 1 }); + + expect(result).toEqual([{ id: 1, name: 'John' }]); + expect(result).toHaveLength(1); + }); + + test('prepared statement with placeholder in .offset', async (ctx) => { + const { db } = ctx.sqlite; + + await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]).run(); + const stmt = db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .prepare(); + + const result = await stmt.all({ limit: 1, offset: 1 }); + + expect(result).toEqual([{ id: 2, name: 'John1' }]); + }); + + test('prepared statement built using $dynamic', async (ctx) => { + const { db } = ctx.sqlite; + + function withLimitOffset(qb: any) { + return qb.limit(sql.placeholder('limit')).offset(sql.placeholder('offset')); + } + + await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]).run(); + const stmt = db + .select({ + id: usersTable.id, + name: usersTable.name, + }) + .from(usersTable) + .$dynamic(); + withLimitOffset(stmt).prepare('stmt_limit'); + + const result = await stmt.all({ limit: 1, offset: 1 }); + + expect(result).toEqual([{ id: 2, name: 'John1' }]); + expect(result).toHaveLength(1); + }); + test('select with group by as field', async (ctx) => { const { db } = ctx.sqlite; @@ -1370,7 +1454,7 @@ export function tests() { cityId: integer('city_id').notNull(), }).existing(); - await db.run(sql`create view new_yorkers as ${getViewConfig(newYorkers1).query}`); + await db.run(sql`create view if not exists new_yorkers as ${getViewConfig(newYorkers1).query}`); await db.insert(citiesTable).values([{ name: 'New York' }, { name: 'Paris' }]).run(); @@ -1764,7 +1848,7 @@ export function tests() { await db.run( sql`create table ${users} (id integer not null primary key, name text not null, city_id integer not null)`, ); - await db.run(sql`create view ${newYorkers} as ${getViewConfig(newYorkers).query}`); + await db.run(sql`create view if not exists ${newYorkers} as ${getViewConfig(newYorkers).query}`); db.insert(users).values([ { name: 'John', cityId: 1 }, @@ -2509,6 +2593,34 @@ export function tests() { }).rejects.toThrowError(); }); + test('define constraints as array', async (_ctx) => { + const table = sqliteTable('name', { + id: int(), + }, (t) => [ + index('name').on(t.id), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + + test('define constraints as array inside third param', async (_ctx) => { + const table = sqliteTable('name', { + id: int(), + }, (t) => [ + index('name').on(t.id), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + test('aggregate function: count', async (ctx) => { const { db } = ctx.sqlite; const table = aggregateTable; @@ -2661,6 +2773,256 @@ export function tests() { expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); } }); + + test('$count separate', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable); + + await db.run(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(4); + }); + + test('$count embedded', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + await db.run(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + }); + + test('$count separate reuse', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.$count(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.run(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual(4); + expect(count2).toStrictEqual(5); + expect(count3).toStrictEqual(6); + }); + + test('$count embedded reuse', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.run(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + expect(count2).toStrictEqual([ + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + ]); + expect(count3).toStrictEqual([ + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + ]); + }); + + test('$count separate with filters', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable, gt(countTestTable.id, 1)); + + await db.run(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(3); + }); + + test('$count embedded with filters', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable, gt(countTestTable.id, 1)), + }).from(countTestTable); + + await db.run(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 3 }, + { count: 3 }, + { count: 3 }, + { count: 3 }, + ]); + }); + + test('update with limit and order by', async (ctx) => { + const { db } = ctx.sqlite; + + await db.insert(usersTable).values([ + { name: 'Barry', verified: false }, + { name: 'Alan', verified: false }, + { name: 'Carl', verified: false }, + ]); + + await db.update(usersTable).set({ verified: true }).limit(2).orderBy(asc(usersTable.name)); + + const result = await db.select({ name: usersTable.name, verified: usersTable.verified }).from(usersTable).orderBy( + asc(usersTable.name), + ); + + expect(result).toStrictEqual([ + { name: 'Alan', verified: true }, + { name: 'Barry', verified: true }, + { name: 'Carl', verified: false }, + ]); + }); + + test('delete with limit and order by', async (ctx) => { + const { db } = ctx.sqlite; + + await db.insert(usersTable).values([ + { name: 'Barry', verified: false }, + { name: 'Alan', verified: false }, + { name: 'Carl', verified: false }, + ]); + + await db.delete(usersTable).where(eq(usersTable.verified, false)).limit(1).orderBy(asc(usersTable.name)); + + const result = await db.select({ name: usersTable.name, verified: usersTable.verified }).from(usersTable).orderBy( + asc(usersTable.name), + ); + expect(result).toStrictEqual([ + { name: 'Barry', verified: false }, + { name: 'Carl', verified: false }, + ]); + }); }); test('table configs: unique third param', () => { @@ -2734,4 +3096,341 @@ export function tests() { expect(users.length).toBeGreaterThan(0); }); + + test('update ... from', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists \`cities\``); + await db.run(sql`drop table if exists \`users2\``); + await db.run(sql` + create table \`cities\` ( + \`id\` integer primary key autoincrement, + \`name\` text not null + ) + `); + await db.run(sql` + create table \`users2\` ( + \`id\` integer primary key autoincrement, + \`name\` text not null, + \`city_id\` integer references \`cities\`(\`id\`) + ) + `); + + await db.insert(citiesTable).values([ + { name: 'New York City' }, + { name: 'Seattle' }, + ]); + await db.insert(users2Table).values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 2 }, + ]); + + const result = await db + .update(users2Table) + .set({ + cityId: citiesTable.id, + }) + .from(citiesTable) + .where(and(eq(citiesTable.name, 'Seattle'), eq(users2Table.name, 'John'))) + .returning(); + + expect(result).toStrictEqual([{ + id: 1, + name: 'John', + cityId: 2, + }]); + }); + + test('update ... from with alias', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists \`users2\``); + await db.run(sql`drop table if exists \`cities\``); + await db.run(sql` + create table \`cities\` ( + \`id\` integer primary key autoincrement, + \`name\` text not null + ) + `); + await db.run(sql` + create table \`users2\` ( + \`id\` integer primary key autoincrement, + \`name\` text not null, + \`city_id\` integer references \`cities\`(\`id\`) + ) + `); + + await db.insert(citiesTable).values([ + { name: 'New York City' }, + { name: 'Seattle' }, + ]); + await db.insert(users2Table).values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 2 }, + ]); + + const cities = alias(citiesTable, 'c'); + const result = await db + .update(users2Table) + .set({ + cityId: cities.id, + }) + .from(cities) + .where(and(eq(cities.name, 'Seattle'), eq(users2Table.name, 'John'))) + .returning(); + + expect(result).toStrictEqual([{ + id: 1, + name: 'John', + cityId: 2, + }]); + + await db.run(sql`drop table if exists \`users2\``); + }); + + test('update ... from with join', async (ctx) => { + const { db } = ctx.sqlite; + + const states = sqliteTable('states', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }); + const cities = sqliteTable('cities', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + stateId: integer('state_id').references(() => states.id), + }); + const users = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + cityId: integer('city_id').notNull().references(() => cities.id), + }); + + await db.run(sql`drop table if exists \`states\``); + await db.run(sql`drop table if exists \`cities\``); + await db.run(sql`drop table if exists \`users\``); + await db.run(sql` + create table \`states\` ( + \`id\` integer primary key autoincrement, + \`name\` text not null + ) + `); + await db.run(sql` + create table \`cities\` ( + \`id\` integer primary key autoincrement, + \`name\` text not null, + \`state_id\` integer references \`states\`(\`id\`) + ) + `); + await db.run(sql` + create table \`users\` ( + \`id\` integer primary key autoincrement, + \`name\` text not null, + \`city_id\` integer not null references \`cities\`(\`id\`) + ) + `); + + await db.insert(states).values([ + { name: 'New York' }, + { name: 'Washington' }, + ]); + await db.insert(cities).values([ + { name: 'New York City', stateId: 1 }, + { name: 'Seattle', stateId: 2 }, + { name: 'London' }, + ]); + await db.insert(users).values([ + { name: 'John', cityId: 1 }, + { name: 'Jane', cityId: 2 }, + { name: 'Jack', cityId: 3 }, + ]); + + const result1 = await db + .update(users) + .set({ + cityId: cities.id, + }) + .from(cities) + .leftJoin(states, eq(cities.stateId, states.id)) + .where(and(eq(cities.name, 'Seattle'), eq(users.name, 'John'))) + .returning(); + const result2 = await db + .update(users) + .set({ + cityId: cities.id, + }) + .from(cities) + .leftJoin(states, eq(cities.stateId, states.id)) + .where(and(eq(cities.name, 'London'), eq(users.name, 'Jack'))) + .returning(); + + expect(result1).toStrictEqual([{ + id: 1, + name: 'John', + cityId: 2, + }]); + expect(result2).toStrictEqual([{ + id: 3, + name: 'Jack', + cityId: 3, + }]); + }); + + test('insert into ... select', async (ctx) => { + const { db } = ctx.sqlite; + + const notifications = sqliteTable('notifications_insert_into', { + id: integer('id').primaryKey({ autoIncrement: true }), + sentAt: integer('sent_at', { mode: 'timestamp' }).notNull().default(sql`current_timestamp`), + message: text('message').notNull(), + }); + const users = sqliteTable('users_insert_into', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }); + const userNotications = sqliteTable('user_notifications_insert_into', { + userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + notificationId: integer('notification_id').notNull().references(() => notifications.id, { onDelete: 'cascade' }), + }, (t) => ({ + pk: primaryKey({ columns: [t.userId, t.notificationId] }), + })); + + await db.run(sql`drop table if exists notifications_insert_into`); + await db.run(sql`drop table if exists users_insert_into`); + await db.run(sql`drop table if exists user_notifications_insert_into`); + await db.run(sql` + create table notifications_insert_into ( + id integer primary key autoincrement, + sent_at integer not null default (current_timestamp), + message text not null + ) + `); + await db.run(sql` + create table users_insert_into ( + id integer primary key autoincrement, + name text not null + ) + `); + await db.run(sql` + create table user_notifications_insert_into ( + user_id integer references users_insert_into(id) on delete cascade, + notification_id integer references notifications_insert_into(id) on delete cascade, + primary key (user_id, notification_id) + ) + `); + + const newNotification = await db + .insert(notifications) + .values({ message: 'You are one of the 3 lucky winners!' }) + .returning({ id: notifications.id }) + .then((result) => result[0]); + await db.insert(users).values([ + { name: 'Alice' }, + { name: 'Bob' }, + { name: 'Charlie' }, + { name: 'David' }, + { name: 'Eve' }, + ]); + + const sentNotifications = await db + .insert(userNotications) + .select( + db + .select({ + userId: users.id, + notificationId: sql`${newNotification!.id}`.as('notification_id'), + }) + .from(users) + .where(inArray(users.name, ['Alice', 'Charlie', 'Eve'])) + .orderBy(asc(users.id)), + ) + .returning(); + + expect(sentNotifications).toStrictEqual([ + { userId: 1, notificationId: newNotification!.id }, + { userId: 3, notificationId: newNotification!.id }, + { userId: 5, notificationId: newNotification!.id }, + ]); + }); + + test('insert into ... select with keys in different order', async (ctx) => { + const { db } = ctx.sqlite; + + const users1 = sqliteTable('users1', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }); + const users2 = sqliteTable('users2', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists users1`); + await db.run(sql`drop table if exists users2`); + await db.run(sql` + create table users1 ( + id integer primary key autoincrement, + name text not null + ) + `); + await db.run(sql` + create table users2 ( + id integer primary key autoincrement, + name text not null + ) + `); + + await expect(async () => { + db + .insert(users1) + .select( + db + .select({ + name: users2.name, + id: users2.id, + }) + .from(users2), + ); + }).rejects.toThrowError(); + }); + + test('Object keys as column names', async (ctx) => { + const { db } = ctx.sqlite; + + // Tests the following: + // Column with optional config without providing a value + // Column with optional config providing a value + // Column without config + const users = sqliteTable('users', { + id: integer().primaryKey({ autoIncrement: true }), + createdAt: integer({ mode: 'timestamp' }), + name: text(), + }); + + await db.run(sql`drop table if exists users`); + await db.run( + sql` + create table users ( + \`id\` integer primary key autoincrement, + \`createdAt\` integer, + \`name\` text + ) + `, + ); + + await db.insert(users).values([ + { createdAt: new Date(Date.now() - 2592000000), name: 'John' }, + { createdAt: new Date(Date.now() - 86400000), name: 'Jane' }, + ]); + const result = await db + .select({ id: users.id, name: users.name }) + .from(users) + .where(gt(users.createdAt, new Date(Date.now() - 2592000000))); + + expect(result).toEqual([ + { id: 2, name: 'Jane' }, + ]); + + await db.run(sql`drop table users`); + }); } diff --git a/integration-tests/tests/sqlite/sqlite-proxy.test.ts b/integration-tests/tests/sqlite/sqlite-proxy.test.ts index 9066b2bec..2aec14be5 100644 --- a/integration-tests/tests/sqlite/sqlite-proxy.test.ts +++ b/integration-tests/tests/sqlite/sqlite-proxy.test.ts @@ -72,7 +72,7 @@ beforeAll(async () => { return { rows: rows.data }; } catch (e: any) { - console.error('Error from sqlite proxy server:', e.response.data); + console.error('Error from sqlite proxy server:', e.response?.data ?? e.message); throw e; } }); diff --git a/integration-tests/tests/utils/is-config.test.ts b/integration-tests/tests/utils/is-config.test.ts new file mode 100644 index 000000000..a71a753c2 --- /dev/null +++ b/integration-tests/tests/utils/is-config.test.ts @@ -0,0 +1,494 @@ +import 'dotenv/config'; +import { PGlite as pglite } from '@electric-sql/pglite'; +import { createClient as libsql } from '@libsql/client'; +import { Client as neonClient, neon, neonConfig, Pool as neonPool } from '@neondatabase/serverless'; +import { connect as planetscale } from '@planetscale/database'; +import { connect as tidb } from '@tidbcloud/serverless'; +import { createClient as vcClient, sql as vcSql } from '@vercel/postgres'; +import betterSqlite3 from 'better-sqlite3'; +import { type DrizzleConfig, isConfig } from 'drizzle-orm'; +import { createConnection as ms2Connection, createPool as ms2Pool } from 'mysql2'; +import { createConnection as ms2pConnection, createPool as ms2pPool } from 'mysql2/promise'; +import pg from 'pg'; +import postgres from 'postgres'; +import { describe, expect } from 'vitest'; +import ws from 'ws'; + +neonConfig.webSocketConstructor = ws; + +if ( + !process.env['PG_CONNECTION_STRING'] || !process.env['MYSQL_CONNECTION_STRING'] + || !process.env['PLANETSCALE_CONNECTION_STRING'] || !process.env['TIDB_CONNECTION_STRING'] + || !process.env['NEON_CONNECTION_STRING'] + // todo get back after we will have a pool for vercel + // || !process.env['VERCEL_CONNECTION_STRING'] +) { + throw new Error('process.env is missing some connection strings!'); +} + +// process.env['POSTGRES_URL'] = process.env['VERCEL_CONNECTION_STRING']; + +describe('Objects', (it) => { + it('Passes configs', () => { + expect(isConfig({} as DrizzleConfig)).toEqual(true); + + expect( + isConfig({ + casing: 'camelCase', + } as DrizzleConfig), + ).toEqual(true); + + expect( + isConfig({ + logger: true, + } as DrizzleConfig), + ).toEqual(true); + + expect( + isConfig({ + logger: { + logQuery: () => {}, + }, + } as DrizzleConfig), + ).toEqual(true); + + expect( + isConfig({ + schema: { + any: true, + }, + } as DrizzleConfig), + ).toEqual(true); + + expect( + isConfig({ + casing: 'camelCase', + logger: true, + schema: { + any: true, + }, + } as DrizzleConfig), + ).toEqual(true); + + expect( + isConfig({ + casing: 'camelCase', + trash: true, + } as DrizzleConfig), + ).toEqual(true); + }); + + it('Rejects non-configs', () => { + expect(isConfig('')).toEqual(false); + + expect(isConfig('data')).toEqual(false); + + expect(isConfig(true)).toEqual(false); + + expect(isConfig(false)).toEqual(false); + + expect(isConfig(null)).toEqual(false); + + expect(isConfig(undefined)).toEqual(false); + + expect(isConfig(5)).toEqual(false); + + expect(isConfig(BigInt(5))).toEqual(false); + + expect(isConfig(new Date())).toEqual(false); + + expect( + isConfig({ + trash: true, + } as DrizzleConfig), + ).toEqual(false); + }); +}); + +describe('Rejects drivers', (it) => { + it('libsql', () => { + const cl = libsql({ + url: ':memory:', + }); + + expect(isConfig(cl)).toEqual(false); + }); + + it('better-sqlite3', () => { + const cl = new betterSqlite3(':memory:'); + + expect(isConfig(cl)).toEqual(false); + }); + + it('pglite', () => { + const cl = new pglite('memory://'); + + expect(isConfig(cl)).toEqual(false); + }); + + it('node-postgres:Pool', () => { + const cl = new pg.Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + + expect(isConfig(cl)).toEqual(false); + }); + + it('node-postgres:Client', async () => { + const cl = new pg.Client({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + + const res = isConfig(cl); + + await cl.end(); + + expect(res).toEqual(false); + }); + + it('node-postgres:PoolClient', async () => { + const cl = new pg.Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + + const con = await cl.connect(); + + const res = isConfig(con); + + con.release(); + + expect(res).toEqual(false); + }); + + it('postgres-js', () => { + const cl = postgres(process.env['PG_CONNECTION_STRING']!); + + expect(isConfig(cl)).toEqual(false); + }); + + it('vercel:sql', () => { + expect(isConfig(vcSql)).toEqual(false); + }); + + // it('vercel:Pool', () => { + // const cl = vcPool({ + // connectionString: process.env['VERCEL_CONNECTION_STRING'], + // }); + + // expect(isConfig(cl)).toEqual(false); + // }); + + it('vercel:Client', async () => { + const cl = vcClient({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + + const res = isConfig(cl); + + expect(res).toEqual(false); + }); + + // it('vercel:PoolClient', async () => { + // const cl = vcPool({ + // connectionString: process.env['VERCEL_CONNECTION_STRING'], + // }); + + // const con = await cl.connect(); + + // const res = isConfig(con); + + // con.release(); + + // expect(res).toEqual(false); + // }); + + it('neon-serverless:Pool', async () => { + const cl = new neonPool({ + connectionString: process.env['NEON_CONNECTION_STRING']!, + }); + + expect(isConfig(cl)).toEqual(false); + }); + + it('neon-serverless:Client', async () => { + const cl = new neonClient({ + connectionString: process.env['NEON_CONNECTION_STRING']!, + }); + + const res = isConfig(cl); + + await cl.end(); + + expect(res).toEqual(false); + }); + + it('neon-serverless:PoolClient', async () => { + const cl = new neonPool({ + connectionString: process.env['NEON_CONNECTION_STRING']!, + }); + + const con = await cl.connect(); + + const res = isConfig(con); + + con.release(); + + expect(res).toEqual(false); + }); + + it('neon-http', async () => { + const cl = neon(process.env['NEON_CONNECTION_STRING']!); + + expect(isConfig(cl)).toEqual(false); + }); + + it('planetscale', async () => { + const cl = planetscale({ + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }); + + expect(isConfig(cl)).toEqual(false); + }); + + it('mysql2:Pool', async () => { + const cl = ms2Pool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + expect(isConfig(cl)).toEqual(false); + }); + + it('mysql2:Connection', async () => { + const cl = ms2Connection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + expect(isConfig(cl)).toEqual(false); + }); + + it('mysql2/promise:Pool', async () => { + const cl = await ms2pPool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const res = isConfig(cl); + + await cl.end(); + + expect(res).toEqual(false); + }); + + it('mysql2/promise:Connection', async () => { + const cl = await ms2pConnection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const res = isConfig(cl); + + await cl.end(); + + expect(res).toEqual(false); + }); + + it('tidb', async () => { + const cl = tidb({ + url: process.env['TIDB_CONNECTION_STRING'], + }); + + expect(isConfig(cl)).toEqual(false); + }); +}); + +describe('Accepts drivers in .client', (it) => { + it('libsql', () => { + const cl = libsql({ + url: ':memory:', + }); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('better-sqlite3', () => { + const cl = new betterSqlite3(':memory:'); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('pglite', () => { + const cl = new pglite('memory://'); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('node-postgres:Pool', () => { + const cl = new pg.Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('node-postgres:Client', async () => { + const cl = new pg.Client({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + + const res = isConfig({ client: cl }); + + await cl.end(); + + expect(res).toEqual(true); + }); + + it('node-postgres:PoolClient', async () => { + const cl = new pg.Pool({ + connectionString: process.env['PG_CONNECTION_STRING'], + }); + + const con = await cl.connect(); + + const res = isConfig({ client: con }); + + con.release(); + + expect(res).toEqual(true); + }); + + it('postgres-js', () => { + const cl = postgres(process.env['PG_CONNECTION_STRING']!); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('vercel:sql', () => { + expect(isConfig({ client: vcSql })).toEqual(true); + }); + + // it('vercel:Pool', () => { + // const cl = vcPool({ + // connectionString: process.env['VERCEL_CONNECTION_STRING'], + // }); + + // expect(isConfig({client:cl})).toEqual(true); + // }); + + it('vercel:Client', async () => { + const cl = vcClient({ + connectionString: process.env['NEON_CONNECTION_STRING'], + }); + + const res = isConfig({ client: cl }); + + expect(res).toEqual(true); + }); + + // it('vercel:PoolClient', async () => { + // const cl = vcPool({ + // connectionString: process.env['VERCEL_CONNECTION_STRING'], + // }); + + // const con = await cl.connect(); + + // const res = isConfig({ client: con }); + + // con.release(); + + // expect(res).toEqual(true); + // }); + + it('neon-serverless:Pool', async () => { + const cl = new neonPool({ + connectionString: process.env['NEON_CONNECTION_STRING']!, + }); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('neon-serverless:Client', async () => { + const cl = new neonClient({ + connectionString: process.env['NEON_CONNECTION_STRING']!, + }); + + const res = isConfig({ client: cl }); + + await cl.end(); + + expect(res).toEqual(true); + }); + + it('neon-serverless:PoolClient', async () => { + const cl = new neonPool({ + connectionString: process.env['NEON_CONNECTION_STRING']!, + }); + + const con = await cl.connect(); + + const res = isConfig({ client: con }); + + con.release(); + + expect(res).toEqual(true); + }); + + it('neon-http', async () => { + const cl = neon(process.env['NEON_CONNECTION_STRING']!); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('planetscale', async () => { + const cl = planetscale({ + url: process.env['PLANETSCALE_CONNECTION_STRING'], + }); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('mysql2:Pool', async () => { + const cl = ms2Pool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('mysql2:Connection', async () => { + const cl = ms2Connection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + expect(isConfig({ client: cl })).toEqual(true); + }); + + it('mysql2/promise:Pool', async () => { + const cl = await ms2pPool({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const res = isConfig({ client: cl }); + + await cl.end(); + + expect(res).toEqual(true); + }); + + it('mysql2/promise:Connection', async () => { + const cl = await ms2pConnection({ + uri: process.env['MYSQL_CONNECTION_STRING'], + }); + + const res = isConfig({ client: cl }); + + await cl.end(); + + expect(res).toEqual(true); + }); + + it('tidb', async () => { + const cl = tidb({ + url: process.env['TIDB_CONNECTION_STRING'], + }); + + expect(isConfig({ client: cl })).toEqual(true); + }); +}); diff --git a/integration-tests/vitest.config.ts b/integration-tests/vitest.config.ts index 5187d2cfc..e8a8be220 100644 --- a/integration-tests/vitest.config.ts +++ b/integration-tests/vitest.config.ts @@ -5,29 +5,44 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { include: [ + 'tests/seeder/**/*.test.ts', 'tests/extensions/postgis/**/*', 'tests/relational/**/*.test.ts', 'tests/pg/**/*.test.ts', 'tests/mysql/**/*.test.ts', + 'tests/singlestore/**/*.test.ts', 'tests/sqlite/**/*.test.ts', 'tests/replicas/**/*', 'tests/imports/**/*', 'tests/extensions/vectors/**/*', 'tests/version.test.ts', + 'tests/pg/node-postgres.test.ts', + 'tests/utils/is-config.test.ts', + 'js-tests/driver-init/commonjs/*.test.cjs', + 'js-tests/driver-init/module/*.test.mjs', ], exclude: [ ...(process.env.SKIP_EXTERNAL_DB_TESTS ? [ 'tests/relational/mysql.planetscale.test.ts', - 'tests/neon-http-batch.test.ts', + 'tests/pg/neon-serverless.test.ts', 'tests/mysql/tidb-serverless.test.ts', 'tests/mysql/mysql-planetscale.test.ts', 'tests/sqlite/libsql.test.ts', - 'tests/mysql/tidb-serverless.test.ts', 'tests/sqlite/libsql-batch.test.ts', - 'tests/pg/neon-http.test.ts', 'tests/pg/neon-http-batch.test.ts', + 'tests/utils/is-config.test.ts', // Uses external DBs in some cases + 'js-tests/driver-init/commonjs/neon-http.test.cjs', + 'js-tests/driver-init/commonjs/neon-ws.test.cjs', + 'js-tests/driver-init/commonjs/planetscale.test.cjs', + 'js-tests/driver-init/commonjs/tidb.test.cjs', + 'js-tests/driver-init/commonjs/vercel.test.cjs', + 'js-tests/driver-init/module/neon-http.test.mjs', + 'js-tests/driver-init/module/neon-ws.test.mjs', + 'js-tests/driver-init/module/planetscale.test.mjs', + 'js-tests/driver-init/module/tidb.test.mjs', + 'js-tests/driver-init/module/vercel.test.mjs', ] : []), 'tests/pg/awsdatapi.test.ts', @@ -37,6 +52,21 @@ export default defineConfig({ // Have a strange "invalid SQL: ERROR: must be owner of schema public" error. Will need to check with xata team 'tests/pg/xata-http.test.ts', 'tests/pg/neon-http-batch.ts', + // todo: remove + 'js-tests/driver-init/module/vercel.test.mjs', + 'js-tests/driver-init/commonjs/vercel.test.cjs', + // move back after decide on speed + 'tests/sqlite/libsql-ws.test.ts', + 'tests/sqlite/libsql-http.test.ts', + 'tests/mysql/tidb-serverless.test.ts', + // waiting for json_array from singlestore team + 'tests/relational/singlestore.test.ts', + // get back when planetscale will open free tier for our CI/CD + 'tests/mysql/mysql-planetscale.test.ts', + 'tests/relational/mysql.planetscale.test.ts', + 'js-tests/driver-init/module/planetscale.test.mjs', + 'js-tests/driver-init/module/planetscale.test.cjs', + 'js-tests/driver-init/commonjs/planetscale.test.cjs', ], typecheck: { tsconfig: 'tsconfig.json', diff --git a/package.json b/package.json index 3327aad18..6b4715b0e 100755 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "name": "drizzle-root", "private": true, "scripts": { "build:orm": "turbo run build --filter drizzle-orm --color", @@ -8,10 +9,11 @@ "test": "turbo run test --color", "t": "pnpm test", "test:types": "turbo run test:types --color", - "lint": "concurrently -n eslint,dprint \"eslint --ext ts .\" \"dprint check --list-different\"" + "lint": "dprint check --list-different", + "lint:fix": "dprint fmt" }, "devDependencies": { - "@arethetypeswrong/cli": "^0.15.3", + "@arethetypeswrong/cli": "0.15.3", "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/experimental-utils": "^5.62.0", @@ -34,12 +36,8 @@ "resolve-tspaths": "^0.8.16", "tsup": "^7.2.0", "tsx": "^4.10.5", - "turbo": "^1.10.14", - "typescript": "5.4.5" + "turbo": "^2.2.3", + "typescript": "5.6.3" }, - "pnpm": { - "patchedDependencies": { - "typescript@5.4.5": "patches/typescript@5.4.5.patch" - } - } + "packageManager": "pnpm@9.7.0" } diff --git a/patches/typescript@5.4.5.patch b/patches/typescript@5.6.3.patch similarity index 100% rename from patches/typescript@5.4.5.patch rename to patches/typescript@5.6.3.patch diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fb00d93d..a267d4379 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,30 +4,25 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -patchedDependencies: - typescript@5.4.5: - hash: q3iy4fwdhi5sis3wty7d4nbsme - path: patches/typescript@5.4.5.patch - importers: .: devDependencies: '@arethetypeswrong/cli': - specifier: ^0.15.3 + specifier: 0.15.3 version: 0.15.3 '@trivago/prettier-plugin-sort-imports': specifier: ^4.2.0 version: 4.2.0(prettier@3.0.3) '@typescript-eslint/eslint-plugin': specifier: ^6.7.3 - version: 6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0)(typescript@5.6.3) '@typescript-eslint/experimental-utils': specifier: ^5.62.0 - version: 5.62.0(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 5.62.0(eslint@8.50.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: ^6.7.3 - version: 6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 6.7.3(eslint@8.50.0)(typescript@5.6.3) bun-types: specifier: ^1.0.3 version: 1.0.3 @@ -45,7 +40,7 @@ importers: version: link:drizzle-orm/dist drizzle-orm-old: specifier: npm:drizzle-orm@^0.27.2 - version: drizzle-orm@0.27.2(@aws-sdk/client-rds-data@3.583.0)(@cloudflare/workers-types@4.20240524.0)(@libsql/client@0.6.0)(@neondatabase/serverless@0.9.3)(@opentelemetry/api@1.8.0)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(@vercel/postgres@0.8.0)(better-sqlite3@10.0.0)(bun-types@1.0.3)(knex@3.1.0(better-sqlite3@10.0.0)(mysql2@3.9.8)(pg@8.11.5)(sqlite3@5.1.7))(kysely@0.27.3)(mysql2@3.9.8)(pg@8.11.5)(postgres@3.4.4)(sql.js@1.10.3)(sqlite3@5.1.7) + version: drizzle-orm@0.27.2(@aws-sdk/client-rds-data@3.583.0)(@cloudflare/workers-types@4.20241112.0)(@libsql/client@0.10.0)(@neondatabase/serverless@0.10.3)(@opentelemetry/api@1.8.0)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(@vercel/postgres@0.8.0)(better-sqlite3@11.5.0)(bun-types@1.0.3)(knex@2.5.1(better-sqlite3@11.5.0)(mysql2@3.11.0)(pg@8.13.1)(sqlite3@5.1.7))(kysely@0.25.0)(mysql2@3.11.0)(pg@8.13.1)(postgres@3.4.4)(sql.js@1.10.3)(sqlite3@5.1.7) eslint: specifier: ^8.50.0 version: 8.50.0 @@ -54,7 +49,7 @@ importers: version: link:eslint/eslint-plugin-drizzle-internal eslint-plugin-import: specifier: ^2.28.1 - version: 2.28.1(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0) + version: 2.28.1(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0) eslint-plugin-no-instanceof: specifier: ^1.0.1 version: 1.0.1 @@ -63,7 +58,7 @@ importers: version: 48.0.1(eslint@8.50.0) eslint-plugin-unused-imports: specifier: ^3.0.0 - version: 3.0.0(@typescript-eslint/eslint-plugin@6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0) + version: 3.0.0(@typescript-eslint/eslint-plugin@6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0) glob: specifier: ^10.3.10 version: 10.3.10 @@ -75,25 +70,25 @@ importers: version: 0.23.9 resolve-tspaths: specifier: ^0.8.16 - version: 0.8.16(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 0.8.16(typescript@5.6.3) tsup: specifier: ^7.2.0 - version: 7.2.0(postcss@8.4.39)(ts-node@10.9.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 7.2.0(postcss@8.4.39)(ts-node@10.9.2(typescript@5.6.3))(typescript@5.6.3) tsx: specifier: ^4.10.5 version: 4.10.5 turbo: - specifier: ^1.10.14 - version: 1.10.14 + specifier: ^2.2.3 + version: 2.3.0 typescript: - specifier: 5.4.5 - version: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + specifier: 5.6.3 + version: 5.6.3 drizzle-kit: dependencies: '@drizzle-team/brocli': - specifier: ^0.8.2 - version: 0.8.2 + specifier: ^0.10.2 + version: 0.10.2 '@esbuild-kit/esm-loader': specifier: ^2.5.5 version: 2.5.5 @@ -114,8 +109,8 @@ importers: specifier: ^4.20230518.0 version: 4.20240524.0 '@electric-sql/pglite': - specifier: ^0.1.5 - version: 0.1.5 + specifier: ^0.2.12 + version: 0.2.12 '@hono/node-server': specifier: ^1.9.0 version: 1.12.0 @@ -123,8 +118,8 @@ importers: specifier: ^0.2.1 version: 0.2.2(hono@4.5.0)(zod@3.23.7) '@libsql/client': - specifier: ^0.4.2 - version: 0.4.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + specifier: ^0.10.0 + version: 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) '@neondatabase/serverless': specifier: ^0.9.1 version: 0.9.3 @@ -146,6 +141,9 @@ importers: '@types/json-diff': specifier: ^1.0.3 version: 1.0.3 + '@types/micromatch': + specifier: ^4.0.9 + version: 4.0.9 '@types/minimatch': specifier: ^5.1.2 version: 5.1.2 @@ -169,10 +167,10 @@ importers: version: 8.5.11 '@typescript-eslint/eslint-plugin': specifier: ^7.2.0 - version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: ^7.2.0 - version: 7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 7.16.1(eslint@8.57.0)(typescript@5.6.3) '@vercel/postgres': specifier: ^0.8.0 version: 0.8.0 @@ -182,6 +180,9 @@ importers: better-sqlite3: specifier: ^9.4.3 version: 9.6.0 + bun-types: + specifier: ^0.6.6 + version: 0.6.14 camelcase: specifier: ^7.0.1 version: 7.0.1 @@ -198,8 +199,8 @@ importers: specifier: ^16.0.3 version: 16.4.5 drizzle-kit: - specifier: 0.21.2 - version: 0.21.2 + specifier: 0.25.0-b1faa33 + version: 0.25.0-b1faa33 drizzle-orm: specifier: workspace:./drizzle-orm/dist version: link:drizzle-orm/dist @@ -233,15 +234,21 @@ importers: json-diff: specifier: 1.0.6 version: 1.0.6 + micromatch: + specifier: ^4.0.8 + version: 4.0.8 minimatch: specifier: ^7.4.3 version: 7.4.6 mysql2: - specifier: 2.3.3 - version: 2.3.3 + specifier: 3.3.3 + version: 3.3.3 node-fetch: specifier: ^3.3.2 version: 3.3.2 + ohm-js: + specifier: ^17.1.0 + version: 17.1.0 pg: specifier: ^8.11.5 version: 8.11.5 @@ -262,19 +269,19 @@ importers: version: 2.2.1 tsup: specifier: ^8.0.2 - version: 8.1.2(postcss@8.4.39)(tsx@3.14.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(yaml@2.4.2) + version: 8.1.2(postcss@8.4.39)(tsx@3.14.0)(typescript@5.6.3)(yaml@2.4.2) tsx: specifier: ^3.12.1 version: 3.14.0 typescript: - specifier: ^5.4.3 - version: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + specifier: ^5.6.3 + version: 5.6.3 uuid: specifier: ^9.0.1 version: 9.0.1 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@18.19.33)(lightningcss@1.25.1)(terser@5.31.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@18.19.33)(lightningcss@1.25.1)(terser@5.31.0)) vitest: specifier: ^1.4.0 version: 1.6.0(@types/node@18.19.33)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) @@ -297,17 +304,23 @@ importers: specifier: ^3.549.0 version: 3.583.0 '@cloudflare/workers-types': - specifier: ^4.20230904.0 - version: 4.20240512.0 + specifier: ^4.20241112.0 + version: 4.20241112.0 '@electric-sql/pglite': - specifier: ^0.1.1 - version: 0.1.5 + specifier: ^0.2.12 + version: 0.2.12 '@libsql/client': - specifier: ^0.5.6 - version: 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + specifier: ^0.10.0 + version: 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) + '@libsql/client-wasm': + specifier: ^0.10.0 + version: 0.10.0 + '@miniflare/d1': + specifier: ^2.14.4 + version: 2.14.4 '@neondatabase/serverless': - specifier: ^0.9.0 - version: 0.9.0 + specifier: ^0.10.0 + version: 0.10.0 '@op-engineering/op-sqlite': specifier: ^2.0.16 version: 2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -346,7 +359,7 @@ importers: version: 0.8.0 '@xata.io/client': specifier: ^0.29.3 - version: 0.29.4(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 0.29.4(typescript@5.6.3) better-sqlite3: specifier: ^8.4.0 version: 8.7.0 @@ -357,8 +370,8 @@ importers: specifier: ^10.1.0 version: 10.1.0 expo-sqlite: - specifier: ^13.2.0 - version: 13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) + specifier: ^14.0.0 + version: 14.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) knex: specifier: ^2.4.2 version: 2.5.1(better-sqlite3@8.7.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7) @@ -394,7 +407,7 @@ importers: version: 3.14.0 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0)) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.12.12)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) @@ -405,17 +418,96 @@ importers: specifier: ^7.2.2 version: 7.2.2 - drizzle-typebox: + drizzle-seed: + dependencies: + pure-rand: + specifier: ^6.1.0 + version: 6.1.0 devDependencies: + '@arethetypeswrong/cli': + specifier: ^0.16.1 + version: 0.16.4 + '@electric-sql/pglite': + specifier: ^0.2.12 + version: 0.2.12 '@rollup/plugin-terser': - specifier: ^0.4.1 - version: 0.4.1(rollup@3.27.2) + specifier: ^0.4.4 + version: 0.4.4(rollup@4.27.3) + '@rollup/plugin-typescript': + specifier: ^11.1.6 + version: 11.1.6(rollup@4.27.3)(tslib@2.8.1)(typescript@5.6.3) + '@types/better-sqlite3': + specifier: ^7.6.11 + version: 7.6.12 + '@types/dockerode': + specifier: ^3.3.31 + version: 3.3.32 + '@types/node': + specifier: ^22.5.4 + version: 22.9.1 + '@types/pg': + specifier: ^8.11.6 + version: 8.11.6 + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + better-sqlite3: + specifier: ^11.1.2 + version: 11.5.0 + cpy: + specifier: ^11.1.0 + version: 11.1.0 + dockerode: + specifier: ^4.0.2 + version: 4.0.2 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + drizzle-kit: + specifier: workspace:./drizzle-kit/dist + version: link:drizzle-kit/dist + drizzle-orm: + specifier: workspace:./drizzle-orm/dist + version: link:drizzle-orm/dist + get-port: + specifier: ^7.1.0 + version: 7.1.0 + mysql2: + specifier: ^3.3.3 + version: 3.3.3 + pg: + specifier: ^8.12.0 + version: 8.13.1 + resolve-tspaths: + specifier: ^0.8.19 + version: 0.8.22(typescript@5.6.3) + rollup: + specifier: ^4.21.2 + version: 4.27.3 + tslib: + specifier: ^2.7.0 + version: 2.8.1 + tsx: + specifier: ^4.19.0 + version: 4.19.2 + uuid: + specifier: ^10.0.0 + version: 10.0.0 + vitest: + specifier: ^2.0.5 + version: 2.1.2(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0) + zx: + specifier: ^8.1.5 + version: 8.2.2 + + drizzle-typebox: + devDependencies: '@rollup/plugin-typescript': specifier: ^11.1.0 - version: 11.1.1(rollup@3.27.2)(tslib@2.6.2)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 11.1.1(rollup@3.27.2)(tslib@2.8.1)(typescript@5.6.3) '@sinclair/typebox': - specifier: ^0.29.6 - version: 0.29.6 + specifier: ^0.34.8 + version: 0.34.10 '@types/node': specifier: ^18.15.10 version: 18.15.10 @@ -433,7 +525,7 @@ importers: version: 3.27.2 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0)) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@18.15.10)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) @@ -443,12 +535,9 @@ importers: drizzle-valibot: devDependencies: - '@rollup/plugin-terser': - specifier: ^0.4.1 - version: 0.4.1(rollup@3.27.2) '@rollup/plugin-typescript': specifier: ^11.1.0 - version: 11.1.1(rollup@3.27.2)(tslib@2.6.2)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 11.1.1(rollup@3.27.2)(tslib@2.8.1)(typescript@5.6.3) '@types/node': specifier: ^18.15.10 version: 18.15.10 @@ -465,11 +554,11 @@ importers: specifier: ^3.20.7 version: 3.27.2 valibot: - specifier: ^0.30.0 - version: 0.30.0 + specifier: 1.0.0-beta.7 + version: 1.0.0-beta.7(typescript@5.6.3) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0)) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@18.15.10)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) @@ -479,12 +568,9 @@ importers: drizzle-zod: devDependencies: - '@rollup/plugin-terser': - specifier: ^0.4.1 - version: 0.4.1(rollup@3.20.7) '@rollup/plugin-typescript': specifier: ^11.1.0 - version: 11.1.0(rollup@3.20.7)(tslib@2.6.2)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 11.1.0(rollup@3.20.7)(tslib@2.8.1)(typescript@5.6.3) '@types/node': specifier: ^18.15.10 version: 18.15.10 @@ -502,7 +588,7 @@ importers: version: 3.20.7 vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0)) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@18.15.10)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) @@ -549,17 +635,17 @@ importers: specifier: ^3.549.0 version: 3.569.0(@aws-sdk/client-sso-oidc@3.583.0) '@electric-sql/pglite': - specifier: ^0.1.1 - version: 0.1.5 + specifier: 0.2.12 + version: 0.2.12 '@libsql/client': - specifier: ^0.5.6 - version: 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + specifier: ^0.10.0 + version: 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) '@miniflare/d1': - specifier: ^2.14.2 - version: 2.14.2 + specifier: ^2.14.4 + version: 2.14.4 '@miniflare/shared': - specifier: ^2.14.2 - version: 2.14.2 + specifier: ^2.14.4 + version: 2.14.4 '@planetscale/database': specifier: ^1.16.0 version: 1.18.0 @@ -577,7 +663,7 @@ importers: version: 0.8.0 '@xata.io/client': specifier: ^0.29.3 - version: 0.29.4(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 0.29.4(typescript@5.6.3) async-retry: specifier: ^1.3.3 version: 1.3.3 @@ -593,6 +679,9 @@ importers: drizzle-prisma-generator: specifier: ^0.1.2 version: 0.1.4 + drizzle-seed: + specifier: workspace:../drizzle-seed/dist + version: link:../drizzle-seed/dist drizzle-typebox: specifier: workspace:../drizzle-typebox/dist version: link:../drizzle-typebox/dist @@ -639,15 +728,21 @@ importers: specifier: ^0.5.6 version: 0.5.6 vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@20.12.12)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) + specifier: ^2.1.2 + version: 2.1.2(@types/node@20.12.12)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) + ws: + specifier: ^8.16.0 + version: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) zod: specifier: ^3.20.2 version: 3.23.7 devDependencies: + '@cloudflare/workers-types': + specifier: ^4.20241004.0 + version: 4.20241004.0 '@neondatabase/serverless': - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.10.0 + version: 0.10.0 '@originjs/vite-plugin-commonjs': specifier: ^1.0.3 version: 1.0.3 @@ -681,9 +776,12 @@ importers: '@types/uuid': specifier: ^9.0.1 version: 9.0.8 + '@types/ws': + specifier: ^8.5.10 + version: 8.5.11 '@vitest/ui': specifier: ^1.6.0 - version: 1.6.0(vitest@1.6.0) + version: 1.6.0(vitest@2.1.2) ava: specifier: ^5.3.0 version: 5.3.0(@ava/typescript@5.0.0) @@ -695,7 +793,7 @@ importers: version: 7.0.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.12.12)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + version: 10.9.2(@types/node@20.12.12)(typescript@5.6.3) tsx: specifier: ^4.14.0 version: 4.16.2 @@ -704,7 +802,7 @@ importers: version: 5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0)) + version: 4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0)) zx: specifier: ^7.2.2 version: 7.2.2 @@ -727,10 +825,19 @@ packages: engines: {node: '>=18'} hasBin: true + '@arethetypeswrong/cli@0.16.4': + resolution: {integrity: sha512-qMmdVlJon5FtA+ahn0c1oAVNxiq4xW5lqFiTZ21XHIeVwAVIQ+uRz4UEivqRMsjVV1grzRgJSKqaOrq1MvlVyQ==} + engines: {node: '>=18'} + hasBin: true + '@arethetypeswrong/core@0.15.1': resolution: {integrity: sha512-FYp6GBAgsNz81BkfItRz8RLZO03w5+BaeiPma1uCfmxTnxbtuMrI/dbzGiOk8VghO108uFI0oJo0OkewdSHw7g==} engines: {node: '>=18'} + '@arethetypeswrong/core@0.16.4': + resolution: {integrity: sha512-RI3HXgSuKTfcBf1hSEg1P9/cOvmI0flsMm6/QL3L3wju4AlHDqd55JFPfXs4pzgEAgy5L9pul4/HPPz99x2GvA==} + engines: {node: '>=18'} + '@ava/typescript@5.0.0': resolution: {integrity: sha512-2twsQz2fUd95QK1MtKuEnjkiN47SKHZfi/vWj040EN6Eo2ZW3SNcAwncJqXXoMTYZTWtBRXYp3Fg8z+JkFI9aQ==} engines: {node: ^18.18 || ^20.8 || ^21 || ^22} @@ -1925,12 +2032,15 @@ packages: cpu: [x64] os: [win32] - '@cloudflare/workers-types@4.20240512.0': - resolution: {integrity: sha512-o2yTEWg+YK/I1t/Me+dA0oarO0aCbjibp6wSeaw52DSE9tDyKJ7S+Qdyw/XsMrKn4t8kF6f/YOba+9O4MJfW9w==} - '@cloudflare/workers-types@4.20240524.0': resolution: {integrity: sha512-GpSr4uE7y39DU9f0+wmrL76xd03wn0jy1ClITaa3ZZltKjirAV8TW1GzHrvvKyVGx6u3lekrFnB1HzVHsCYHDQ==} + '@cloudflare/workers-types@4.20241004.0': + resolution: {integrity: sha512-3LrPvtecs4umknOF1bTPNLHUG/ZjeSE6PYBQ/tbO7lwaVhjZTaTugiaCny2byrZupBlVNuubQVktcAgMfw0C1A==} + + '@cloudflare/workers-types@4.20241112.0': + resolution: {integrity: sha512-Q4p9bAWZrX14bSCKY9to19xl0KMU7nsO5sJ2cTVspHoypsjPUMeQCsjHjmsO2C4Myo8/LPeDvmqFmkyNAPPYZw==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -1974,20 +2084,22 @@ packages: cpu: [x64] os: [win32] - '@drizzle-team/brocli@0.8.2': - resolution: {integrity: sha512-zTrFENsqGvOkBOuHDC1pXCkDXNd2UhP4lI3gYGhQ1R1SPeAAfqzPsV1dcpMy4uNU6kB5VpU5NGhvwxVNETR02A==} + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} '@drizzle-team/studio@0.0.5': resolution: {integrity: sha512-ps5qF0tMxWRVu+V5gvCRrQNqlY92aTnIKdq27gm9LZMSdaKYZt6AVvSK1dlUMzs6Rt0Jm80b+eWct6xShBKhIw==} - '@electric-sql/pglite@0.1.5': - resolution: {integrity: sha512-eymv4ONNvoPZQTvOQIi5dbpR+J5HzEv0qQH9o/y3gvNheJV/P/NFcrbsfJZYTsDKoq7DKrTiFNexsRkJKy8x9Q==} + '@electric-sql/pglite@0.2.12': + resolution: {integrity: sha512-J/X42ujcoFEbOkgRyoNqZB5qcqrnJRWVlwpH3fKYoJkTz49N91uAK/rDSSG/85WRas9nC9mdV4FnMTxnQWE/rw==} '@esbuild-kit/core-utils@3.1.0': resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==} + deprecated: 'Merged into tsx: https://tsx.is' '@esbuild-kit/esm-loader@2.5.5': resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==} + deprecated: 'Merged into tsx: https://tsx.is' '@esbuild-plugins/node-globals-polyfill@0.2.3': resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} @@ -2974,10 +3086,12 @@ packages: '@humanwhocodes/config-array@0.11.11': resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/config-array@0.11.13': resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} @@ -2990,9 +3104,11 @@ packages: '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead '@humanwhocodes/object-schema@2.0.1': resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + deprecated: Use @eslint/object-schema instead '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} @@ -3057,9 +3173,6 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.3': - resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==} - '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} @@ -3069,6 +3182,9 @@ packages: '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.18': resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} @@ -3078,138 +3194,129 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@libsql/client@0.4.3': - resolution: {integrity: sha512-AUYKnSPqAsFBVWBvmtrb4dG3pQlvTKT92eztAest9wQU2iJkabH8WzHLDb3dKFWKql7/kiCqvBQUVpozDwhekQ==} - - '@libsql/client@0.5.6': - resolution: {integrity: sha512-UBjmDoxz75Z2sHdP+ETCROpeLA/77VMesiff8R4UWK1rnaWbh6/YoCLDILMJL3Rh0udQeKxjL8MjXthqohax+g==} - - '@libsql/client@0.6.0': - resolution: {integrity: sha512-qhQzTG/y2IEVbL3+9PULDvlQFWJ/RnjFXECr/Nc3nRngGiiMysDaOV5VUzYk7DulUX98EA4wi+z3FspKrUplUA==} + '@libsql/client-wasm@0.10.0': + resolution: {integrity: sha512-xSlpGdBGEr4mRtjCnDejTqtDpct2ng8cqHUQs+S4xG1yv0h+hLdzOtQJSY9JV9T/2MWWDfdCiEntPs2SdErSJA==} + bundledDependencies: + - '@libsql/libsql-wasm-experimental' - '@libsql/core@0.4.3': - resolution: {integrity: sha512-r28iYBtaLBW9RRgXPFh6cGCsVI/rwRlOzSOpAu/1PVTm6EJ3t233pUf97jETVHU0vjdr1d8VvV6fKAvJkokqCw==} + '@libsql/client@0.10.0': + resolution: {integrity: sha512-2ERn08T4XOVx34yBtUPq0RDjAdd9TJ5qNH/izugr208ml2F94mk92qC64kXyDVQINodWJvp3kAdq6P4zTtCZ7g==} - '@libsql/core@0.5.6': - resolution: {integrity: sha512-3vicUAydq6jPth410n4AsHHm1n2psTwvkSf94nfJlSXutGSZsl0updn2N/mJBgqUHkbuFoWZtlMifF0SwBj1xQ==} + '@libsql/core@0.10.0': + resolution: {integrity: sha512-rqynAXGaiSpTsykOZdBtI1N4z4O+KZ6mt33K/aHeXAY0gSIfK/ctxuWa0Y1Bjo4FMz1idBTCXz4Ps5kITOvZZw==} - '@libsql/core@0.6.0': - resolution: {integrity: sha512-affAB8vSqQwqI9NBDJ5uJCVaHoOAS2pOpbv1kWConh1SBbmJBnHHd4KG73RAJ2sgd2+NbT9WA+XJBqxgp28YSw==} - - '@libsql/darwin-arm64@0.2.0': - resolution: {integrity: sha512-+qyT2W/n5CFH1YZWv2mxW4Fsoo4dX9Z9M/nvbQqZ7H84J8hVegvVAsIGYzcK8xAeMEcpU5yGKB1Y9NoDY4hOSQ==} + '@libsql/darwin-arm64@0.3.19': + resolution: {integrity: sha512-rmOqsLcDI65zzxlUOoEiPJLhqmbFsZF6p4UJQ2kMqB+Kc0Rt5/A1OAdOZ/Wo8fQfJWjR1IbkbpEINFioyKf+nQ==} cpu: [arm64] os: [darwin] - '@libsql/darwin-arm64@0.3.18': - resolution: {integrity: sha512-Zt49dt+cwhPCkuoWgvjbQd4ckNfCJR5xzIAyhgHl3CBZqZaEuaXTOGKLNQT7bnFRPuQcdLt5PBT1cenKu2N6pA==} + '@libsql/darwin-arm64@0.4.1': + resolution: {integrity: sha512-XICT9/OyU8Aa9Iv1xZIHgvM09n/1OQUk3VC+s5uavzdiGHrDMkOWzN47JN7/FiMa/NWrcgoEiDMk3+e7mE53Ig==} cpu: [arm64] os: [darwin] - '@libsql/darwin-x64@0.2.0': - resolution: {integrity: sha512-hwmO2mF1n8oDHKFrUju6Jv+n9iFtTf5JUK+xlnIE3Td0ZwGC/O1R/Z/btZTd9nD+vsvakC8SJT7/Q6YlWIkhEw==} + '@libsql/darwin-x64@0.3.19': + resolution: {integrity: sha512-q9O55B646zU+644SMmOQL3FIfpmEvdWpRpzubwFc2trsa+zoBlSkHuzU9v/C+UNoPHQVRMP7KQctJ455I/h/xw==} cpu: [x64] os: [darwin] - '@libsql/darwin-x64@0.3.18': - resolution: {integrity: sha512-faq6HUGDaNaueeqPei5cypHaD/hhazUyfHo094CXiEeRZq6ZKtNl5PHdlr8jE/Uw8USNpVVQaLdnvSgKcpRPHw==} + '@libsql/darwin-x64@0.4.1': + resolution: {integrity: sha512-pSKxhRrhu4SsTD+IBRZXcs1SkwMdeAG1tv6Z/Ctp/sOEYrgkU8MDKLqkOr9NsmwpK4S0+JdwjkLMyhTkct/5TQ==} cpu: [x64] os: [darwin] - '@libsql/hrana-client@0.5.6': - resolution: {integrity: sha512-mjQoAmejZ1atG+M3YR2ZW+rg6ceBByH/S/h17ZoYZkqbWrvohFhXyz2LFxj++ARMoY9m6w3RJJIRdJdmnEUlFg==} - - '@libsql/hrana-client@0.6.0': - resolution: {integrity: sha512-k+fqzdjqg3IvWfKmVJK5StsbjeTcyNAXFelUbXbGNz3yH1gEVT9mZ6kmhsIXP30ZSyVV0AE1Gi25p82mxC9hwg==} + '@libsql/hrana-client@0.6.2': + resolution: {integrity: sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw==} - '@libsql/isomorphic-fetch@0.1.12': - resolution: {integrity: sha512-MRo4UcmjAGAa3ac56LoD5OE13m2p0lu0VEtZC2NZMcogM/jc5fU9YtMQ3qbPjFJ+u2BBjFZgMPkQaLS1dlMhpg==} - - '@libsql/isomorphic-fetch@0.2.1': - resolution: {integrity: sha512-Sv07QP1Aw8A5OOrmKgRUBKe2fFhF2hpGJhtHe3d1aRnTESZCGkn//0zDycMKTGamVWb3oLYRroOsCV8Ukes9GA==} + '@libsql/isomorphic-fetch@0.2.5': + resolution: {integrity: sha512-8s/B2TClEHms2yb+JGpsVRTPBfy1ih/Pq6h6gvyaNcYnMVJvgQRY7wAa8U2nD0dppbCuDU5evTNMEhrQ17ZKKg==} + engines: {node: '>=18.0.0'} '@libsql/isomorphic-ws@0.1.5': resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} - '@libsql/linux-arm64-gnu@0.2.0': - resolution: {integrity: sha512-1w2lPXIYtnBaK5t/Ej5E8x7lPiE+jP3KATI/W4yei5Z/ONJh7jQW5PJ7sYU95vTME3hWEM1FXN6kvzcpFAte7w==} + '@libsql/linux-arm64-gnu@0.3.19': + resolution: {integrity: sha512-mgeAUU1oqqh57k7I3cQyU6Trpdsdt607eFyEmH5QO7dv303ti+LjUvh1pp21QWV6WX7wZyjeJV1/VzEImB+jRg==} cpu: [arm64] os: [linux] - '@libsql/linux-arm64-gnu@0.3.18': - resolution: {integrity: sha512-5m9xtDAhoyLSV54tho9uQ2ZIDeJWc0vU3Xpe/VK4+6bpURISs23qNhXiCrZnnq3oV0hFlBfcIgQUIATmb6jD2A==} + '@libsql/linux-arm64-gnu@0.4.1': + resolution: {integrity: sha512-9lpvb24tO2qZd9nq5dlq3ESA3hSKYWBIK7lJjfiCM6f7a70AUwBY9QoPJV9q4gILIyVnR1YBGrlm50nnb+dYgw==} cpu: [arm64] os: [linux] - '@libsql/linux-arm64-musl@0.2.0': - resolution: {integrity: sha512-lkblBEJ7xuNiWNjP8DDq0rqoWccszfkUS7Efh5EjJ+GDWdCBVfh08mPofIZg0fZVLWQCY3j+VZCG1qZfATBizg==} + '@libsql/linux-arm64-musl@0.3.19': + resolution: {integrity: sha512-VEZtxghyK6zwGzU9PHohvNxthruSxBEnRrX7BSL5jQ62tN4n2JNepJ6SdzXp70pdzTfwroOj/eMwiPt94gkVRg==} cpu: [arm64] os: [linux] - '@libsql/linux-arm64-musl@0.3.18': - resolution: {integrity: sha512-oYD5+oM2gPEalp+EoR5DVQBRtdGjLsocjsRbQs5O2m4WOBJKER7VUfDYZHsifLGZoBSc11Yo6s9IR9rjGWy20w==} + '@libsql/linux-arm64-musl@0.4.1': + resolution: {integrity: sha512-lyxi+lFxE+NcBRDMQCxCtDg3c4WcKAbc9u63d5+B23Vm+UgphD9XY4seu+tGrBy1MU2tuNVix7r9S7ECpAaVrA==} cpu: [arm64] os: [linux] - '@libsql/linux-x64-gnu@0.2.0': - resolution: {integrity: sha512-+x/d289KeJydwOhhqSxKT+6MSQTCfLltzOpTzPccsvdt5fxg8CBi+gfvEJ4/XW23Sa+9bc7zodFP0i6MOlxX7w==} + '@libsql/linux-x64-gnu@0.3.19': + resolution: {integrity: sha512-2t/J7LD5w2f63wGihEO+0GxfTyYIyLGEvTFEsMO16XI5o7IS9vcSHrxsvAJs4w2Pf907uDjmc7fUfMg6L82BrQ==} cpu: [x64] os: [linux] - '@libsql/linux-x64-gnu@0.3.18': - resolution: {integrity: sha512-QDSSP60nS8KIldGE7H3bpEflQHiL1erwED6huoVJdmDFxsyDJX2CYdWUWW8Za0ZUOvUbnEWAOyMhp6j1dBbZqw==} + '@libsql/linux-x64-gnu@0.4.1': + resolution: {integrity: sha512-psvuQ3UFBEmDFV8ZHG+WkUHIJiWv+elZ+zIPvOVedlIKdxG1O+8WthWUAhFHOGnbiyzc4sAZ4c3de1oCvyHxyQ==} cpu: [x64] os: [linux] - '@libsql/linux-x64-musl@0.2.0': - resolution: {integrity: sha512-5Xn0c5A6vKf9D1ASpgk7mef//FuY7t5Lktj/eiU4n3ryxG+6WTpqstTittJUgepVjcleLPYxIhQAYeYwTYH1IQ==} + '@libsql/linux-x64-musl@0.3.19': + resolution: {integrity: sha512-BLsXyJaL8gZD8+3W2LU08lDEd9MIgGds0yPy5iNPp8tfhXx3pV/Fge2GErN0FC+nzt4DYQtjL+A9GUMglQefXQ==} cpu: [x64] os: [linux] - '@libsql/linux-x64-musl@0.3.18': - resolution: {integrity: sha512-5SXwTlaLCUPzxYyq+P0c7Ko7tcEjpd1X6RZKe1DuRFmJPg6f7j2+LrPEhMSIbqKcrl5ACUUAyoKmGZqNYwz23w==} + '@libsql/linux-x64-musl@0.4.1': + resolution: {integrity: sha512-PDidJ3AhGDqosGg3OAZzGxMFIbnuOALya4BoezJKl667AFv3x7BBQ30H81Mngsq3Fh8RkJkXSdWfL91+Txb1iA==} cpu: [x64] os: [linux] - '@libsql/win32-x64-msvc@0.2.0': - resolution: {integrity: sha512-rpK+trBIpRST15m3cMYg5aPaX7kvCIottxY7jZPINkKAaScvfbn9yulU/iZUM9YtuK96Y1ZmvwyVIK/Y5DzoMQ==} + '@libsql/win32-x64-msvc@0.3.19': + resolution: {integrity: sha512-ay1X9AobE4BpzG0XPw1gplyLZPGHIgJOovvW23gUrukRegiUP62uzhpRbKNogLlUOynyXeq//prHgPXiebUfWg==} cpu: [x64] os: [win32] - '@libsql/win32-x64-msvc@0.3.18': - resolution: {integrity: sha512-9EEIHz+e8tTbx9TMkb8ByZnzxc0pYFirK1nSbqC6cFEST95fiY0NCfQ/zAzJxe90KckbjifX6BbO69eWIi3TAg==} + '@libsql/win32-x64-msvc@0.4.1': + resolution: {integrity: sha512-IdODVqV/PrdOnHA/004uWyorZQuRsB7U7bCRCE3vXgABj3eJLJGc6cv2C6ksEaEoVxJbD8k53H4VVAGrtYwXzQ==} cpu: [x64] os: [win32] - '@miniflare/core@2.14.2': - resolution: {integrity: sha512-n/smm5ZTg7ilGM4fxO7Gxhbe573oc8Za06M3b2fO+lPWqF6NJcEKdCC+sJntVFbn3Cbbd2G1ChISmugPfmlCkQ==} + '@miniflare/core@2.14.4': + resolution: {integrity: sha512-FMmZcC1f54YpF4pDWPtdQPIO8NXfgUxCoR9uyrhxKJdZu7M6n8QKopPVNuaxR40jcsdxb7yKoQoFWnHfzJD9GQ==} engines: {node: '>=16.13'} - '@miniflare/d1@2.14.2': - resolution: {integrity: sha512-3NPJyBLbFfzz9VAAdIZrDRdRpyslVCJoZHQk0/0CX3z2mJIfcQzjZhox2cYCFNH8NMJ7pRg6AeSMPYAnDKECDg==} + '@miniflare/d1@2.14.4': + resolution: {integrity: sha512-pMBVq9XWxTDdm+RRCkfXZP+bREjPg1JC8s8C0JTovA9OGmLQXqGTnFxIaS9vf1d8k3uSUGhDzPTzHr0/AUW1gA==} engines: {node: '>=16.7'} - '@miniflare/queues@2.14.2': - resolution: {integrity: sha512-OylkRs4lOWKvGnX+Azab3nx+1qwC87M36/hkgAU1RRvVDCOxOrYLvNLUczFfgmgMBwpYsmmW8YOIASlI3p4Qgw==} + '@miniflare/queues@2.14.4': + resolution: {integrity: sha512-aXQ5Ik8Iq1KGMBzGenmd6Js/jJgqyYvjom95/N9GptCGpiVWE5F0XqC1SL5rCwURbHN+aWY191o8XOFyY2nCUA==} engines: {node: '>=16.7'} - '@miniflare/shared@2.14.2': - resolution: {integrity: sha512-dDnYIztz10zDQjaFJ8Gy9UaaBWZkw3NyhFdpX6tAeyPA/2lGvkftc42MYmNi8s5ljqkZAtKgWAJnSf2K75NCJw==} + '@miniflare/shared@2.14.4': + resolution: {integrity: sha512-upl4RSB3hyCnITOFmRZjJj4A72GmkVrtfZTilkdq5Qe5TTlzsjVeDJp7AuNUM9bM8vswRo+N5jOiot6O4PVwwQ==} engines: {node: '>=16.13'} - '@miniflare/watcher@2.14.2': - resolution: {integrity: sha512-/TL0np4uYDl+6MdseDApZmDdlJ6Y7AY5iDY0TvUQJG9nyBoCjX6w0Zn4SiKDwO6660rPtSqZ5c7HzbPhGb5vsA==} + '@miniflare/watcher@2.14.4': + resolution: {integrity: sha512-PYn05ET2USfBAeXF6NZfWl0O32KVyE8ncQ/ngysrh3hoIV7l3qGGH7ubeFx+D8VWQ682qYhwGygUzQv2j1tGGg==} engines: {node: '>=16.13'} '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} + '@neondatabase/serverless@0.10.0': + resolution: {integrity: sha512-+0mjRGJFL2kGyTtWo60PxIcgv0a/X/vCu4DV2iS3tL+Rl/OrFocJoN3aNajugvgBQj624aOK7LowLijoQHWIXg==} + + '@neondatabase/serverless@0.10.3': + resolution: {integrity: sha512-F4kqSj++GUwLnO3OzPb95Y/xn3qVLkjJA/36YTqT7c3MRgA/IBOIs/Is1+HBZkGfEwfMG3A9tFkxiEg5eBjxDw==} + '@neondatabase/serverless@0.7.2': resolution: {integrity: sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==} - '@neondatabase/serverless@0.9.0': - resolution: {integrity: sha512-mmJnUAzlzvxNSZuuhI6kgJjH+JgFdBMYUWxihtq/nj0Tjt+Y5UU3W+SvRFoucnd5NObYkuLYQzk+zV5DGFKGJg==} - '@neondatabase/serverless@0.9.3': resolution: {integrity: sha512-6ZBK8asl2Z3+ADEaELvbaVVGVlmY1oAzkxxZfpmXPKFuJhbDN+5fU3zYBamsahS/Ch1zE+CVWB3R+8QEI2LMSw==} @@ -3404,11 +3511,11 @@ packages: resolution: {integrity: sha512-lzD84av1ZQhYUS+jsGqJiCMaJO2dn9u+RTT9n9q6D3SaKVwWqv+7AoRKqBu19bkwyE+iFRl1ymr40QS90jVFYg==} engines: {node: '>=14.15'} - '@rollup/plugin-terser@0.4.1': - resolution: {integrity: sha512-aKS32sw5a7hy+fEXVy+5T95aDIwjpGHCTv833HXVtyKMDoVS7pBr5K3L9hEQoNqbJFjfANPrNpIXlTQ7is00eA==} + '@rollup/plugin-terser@0.4.4': + resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.x || ^3.x + rollup: ^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -3439,6 +3546,19 @@ packages: tslib: optional: true + '@rollup/plugin-typescript@11.1.6': + resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.14.0||^3.0.0||^4.0.0 + tslib: '*' + typescript: '>=3.7.0' + peerDependenciesMeta: + rollup: + optional: true + tslib: + optional: true + '@rollup/pluginutils@5.0.2': resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} engines: {node: '>=14.0.0'} @@ -3448,19 +3568,23 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.18.0': - resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} - cpu: [arm] - os: [android] + '@rollup/pluginutils@5.1.3': + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true '@rollup/rollup-android-arm-eabi@4.18.1': resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.18.0': - resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} - cpu: [arm64] + '@rollup/rollup-android-arm-eabi@4.27.3': + resolution: {integrity: sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==} + cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.18.1': @@ -3468,19 +3592,19 @@ packages: cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.18.0': - resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + '@rollup/rollup-android-arm64@4.27.3': + resolution: {integrity: sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==} cpu: [arm64] - os: [darwin] + os: [android] '@rollup/rollup-darwin-arm64@4.18.1': resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.18.0': - resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} - cpu: [x64] + '@rollup/rollup-darwin-arm64@4.27.3': + resolution: {integrity: sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==} + cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.18.1': @@ -3488,18 +3612,28 @@ packages: cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': - resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} - cpu: [arm] - os: [linux] + '@rollup/rollup-darwin-x64@4.27.3': + resolution: {integrity: sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.27.3': + resolution: {integrity: sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.27.3': + resolution: {integrity: sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==} + cpu: [x64] + os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.18.1': resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.18.0': - resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + '@rollup/rollup-linux-arm-gnueabihf@4.27.3': + resolution: {integrity: sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==} cpu: [arm] os: [linux] @@ -3508,9 +3642,9 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.18.0': - resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} - cpu: [arm64] + '@rollup/rollup-linux-arm-musleabihf@4.27.3': + resolution: {integrity: sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==} + cpu: [arm] os: [linux] '@rollup/rollup-linux-arm64-gnu@4.18.1': @@ -3518,8 +3652,8 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.18.0': - resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + '@rollup/rollup-linux-arm64-gnu@4.27.3': + resolution: {integrity: sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==} cpu: [arm64] os: [linux] @@ -3528,9 +3662,9 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': - resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} - cpu: [ppc64] + '@rollup/rollup-linux-arm64-musl@4.27.3': + resolution: {integrity: sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==} + cpu: [arm64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.18.1': @@ -3538,9 +3672,9 @@ packages: cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.18.0': - resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} - cpu: [riscv64] + '@rollup/rollup-linux-powerpc64le-gnu@4.27.3': + resolution: {integrity: sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==} + cpu: [ppc64] os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.18.1': @@ -3548,9 +3682,9 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.18.0': - resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} - cpu: [s390x] + '@rollup/rollup-linux-riscv64-gnu@4.27.3': + resolution: {integrity: sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==} + cpu: [riscv64] os: [linux] '@rollup/rollup-linux-s390x-gnu@4.18.1': @@ -3558,9 +3692,9 @@ packages: cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.18.0': - resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} - cpu: [x64] + '@rollup/rollup-linux-s390x-gnu@4.27.3': + resolution: {integrity: sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==} + cpu: [s390x] os: [linux] '@rollup/rollup-linux-x64-gnu@4.18.1': @@ -3568,8 +3702,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.18.0': - resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + '@rollup/rollup-linux-x64-gnu@4.27.3': + resolution: {integrity: sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==} cpu: [x64] os: [linux] @@ -3578,19 +3712,19 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.18.0': - resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} - cpu: [arm64] - os: [win32] + '@rollup/rollup-linux-x64-musl@4.27.3': + resolution: {integrity: sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==} + cpu: [x64] + os: [linux] '@rollup/rollup-win32-arm64-msvc@4.18.1': resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.18.0': - resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} - cpu: [ia32] + '@rollup/rollup-win32-arm64-msvc@4.27.3': + resolution: {integrity: sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==} + cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.18.1': @@ -3598,9 +3732,9 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.18.0': - resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} - cpu: [x64] + '@rollup/rollup-win32-ia32-msvc@4.27.3': + resolution: {integrity: sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==} + cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.18.1': @@ -3608,6 +3742,11 @@ packages: cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.27.3': + resolution: {integrity: sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==} + cpu: [x64] + os: [win32] + '@segment/loosely-validate-event@2.0.0': resolution: {integrity: sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==} @@ -3623,13 +3762,17 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sinclair/typebox@0.29.6': - resolution: {integrity: sha512-aX5IFYWlMa7tQ8xZr3b2gtVReCvg7f3LEhjir/JAjX2bJCMVJA5tIPv30wTD4KDfcwMd7DDYY3hFDeGmOgtrZQ==} + '@sinclair/typebox@0.34.10': + resolution: {integrity: sha512-bJ3mIrYjEwenwwt+xAUq3GnOf1O4r2sApPzmfmF90XYMiKxjDzFSWSpWxqzSlQq3pCXuHP2UPxVPKeUFGJxb+A==} '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -4002,9 +4145,15 @@ packages: '@types/better-sqlite3@7.6.10': resolution: {integrity: sha512-TZBjD+yOsyrUJGmcUj6OS3JADk3+UZcNv3NOBqGkM09bZdi28fNZw8ODqbMOLfKCu7RYCO62/ldq1iHbzxqoPw==} + '@types/better-sqlite3@7.6.12': + resolution: {integrity: sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==} + '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + '@types/braces@3.0.4': + resolution: {integrity: sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -4014,6 +4163,9 @@ packages: '@types/dockerode@3.3.29': resolution: {integrity: sha512-5PRRq/yt5OT/Jf77ltIdz4EiR9+VLnPF+HpU4xGFwUqmV24Co2HKBNW3w+slqZ1CYchbcDeqJASHDYWzZCcMiQ==} + '@types/dockerode@3.3.32': + resolution: {integrity: sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg==} + '@types/emscripten@1.39.11': resolution: {integrity: sha512-dOeX2BeNA7j6BTEqJQL3ut0bRCfsyQMd5i4FT8JfHfYhAOuJPCGh0dQFbxVJxUyQ+75x6enhDdndGb624/QszA==} @@ -4023,6 +4175,9 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/express-serve-static-core@4.19.0': resolution: {integrity: sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==} @@ -4059,6 +4214,9 @@ packages: '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/micromatch@4.0.9': + resolution: {integrity: sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -4068,9 +4226,6 @@ packages: '@types/minimist@1.2.2': resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} - '@types/node-fetch@2.6.11': - resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} @@ -4086,6 +4241,9 @@ packages: '@types/node@20.12.12': resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} + '@types/node@22.9.1': + resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==} + '@types/normalize-package-data@2.4.1': resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -4134,6 +4292,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} @@ -4369,15 +4530,42 @@ packages: '@vitest/expect@1.6.0': resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@vitest/expect@2.1.2': + resolution: {integrity: sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==} + + '@vitest/mocker@2.1.2': + resolution: {integrity: sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==} + peerDependencies: + '@vitest/spy': 2.1.2 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.2': + resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==} + '@vitest/runner@1.6.0': resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + '@vitest/runner@2.1.2': + resolution: {integrity: sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==} + '@vitest/snapshot@1.6.0': resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + '@vitest/snapshot@2.1.2': + resolution: {integrity: sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==} + '@vitest/spy@1.6.0': resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@vitest/spy@2.1.2': + resolution: {integrity: sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==} + '@vitest/ui@1.6.0': resolution: {integrity: sha512-k3Lyo+ONLOgylctiGovRKy7V4+dIN2yxstX3eY5cWFXH6WP+ooVX79YSyi0GagdTQzLmT43BF27T0s6dOIPBXA==} peerDependencies: @@ -4386,6 +4574,9 @@ packages: '@vitest/utils@1.6.0': resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@vitest/utils@2.1.2': + resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==} + '@xata.io/client@0.29.4': resolution: {integrity: sha512-dRff4E/wINr0SYIlOHwApo0h8jzpAHVf2RcbGMkK9Xrddbe90KmCEx/gue9hLhBOoCCp6qUht2h9BsuVPruymw==} peerDependencies: @@ -4394,6 +4585,7 @@ packages: '@xmldom/xmldom@0.7.13': resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} engines: {node: '>=10.0.0'} + deprecated: this version is no longer supported, please update to at least 0.8.* '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} @@ -4429,11 +4621,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.8.2: - resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} - engines: {node: '>=0.4.0'} - hasBin: true - agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -4468,6 +4655,10 @@ packages: resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} engines: {node: '>=14.16'} + ansi-escapes@7.0.0: + resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} + engines: {node: '>=18'} + ansi-fragments@0.2.1: resolution: {integrity: sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==} @@ -4483,6 +4674,10 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -4521,6 +4716,7 @@ packages: are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -4599,6 +4795,10 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types@0.15.2: resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} engines: {node: '>=4'} @@ -4642,6 +4842,10 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + aws-ssl-profiles@1.1.1: + resolution: {integrity: sha512-+H+kuK34PfMaI9PNU/NSjBKL5hh/KDM9J72kwYeYEm0A8B1AC4fuCy3qsjnA7lxklgyXsB68yn8Z2xoZEjgwCQ==} + engines: {node: '>= 6.0.0'} + axios@1.6.8: resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} @@ -4687,8 +4891,8 @@ packages: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} - better-sqlite3@10.0.0: - resolution: {integrity: sha512-rOz0JY8bt9oMgrFssP7GnvA5R3yln73y/NizzWqy3WlFth8Ux8+g4r/N9fjX97nn4X1YX6MTER2doNpTu5pqiA==} + better-sqlite3@11.5.0: + resolution: {integrity: sha512-e/6eggfOutzoK0JWiU36jsisdWoHOfN9iWiW/SieKvb7SAa6aGNmBM/UKyp+/wWSXpLlWNN8tCPwoDNPhzUvuQ==} better-sqlite3@8.7.0: resolution: {integrity: sha512-99jZU4le+f3G6aIl6PmmV0cxUIWqKieHxsiF7G34CVFiE+/UabpYqkU0NJIkY/96mQKikHeBjtR27vFfs5JpEw==} @@ -4740,10 +4944,6 @@ packages: brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -4888,6 +5088,10 @@ packages: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} engines: {node: '>=4'} + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -4910,6 +5114,10 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -4947,6 +5155,9 @@ packages: ci-parallel-vars@1.0.1: resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==} + cjs-module-lexer@1.4.1: + resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + clean-regexp@1.0.0: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} @@ -4975,6 +5186,11 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} @@ -4983,6 +5199,10 @@ packages: resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} engines: {node: 10.* || >= 12.*} + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + cli-truncate@3.1.0: resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5147,6 +5367,10 @@ packages: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} + copy-file@11.0.0: + resolution: {integrity: sha512-mFsNh/DIANLqFt5VHZoGirdg7bK5+oTWlhnGu6tgRhzBlnEKWaPX2xrFaLltii/6rmhqFMJqffUgknuRdpYlHw==} + engines: {node: '>=18'} + core-js-compat@3.37.1: resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} @@ -5174,6 +5398,10 @@ packages: resolution: {integrity: sha512-VC2Gs20JcTyeQob6UViBLnyP0bYHkBh6EiKzot9vi2DmeGlFT9Wd7VG3NBrkNx/jYvFBeyDOMMHdHQhbtKLgHQ==} engines: {node: '>=16'} + cpy@11.1.0: + resolution: {integrity: sha512-QGHetPSSuprVs+lJmMDcivvrBwTKASzXQ5qxFvRC2RFESjjod71bDvFvhxTjDgkNjrrb72AI6JPjfYwxrIy33A==} + engines: {node: '>=18'} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -5284,6 +5512,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -5296,6 +5533,10 @@ packages: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -5399,10 +5640,18 @@ packages: resolution: {integrity: sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==} engines: {node: '>= 8.0'} + docker-modem@5.0.3: + resolution: {integrity: sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==} + engines: {node: '>= 8.0'} + dockerode@3.3.5: resolution: {integrity: sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==} engines: {node: '>= 8.0'} + dockerode@4.0.2: + resolution: {integrity: sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==} + engines: {node: '>= 8.0'} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -5435,8 +5684,8 @@ packages: resolution: {integrity: sha512-Rba5VW1O2JfJlwVBeZ8Zwt2E2us5oZ08PQBDiVSGlug53TOc8hzXjblZFuF+dnll9/RQEHrkzBmJFgqTvn5Rxg==} hasBin: true - drizzle-kit@0.21.2: - resolution: {integrity: sha512-U87IhZyCt/9d0ZT/Na3KFJVY31tSxtTx/n9UMcWFpW/5c2Ede39xiCG5efNV/0iimsv97UIRtDI0ldLBW5lbcg==} + drizzle-kit@0.25.0-b1faa33: + resolution: {integrity: sha512-WMRuEgxt1oTc62EPVQhGD+pGs6LiqzT8UqxuI6mKfA5SCeCEIt87nFzzJ5WlwsqbuoSgXBXc5zhsHvqXRD03DA==} hasBin: true drizzle-orm@0.27.2: @@ -5557,6 +5806,10 @@ packages: engines: {node: '>=4'} hasBin: true + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + eol@0.9.1: resolution: {integrity: sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==} @@ -5910,16 +6163,19 @@ packages: eslint@8.50.0: resolution: {integrity: sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@8.53.0: resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true esm@3.2.25: @@ -6045,8 +6301,8 @@ packages: expo-modules-core@1.12.11: resolution: {integrity: sha512-CF5G6hZo/6uIUz6tj4dNRlvE5L4lakYukXPqz5ZHQ+6fLk1NQVZbRdpHjMkxO/QSBQcKUzG/ngeytpoJus7poQ==} - expo-sqlite@13.4.0: - resolution: {integrity: sha512-5f7d2EDM+pgerM33KndtX4gWw2nuVaXY68nnqx7PhkiYeyEmeNfZ29bIFtpBzNb/L5l0/DTtRxuSqftxbknFtw==} + expo-sqlite@14.0.6: + resolution: {integrity: sha512-T3YNx7LT7lM4UQRgi8ml+cj0Wf3Ep09+B4CVaWtUCjdyYJIZjsHDT65hypKG+r6btTLLEd11hjlrstNQhzt5gQ==} peerDependencies: expo: '*' @@ -6125,10 +6381,6 @@ packages: file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -6286,6 +6538,7 @@ packages: gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -6426,6 +6679,10 @@ packages: resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + globby@14.0.2: + resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} + engines: {node: '>=18'} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -6518,6 +6775,9 @@ packages: resolution: {integrity: sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==} engines: {node: '>=8'} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + hono@4.0.1: resolution: {integrity: sha512-S9cREGPJIAK437RhroOf1PGlJPIlt5itl69OmQ6onPLo5pdCbSHGL8v4uAKxrdHjcTyuoyvKPqWm5jv0dGkdFA==} engines: {node: '>=16.0.0'} @@ -7091,42 +7351,10 @@ packages: tedious: optional: true - knex@3.1.0: - resolution: {integrity: sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==} - engines: {node: '>=16'} - hasBin: true - peerDependencies: - better-sqlite3: '*' - mysql: '*' - mysql2: '*' - pg: '*' - pg-native: '*' - sqlite3: '*' - tedious: '*' - peerDependenciesMeta: - better-sqlite3: - optional: true - mysql: - optional: true - mysql2: - optional: true - pg: - optional: true - pg-native: - optional: true - sqlite3: - optional: true - tedious: - optional: true - kysely@0.25.0: resolution: {integrity: sha512-srn0efIMu5IoEBk0tBmtGnoUss4uwvxtbFQWG/U2MosfqIace1l43IFP1PmEpHRDp+Z79xIcKEqmHH3dAvQdQA==} engines: {node: '>=14.0.0'} - kysely@0.27.3: - resolution: {integrity: sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==} - engines: {node: '>=14.0.0'} - leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -7135,13 +7363,12 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - libsql@0.2.0: - resolution: {integrity: sha512-ELBRqhpJx5Dap0187zKQnntZyk4EjlDHSrjIVL8t+fQ5e8IxbQTeYgZgigMjB1EvrETdkm0Y0VxBGhzPQ+t0Jg==} - cpu: [x64, arm64] + libsql@0.3.19: + resolution: {integrity: sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==} os: [darwin, linux, win32] - libsql@0.3.18: - resolution: {integrity: sha512-lvhKr7WV3NLWRbXkjn/MeKqXOAqWKU0PX9QYrvDh7fneukapj+iUQ4qgJASrQyxcCrEsClXCQiiK5W6OoYPAlA==} + libsql@0.4.1: + resolution: {integrity: sha512-qZlR9Yu1zMBeLChzkE/cKfoKV3Esp9cn9Vx5Zirn4AVhDWPcjYhKwbtJcMuHehgk3mH+fJr9qW+3vesBWbQpBg==} os: [darwin, linux, win32] lighthouse-logger@1.4.2: @@ -7323,9 +7550,6 @@ packages: resolution: {integrity: sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==} hasBin: true - long@4.0.0: - resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} - long@5.2.3: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} @@ -7336,10 +7560,16 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -7368,6 +7598,9 @@ packages: magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -7395,6 +7628,12 @@ packages: peerDependencies: marked: '>=1 <12' + marked-terminal@7.2.1: + resolution: {integrity: sha512-rQ1MoMFXZICWNsKMiiHwP/Z+92PLKskTPXj+e7uwXmuMPkNn7iTqC+IvDekVm1MPeC9wYQeLxeFaOvudRR/XbQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + marked: '>=1 <15' + marked@9.1.6: resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} engines: {node: '>= 16'} @@ -7518,14 +7757,14 @@ packages: engines: {node: '>=18'} hasBin: true - micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - micromatch@4.0.7: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -7672,18 +7911,14 @@ packages: resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==} engines: {node: '>=0.8.0'} - mysql2@2.3.3: - resolution: {integrity: sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==} + mysql2@3.11.0: + resolution: {integrity: sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==} engines: {node: '>= 8.0'} mysql2@3.3.3: resolution: {integrity: sha512-MxDQJztArk4JFX1PKVjDhIXRzAmVJfuqZrVU+my6NeYBAA/XZRaDw5q7vga8TNvgyy3Lv3rivBFBBuJFbsdjaw==} engines: {node: '>= 8.0'} - mysql2@3.9.8: - resolution: {integrity: sha512-+5JKNjPuks1FNMoy9TYpl77f+5frbTklz7eb3XDwbpsERRLEeXiW2PDEkakYF50UuKU2qwfGnyXpKYvukv8mGA==} - engines: {node: '>= 8.0'} - mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -7835,6 +8070,7 @@ packages: npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. npx-import@1.1.4: resolution: {integrity: sha512-3ShymTWOgqGyNlh5lMJAejLuIv3W1K3fbI5Ewc6YErZU3Sp0PqsNs8UIU1O8z5+KVl/Du5ag56Gza9vdorGEoA==} @@ -7886,6 +8122,10 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + ohm-js@17.1.0: + resolution: {integrity: sha512-xc3B5dgAjTBQGHaH7B58M2Pmv6WvzrJ/3/7LeUzXNg0/sY3jQPdSd/S2SstppaleO77rifR1tyhdfFGNIwxf2Q==} + engines: {node: '>=0.12.1'} + oidc-token-hash@5.0.3: resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} engines: {node: ^10.13.0 || >=12.0.0} @@ -7964,10 +8204,18 @@ packages: resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-event@6.0.1: + resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==} + engines: {node: '>=16.17'} + p-filter@3.0.0: resolution: {integrity: sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-filter@4.1.0: + resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==} + engines: {node: '>=18'} + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -8016,10 +8264,18 @@ packages: resolution: {integrity: sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==} engines: {node: '>=16'} + p-map@7.0.2: + resolution: {integrity: sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==} + engines: {node: '>=18'} + p-timeout@5.1.0: resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} engines: {node: '>=12'} + p-timeout@6.1.3: + resolution: {integrity: sha512-UJUyfKbwvr/uZSV6btANfb+0t/mOhKV/KXcCUTp8FcQI+v/0d+wXqH4htrW0E4rR6WiEO/EPvUFiV9D5OI4vlw==} + engines: {node: '>=14.16'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -8047,6 +8303,15 @@ packages: resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} engines: {node: '>=10'} + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -8103,12 +8368,20 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} @@ -8118,12 +8391,12 @@ packages: pg-connection-string@2.6.1: resolution: {integrity: sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==} - pg-connection-string@2.6.2: - resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} - pg-connection-string@2.6.4: resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -8137,9 +8410,17 @@ packages: peerDependencies: pg: '>=8.0' + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' + pg-protocol@1.6.1: resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -8157,6 +8438,15 @@ packages: pg-native: optional: true + pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} @@ -8174,6 +8464,10 @@ packages: resolution: {integrity: sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==} engines: {node: '>=10'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -8355,6 +8649,9 @@ packages: bluebird: optional: true + promise-limit@2.7.0: + resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} @@ -8395,6 +8692,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qrcode-terminal@0.11.0: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} hasBin: true @@ -8591,6 +8891,12 @@ packages: peerDependencies: typescript: '>=3.0.3' + resolve-tspaths@0.8.22: + resolution: {integrity: sha512-x9loBJyTLdx3grlcNpH/Y2t8IkfadtbzYhzpo683C6olazn0/4Y3cfSBiqDA0f2vSmq5tITKJCN9e1ezBh6jhA==} + hasBin: true + peerDependencies: + typescript: '>=3.0.3' + resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} @@ -8651,6 +8957,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@5.0.0: @@ -8678,13 +8985,13 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true - rollup@4.18.0: - resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + rollup@4.18.1: + resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rollup@4.18.1: - resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} + rollup@4.27.3: + resolution: {integrity: sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -8862,6 +9169,10 @@ packages: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + slice-ansi@2.1.0: resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==} engines: {node: '>=6'} @@ -8878,8 +9189,8 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smob@0.0.6: - resolution: {integrity: sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==} + smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} socks-proxy-agent@6.2.1: resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} @@ -9152,6 +9463,10 @@ packages: resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} engines: {node: '>=14.18'} + supports-hyperlinks@3.1.0: + resolution: {integrity: sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==} + engines: {node: '>=14.18'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -9206,11 +9521,6 @@ packages: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} - terser@5.17.1: - resolution: {integrity: sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==} - engines: {node: '>=10'} - hasBin: true - terser@5.31.0: resolution: {integrity: sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==} engines: {node: '>=10'} @@ -9258,14 +9568,32 @@ packages: tinybench@2.8.0: resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.0: + resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + tinyspy@2.2.1: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -9358,6 +9686,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@7.2.0: resolution: {integrity: sha512-vDHlczXbgUvY3rWvqFEbSqmC1L7woozbzngMqTtL2PGBODTtWlRwGDDawhvWzr5c1QjKe4OAKqJGfE1xeXUvtQ==} engines: {node: '>=16.14'} @@ -9413,41 +9744,46 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - turbo-darwin-64@1.10.14: - resolution: {integrity: sha512-I8RtFk1b9UILAExPdG/XRgGQz95nmXPE7OiGb6ytjtNIR5/UZBS/xVX/7HYpCdmfriKdVwBKhalCoV4oDvAGEg==} + turbo-darwin-64@2.3.0: + resolution: {integrity: sha512-pji+D49PhFItyQjf2QVoLZw2d3oRGo8gJgKyOiRzvip78Rzie74quA8XNwSg/DuzM7xx6gJ3p2/LylTTlgZXxQ==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@1.10.14: - resolution: {integrity: sha512-KAdUWryJi/XX7OD0alOuOa0aJ5TLyd4DNIYkHPHYcM6/d7YAovYvxRNwmx9iv6Vx6IkzTnLeTiUB8zy69QkG9Q==} + turbo-darwin-arm64@2.3.0: + resolution: {integrity: sha512-AJrGIL9BO41mwDF/IBHsNGwvtdyB911vp8f5mbNo1wG66gWTvOBg7WCtYQBvCo11XTenTfXPRSsAb7w3WAZb6w==} cpu: [arm64] os: [darwin] - turbo-linux-64@1.10.14: - resolution: {integrity: sha512-BOBzoREC2u4Vgpap/WDxM6wETVqVMRcM8OZw4hWzqCj2bqbQ6L0wxs1LCLWVrghQf93JBQtIGAdFFLyCSBXjWQ==} + turbo-linux-64@2.3.0: + resolution: {integrity: sha512-jZqW6vc2sPJT3M/3ZmV1Cg4ecQVPqsbHncG/RnogHpBu783KCSXIndgxvUQNm9qfgBYbZDBnP1md63O4UTElhw==} cpu: [x64] os: [linux] - turbo-linux-arm64@1.10.14: - resolution: {integrity: sha512-D8T6XxoTdN5D4V5qE2VZG+/lbZX/89BkAEHzXcsSUTRjrwfMepT3d2z8aT6hxv4yu8EDdooZq/2Bn/vjMI32xw==} + turbo-linux-arm64@2.3.0: + resolution: {integrity: sha512-HUbDLJlvd/hxuyCNO0BmEWYQj0TugRMvSQeG8vHJH+Lq8qOgDAe7J0K73bFNbZejZQxW3C3XEiZFB3pnpO78+A==} cpu: [arm64] os: [linux] - turbo-windows-64@1.10.14: - resolution: {integrity: sha512-zKNS3c1w4i6432N0cexZ20r/aIhV62g69opUn82FLVs/zk3Ie0GVkSB6h0rqIvMalCp7enIR87LkPSDGz9K4UA==} + turbo-windows-64@2.3.0: + resolution: {integrity: sha512-c5rxrGNTYDWX9QeMzWLFE9frOXnKjHGEvQMp1SfldDlbZYsloX9UKs31TzUThzfTgTiz8NYuShaXJ2UvTMnV/g==} cpu: [x64] os: [win32] - turbo-windows-arm64@1.10.14: - resolution: {integrity: sha512-rkBwrTPTxNSOUF7of8eVvvM+BkfkhA2OvpHM94if8tVsU+khrjglilp8MTVPHlyS9byfemPAmFN90oRIPB05BA==} + turbo-windows-arm64@2.3.0: + resolution: {integrity: sha512-7qfUuYhfIVb1AZgs89DxhXK+zZez6O2ocmixEQ4hXZK7ytnBt5vaz2zGNJJKFNYIL5HX1C3tuHolnpNgDNCUIg==} cpu: [arm64] os: [win32] - turbo@1.10.14: - resolution: {integrity: sha512-hr9wDNYcsee+vLkCDIm8qTtwhJ6+UAMJc3nIY6+PNgUTtXcQgHxCq8BGoL7gbABvNWv76CNbK5qL4Lp9G3ZYRA==} + turbo@2.3.0: + resolution: {integrity: sha512-/uOq5o2jwRPyaUDnwBpOR5k9mQq4c3wziBgWNWttiYQPmbhDtrKYPRBxTvA2WpgQwRIbt8UM612RMN8n/TvmHA==} hasBin: true tweetnacl@0.14.5: @@ -9552,8 +9888,13 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + typescript@5.6.1-rc: + resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true @@ -9569,9 +9910,8 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@5.28.2: - resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==} - engines: {node: '>=14.0'} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -9600,6 +9940,10 @@ packages: resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} engines: {node: '>=4'} + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + unique-filename@1.1.1: resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} @@ -9668,6 +10012,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@7.0.3: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} hasBin: true @@ -9688,8 +10036,13 @@ packages: v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - valibot@0.30.0: - resolution: {integrity: sha512-5POBdbSkM+3nvJ6ZlyQHsggisfRtyT4tVTo1EIIShs6qCdXJnyWU5TJ68vr8iTg5zpOLjXLRiBqNx+9zwZz/rA==} + valibot@1.0.0-beta.7: + resolution: {integrity: sha512-8CsDu3tqyg7quEHMzCOYdQ/d9NlmVQKtd4AlFje6oJpvqo70EIZjSakKIeWltJyNAiUtdtLe0LAk4625gavoeQ==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true valid-url@1.0.9: resolution: {integrity: sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==} @@ -9717,6 +10070,11 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true + vite-node@2.1.2: + resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-tsconfig-paths@4.3.2: resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: @@ -9806,6 +10164,31 @@ packages: jsdom: optional: true + vitest@2.1.2: + resolution: {integrity: sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.2 + '@vitest/ui': 2.1.2 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} @@ -9883,6 +10266,11 @@ packages: engines: {node: '>=8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -10087,6 +10475,11 @@ packages: engines: {node: '>= 16.0.0'} hasBin: true + zx@8.2.2: + resolution: {integrity: sha512-HSIdpU5P2ONI0nssnhsUZNCH9Sd/Z8LIFk9n8QTbu6JufzJx7qR7ajrMN21s06JqWSApcN012377iWsv8Vs5bg==} + engines: {node: '>= 12.17.0'} + hasBin: true + snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} @@ -10108,6 +10501,16 @@ snapshots: marked-terminal: 6.2.0(marked@9.1.6) semver: 7.6.2 + '@arethetypeswrong/cli@0.16.4': + dependencies: + '@arethetypeswrong/core': 0.16.4 + chalk: 4.1.2 + cli-table3: 0.6.3 + commander: 10.0.1 + marked: 9.1.6 + marked-terminal: 7.2.1(marked@9.1.6) + semver: 7.6.2 + '@arethetypeswrong/core@0.15.1': dependencies: '@andrewbranch/untar.js': 1.0.3 @@ -10117,6 +10520,16 @@ snapshots: typescript: 5.3.3 validate-npm-package-name: 5.0.0 + '@arethetypeswrong/core@0.16.4': + dependencies: + '@andrewbranch/untar.js': 1.0.3 + cjs-module-lexer: 1.4.1 + fflate: 0.8.2 + lru-cache: 10.4.3 + semver: 7.6.2 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.0 + '@ava/typescript@5.0.0': dependencies: escape-string-regexp: 5.0.0 @@ -10202,7 +10615,7 @@ snapshots: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10252,7 +10665,7 @@ snapshots: '@smithy/util-stream': 2.2.0 '@smithy/util-utf8': 2.3.0 '@smithy/util-waiter': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10260,8 +10673,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) - '@aws-sdk/client-sts': 3.583.0 + '@aws-sdk/client-sso-oidc': 3.583.0 + '@aws-sdk/client-sts': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/core': 3.582.0 '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -10343,15 +10756,15 @@ snapshots: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)': + '@aws-sdk/client-sso-oidc@3.583.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.583.0 + '@aws-sdk/client-sts': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/core': 3.582.0 '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -10388,9 +10801,8 @@ snapshots: '@smithy/util-middleware': 3.0.0 '@smithy/util-retry': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso@3.478.0': @@ -10431,7 +10843,7 @@ snapshots: '@smithy/util-endpoints': 1.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10474,7 +10886,7 @@ snapshots: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10517,7 +10929,7 @@ snapshots: '@smithy/util-middleware': 3.0.0 '@smithy/util-retry': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10562,7 +10974,7 @@ snapshots: '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 fast-xml-parser: 4.2.5 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10607,7 +11019,7 @@ snapshots: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10652,16 +11064,16 @@ snapshots: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/client-sts@3.583.0': + '@aws-sdk/client-sts@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/client-sso-oidc': 3.583.0 '@aws-sdk/core': 3.582.0 '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -10698,8 +11110,9 @@ snapshots: '@smithy/util-middleware': 3.0.0 '@smithy/util-retry': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/core@3.477.0': @@ -10709,7 +11122,7 @@ snapshots: '@smithy/signature-v4': 2.3.0 '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/core@3.567.0': dependencies: @@ -10719,7 +11132,7 @@ snapshots: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 fast-xml-parser: 4.2.5 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/core@3.582.0': dependencies: @@ -10729,7 +11142,7 @@ snapshots: '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 fast-xml-parser: 4.2.5 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-cognito-identity@3.569.0': dependencies: @@ -10737,7 +11150,7 @@ snapshots: '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10746,21 +11159,21 @@ snapshots: '@aws-sdk/types': 3.468.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-env@3.568.0': dependencies: '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-env@3.577.0': dependencies: '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-http@3.568.0': dependencies: @@ -10772,7 +11185,7 @@ snapshots: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 '@smithy/util-stream': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-http@3.582.0': dependencies: @@ -10784,7 +11197,7 @@ snapshots: '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 '@smithy/util-stream': 3.0.1 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-ini@3.478.0': dependencies: @@ -10797,7 +11210,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10813,7 +11226,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -10830,7 +11243,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -10847,14 +11260,14 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/credential-provider-ini@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)': dependencies: - '@aws-sdk/client-sts': 3.583.0 + '@aws-sdk/client-sts': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/credential-provider-env': 3.577.0 '@aws-sdk/credential-provider-process': 3.577.0 '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) @@ -10864,7 +11277,7 @@ snapshots: '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -10881,7 +11294,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -10898,7 +11311,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -10917,7 +11330,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -10936,7 +11349,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -10955,7 +11368,7 @@ snapshots: '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -10967,7 +11380,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-process@3.568.0': dependencies: @@ -10975,7 +11388,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-process@3.577.0': dependencies: @@ -10983,7 +11396,7 @@ snapshots: '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-sso@3.478.0': dependencies: @@ -10993,7 +11406,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -11005,7 +11418,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -11018,7 +11431,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -11031,7 +11444,7 @@ snapshots: '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -11041,7 +11454,7 @@ snapshots: '@aws-sdk/types': 3.468.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-web-identity@3.568.0(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0))': dependencies: @@ -11049,7 +11462,7 @@ snapshots: '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-web-identity@3.568.0(@aws-sdk/client-sts@3.569.0)': dependencies: @@ -11057,15 +11470,15 @@ snapshots: '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-provider-web-identity@3.577.0(@aws-sdk/client-sts@3.583.0)': dependencies: - '@aws-sdk/client-sts': 3.583.0 + '@aws-sdk/client-sts': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/credential-providers@3.569.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: @@ -11084,7 +11497,7 @@ snapshots: '@smithy/credential-provider-imds': 2.3.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -11094,60 +11507,60 @@ snapshots: '@aws-sdk/types': 3.468.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-host-header@3.567.0': dependencies: '@aws-sdk/types': 3.567.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-host-header@3.577.0': dependencies: '@aws-sdk/types': 3.577.0 '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-logger@3.468.0': dependencies: '@aws-sdk/types': 3.468.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-logger@3.568.0': dependencies: '@aws-sdk/types': 3.567.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-logger@3.577.0': dependencies: '@aws-sdk/types': 3.577.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-recursion-detection@3.468.0': dependencies: '@aws-sdk/types': 3.468.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-recursion-detection@3.567.0': dependencies: '@aws-sdk/types': 3.567.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-recursion-detection@3.577.0': dependencies: '@aws-sdk/types': 3.577.0 '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-signing@3.468.0': dependencies: @@ -11157,7 +11570,7 @@ snapshots: '@smithy/signature-v4': 2.3.0 '@smithy/types': 2.12.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-user-agent@3.478.0': dependencies: @@ -11165,7 +11578,7 @@ snapshots: '@aws-sdk/util-endpoints': 3.478.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-user-agent@3.567.0': dependencies: @@ -11173,7 +11586,7 @@ snapshots: '@aws-sdk/util-endpoints': 3.567.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/middleware-user-agent@3.583.0': dependencies: @@ -11181,7 +11594,7 @@ snapshots: '@aws-sdk/util-endpoints': 3.583.0 '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/region-config-resolver@3.470.0': dependencies: @@ -11189,7 +11602,7 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/util-config-provider': 2.3.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/region-config-resolver@3.567.0': dependencies: @@ -11198,7 +11611,7 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/util-config-provider': 2.3.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/region-config-resolver@3.577.0': dependencies: @@ -11207,7 +11620,7 @@ snapshots: '@smithy/types': 3.0.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/token-providers@3.478.0': dependencies: @@ -11247,7 +11660,7 @@ snapshots: '@smithy/util-endpoints': 1.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -11258,110 +11671,110 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/token-providers@3.568.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/client-sso-oidc': 3.583.0 '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/client-sso-oidc': 3.583.0 '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/types@3.468.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/types@3.567.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/types@3.577.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-endpoints@3.478.0': dependencies: '@aws-sdk/types': 3.468.0 '@smithy/util-endpoints': 1.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-endpoints@3.567.0': dependencies: '@aws-sdk/types': 3.567.0 '@smithy/types': 2.12.0 '@smithy/util-endpoints': 1.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-endpoints@3.583.0': dependencies: '@aws-sdk/types': 3.577.0 '@smithy/types': 3.0.0 '@smithy/util-endpoints': 2.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-locate-window@3.568.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-user-agent-browser@3.468.0': dependencies: '@aws-sdk/types': 3.468.0 '@smithy/types': 2.12.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-user-agent-browser@3.567.0': dependencies: '@aws-sdk/types': 3.567.0 '@smithy/types': 2.12.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-user-agent-browser@3.577.0': dependencies: '@aws-sdk/types': 3.577.0 '@smithy/types': 3.0.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-user-agent-node@3.470.0': dependencies: '@aws-sdk/types': 3.468.0 '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-user-agent-node@3.568.0': dependencies: '@aws-sdk/types': 3.567.0 '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-user-agent-node@3.577.0': dependencies: '@aws-sdk/types': 3.577.0 '@smithy/node-config-provider': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@aws-sdk/util-utf8-browser@3.259.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@babel/code-frame@7.10.4': dependencies: @@ -11397,7 +11810,7 @@ snapshots: '@babel/traverse': 7.24.6 '@babel/types': 7.24.6 convert-source-map: 2.0.0 - debug: 4.3.5 + debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -11458,7 +11871,7 @@ snapshots: '@babel/core': 7.24.6 '@babel/helper-compilation-targets': 7.24.6 '@babel/helper-plugin-utils': 7.24.6 - debug: 4.3.5 + debug: 4.3.7 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -12330,7 +12743,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.6 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 - debug: 4.3.5 + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12379,10 +12792,12 @@ snapshots: '@cloudflare/workerd-windows-64@1.20240712.0': optional: true - '@cloudflare/workers-types@4.20240512.0': {} - '@cloudflare/workers-types@4.20240524.0': {} + '@cloudflare/workers-types@4.20241004.0': {} + + '@cloudflare/workers-types@4.20241112.0': {} + '@colors/colors@1.5.0': optional: true @@ -12411,11 +12826,11 @@ snapshots: '@dprint/win32-x64@0.46.3': optional: true - '@drizzle-team/brocli@0.8.2': {} + '@drizzle-team/brocli@0.10.2': {} '@drizzle-team/studio@0.0.5': {} - '@electric-sql/pglite@0.1.5': {} + '@electric-sql/pglite@0.2.12': {} '@esbuild-kit/core-utils@3.1.0': dependencies: @@ -12915,7 +13330,7 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.5 + debug: 4.3.7 espree: 10.0.1 globals: 14.0.0 ignore: 5.3.1 @@ -12972,7 +13387,7 @@ snapshots: chalk: 4.1.2 ci-info: 3.9.0 connect: 3.7.0 - debug: 4.3.5 + debug: 4.3.7 env-editor: 0.4.2 fast-glob: 3.3.2 find-yarn-workspace-root: 2.0.0 @@ -13040,7 +13455,7 @@ snapshots: '@expo/plist': 0.1.3 '@expo/sdk-runtime-versions': 1.0.0 chalk: 4.1.2 - debug: 4.3.5 + debug: 4.3.7 find-up: 5.0.0 getenv: 1.0.0 glob: 7.1.6 @@ -13085,14 +13500,14 @@ snapshots: rimraf: 2.7.1 sudo-prompt: 8.2.5 tmp: 0.0.33 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - supports-color '@expo/env@0.3.0': dependencies: chalk: 4.1.2 - debug: 4.3.5 + debug: 4.3.7 dotenv: 16.4.5 dotenv-expand: 11.0.6 getenv: 1.0.0 @@ -13131,7 +13546,7 @@ snapshots: '@expo/json-file': 8.3.3 '@expo/spawn-async': 1.7.2 chalk: 4.1.2 - debug: 4.3.5 + debug: 4.3.7 find-yarn-workspace-root: 2.0.0 fs-extra: 9.1.0 getenv: 1.0.0 @@ -13157,7 +13572,7 @@ snapshots: find-up: 5.0.0 find-yarn-workspace-root: 2.0.0 js-yaml: 3.14.1 - micromatch: 4.0.7 + micromatch: 4.0.8 npm-package-arg: 7.0.0 ora: 3.4.0 split: 1.0.1 @@ -13177,7 +13592,7 @@ snapshots: '@expo/image-utils': 0.5.1(encoding@0.1.13) '@expo/json-file': 8.3.3 '@react-native/normalize-colors': 0.74.83 - debug: 4.3.5 + debug: 4.3.7 expo-modules-autolinking: 1.11.1 fs-extra: 9.1.0 resolve-from: 5.0.0 @@ -13352,11 +13767,6 @@ snapshots: '@jridgewell/set-array@1.2.1': {} - '@jridgewell/source-map@0.3.3': - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - '@jridgewell/source-map@0.3.6': dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -13366,6 +13776,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.18': dependencies: '@jridgewell/resolve-uri': 3.1.0 @@ -13381,175 +13793,133 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@libsql/client@0.4.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': + '@libsql/client-wasm@0.10.0': dependencies: - '@libsql/core': 0.4.3 - '@libsql/hrana-client': 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + '@libsql/core': 0.10.0 js-base64: 3.7.7 - optionalDependencies: - libsql: 0.2.0 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@libsql/client@0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': - dependencies: - '@libsql/core': 0.5.6 - '@libsql/hrana-client': 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) - js-base64: 3.7.7 - libsql: 0.3.18 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - '@libsql/client@0.6.0': + '@libsql/client@0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3)': dependencies: - '@libsql/core': 0.6.0 - '@libsql/hrana-client': 0.6.0 + '@libsql/core': 0.10.0 + '@libsql/hrana-client': 0.6.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) js-base64: 3.7.7 - libsql: 0.3.18 + libsql: 0.4.1 + promise-limit: 2.7.0 transitivePeerDependencies: - bufferutil - utf-8-validate - optional: true - '@libsql/core@0.4.3': + '@libsql/core@0.10.0': dependencies: js-base64: 3.7.7 - '@libsql/core@0.5.6': - dependencies: - js-base64: 3.7.7 - - '@libsql/core@0.6.0': - dependencies: - js-base64: 3.7.7 + '@libsql/darwin-arm64@0.3.19': optional: true - '@libsql/darwin-arm64@0.2.0': + '@libsql/darwin-arm64@0.4.1': optional: true - '@libsql/darwin-arm64@0.3.18': + '@libsql/darwin-x64@0.3.19': optional: true - '@libsql/darwin-x64@0.2.0': + '@libsql/darwin-x64@0.4.1': optional: true - '@libsql/darwin-x64@0.3.18': - optional: true - - '@libsql/hrana-client@0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': - dependencies: - '@libsql/isomorphic-fetch': 0.1.12(encoding@0.1.13) - '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3) - js-base64: 3.7.7 - node-fetch: 3.3.2 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@libsql/hrana-client@0.6.0': + '@libsql/hrana-client@0.6.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)': dependencies: - '@libsql/isomorphic-fetch': 0.2.1 + '@libsql/isomorphic-fetch': 0.2.5 '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3) js-base64: 3.7.7 node-fetch: 3.3.2 transitivePeerDependencies: - bufferutil - utf-8-validate - optional: true - '@libsql/isomorphic-fetch@0.1.12(encoding@0.1.13)': - dependencies: - '@types/node-fetch': 2.6.11 - node-fetch: 2.7.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding - - '@libsql/isomorphic-fetch@0.2.1': - optional: true + '@libsql/isomorphic-fetch@0.2.5': {} '@libsql/isomorphic-ws@0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3)': dependencies: '@types/ws': 8.5.11 - ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - utf-8-validate - '@libsql/linux-arm64-gnu@0.2.0': + '@libsql/linux-arm64-gnu@0.3.19': optional: true - '@libsql/linux-arm64-gnu@0.3.18': + '@libsql/linux-arm64-gnu@0.4.1': optional: true - '@libsql/linux-arm64-musl@0.2.0': + '@libsql/linux-arm64-musl@0.3.19': optional: true - '@libsql/linux-arm64-musl@0.3.18': + '@libsql/linux-arm64-musl@0.4.1': optional: true - '@libsql/linux-x64-gnu@0.2.0': + '@libsql/linux-x64-gnu@0.3.19': optional: true - '@libsql/linux-x64-gnu@0.3.18': + '@libsql/linux-x64-gnu@0.4.1': optional: true - '@libsql/linux-x64-musl@0.2.0': + '@libsql/linux-x64-musl@0.3.19': optional: true - '@libsql/linux-x64-musl@0.3.18': + '@libsql/linux-x64-musl@0.4.1': optional: true - '@libsql/win32-x64-msvc@0.2.0': + '@libsql/win32-x64-msvc@0.3.19': optional: true - '@libsql/win32-x64-msvc@0.3.18': + '@libsql/win32-x64-msvc@0.4.1': optional: true - '@miniflare/core@2.14.2': + '@miniflare/core@2.14.4': dependencies: '@iarna/toml': 2.2.5 - '@miniflare/queues': 2.14.2 - '@miniflare/shared': 2.14.2 - '@miniflare/watcher': 2.14.2 + '@miniflare/queues': 2.14.4 + '@miniflare/shared': 2.14.4 + '@miniflare/watcher': 2.14.4 busboy: 1.6.0 dotenv: 10.0.0 kleur: 4.1.5 set-cookie-parser: 2.6.0 - undici: 5.28.2 + undici: 5.28.4 urlpattern-polyfill: 4.0.3 - '@miniflare/d1@2.14.2': + '@miniflare/d1@2.14.4': dependencies: - '@miniflare/core': 2.14.2 - '@miniflare/shared': 2.14.2 + '@miniflare/core': 2.14.4 + '@miniflare/shared': 2.14.4 - '@miniflare/queues@2.14.2': + '@miniflare/queues@2.14.4': dependencies: - '@miniflare/shared': 2.14.2 + '@miniflare/shared': 2.14.4 - '@miniflare/shared@2.14.2': + '@miniflare/shared@2.14.4': dependencies: '@types/better-sqlite3': 7.6.10 kleur: 4.1.5 npx-import: 1.1.4 picomatch: 2.3.1 - '@miniflare/watcher@2.14.2': + '@miniflare/watcher@2.14.4': dependencies: - '@miniflare/shared': 2.14.2 + '@miniflare/shared': 2.14.4 '@neon-rs/load@0.0.4': {} - '@neondatabase/serverless@0.7.2': + '@neondatabase/serverless@0.10.0': dependencies: - '@types/pg': 8.6.6 + '@types/pg': 8.11.6 - '@neondatabase/serverless@0.9.0': + '@neondatabase/serverless@0.10.3': + dependencies: + '@types/pg': 8.11.6 + optional: true + + '@neondatabase/serverless@0.7.2': dependencies: '@types/pg': 8.6.6 @@ -13941,39 +14311,40 @@ snapshots: transitivePeerDependencies: - supports-color - '@rollup/plugin-terser@0.4.1(rollup@3.20.7)': - dependencies: - serialize-javascript: 6.0.1 - smob: 0.0.6 - terser: 5.17.1 - optionalDependencies: - rollup: 3.20.7 - - '@rollup/plugin-terser@0.4.1(rollup@3.27.2)': + '@rollup/plugin-terser@0.4.4(rollup@4.27.3)': dependencies: serialize-javascript: 6.0.1 - smob: 0.0.6 - terser: 5.17.1 + smob: 1.5.0 + terser: 5.31.0 optionalDependencies: - rollup: 3.27.2 + rollup: 4.27.3 - '@rollup/plugin-typescript@11.1.0(rollup@3.20.7)(tslib@2.6.2)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@rollup/plugin-typescript@11.1.0(rollup@3.20.7)(tslib@2.8.1)(typescript@5.6.3)': dependencies: '@rollup/pluginutils': 5.0.2(rollup@3.20.7) resolve: 1.22.1 - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 optionalDependencies: rollup: 3.20.7 - tslib: 2.6.2 + tslib: 2.8.1 - '@rollup/plugin-typescript@11.1.1(rollup@3.27.2)(tslib@2.6.2)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@rollup/plugin-typescript@11.1.1(rollup@3.27.2)(tslib@2.8.1)(typescript@5.6.3)': dependencies: '@rollup/pluginutils': 5.0.2(rollup@3.27.2) resolve: 1.22.2 - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 optionalDependencies: rollup: 3.27.2 - tslib: 2.6.2 + tslib: 2.8.1 + + '@rollup/plugin-typescript@11.1.6(rollup@4.27.3)(tslib@2.8.1)(typescript@5.6.3)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.27.3) + resolve: 1.22.8 + typescript: 5.6.3 + optionalDependencies: + rollup: 4.27.3 + tslib: 2.8.1 '@rollup/pluginutils@5.0.2(rollup@3.20.7)': dependencies: @@ -13991,102 +14362,116 @@ snapshots: optionalDependencies: rollup: 3.27.2 - '@rollup/rollup-android-arm-eabi@4.18.0': - optional: true + '@rollup/pluginutils@5.1.3(rollup@4.27.3)': + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.27.3 '@rollup/rollup-android-arm-eabi@4.18.1': optional: true - '@rollup/rollup-android-arm64@4.18.0': + '@rollup/rollup-android-arm-eabi@4.27.3': optional: true '@rollup/rollup-android-arm64@4.18.1': optional: true - '@rollup/rollup-darwin-arm64@4.18.0': + '@rollup/rollup-android-arm64@4.27.3': optional: true '@rollup/rollup-darwin-arm64@4.18.1': optional: true - '@rollup/rollup-darwin-x64@4.18.0': + '@rollup/rollup-darwin-arm64@4.27.3': optional: true '@rollup/rollup-darwin-x64@4.18.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + '@rollup/rollup-darwin-x64@4.27.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.27.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.27.3': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.18.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.18.0': + '@rollup/rollup-linux-arm-gnueabihf@4.27.3': optional: true '@rollup/rollup-linux-arm-musleabihf@4.18.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.18.0': + '@rollup/rollup-linux-arm-musleabihf@4.27.3': optional: true '@rollup/rollup-linux-arm64-gnu@4.18.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.18.0': + '@rollup/rollup-linux-arm64-gnu@4.27.3': optional: true '@rollup/rollup-linux-arm64-musl@4.18.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + '@rollup/rollup-linux-arm64-musl@4.27.3': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.18.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.18.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.27.3': optional: true '@rollup/rollup-linux-riscv64-gnu@4.18.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.18.0': + '@rollup/rollup-linux-riscv64-gnu@4.27.3': optional: true '@rollup/rollup-linux-s390x-gnu@4.18.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.18.0': + '@rollup/rollup-linux-s390x-gnu@4.27.3': optional: true '@rollup/rollup-linux-x64-gnu@4.18.1': optional: true - '@rollup/rollup-linux-x64-musl@4.18.0': + '@rollup/rollup-linux-x64-gnu@4.27.3': optional: true '@rollup/rollup-linux-x64-musl@4.18.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.18.0': + '@rollup/rollup-linux-x64-musl@4.27.3': optional: true '@rollup/rollup-win32-arm64-msvc@4.18.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.18.0': + '@rollup/rollup-win32-arm64-msvc@4.27.3': optional: true '@rollup/rollup-win32-ia32-msvc@4.18.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.18.0': + '@rollup/rollup-win32-ia32-msvc@4.27.3': optional: true '@rollup/rollup-win32-x64-msvc@4.18.1': optional: true + '@rollup/rollup-win32-x64-msvc@4.27.3': + optional: true + '@segment/loosely-validate-event@2.0.0': dependencies: component-type: 1.2.2 @@ -14102,10 +14487,12 @@ snapshots: '@sinclair/typebox@0.27.8': {} - '@sinclair/typebox@0.29.6': {} + '@sinclair/typebox@0.34.10': {} '@sindresorhus/is@4.6.0': {} + '@sindresorhus/merge-streams@2.3.0': {} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -14117,12 +14504,12 @@ snapshots: '@smithy/abort-controller@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/abort-controller@3.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/config-resolver@2.2.0': dependencies: @@ -14130,7 +14517,7 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/util-config-provider': 2.3.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/config-resolver@3.0.0': dependencies: @@ -14138,7 +14525,7 @@ snapshots: '@smithy/types': 3.0.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/core@1.4.2': dependencies: @@ -14149,7 +14536,7 @@ snapshots: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/core@2.0.1': dependencies: @@ -14160,7 +14547,7 @@ snapshots: '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/credential-provider-imds@2.3.0': dependencies: @@ -14168,7 +14555,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 '@smithy/url-parser': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/credential-provider-imds@3.0.0': dependencies: @@ -14176,37 +14563,37 @@ snapshots: '@smithy/property-provider': 3.0.0 '@smithy/types': 3.0.0 '@smithy/url-parser': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/eventstream-codec@2.2.0': dependencies: '@aws-crypto/crc32': 3.0.0 '@smithy/types': 2.12.0 '@smithy/util-hex-encoding': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/eventstream-serde-browser@2.2.0': dependencies: '@smithy/eventstream-serde-universal': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/eventstream-serde-config-resolver@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/eventstream-serde-node@2.2.0': dependencies: '@smithy/eventstream-serde-universal': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/eventstream-serde-universal@2.2.0': dependencies: '@smithy/eventstream-codec': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/fetch-http-handler@2.5.0': dependencies: @@ -14214,7 +14601,7 @@ snapshots: '@smithy/querystring-builder': 2.2.0 '@smithy/types': 2.12.0 '@smithy/util-base64': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/fetch-http-handler@3.0.1': dependencies: @@ -14222,51 +14609,51 @@ snapshots: '@smithy/querystring-builder': 3.0.0 '@smithy/types': 3.0.0 '@smithy/util-base64': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/hash-node@2.2.0': dependencies: '@smithy/types': 2.12.0 '@smithy/util-buffer-from': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/hash-node@3.0.0': dependencies: '@smithy/types': 3.0.0 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/invalid-dependency@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/invalid-dependency@3.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/is-array-buffer@2.2.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/is-array-buffer@3.0.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/middleware-content-length@2.2.0': dependencies: '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/middleware-content-length@3.0.0': dependencies: '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/middleware-endpoint@2.5.1': dependencies: @@ -14276,7 +14663,7 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/url-parser': 2.2.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/middleware-endpoint@3.0.0': dependencies: @@ -14286,7 +14673,7 @@ snapshots: '@smithy/types': 3.0.0 '@smithy/url-parser': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/middleware-retry@2.3.1': dependencies: @@ -14297,7 +14684,7 @@ snapshots: '@smithy/types': 2.12.0 '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 uuid: 9.0.1 '@smithy/middleware-retry@3.0.1': @@ -14309,42 +14696,42 @@ snapshots: '@smithy/types': 3.0.0 '@smithy/util-middleware': 3.0.0 '@smithy/util-retry': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 uuid: 9.0.1 '@smithy/middleware-serde@2.3.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/middleware-serde@3.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/middleware-stack@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/middleware-stack@3.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/node-config-provider@2.3.0': dependencies: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/node-config-provider@3.0.0': dependencies: '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/node-http-handler@2.5.0': dependencies: @@ -14352,7 +14739,7 @@ snapshots: '@smithy/protocol-http': 3.3.0 '@smithy/querystring-builder': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/node-http-handler@3.0.0': dependencies: @@ -14360,49 +14747,49 @@ snapshots: '@smithy/protocol-http': 4.0.0 '@smithy/querystring-builder': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/property-provider@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/property-provider@3.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/protocol-http@3.3.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/protocol-http@4.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/querystring-builder@2.2.0': dependencies: '@smithy/types': 2.12.0 '@smithy/util-uri-escape': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/querystring-builder@3.0.0': dependencies: '@smithy/types': 3.0.0 '@smithy/util-uri-escape': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/querystring-parser@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/querystring-parser@3.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/service-error-classification@2.1.5': dependencies: @@ -14415,12 +14802,12 @@ snapshots: '@smithy/shared-ini-file-loader@2.4.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/shared-ini-file-loader@3.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/signature-v4@2.3.0': dependencies: @@ -14430,7 +14817,7 @@ snapshots: '@smithy/util-middleware': 2.2.0 '@smithy/util-uri-escape': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/signature-v4@3.0.0': dependencies: @@ -14440,7 +14827,7 @@ snapshots: '@smithy/util-middleware': 3.0.0 '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/smithy-client@2.5.1': dependencies: @@ -14449,7 +14836,7 @@ snapshots: '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 '@smithy/util-stream': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/smithy-client@3.0.1': dependencies: @@ -14458,73 +14845,73 @@ snapshots: '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 '@smithy/util-stream': 3.0.1 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/types@2.12.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/types@3.0.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/url-parser@2.2.0': dependencies: '@smithy/querystring-parser': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/url-parser@3.0.0': dependencies: '@smithy/querystring-parser': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-base64@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-base64@3.0.0': dependencies: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-body-length-browser@2.2.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-body-length-browser@3.0.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-body-length-node@2.3.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-body-length-node@3.0.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-buffer-from@3.0.0': dependencies: '@smithy/is-array-buffer': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-config-provider@2.3.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-config-provider@3.0.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-defaults-mode-browser@2.2.1': dependencies: @@ -14532,7 +14919,7 @@ snapshots: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-defaults-mode-browser@3.0.1': dependencies: @@ -14540,7 +14927,7 @@ snapshots: '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-defaults-mode-node@2.3.1': dependencies: @@ -14550,7 +14937,7 @@ snapshots: '@smithy/property-provider': 2.2.0 '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-defaults-mode-node@3.0.1': dependencies: @@ -14560,49 +14947,49 @@ snapshots: '@smithy/property-provider': 3.0.0 '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-endpoints@1.2.0': dependencies: '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-endpoints@2.0.0': dependencies: '@smithy/node-config-provider': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-hex-encoding@2.2.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-hex-encoding@3.0.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-middleware@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-middleware@3.0.0': dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-retry@2.2.0': dependencies: '@smithy/service-error-classification': 2.1.5 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-retry@3.0.0': dependencies: '@smithy/service-error-classification': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-stream@2.2.0': dependencies: @@ -14613,7 +15000,7 @@ snapshots: '@smithy/util-buffer-from': 2.2.0 '@smithy/util-hex-encoding': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-stream@3.0.1': dependencies: @@ -14624,31 +15011,31 @@ snapshots: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-uri-escape@2.2.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-uri-escape@3.0.0': dependencies: - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-utf8@3.0.0': dependencies: '@smithy/util-buffer-from': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.1 '@smithy/util-waiter@2.2.0': dependencies: '@smithy/abort-controller': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 '@tidbcloud/serverless@0.1.1': {} @@ -14689,11 +15076,17 @@ snapshots: dependencies: '@types/node': 20.12.12 + '@types/better-sqlite3@7.6.12': + dependencies: + '@types/node': 20.12.12 + '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 '@types/node': 20.12.12 + '@types/braces@3.0.4': {} + '@types/connect@3.4.38': dependencies: '@types/node': 20.12.12 @@ -14709,12 +15102,20 @@ snapshots: '@types/node': 20.12.12 '@types/ssh2': 1.15.0 + '@types/dockerode@3.3.32': + dependencies: + '@types/docker-modem': 3.0.6 + '@types/node': 20.12.12 + '@types/ssh2': 1.15.0 + '@types/emscripten@1.39.11': {} '@types/estree@1.0.1': {} '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} + '@types/express-serve-static-core@4.19.0': dependencies: '@types/node': 20.12.12 @@ -14761,17 +15162,16 @@ snapshots: dependencies: '@types/node': 20.12.12 + '@types/micromatch@4.0.9': + dependencies: + '@types/braces': 3.0.4 + '@types/mime@1.3.5': {} '@types/minimatch@5.1.2': {} '@types/minimist@1.2.2': {} - '@types/node-fetch@2.6.11': - dependencies: - '@types/node': 20.12.12 - form-data: 4.0.0 - '@types/node-forge@1.3.11': dependencies: '@types/node': 20.12.12 @@ -14790,6 +15190,10 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/node@22.9.1': + dependencies: + undici-types: 6.19.8 + '@types/normalize-package-data@2.4.1': {} '@types/pg@8.11.6': @@ -14845,6 +15249,8 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/uuid@10.0.0': {} + '@types/uuid@9.0.8': {} '@types/which@3.0.0': {} @@ -14863,13 +15269,13 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/eslint-plugin@6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0)(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.9.0 - '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.6.3) '@typescript-eslint/scope-manager': 6.7.3 - '@typescript-eslint/type-utils': 6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) - '@typescript-eslint/utils': 6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/type-utils': 6.7.3(eslint@8.50.0)(typescript@5.6.3) + '@typescript-eslint/utils': 6.7.3(eslint@8.50.0)(typescript@5.6.3) '@typescript-eslint/visitor-keys': 6.7.3 debug: 4.3.4 eslint: 8.50.0 @@ -14877,33 +15283,33 @@ snapshots: ignore: 5.2.4 natural-compare: 1.4.0 semver: 7.6.2 - ts-api-utils: 1.0.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + ts-api-utils: 1.0.3(typescript@5.6.3) optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/eslint-plugin@7.16.1(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/parser': 7.16.1(eslint@8.57.0)(typescript@5.6.3) '@typescript-eslint/scope-manager': 7.16.1 - '@typescript-eslint/type-utils': 7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) - '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/type-utils': 7.16.1(eslint@8.57.0)(typescript@5.6.3) + '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.16.1 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/experimental-utils@5.62.0(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/experimental-utils@5.62.0(eslint@8.50.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/utils': 5.62.0(eslint@8.50.0)(typescript@5.6.3) eslint: 8.50.0 transitivePeerDependencies: - supports-color @@ -14922,29 +15328,29 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 6.7.3 '@typescript-eslint/types': 6.7.3 - '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.6.3) '@typescript-eslint/visitor-keys': 6.7.3 debug: 4.3.4 eslint: 8.50.0 optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 7.16.1 '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.16.1 debug: 4.3.4 eslint: 8.57.0 optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -14981,27 +15387,27 @@ snapshots: '@typescript-eslint/types': 7.16.1 '@typescript-eslint/visitor-keys': 7.16.1 - '@typescript-eslint/type-utils@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/type-utils@6.7.3(eslint@8.50.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) - '@typescript-eslint/utils': 6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.6.3) + '@typescript-eslint/utils': 6.7.3(eslint@8.50.0)(typescript@5.6.3) debug: 4.3.4 eslint: 8.50.0 - ts-api-utils: 1.0.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + ts-api-utils: 1.0.3(typescript@5.6.3) optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/type-utils@7.16.1(eslint@8.57.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) - '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.6.3) + '@typescript-eslint/utils': 7.16.1(eslint@8.57.0)(typescript@5.6.3) debug: 4.3.4 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -15013,7 +15419,7 @@ snapshots: '@typescript-eslint/types@7.16.1': {} - '@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -15021,9 +15427,9 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 - tsutils: 3.21.0(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + tsutils: 3.21.0(typescript@5.6.3) optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -15041,7 +15447,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@6.7.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/typescript-estree@6.7.3(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 6.7.3 '@typescript-eslint/visitor-keys': 6.7.3 @@ -15049,13 +15455,13 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 - ts-api-utils: 1.0.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + ts-api-utils: 1.0.3(typescript@5.6.3) optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@7.16.1(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/typescript-estree@7.16.1(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 7.16.1 '@typescript-eslint/visitor-keys': 7.16.1 @@ -15064,20 +15470,20 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.4 semver: 7.6.2 - ts-api-utils: 1.3.0(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@5.62.0(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/utils@5.62.0(eslint@8.50.0)(typescript@5.6.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) '@types/json-schema': 7.0.13 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.6.3) eslint: 8.50.0 eslint-scope: 5.1.1 semver: 7.6.2 @@ -15099,26 +15505,26 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/utils@6.7.3(eslint@8.50.0)(typescript@5.6.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) '@types/json-schema': 7.0.13 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 6.7.3 '@typescript-eslint/types': 6.7.3 - '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.6.3) eslint: 8.50.0 semver: 7.6.2 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@7.16.1(eslint@8.57.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@typescript-eslint/utils@7.16.1(eslint@8.57.0)(typescript@5.6.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@typescript-eslint/scope-manager': 7.16.1 '@typescript-eslint/types': 7.16.1 - '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.6.3) eslint: 8.57.0 transitivePeerDependencies: - supports-color @@ -15182,22 +15588,64 @@ snapshots: '@vitest/utils': 1.6.0 chai: 4.4.1 + '@vitest/expect@2.1.2': + dependencies: + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0))': + dependencies: + '@vitest/spy': 2.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.11 + optionalDependencies: + vite: 5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0) + + '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.3.3(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0))': + dependencies: + '@vitest/spy': 2.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.11 + optionalDependencies: + vite: 5.3.3(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0) + + '@vitest/pretty-format@2.1.2': + dependencies: + tinyrainbow: 1.2.0 + '@vitest/runner@1.6.0': dependencies: '@vitest/utils': 1.6.0 p-limit: 5.0.0 pathe: 1.1.2 + '@vitest/runner@2.1.2': + dependencies: + '@vitest/utils': 2.1.2 + pathe: 1.1.2 + '@vitest/snapshot@1.6.0': dependencies: magic-string: 0.30.10 pathe: 1.1.2 pretty-format: 29.7.0 + '@vitest/snapshot@2.1.2': + dependencies: + '@vitest/pretty-format': 2.1.2 + magic-string: 0.30.11 + pathe: 1.1.2 + '@vitest/spy@1.6.0': dependencies: tinyspy: 2.2.1 + '@vitest/spy@2.1.2': + dependencies: + tinyspy: 3.0.2 + '@vitest/ui@1.6.0(vitest@1.6.0)': dependencies: '@vitest/utils': 1.6.0 @@ -15208,6 +15656,18 @@ snapshots: picocolors: 1.0.1 sirv: 2.0.4 vitest: 1.6.0(@types/node@18.19.33)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) + optional: true + + '@vitest/ui@1.6.0(vitest@2.1.2)': + dependencies: + '@vitest/utils': 1.6.0 + fast-glob: 3.3.2 + fflate: 0.8.2 + flatted: 3.3.1 + pathe: 1.1.2 + picocolors: 1.0.1 + sirv: 2.0.4 + vitest: 2.1.2(@types/node@20.12.12)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) '@vitest/utils@1.6.0': dependencies: @@ -15216,9 +15676,15 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 - '@xata.io/client@0.29.4(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))': + '@vitest/utils@2.1.2': + dependencies: + '@vitest/pretty-format': 2.1.2 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + + '@xata.io/client@0.29.4(typescript@5.6.3)': dependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 '@xmldom/xmldom@0.7.13': {} @@ -15250,8 +15716,6 @@ snapshots: acorn@8.11.3: {} - acorn@8.8.2: {} - agent-base@6.0.2: dependencies: debug: 4.3.4 @@ -15292,6 +15756,10 @@ snapshots: dependencies: type-fest: 3.13.1 + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + ansi-fragments@0.2.1: dependencies: colorette: 1.4.0 @@ -15304,6 +15772,8 @@ snapshots: ansi-regex@6.0.1: {} + ansi-regex@6.1.0: {} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -15432,13 +15902,15 @@ snapshots: assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + ast-types@0.15.2: dependencies: - tslib: 2.6.2 + tslib: 2.8.1 ast-types@0.16.1: dependencies: - tslib: 2.6.2 + tslib: 2.8.1 astral-regex@1.0.0: {} @@ -15508,6 +15980,9 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 + aws-ssl-profiles@1.1.1: + optional: true + axios@1.6.8: dependencies: follow-redirects: 1.15.6 @@ -15580,11 +16055,10 @@ snapshots: dependencies: open: 8.4.2 - better-sqlite3@10.0.0: + better-sqlite3@11.5.0: dependencies: bindings: 1.5.0 prebuild-install: 7.1.2 - optional: true better-sqlite3@8.7.0: dependencies: @@ -15654,10 +16128,6 @@ snapshots: dependencies: balanced-match: 1.0.2 - braces@3.0.2: - dependencies: - fill-range: 7.0.1 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -15757,7 +16227,7 @@ snapshots: '@npmcli/fs': 3.1.1 fs-minipass: 3.0.3 glob: 10.4.1 - lru-cache: 10.2.2 + lru-cache: 10.4.3 minipass: 7.1.2 minipass-collect: 2.0.1 minipass-flush: 1.0.5 @@ -15805,7 +16275,7 @@ snapshots: capnp-ts@0.7.0: dependencies: debug: 4.3.4 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -15828,6 +16298,14 @@ snapshots: pathval: 1.1.1 type-detect: 4.0.8 + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -15849,6 +16327,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + check-error@2.1.1: {} + chokidar@3.5.3: dependencies: anymatch: 3.1.3 @@ -15896,6 +16376,8 @@ snapshots: ci-parallel-vars@1.0.1: {} + cjs-module-lexer@1.4.1: {} + clean-regexp@1.0.0: dependencies: escape-string-regexp: 1.0.5 @@ -15924,6 +16406,15 @@ snapshots: dependencies: restore-cursor: 3.1.0 + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + cli-spinners@2.9.2: {} cli-table3@0.6.3: @@ -15932,6 +16423,12 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + cli-truncate@3.1.0: dependencies: slice-ansi: 5.0.0 @@ -16093,6 +16590,11 @@ snapshots: dependencies: is-what: 4.1.16 + copy-file@11.0.0: + dependencies: + graceful-fs: 4.2.11 + p-event: 6.0.1 + core-js-compat@3.37.1: dependencies: browserslist: 4.23.0 @@ -16134,6 +16636,15 @@ snapshots: p-filter: 3.0.0 p-map: 6.0.0 + cpy@11.1.0: + dependencies: + copy-file: 11.0.0 + globby: 14.0.2 + junk: 4.0.1 + micromatch: 4.0.8 + p-filter: 4.1.0 + p-map: 7.0.2 + create-require@1.1.1: {} cross-env@7.0.3: @@ -16229,6 +16740,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.7: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decompress-response@6.0.0: @@ -16239,6 +16754,8 @@ snapshots: dependencies: type-detect: 4.0.8 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -16330,6 +16847,15 @@ snapshots: transitivePeerDependencies: - supports-color + docker-modem@5.0.3: + dependencies: + debug: 4.3.7 + readable-stream: 3.6.2 + split-ca: 1.0.1 + ssh2: 1.15.0 + transitivePeerDependencies: + - supports-color + dockerode@3.3.5: dependencies: '@balena/dockerignore': 1.0.2 @@ -16338,6 +16864,14 @@ snapshots: transitivePeerDependencies: - supports-color + dockerode@4.0.2: + dependencies: + '@balena/dockerignore': 1.0.2 + docker-modem: 5.0.3 + tar-fs: 2.0.1 + transitivePeerDependencies: + - supports-color + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -16385,38 +16919,33 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-kit@0.21.2: + drizzle-kit@0.25.0-b1faa33: dependencies: + '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.5.5 - commander: 9.5.0 - env-paths: 3.0.0 esbuild: 0.19.12 esbuild-register: 3.5.0(esbuild@0.19.12) - glob: 8.1.0 - hanji: 0.0.5 - json-diff: 0.9.0 - zod: 3.23.7 transitivePeerDependencies: - supports-color - drizzle-orm@0.27.2(@aws-sdk/client-rds-data@3.583.0)(@cloudflare/workers-types@4.20240524.0)(@libsql/client@0.6.0)(@neondatabase/serverless@0.9.3)(@opentelemetry/api@1.8.0)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(@vercel/postgres@0.8.0)(better-sqlite3@10.0.0)(bun-types@1.0.3)(knex@3.1.0(better-sqlite3@10.0.0)(mysql2@3.9.8)(pg@8.11.5)(sqlite3@5.1.7))(kysely@0.27.3)(mysql2@3.9.8)(pg@8.11.5)(postgres@3.4.4)(sql.js@1.10.3)(sqlite3@5.1.7): + drizzle-orm@0.27.2(@aws-sdk/client-rds-data@3.583.0)(@cloudflare/workers-types@4.20241112.0)(@libsql/client@0.10.0)(@neondatabase/serverless@0.10.3)(@opentelemetry/api@1.8.0)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.12)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(@vercel/postgres@0.8.0)(better-sqlite3@11.5.0)(bun-types@1.0.3)(knex@2.5.1(better-sqlite3@11.5.0)(mysql2@3.11.0)(pg@8.13.1)(sqlite3@5.1.7))(kysely@0.25.0)(mysql2@3.11.0)(pg@8.13.1)(postgres@3.4.4)(sql.js@1.10.3)(sqlite3@5.1.7): optionalDependencies: '@aws-sdk/client-rds-data': 3.583.0 - '@cloudflare/workers-types': 4.20240524.0 - '@libsql/client': 0.6.0 - '@neondatabase/serverless': 0.9.3 + '@cloudflare/workers-types': 4.20241112.0 + '@libsql/client': 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) + '@neondatabase/serverless': 0.10.3 '@opentelemetry/api': 1.8.0 '@planetscale/database': 1.18.0 - '@types/better-sqlite3': 7.6.10 + '@types/better-sqlite3': 7.6.12 '@types/pg': 8.11.6 '@types/sql.js': 1.4.9 '@vercel/postgres': 0.8.0 - better-sqlite3: 10.0.0 + better-sqlite3: 11.5.0 bun-types: 1.0.3 - knex: 3.1.0(better-sqlite3@10.0.0)(mysql2@3.9.8)(pg@8.11.5)(sqlite3@5.1.7) - kysely: 0.27.3 - mysql2: 3.9.8 - pg: 8.11.5 + knex: 2.5.1(better-sqlite3@11.5.0)(mysql2@3.11.0)(pg@8.13.1)(sqlite3@5.1.7) + kysely: 0.25.0 + mysql2: 3.11.0 + pg: 8.13.1 postgres: 3.4.4 sql.js: 1.10.3 sqlite3: 5.1.7 @@ -16461,6 +16990,8 @@ snapshots: envinfo@7.13.0: {} + environment@1.1.0: {} + eol@0.9.1: {} err-code@2.0.3: @@ -16911,17 +17442,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint-import-resolver-node@0.3.9)(eslint@8.50.0): + eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@8.50.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.6.3) eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0): + eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0): dependencies: array-includes: 3.1.6 array.prototype.findlastindex: 1.2.2 @@ -16931,7 +17462,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint-import-resolver-node@0.3.9)(eslint@8.50.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@8.50.0) has: 1.0.3 is-core-module: 2.13.0 is-glob: 4.0.3 @@ -16942,7 +17473,7 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.14.2 optionalDependencies: - '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.6.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -16978,12 +17509,12 @@ snapshots: semver: 7.6.2 strip-indent: 3.0.0 - eslint-plugin-unused-imports@3.0.0(@typescript-eslint/eslint-plugin@6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0): + eslint-plugin-unused-imports@3.0.0(@typescript-eslint/eslint-plugin@6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0): dependencies: eslint: 8.50.0 eslint-rule-composer: 0.3.0 optionalDependencies: - '@typescript-eslint/eslint-plugin': 6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(eslint@8.50.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + '@typescript-eslint/eslint-plugin': 6.7.3(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.6.3))(eslint@8.50.0)(typescript@5.6.3) eslint-rule-composer@0.3.0: {} @@ -17282,7 +17813,7 @@ snapshots: dependencies: invariant: 2.2.4 - expo-sqlite@13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-sqlite@14.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): dependencies: '@expo/websql': 1.0.1 expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) @@ -17362,7 +17893,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.5 + micromatch: 4.0.7 fast-glob@3.3.2: dependencies: @@ -17432,10 +17963,6 @@ snapshots: file-uri-to-path@1.0.0: {} - fill-range@7.0.1: - dependencies: - to-regex-range: 5.0.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -17491,7 +18018,7 @@ snapshots: find-yarn-workspace-root@2.0.0: dependencies: - micromatch: 4.0.7 + micromatch: 4.0.8 flat-cache@3.1.0: dependencies: @@ -17781,6 +18308,15 @@ snapshots: merge2: 1.4.1 slash: 4.0.0 + globby@14.0.2: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 + fast-glob: 3.3.2 + ignore: 5.3.1 + path-type: 5.0.0 + slash: 5.1.0 + unicorn-magic: 0.1.0 + globrex@0.1.2: {} gopd@1.0.1: @@ -17794,7 +18330,7 @@ snapshots: graphql-tag@2.12.6(graphql@15.8.0): dependencies: graphql: 15.8.0 - tslib: 2.6.2 + tslib: 2.8.1 graphql@15.8.0: {} @@ -17860,6 +18396,8 @@ snapshots: dependencies: source-map: 0.7.4 + highlight.js@10.7.3: {} + hono@4.0.1: {} hono@4.5.0: {} @@ -18205,7 +18743,7 @@ snapshots: '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 @@ -18299,7 +18837,7 @@ snapshots: chalk: 4.1.2 flow-parser: 0.236.0 graceful-fs: 4.2.11 - micromatch: 4.0.7 + micromatch: 4.0.8 neo-async: 2.6.2 node-dir: 0.1.17 recast: 0.21.5 @@ -18382,7 +18920,7 @@ snapshots: kleur@4.1.5: {} - knex@2.5.1(better-sqlite3@8.7.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7): + knex@2.5.1(better-sqlite3@11.5.0)(mysql2@3.11.0)(pg@8.13.1)(sqlite3@5.1.7): dependencies: colorette: 2.0.19 commander: 10.0.1 @@ -18399,14 +18937,15 @@ snapshots: tarn: 3.0.2 tildify: 2.0.0 optionalDependencies: - better-sqlite3: 8.7.0 - mysql2: 3.3.3 - pg: 8.11.5 + better-sqlite3: 11.5.0 + mysql2: 3.11.0 + pg: 8.13.1 sqlite3: 5.1.7 transitivePeerDependencies: - supports-color + optional: true - knex@3.1.0(better-sqlite3@10.0.0)(mysql2@3.9.8)(pg@8.11.5)(sqlite3@5.1.7): + knex@2.5.1(better-sqlite3@8.7.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7): dependencies: colorette: 2.0.19 commander: 10.0.1 @@ -18417,25 +18956,21 @@ snapshots: getopts: 2.3.0 interpret: 2.2.0 lodash: 4.17.21 - pg-connection-string: 2.6.2 + pg-connection-string: 2.6.1 rechoir: 0.8.0 resolve-from: 5.0.0 tarn: 3.0.2 tildify: 2.0.0 optionalDependencies: - better-sqlite3: 10.0.0 - mysql2: 3.9.8 + better-sqlite3: 8.7.0 + mysql2: 3.3.3 pg: 8.11.5 sqlite3: 5.1.7 transitivePeerDependencies: - supports-color - optional: true kysely@0.25.0: {} - kysely@0.27.3: - optional: true - leven@3.1.0: {} levn@0.4.1: @@ -18443,32 +18978,32 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libsql@0.2.0: + libsql@0.3.19: dependencies: '@neon-rs/load': 0.0.4 detect-libc: 2.0.2 optionalDependencies: - '@libsql/darwin-arm64': 0.2.0 - '@libsql/darwin-x64': 0.2.0 - '@libsql/linux-arm64-gnu': 0.2.0 - '@libsql/linux-arm64-musl': 0.2.0 - '@libsql/linux-x64-gnu': 0.2.0 - '@libsql/linux-x64-musl': 0.2.0 - '@libsql/win32-x64-msvc': 0.2.0 - optional: true + '@libsql/darwin-arm64': 0.3.19 + '@libsql/darwin-x64': 0.3.19 + '@libsql/linux-arm64-gnu': 0.3.19 + '@libsql/linux-arm64-musl': 0.3.19 + '@libsql/linux-x64-gnu': 0.3.19 + '@libsql/linux-x64-musl': 0.3.19 + '@libsql/win32-x64-msvc': 0.3.19 - libsql@0.3.18: + libsql@0.4.1: dependencies: '@neon-rs/load': 0.0.4 detect-libc: 2.0.2 + libsql: 0.3.19 optionalDependencies: - '@libsql/darwin-arm64': 0.3.18 - '@libsql/darwin-x64': 0.3.18 - '@libsql/linux-arm64-gnu': 0.3.18 - '@libsql/linux-arm64-musl': 0.3.18 - '@libsql/linux-x64-gnu': 0.3.18 - '@libsql/linux-x64-musl': 0.3.18 - '@libsql/win32-x64-msvc': 0.3.18 + '@libsql/darwin-arm64': 0.4.1 + '@libsql/darwin-x64': 0.4.1 + '@libsql/linux-arm64-gnu': 0.4.1 + '@libsql/linux-arm64-musl': 0.4.1 + '@libsql/linux-x64-gnu': 0.4.1 + '@libsql/linux-x64-musl': 0.4.1 + '@libsql/win32-x64-msvc': 0.4.1 lighthouse-logger@1.4.2: dependencies: @@ -18613,8 +19148,6 @@ snapshots: dayjs: 1.11.11 yargs: 15.4.1 - long@4.0.0: {} - long@5.2.3: {} loose-envify@1.4.0: @@ -18625,8 +19158,12 @@ snapshots: dependencies: get-func-name: 2.0.2 + loupe@3.1.2: {} + lru-cache@10.2.2: {} + lru-cache@10.4.3: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -18653,6 +19190,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -18703,6 +19244,17 @@ snapshots: node-emoji: 2.1.3 supports-hyperlinks: 3.0.0 + marked-terminal@7.2.1(marked@9.1.6): + dependencies: + ansi-escapes: 7.0.0 + ansi-regex: 6.1.0 + chalk: 5.3.0 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 9.1.6 + node-emoji: 2.1.3 + supports-hyperlinks: 3.1.0 + marked@9.1.6: {} marky@1.2.5: {} @@ -18808,7 +19360,7 @@ snapshots: graceful-fs: 4.2.11 invariant: 2.2.4 jest-worker: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 node-abort-controller: 3.1.1 nullthrows: 1.1.1 walker: 1.0.8 @@ -18932,12 +19484,12 @@ snapshots: - supports-color - utf-8-validate - micromatch@4.0.5: + micromatch@4.0.7: dependencies: - braces: 3.0.2 + braces: 3.0.3 picomatch: 2.3.1 - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -19079,19 +19631,9 @@ snapshots: rimraf: 2.4.5 optional: true - mysql2@2.3.3: - dependencies: - denque: 2.1.0 - generate-function: 2.3.1 - iconv-lite: 0.6.3 - long: 4.0.0 - lru-cache: 6.0.0 - named-placeholders: 1.1.3 - seq-queue: 0.0.5 - sqlstring: 2.3.3 - - mysql2@3.3.3: + mysql2@3.11.0: dependencies: + aws-ssl-profiles: 1.1.1 denque: 2.1.0 generate-function: 2.3.1 iconv-lite: 0.6.3 @@ -19100,8 +19642,9 @@ snapshots: named-placeholders: 1.1.3 seq-queue: 0.0.5 sqlstring: 2.3.3 + optional: true - mysql2@3.9.8: + mysql2@3.3.3: dependencies: denque: 2.1.0 generate-function: 2.3.1 @@ -19111,7 +19654,6 @@ snapshots: named-placeholders: 1.1.3 seq-queue: 0.0.5 sqlstring: 2.3.3 - optional: true mz@2.7.0: dependencies: @@ -19229,7 +19771,7 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.4 + resolve: 1.22.8 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -19318,6 +19860,8 @@ snapshots: obuf@1.1.2: {} + ohm-js@17.1.0: {} + oidc-token-hash@5.0.3: {} on-finished@2.3.0: @@ -19413,10 +19957,18 @@ snapshots: dependencies: p-timeout: 5.1.0 + p-event@6.0.1: + dependencies: + p-timeout: 6.1.3 + p-filter@3.0.0: dependencies: p-map: 5.5.0 + p-filter@4.1.0: + dependencies: + p-map: 7.0.2 + p-finally@1.0.0: {} p-limit@2.3.0: @@ -19461,8 +20013,12 @@ snapshots: p-map@6.0.0: {} + p-map@7.0.2: {} + p-timeout@5.1.0: {} + p-timeout@6.1.3: {} + p-try@2.2.0: {} parent-module@1.0.1: @@ -19489,6 +20045,14 @@ snapshots: dependencies: pngjs: 3.4.0 + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + parseurl@1.3.3: {} password-prompt@1.1.3: @@ -19528,10 +20092,14 @@ snapshots: path-type@4.0.0: {} + path-type@5.0.0: {} + pathe@1.1.2: {} pathval@1.1.1: {} + pathval@2.0.0: {} + pause-stream@0.0.11: dependencies: through: 2.3.8 @@ -19541,11 +20109,10 @@ snapshots: pg-connection-string@2.6.1: {} - pg-connection-string@2.6.2: - optional: true - pg-connection-string@2.6.4: {} + pg-connection-string@2.7.0: {} + pg-int8@1.0.1: {} pg-numeric@1.0.2: {} @@ -19554,8 +20121,14 @@ snapshots: dependencies: pg: 8.11.5 + pg-pool@3.7.0(pg@8.13.1): + dependencies: + pg: 8.13.1 + pg-protocol@1.6.1: {} + pg-protocol@1.7.0: {} + pg-types@2.2.0: dependencies: pg-int8: 1.0.1 @@ -19584,6 +20157,16 @@ snapshots: optionalDependencies: pg-cloudflare: 1.1.1 + pg@8.13.1: + dependencies: + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.1) + pg-protocol: 1.7.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + pgpass@1.0.5: dependencies: split2: 4.2.0 @@ -19596,6 +20179,8 @@ snapshots: picomatch@3.0.1: {} + picomatch@4.0.2: {} + pify@4.0.1: {} pirates@4.0.6: {} @@ -19631,13 +20216,13 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss-load-config@4.0.1(postcss@8.4.39)(ts-node@10.9.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))): + postcss-load-config@4.0.1(postcss@8.4.39)(ts-node@10.9.2(typescript@5.6.3)): dependencies: lilconfig: 2.1.0 yaml: 2.3.1 optionalDependencies: postcss: 8.4.39 - ts-node: 10.9.2(@types/node@20.12.12)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + ts-node: 10.9.2(@types/node@20.12.12)(typescript@5.6.3) postcss-load-config@6.0.1(postcss@8.4.39)(tsx@3.14.0)(yaml@2.4.2): dependencies: @@ -19742,6 +20327,8 @@ snapshots: promise-inflight@1.0.1: optional: true + promise-limit@2.7.0: {} + promise-retry@2.0.1: dependencies: err-code: 2.0.3 @@ -19787,6 +20374,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + qrcode-terminal@0.11.0: {} qs@6.11.0: @@ -19939,7 +20528,7 @@ snapshots: ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.6.2 + tslib: 2.8.1 recast@0.23.9: dependencies: @@ -19947,7 +20536,7 @@ snapshots: esprima: 4.0.1 source-map: 0.6.1 tiny-invariant: 1.3.3 - tslib: 2.6.2 + tslib: 2.8.1 rechoir@0.8.0: dependencies: @@ -20031,12 +20620,19 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve-tspaths@0.8.16(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)): + resolve-tspaths@0.8.16(typescript@5.6.3): dependencies: ansi-colors: 4.1.3 commander: 11.0.0 fast-glob: 3.3.1 - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 + + resolve-tspaths@0.8.22(typescript@5.6.3): + dependencies: + ansi-colors: 4.1.3 + commander: 12.1.0 + fast-glob: 3.3.2 + typescript: 5.6.3 resolve.exports@2.0.2: {} @@ -20128,28 +20724,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - rollup@4.18.0: - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.0 - '@rollup/rollup-android-arm64': 4.18.0 - '@rollup/rollup-darwin-arm64': 4.18.0 - '@rollup/rollup-darwin-x64': 4.18.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 - '@rollup/rollup-linux-arm-musleabihf': 4.18.0 - '@rollup/rollup-linux-arm64-gnu': 4.18.0 - '@rollup/rollup-linux-arm64-musl': 4.18.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 - '@rollup/rollup-linux-riscv64-gnu': 4.18.0 - '@rollup/rollup-linux-s390x-gnu': 4.18.0 - '@rollup/rollup-linux-x64-gnu': 4.18.0 - '@rollup/rollup-linux-x64-musl': 4.18.0 - '@rollup/rollup-win32-arm64-msvc': 4.18.0 - '@rollup/rollup-win32-ia32-msvc': 4.18.0 - '@rollup/rollup-win32-x64-msvc': 4.18.0 - fsevents: 2.3.3 - rollup@4.18.1: dependencies: '@types/estree': 1.0.5 @@ -20172,13 +20746,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.18.1 fsevents: 2.3.3 + rollup@4.27.3: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.27.3 + '@rollup/rollup-android-arm64': 4.27.3 + '@rollup/rollup-darwin-arm64': 4.27.3 + '@rollup/rollup-darwin-x64': 4.27.3 + '@rollup/rollup-freebsd-arm64': 4.27.3 + '@rollup/rollup-freebsd-x64': 4.27.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.27.3 + '@rollup/rollup-linux-arm-musleabihf': 4.27.3 + '@rollup/rollup-linux-arm64-gnu': 4.27.3 + '@rollup/rollup-linux-arm64-musl': 4.27.3 + '@rollup/rollup-linux-powerpc64le-gnu': 4.27.3 + '@rollup/rollup-linux-riscv64-gnu': 4.27.3 + '@rollup/rollup-linux-s390x-gnu': 4.27.3 + '@rollup/rollup-linux-x64-gnu': 4.27.3 + '@rollup/rollup-linux-x64-musl': 4.27.3 + '@rollup/rollup-win32-arm64-msvc': 4.27.3 + '@rollup/rollup-win32-ia32-msvc': 4.27.3 + '@rollup/rollup-win32-x64-msvc': 4.27.3 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 rxjs@7.8.1: dependencies: - tslib: 2.6.2 + tslib: 2.8.1 sade@1.8.1: dependencies: @@ -20368,6 +20966,8 @@ snapshots: slash@4.0.0: {} + slash@5.1.0: {} + slice-ansi@2.1.0: dependencies: ansi-styles: 3.2.1 @@ -20384,7 +20984,7 @@ snapshots: smart-buffer@4.2.0: optional: true - smob@0.0.6: {} + smob@1.5.0: {} socks-proxy-agent@6.2.1: dependencies: @@ -20684,12 +21284,17 @@ snapshots: has-flag: 4.0.0 supports-color: 7.2.0 + supports-hyperlinks@3.1.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + supports-preserve-symlinks-flag@1.0.0: {} synckit@0.9.1: dependencies: '@pkgr/core': 0.1.1 - tslib: 2.6.2 + tslib: 2.8.1 tar-fs@2.0.1: dependencies: @@ -20753,13 +21358,6 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser@5.17.1: - dependencies: - '@jridgewell/source-map': 0.3.3 - acorn: 8.8.2 - commander: 2.20.3 - source-map-support: 0.5.21 - terser@5.31.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -20805,10 +21403,20 @@ snapshots: tinybench@2.8.0: {} + tinybench@2.9.0: {} + + tinyexec@0.3.0: {} + tinypool@0.8.4: {} + tinypool@1.0.1: {} + + tinyrainbow@1.2.0: {} + tinyspy@2.2.1: {} + tinyspy@3.0.2: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -20845,19 +21453,19 @@ snapshots: dependencies: typescript: 5.2.2 - ts-api-utils@1.0.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)): + ts-api-utils@1.0.3(typescript@5.6.3): dependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 - ts-api-utils@1.3.0(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)): + ts-api-utils@1.3.0(typescript@5.6.3): dependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 ts-expose-internals-conditionally@1.0.0-empty.0: {} ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)): + ts-node@10.9.2(@types/node@20.12.12)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -20871,13 +21479,13 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - tsconfck@3.0.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)): + tsconfck@3.0.3(typescript@5.6.3): optionalDependencies: - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 tsconfig-paths@3.14.2: dependencies: @@ -20890,7 +21498,9 @@ snapshots: tslib@2.6.2: {} - tsup@7.2.0(postcss@8.4.39)(ts-node@10.9.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)))(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)): + tslib@2.8.1: {} + + tsup@7.2.0(postcss@8.4.39)(ts-node@10.9.2(typescript@5.6.3))(typescript@5.6.3): dependencies: bundle-require: 4.0.2(esbuild@0.18.20) cac: 6.7.14 @@ -20900,7 +21510,7 @@ snapshots: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.1(postcss@8.4.39)(ts-node@10.9.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))) + postcss-load-config: 4.0.1(postcss@8.4.39)(ts-node@10.9.2(typescript@5.6.3)) resolve-from: 5.0.0 rollup: 3.27.2 source-map: 0.8.0-beta.0 @@ -20908,12 +21518,12 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: postcss: 8.4.39 - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - supports-color - ts-node - tsup@8.1.2(postcss@8.4.39)(tsx@3.14.0)(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(yaml@2.4.2): + tsup@8.1.2(postcss@8.4.39)(tsx@3.14.0)(typescript@5.6.3)(yaml@2.4.2): dependencies: bundle-require: 5.0.0(esbuild@0.23.0) cac: 6.7.14 @@ -20932,17 +21542,17 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: postcss: 8.4.39 - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 transitivePeerDependencies: - jiti - supports-color - tsx - yaml - tsutils@3.21.0(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)): + tsutils@3.21.0(typescript@5.6.3): dependencies: tslib: 1.14.1 - typescript: 5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme) + typescript: 5.6.3 tsx@3.14.0: dependencies: @@ -20966,36 +21576,43 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tsx@4.19.2: + dependencies: + esbuild: 0.23.0 + get-tsconfig: 4.7.5 + optionalDependencies: + fsevents: 2.3.3 + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 - turbo-darwin-64@1.10.14: + turbo-darwin-64@2.3.0: optional: true - turbo-darwin-arm64@1.10.14: + turbo-darwin-arm64@2.3.0: optional: true - turbo-linux-64@1.10.14: + turbo-linux-64@2.3.0: optional: true - turbo-linux-arm64@1.10.14: + turbo-linux-arm64@2.3.0: optional: true - turbo-windows-64@1.10.14: + turbo-windows-64@2.3.0: optional: true - turbo-windows-arm64@1.10.14: + turbo-windows-arm64@2.3.0: optional: true - turbo@1.10.14: + turbo@2.3.0: optionalDependencies: - turbo-darwin-64: 1.10.14 - turbo-darwin-arm64: 1.10.14 - turbo-linux-64: 1.10.14 - turbo-linux-arm64: 1.10.14 - turbo-windows-64: 1.10.14 - turbo-windows-arm64: 1.10.14 + turbo-darwin-64: 2.3.0 + turbo-darwin-arm64: 2.3.0 + turbo-linux-64: 2.3.0 + turbo-linux-arm64: 2.3.0 + turbo-windows-64: 2.3.0 + turbo-windows-arm64: 2.3.0 tweetnacl@0.14.5: {} @@ -21104,7 +21721,9 @@ snapshots: typescript@5.3.3: {} - typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme): {} + typescript@5.6.1-rc: {} + + typescript@5.6.3: {} ua-parser-js@1.0.38: {} @@ -21119,9 +21738,7 @@ snapshots: undici-types@5.26.5: {} - undici@5.28.2: - dependencies: - '@fastify/busboy': 2.1.1 + undici-types@6.19.8: {} undici@5.28.4: dependencies: @@ -21149,6 +21766,8 @@ snapshots: unicode-property-aliases-ecmascript@2.1.0: {} + unicorn-magic@0.1.0: {} + unique-filename@1.1.1: dependencies: unique-slug: 2.0.2 @@ -21207,6 +21826,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + uuid@7.0.3: {} uuid@8.3.2: {} @@ -21222,7 +21843,9 @@ snapshots: v8-compile-cache-lib@3.0.1: {} - valibot@0.30.0: {} + valibot@1.0.0-beta.7(typescript@5.6.3): + optionalDependencies: + typescript: 5.6.3 valid-url@1.0.9: {} @@ -21313,33 +21936,65 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0)): + vite-node@2.1.2(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0): + dependencies: + cac: 6.7.14 + debug: 4.3.7 + pathe: 1.1.2 + vite: 5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + + vite-node@2.1.2(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0): + dependencies: + cac: 6.7.14 + debug: 4.3.7 + pathe: 1.1.2 + vite: 5.3.3(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0)): dependencies: debug: 4.3.4 globrex: 0.1.2 - tsconfck: 3.0.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + tsconfck: 3.0.3(typescript@5.6.3) optionalDependencies: vite: 5.3.3(@types/node@18.15.10)(lightningcss@1.25.1)(terser@5.31.0) transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@18.19.33)(lightningcss@1.25.1)(terser@5.31.0)): + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@18.19.33)(lightningcss@1.25.1)(terser@5.31.0)): dependencies: debug: 4.3.4 globrex: 0.1.2 - tsconfck: 3.0.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + tsconfck: 3.0.3(typescript@5.6.3) optionalDependencies: vite: 5.3.3(@types/node@18.19.33)(lightningcss@1.25.1)(terser@5.31.0) transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@4.3.2(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme))(vite@5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0)): + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0)): dependencies: debug: 4.3.4 globrex: 0.1.2 - tsconfck: 3.0.3(typescript@5.4.5(patch_hash=q3iy4fwdhi5sis3wty7d4nbsme)) + tsconfck: 3.0.3(typescript@5.6.3) optionalDependencies: vite: 5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0) transitivePeerDependencies: @@ -21350,7 +22005,7 @@ snapshots: dependencies: esbuild: 0.20.2 postcss: 8.4.38 - rollup: 4.18.0 + rollup: 4.27.3 optionalDependencies: '@types/node': 18.15.10 fsevents: 2.3.3 @@ -21361,7 +22016,7 @@ snapshots: dependencies: esbuild: 0.20.2 postcss: 8.4.38 - rollup: 4.18.0 + rollup: 4.27.3 optionalDependencies: '@types/node': 18.19.33 fsevents: 2.3.3 @@ -21372,7 +22027,7 @@ snapshots: dependencies: esbuild: 0.20.2 postcss: 8.4.38 - rollup: 4.18.0 + rollup: 4.27.3 optionalDependencies: '@types/node': 20.10.1 fsevents: 2.3.3 @@ -21383,7 +22038,7 @@ snapshots: dependencies: esbuild: 0.20.2 postcss: 8.4.38 - rollup: 4.18.0 + rollup: 4.27.3 optionalDependencies: '@types/node': 20.12.12 fsevents: 2.3.3 @@ -21394,7 +22049,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.39 - rollup: 4.18.0 + rollup: 4.27.3 optionalDependencies: '@types/node': 18.15.10 fsevents: 2.3.3 @@ -21405,7 +22060,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.39 - rollup: 4.18.0 + rollup: 4.27.3 optionalDependencies: '@types/node': 18.19.33 fsevents: 2.3.3 @@ -21416,7 +22071,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.39 - rollup: 4.18.0 + rollup: 4.27.3 optionalDependencies: '@types/node': 20.10.1 fsevents: 2.3.3 @@ -21427,13 +22082,24 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.39 - rollup: 4.18.0 + rollup: 4.27.3 optionalDependencies: '@types/node': 20.12.12 fsevents: 2.3.3 lightningcss: 1.25.1 terser: 5.31.0 + vite@5.3.3(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.39 + rollup: 4.27.3 + optionalDependencies: + '@types/node': 22.9.1 + fsevents: 2.3.3 + lightningcss: 1.25.1 + terser: 5.31.0 + vitest@1.6.0(@types/node@18.15.10)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0): dependencies: '@vitest/expect': 1.6.0 @@ -21570,6 +22236,73 @@ snapshots: - supports-color - terser + vitest@2.1.2(@types/node@20.12.12)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0): + dependencies: + '@vitest/expect': 2.1.2 + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0)) + '@vitest/pretty-format': 2.1.2 + '@vitest/runner': 2.1.2 + '@vitest/snapshot': 2.1.2 + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 + chai: 5.1.1 + debug: 4.3.7 + magic-string: 0.30.11 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinyexec: 0.3.0 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.3.3(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0) + vite-node: 2.1.2(@types/node@20.12.12)(lightningcss@1.25.1)(terser@5.31.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.12.12 + '@vitest/ui': 1.6.0(vitest@2.1.2) + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - stylus + - sugarss + - supports-color + - terser + + vitest@2.1.2(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0): + dependencies: + '@vitest/expect': 2.1.2 + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.3.3(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0)) + '@vitest/pretty-format': 2.1.2 + '@vitest/runner': 2.1.2 + '@vitest/snapshot': 2.1.2 + '@vitest/spy': 2.1.2 + '@vitest/utils': 2.1.2 + chai: 5.1.1 + debug: 4.3.7 + magic-string: 0.30.11 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinyexec: 0.3.0 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.3.3(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0) + vite-node: 2.1.2(@types/node@22.9.1)(lightningcss@1.25.1)(terser@5.31.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.9.1 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - stylus + - sugarss + - supports-color + - terser + vlq@1.0.1: {} walker@1.0.8: @@ -21654,6 +22387,11 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 4.2.3 @@ -21861,3 +22599,8 @@ snapshots: webpod: 0.0.2 which: 3.0.1 yaml: 2.4.2 + + zx@8.2.2: + optionalDependencies: + '@types/fs-extra': 11.0.4 + '@types/node': 20.12.12 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7cf196659..0a4ddf3b4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,5 +4,6 @@ packages: - drizzle-zod - drizzle-typebox - drizzle-valibot + - drizzle-seed - integration-tests - eslint-plugin-drizzle diff --git a/turbo.json b/turbo.json index 3d07b1e88..6af34fa47 100644 --- a/turbo.json +++ b/turbo.json @@ -1,12 +1,25 @@ { "$schema": "https://turbo.build/schema.json", - "pipeline": { + "tasks": { "//#lint": { - "inputs": ["**/*.ts", "!**/node_modules", "!**/dist", "!**/dist-dts"], - "outputMode": "new-only" + "dependsOn": [ + "^test:types", + "drizzle-orm#build" + ], + "inputs": [ + "**/*.ts", + "!**/node_modules", + "!**/dist", + "!**/dist-dts" + ], + "outputLogs": "new-only" }, "test:types": { - "dependsOn": ["^test:types", "^build"], + "dependsOn": [ + "^test:types", + "drizzle-orm#build", + "drizzle-seed#build" + ], "inputs": [ "src/**/*.ts", "tests/**/*.ts", @@ -14,10 +27,53 @@ "tests/tsconfig.json", "../tsconfig.json" ], - "outputMode": "new-only" + "outputLogs": "new-only" + }, + "drizzle-orm#build": { + "inputs": [ + "src/**/*.ts", + "package.json", + "README.md", + "../README.md", + "tsconfig.json", + "tsconfig.*.json", + "tsup.config.ts", + "scripts/build.ts", + "scripts/fix-imports.ts", + "../tsconfig.json" + ], + "outputs": [ + "dist/**", + "dist-dts/**" + ], + "outputLogs": "new-only" + }, + "drizzle-kit#build": { + "dependsOn": [ + "drizzle-orm#build" + ], + "inputs": [ + "src/**/*.ts", + "package.json", + "README.md", + "../README.md", + "tsconfig.json", + "tsconfig.*.json", + "tsup.config.ts", + "scripts/build.ts", + "scripts/fix-imports.ts", + "../tsconfig.json" + ], + "outputs": [ + "dist/**", + "dist-dts/**" + ], + "outputLogs": "new-only" }, - "build": { - "dependsOn": ["^build"], + "drizzle-zod#build": { + "dependsOn": [ + "drizzle-orm#build" + ], "inputs": [ "src/**/*.ts", "package.json", @@ -30,23 +86,147 @@ "scripts/fix-imports.ts", "../tsconfig.json" ], - "outputs": ["dist/**", "dist-dts/**"], - "outputMode": "new-only" + "outputs": [ + "dist/**", + "dist-dts/**" + ], + "outputLogs": "new-only" + }, + "drizzle-typebox#build": { + "dependsOn": [ + "drizzle-orm#build" + ], + "inputs": [ + "src/**/*.ts", + "package.json", + "README.md", + "../README.md", + "tsconfig.json", + "tsconfig.*.json", + "tsup.config.ts", + "scripts/build.ts", + "scripts/fix-imports.ts", + "../tsconfig.json" + ], + "outputs": [ + "dist/**", + "dist-dts/**" + ], + "outputLogs": "new-only" + }, + "drizzle-valibot#build": { + "dependsOn": [ + "drizzle-orm#build" + ], + "inputs": [ + "src/**/*.ts", + "package.json", + "README.md", + "../README.md", + "tsconfig.json", + "tsconfig.*.json", + "tsup.config.ts", + "scripts/build.ts", + "scripts/fix-imports.ts", + "../tsconfig.json" + ], + "outputs": [ + "dist/**", + "dist-dts/**" + ], + "outputLogs": "new-only" + }, + "eslint-plugin-drizzle#build": { + "dependsOn": [ + "drizzle-orm#build" + ], + "inputs": [ + "src/**/*.ts", + "package.json", + "README.md", + "../README.md", + "tsconfig.json", + "tsconfig.*.json", + "tsup.config.ts", + "scripts/build.ts", + "scripts/fix-imports.ts", + "../tsconfig.json" + ], + "outputs": [ + "dist/**", + "dist-dts/**" + ], + "outputLogs": "new-only" + }, + "drizzle-seed#build": { + "dependsOn": [ + "drizzle-orm#build" + ], + "inputs": [ + "src/**/*.ts", + "package.json", + "README.md", + "../README.md", + "tsconfig.json", + "tsconfig.*.json", + "tsup.config.ts", + "scripts/build.ts", + "scripts/fix-imports.ts", + "../tsconfig.json" + ], + "outputs": [ + "dist/**", + "dist-dts/**" + ], + "outputLogs": "new-only" + }, + "integration-tests#build": { + "dependsOn": [ + "drizzle-orm#build", + "drizzle-seed#build" + ], + "inputs": [ + "src/**/*.ts", + "package.json", + "README.md", + "../README.md", + "tsconfig.json", + "tsconfig.*.json", + "tsup.config.ts", + "scripts/build.ts", + "scripts/fix-imports.ts", + "../tsconfig.json" + ], + "outputs": [ + "dist/**", + "dist-dts/**" + ], + "outputLogs": "new-only" }, "pack": { - "dependsOn": ["build", "test:types"], - "inputs": ["dist/**"], - "outputs": ["package.tgz"], - "outputMode": "new-only" + "dependsOn": [ + "build", + "test:types" + ], + "inputs": [ + "dist/**" + ], + "outputs": [ + "package.tgz" + ], + "outputLogs": "new-only" }, "test": { - "dependsOn": ["build", "test:types"], + "dependsOn": [ + "build", + "test:types" + ], "inputs": [ "tests/**/*.test.ts", "tests/**/*.test.cjs", "tests/**/*.test.mjs" ], - "outputMode": "new-only" + "outputLogs": "new-only" } } }