├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .github ├── dependabot.yml ├── workflows │ ├── release.yml │ └── validate.yml └── zizmor.yml ├── .gitignore ├── .golangci.yml ├── .vscode └── extensions.json ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── example.js ├── examples.bats └── jsconfig.json ├── go.mod ├── go.sum ├── index.d.ts ├── package.json ├── register.go ├── register_internal_test.go ├── releases ├── v1.0.0.md ├── v1.0.1.md ├── v1.0.2.md ├── v1.0.3.md ├── v1.0.4.md └── v1.0.5.md ├── smoke.test.js ├── sql ├── drivers.go ├── module.go ├── module_internal_test.go ├── module_test.go ├── options.go ├── options_internal_test.go ├── sql_internal_test.go └── testdata │ └── script.js ├── sqltest ├── sqltest.go ├── sqltest_test.go └── testdata │ └── script.js └── tsconfig.json /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xk6-sql", 3 | "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", 4 | "customizations": { 5 | "vscode": { 6 | "settings": { 7 | "go.lintTool": "golangci-lint", 8 | "go.lintFlags": ["--fast"] 9 | }, 10 | "extensions": [ 11 | "EditorConfig.EditorConfig", 12 | "esbenp.prettier-vscode", 13 | "github.vscode-github-actions", 14 | "github.vscode-pull-request-github", 15 | "jetmartin.bats", 16 | "mads-hartmann.bash-ide-vscode", 17 | "foxundermoon.shell-format" 18 | ] 19 | } 20 | }, 21 | 22 | "features": { 23 | "ghcr.io/devcontainers/features/github-cli:1": {}, 24 | "ghcr.io/devcontainers/features/go:1": { 25 | "version": "1.24", 26 | "golangciLintVersion": "2.1.6" 27 | }, 28 | "ghcr.io/guiyomh/features/goreleaser:0": { "version": "2.9.0" }, 29 | "ghcr.io/michidk/devcontainers-features/bun:1": { "version": "1.2.12" }, 30 | "ghcr.io/szkiba/devcontainer-features/gosec:1": { "version": "2.22.4" }, 31 | "ghcr.io/szkiba/devcontainer-features/govulncheck:1": { 32 | "version": "1.1.4" 33 | }, 34 | "ghcr.io/szkiba/devcontainer-features/cdo:1": { "version": "0.1.2" }, 35 | "ghcr.io/szkiba/devcontainer-features/mdcode:1": { "version": "0.2.0" }, 36 | "ghcr.io/szkiba/devcontainer-features/bats:1": { "version": "1.11.1" }, 37 | "ghcr.io/grafana/devcontainer-features/xk6:1": { "version": "1.0.0" } 38 | }, 39 | 40 | "remoteEnv": { 41 | "GH_TOKEN": "${localEnv:GH_TOKEN}", 42 | "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = true 10 | 11 | [*.{js,ts,yml,yaml,json,sh,bats}] 12 | indent_size = 2 13 | trim_trailing_whitespace = true 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "sunday" 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | day: "sunday" 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["v*.*.*"] 6 | 7 | permissions: {} 8 | 9 | jobs: 10 | release: 11 | name: Release 12 | uses: grafana/xk6/.github/workflows/extension-release.yml@v1.0.0 13 | permissions: 14 | contents: write 15 | with: 16 | cgo: true 17 | go-version: ${{vars.GO_VERSION}} 18 | k6-version: ${{vars.K6_VERSION}} 19 | xk6-version: ${{vars.XK6_VERSION}} 20 | os: ${{vars.OS}} 21 | arch: ${{vars.ARCH}} 22 | with: | 23 | github.com/grafana/xk6-sql-driver-ramsql 24 | github.com/grafana/xk6-sql-driver-sqlite3 25 | github.com/grafana/xk6-sql-driver-mysql 26 | github.com/grafana/xk6-sql-driver-postgres 27 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validate 2 | 3 | permissions: {} 4 | 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: ["main", "master"] 9 | pull_request: 10 | branches: ["main", "master"] 11 | 12 | jobs: 13 | validate: 14 | name: Validate 15 | uses: grafana/xk6/.github/workflows/extension-validate.yml@v1.0.0 16 | permissions: 17 | pages: write 18 | id-token: write 19 | with: 20 | go-version: ${{vars.GO_VERSION}} 21 | go-versions: ${{vars.GO_VERSIONS}} 22 | golangci-lint-version: ${{vars.GOLANGCI_LINT_VERSION}} 23 | platforms: ${{vars.PLATFORMS}} 24 | k6-versions: ${{vars.K6_VERSIONS}} 25 | xk6-version: ${{vars.XK6_VERSION}} 26 | -------------------------------------------------------------------------------- /.github/zizmor.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | unpinned-uses: 3 | config: 4 | policies: 5 | "*": hash-pin 6 | actions/*: any 7 | grafana/*: any 8 | forbidden-uses: 9 | config: 10 | deny: 11 | # Policy-banned by our security team due to CVE-2025-30066 & CVE-2025-30154. 12 | # https://www.cisa.gov/news-events/alerts/2025/03/18/supply-chain-compromise-third-party-tj-actionschanged-files-cve-2025-30066-and-reviewdogaction 13 | # https://nvd.nist.gov/vuln/detail/cve-2025-30066 14 | # https://nvd.nist.gov/vuln/detail/cve-2025-30154 15 | - reviewdog/* 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /k6 2 | /k6.exe 3 | /coverage.txt 4 | /build 5 | /node_modules 6 | yarn.lock 7 | bun.lockb 8 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: all 4 | disable: 5 | # k6 extensions must be registered from the init() function. 6 | - gochecknoinits 7 | 8 | # The constructor of k6 extensions must return an interface. 9 | - ireturn 10 | 11 | # In many cases (e.g. options) it is normal usage not to specify all structure fields. 12 | - exhaustruct 13 | 14 | # Many go standard library API functions have typical parameter names shorter than 3 characters. 15 | # It is better to use the usual parameter names than to create one that conforms to the rule. 16 | - varnamelen 17 | 18 | # Except for general-purpose public APIs, 19 | # wrapping errors is more inconvenient and error prone than useful. 20 | - wrapcheck 21 | settings: 22 | depguard: 23 | rules: 24 | prevent_accidental_imports: 25 | allow: 26 | - $gostd 27 | - github.com/stretchr/testify/require 28 | - go.k6.io/k6 29 | - github.com/grafana/sobek 30 | - github.com/grafana/xk6-sql 31 | - github.com/proullon/ramsql/driver 32 | issues: 33 | max-issues-per-linter: 0 34 | max-same-issues: 0 35 | formatters: 36 | enable: 37 | - gci 38 | - gofmt 39 | - gofumpt 40 | - goimports 41 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["ms-vscode-remote.remote-containers"] 3 | } 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @grafana/k6-extensions 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing! 4 | 5 | All types of contributions are encouraged and valued. Please make sure to read this document before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 6 | 7 | > And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: 8 | > - Star the project 9 | > - Tweet about it 10 | > - Refer this project in your project's readme 11 | > - Mention the project at local meetups and tell your friends/colleagues 12 | 13 | ## Code of Conduct 14 | 15 | Before you begin, make sure to familiarize yourself with the [Code of Conduct](CODE_OF_CONDUCT.md). If you've previously contributed to other open source project, you may recognize it as the classic [Contributor Covenant](https://contributor-covenant.org/). 16 | 17 | ## Asking questions 18 | 19 | Before you ask a question, it is best to search for existing [Issues](https://github.com/grafana/xk6-sql/issues) that might help you. It is also advisable to search the internet for answers first. 20 | 21 | If you then still feel the need to ask a question and need clarification or if you want to chat with the team or the community, you can [join our community forums](https://community.grafana.com/c/grafana-k6). 22 | 23 | ## Reporting Bugs 24 | 25 | A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. 26 | 27 | We use [GitHub issues](https://github.com/grafana/xk6-sql/issues) to track bugs and errors. If you run into an issue with the project: 28 | 29 | - To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error. 30 | - Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. 31 | - Open an [Issue](https://github.com/grafana/xk6-sql/issues). 32 | - Explain the behavior you would expect and the actual behavior. 33 | - Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. 34 | 35 | ## Suggesting Enhancements 36 | 37 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/grafana/xk6-sql/issues). 38 | 39 | - Make sure that you are using the latest version. 40 | - Read the documentation carefully and find out if the functionality is already covered, maybe by an individual configuration. 41 | - Perform a search in [Issues](https://github.com/grafana/xk6-sql/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. 42 | - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. 43 | - Use a **clear and descriptive title** for the issue to identify the suggestion. 44 | - Provide a **step-by-step description of the suggested enhancement** in as many details as possible. 45 | - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. 46 | - **Explain why this enhancement would be useful** to most of users. You may also want to point out the other projects that solved it better and which could serve as inspiration. 47 | 48 | ## Contributing code 49 | 50 | If you'd like to contribute code, this is the basic procedure. 51 | 52 | 1. Find an [issue](https://github.com/grafana/xk6-sql/issues) you'd like to fix. If there is none already, or you'd like to add a feature, please open one, and we can talk about how to do it. Out of respect for your time, please start a discussion regarding any bigger contributions either in a [GitHub Issue](https://github.com/grafana/xk6-sql/issues), in the community forums **before** you get started on the implementation. 53 | 54 | Remember, there's more to software development than code; if it's not properly planned, stuff gets messy real fast. 55 | 56 | 2. Create a fork and open a feature branch - `feature/my-cool-feature` is the classic way to name these, but it really doesn't matter. 57 | 58 | 3. Create a pull request! 59 | 60 | 4. We will discuss implementation details until everyone is happy, then a maintainer will merge it. 61 | 62 | ## Development Environment 63 | 64 | We use [Development Containers](https://containers.dev/) to provide a reproducible development environment. We recommend that you do the same. In this way, it is guaranteed that the appropriate version of the tools required for development will be available. 65 | 66 | **Without installing software** 67 | 68 | You can *contribute without installing any software* using [GitHub Codespaces](https://docs.github.com/en/codespaces). After forking the repository, [create a codespace for your repository](https://docs.github.com/en/codespaces/developing-in-a-codespace/creating-a-codespace-for-a-repository). 69 | 70 | **Using an IDE** 71 | 72 | You can contribute conveniently using [Visual Studio Code](https://code.visualstudio.com/) by installing the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension. You *don't need to install any other software* to contribute. Clone your repository and open the folder with Visual Studio Code. It will automatically detect that the folder contains a Dev Container configuration and ask you whether to open the folder in a container. Choose **"Reopen in Container"**. 73 | 74 | [JetBrains GoLand](https://www.jetbrains.com/help/go/connect-to-devcontainer.html) also has DevContainers support. 75 | 76 | It is worth mentioning [DevPod](https://devpod.sh/docs/) in addition to the above. 77 | 78 | **The hard way** 79 | 80 | All the tools used for development are free and open-source, so you can install them without using *Development Containers*. The `.devcontainer/devcontainer.json` file contains a list of the tools to be installed and their version numbers. 81 | 82 | ### Updating tool versions 83 | 84 | The version numbers of tools used in GitHub workflows are defined as [repository variables](https://github.com/grafana/xk6-sql/settings/variables/actions). The version numbers of tools used in the *Development Containers* are only defined in the `.devcontainer/devcontainer.json` file. The version numbers should be updated carefully to be consistent. 85 | 86 | ## Tasks 87 | 88 | The usual contributor tasks can be performed using GNU make. The `Makefile` defines a target for each task. To execute a task, the name of the task must be specified as an argument to the make command. 89 | 90 | ```bash 91 | make taskname 92 | ``` 93 | 94 | Help on the available targets and their descriptions can be obtained by issuing the `make` command without any arguments. 95 | 96 | ```bash 97 | make 98 | ``` 99 | 100 | More detailed help can be obtained for individual tasks using the [cdo](https://github.com/szkiba/cdo) command: 101 | 102 | ```bash 103 | cdo taskname --help 104 | ``` 105 | 106 | **Authoring the Makefile** 107 | 108 | The `Makefile` is generated from the task list defined in the `CONTRIBUTING.md` file using the [cdo](https://github.com/szkiba/cdo) tool. If a contribution has been made to the task list, the `Makefile` must be regenerated using the [makefile] target. 109 | 110 | ```bash 111 | make makefile 112 | ``` 113 | 114 | ### security - Run security and vulnerability checks 115 | 116 | The [gosec] tool is used for security checks. The [govulncheck] tool is used to check the vulnerability of dependencies. 117 | 118 | ```bash 119 | gosec -quiet ./... 120 | govulncheck ./... 121 | ``` 122 | 123 | [gosec]: https://github.com/securego/gosec 124 | [govulncheck]: https://github.com/golang/vuln 125 | [security]: <#security---run-security-and-vulnerability-checks> 126 | 127 | ### lint - Run the linter 128 | 129 | The [golangci-lint] tool is used for static analysis of the source code. It is advisable to run it before committing the changes. 130 | 131 | ```bash 132 | golangci-lint run 133 | ``` 134 | 135 | [lint]: <#lint---run-the-linter> 136 | [golangci-lint]: https://github.com/golangci/golangci-lint 137 | 138 | ### test - Run the tests 139 | 140 | The `go test` command is used to run the tests and generate the coverage report. 141 | 142 | ```bash 143 | go test -count 1 -race -coverprofile=coverage.txt -timeout 60s ./... 144 | ``` 145 | 146 | [test]: <#test---run-the-tests> 147 | 148 | ### coverage - View the test coverage report 149 | 150 | The go `cover` tool should be used to display the coverage report in the browser. 151 | 152 | Requires 153 | : [test] 154 | 155 | ```bash 156 | go tool cover -html=coverage.txt 157 | ``` 158 | 159 | ### build - Build custom k6 with extension 160 | 161 | The [xk6] tool is used to build the k6. In addition to the xk6-sql extension, the database driver extension you want to use should also be specified. 162 | 163 | ```bash 164 | xk6 build --with github.com/grafana/xk6-sql=. --with github.com/grafana/xk6-sql-driver-ramsql 165 | ``` 166 | 167 | [xk6]: https://github.com/grafana/xk6 168 | [build]: <#build---build-custom-k6-with-extension> 169 | 170 | ### example - Run the examples 171 | 172 | Run the examples embedded in `README.md`. 173 | 174 | ```bash 175 | bats ./examples 176 | ``` 177 | 178 | [example]: #example---run-the-examples 179 | 180 | ### readme - Update README.md 181 | 182 | Update the example code and its output in `README.md` using [mdcode] tool. 183 | 184 | ```bash 185 | mdcode update 186 | ``` 187 | 188 | [mdcode]: 189 | [readme]: #readme---update-readmemd 190 | 191 | ### clean - Clean the working directory 192 | 193 | Delete the work files created in the work directory (also included in .gitignore). 194 | 195 | ```bash 196 | rm -rf ./k6 ./coverage.txt ./build ./node_modules ./bun.lockb 197 | ``` 198 | 199 | [clean]: #clean---clean-the-working-directory 200 | 201 | ### doc - Generate API documentation 202 | 203 | Generate API documentation using `typedoc`. The generated documentation will be placed in the `build/docs` folder. 204 | 205 | ```bash 206 | bun x typedoc --out build/docs 207 | ``` 208 | 209 | [doc]: #doc---generate-api-documentation 210 | 211 | ### all - Run all 212 | 213 | Performs the most important tasks. It can be used to check whether the CI workflow will run successfully. 214 | 215 | Requires 216 | : [clean], [lint], [security], [test], [build], [doc], [example], [readme], [makefile] 217 | 218 | ### format - Format the go source codes 219 | 220 | ```bash 221 | go fmt ./... 222 | ``` 223 | 224 | [format]: #format---format-the-go-source-codes 225 | 226 | ### makefile - Generate the Makefile 227 | 228 | ```bash 229 | cdo --makefile Makefile 230 | ``` 231 | [makefile]: <#makefile---generate-the-makefile> 232 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # File generated by cdo from CONTRIBUTING.md; DO NOT EDIT. 2 | 3 | SHELL=bash 4 | .SHELLFLAGS=-e -o pipefail -c 5 | 6 | .PHONY: __help__ 7 | __help__: 8 | @echo 'Usage: make [target]' 9 | @echo '' 10 | @echo 'Targets:' 11 | @echo ' all Run all' 12 | @echo ' build Build custom k6 with extension' 13 | @echo ' clean Clean the working directory' 14 | @echo ' coverage View the test coverage report' 15 | @echo ' doc Generate API documentation' 16 | @echo ' example Run the examples' 17 | @echo ' format Format the go source codes' 18 | @echo ' lint Run the linter' 19 | @echo ' makefile Generate the Makefile' 20 | @echo ' readme Update README.md' 21 | @echo ' security Run security and vulnerability checks' 22 | @echo ' test Run the tests' 23 | 24 | # Run all 25 | .PHONY: all 26 | all: clean lint security test build doc example readme makefile 27 | 28 | # Build custom k6 with extension 29 | .PHONY: build 30 | build: 31 | @(\ 32 | xk6 build --with github.com/grafana/xk6-sql=. --with github.com/grafana/xk6-sql-driver-ramsql;\ 33 | ) 34 | 35 | # Clean the working directory 36 | .PHONY: clean 37 | clean: 38 | @(\ 39 | rm -rf ./k6 ./coverage.txt ./build ./node_modules ./bun.lockb;\ 40 | ) 41 | 42 | # View the test coverage report 43 | .PHONY: coverage 44 | coverage: test 45 | @(\ 46 | go tool cover -html=coverage.txt;\ 47 | ) 48 | 49 | # Generate API documentation 50 | .PHONY: doc 51 | doc: 52 | @(\ 53 | bun x typedoc --out build/docs;\ 54 | ) 55 | 56 | # Run the examples 57 | .PHONY: example 58 | example: 59 | @(\ 60 | bats ./examples;\ 61 | ) 62 | 63 | # Format the go source codes 64 | .PHONY: format 65 | format: 66 | @(\ 67 | go fmt ./...;\ 68 | ) 69 | 70 | # Run the linter 71 | .PHONY: lint 72 | lint: 73 | @(\ 74 | golangci-lint run;\ 75 | ) 76 | 77 | # Generate the Makefile 78 | .PHONY: makefile 79 | makefile: 80 | @(\ 81 | cdo --makefile Makefile;\ 82 | ) 83 | 84 | # Update README.md 85 | .PHONY: readme 86 | readme: 87 | @(\ 88 | mdcode update;\ 89 | ) 90 | 91 | # Run security and vulnerability checks 92 | .PHONY: security 93 | security: 94 | @(\ 95 | gosec -quiet ./...;\ 96 | govulncheck ./...;\ 97 | ) 98 | 99 | # Run the tests 100 | .PHONY: test 101 | test: 102 | @(\ 103 | go test -count 1 -race -coverprofile=coverage.txt -timeout 60s ./...;\ 104 | ) 105 | 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![API Reference](https://img.shields.io/badge/API-reference-blue?logo=readme&logoColor=lightgray)](https://sql.x.k6.io) 2 | [![GitHub Release](https://img.shields.io/github/v/release/grafana/xk6-sql)](https://github.com/grafana/xk6-sql/releases/latest) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/grafana/xk6-sql)](https://goreportcard.com/report/github.com/grafana/xk6-sql) 4 | [![GitHub Actions](https://github.com/grafana/xk6-sql/actions/workflows/validate.yml/badge.svg)](https://github.com/grafana/xk6-sql/actions/workflows/validate.yml) 5 | 6 | # xk6-sql 7 | 8 | **Use SQL databases from k6 tests.** 9 | 10 | xk6-sql is a [Grafana k6 extension](https://grafana.com/docs/k6/latest/extensions/) that enables the use of SQL databases in [k6](https://grafana.com/docs/k6/latest/) tests. 11 | 12 | Check out the API documentation [here](https://sql.x.k6.io). The TypeScript declaration file can be downloaded from [here](https://sql.x.k6.io/index.d.ts). 13 | 14 | To use the TypeScript declaration file in your IDE (e.g. Visual Studio Code), you need to create a `jsconfig.json` (or `tsconfig.json`) file with the following content: 15 | 16 | ```json file=examples/jsconfig.json 17 | { 18 | "compilerOptions": { 19 | "target": "ES6", 20 | "module": "ES6", 21 | "paths": { 22 | "k6/x/sql": ["./typings/xk6-sql/index.d.ts"] 23 | } 24 | } 25 | } 26 | ``` 27 | 28 | You will need to update the TypeScript declaration file location in the example above to where you downloaded it. 29 | 30 | ## Usage 31 | 32 | To use the xk6-sql API, the `k6/x/sql` module and the driver module corresponding to the database type should be imported. In the example below, `k6/x/sql/driver/ramsql` is the RamSQL database driver module. 33 | 34 | The driver module exports a driver ID. This driver identifier should be used to identify the database driver to be used in the API functions. 35 | 36 | **example** 37 | 38 | ```javascript file=examples/example.js 39 | import sql from "k6/x/sql"; 40 | 41 | // the actual database driver should be used instead of ramsql 42 | import driver from "k6/x/sql/driver/ramsql"; 43 | 44 | const db = sql.open(driver, "roster_db"); 45 | 46 | export function setup() { 47 | db.exec(` 48 | CREATE TABLE IF NOT EXISTS roster 49 | ( 50 | id INTEGER PRIMARY KEY AUTOINCREMENT, 51 | given_name VARCHAR NOT NULL, 52 | family_name VARCHAR NOT NULL 53 | ); 54 | `); 55 | } 56 | 57 | export function teardown() { 58 | db.close(); 59 | } 60 | 61 | export default function () { 62 | let result = db.exec(` 63 | INSERT INTO roster 64 | (given_name, family_name) 65 | VALUES 66 | ('Peter', 'Pan'), 67 | ('Wendy', 'Darling'), 68 | ('Tinker', 'Bell'), 69 | ('James', 'Hook'); 70 | `); 71 | console.log(`${result.rowsAffected()} rows inserted`); 72 | 73 | let rows = db.query("SELECT * FROM roster WHERE given_name = $1;", "Peter"); 74 | for (const row of rows) { 75 | console.log(`${row.family_name}, ${row.given_name}`); 76 | } 77 | } 78 | ``` 79 | 80 | ## Build 81 | 82 | The [xk6](https://github.com/grafana/xk6) build tool can be used to build a k6 that will include **xk6-sql** extension and database drivers. 83 | 84 | > [!IMPORTANT] 85 | > In the command line bellow, **xk6-sql-driver-ramsql** is just an example, it should be replaced with the database driver extension you want to use. 86 | > For example use `--with github.com/grafana/xk6-sql-driver-mysql` to access MySQL databases. 87 | 88 | ```bash 89 | xk6 build --with github.com/grafana/xk6-sql@latest --with github.com/grafana/xk6-sql-driver-ramsql 90 | ``` 91 | 92 | For more build options and how to use xk6, check out the [xk6 documentation](https://github.com/grafana/xk6). 93 | 94 | Supported RDBMSs includes: `mysql`, `postgres`, `sqlite3`, `sqlserver`, `azuresql`, `clickhouse`. 95 | 96 | Check the [xk6-sql-driver GitHub topic](https://github.com/topics/xk6-sql-driver) to discover database driver extensions. 97 | 98 | ## Drivers 99 | 100 | To use the xk6-sql extension, one or more database driver extensions should also be embedded. Database driver extension names typically start with the prefix `xk6-sql-driver-` followed by the name of the database, for example `xk6-sql-driver-mysql` is the name of the MySQL database driver extension. 101 | 102 | For easier discovery, the `xk6-sql-driver` topic is included in the database driver extensions repository. The [xk6-sql-driver GitHub topic search](https://github.com/topics/xk6-sql-driver) therefore lists the available driver extensions. 103 | 104 | ### Create driver 105 | 106 | Check the [grafana/xk6-sql-driver-ramsql](https://github.com/grafana/xk6-sql-driver-ramsql) template repository to create a new driver extension. This is a working driver extension with instructions in its README for customization. 107 | 108 | [Postgres driver extension](https://github.com/grafana/xk6-sql-driver-postgres) and [MySQL driver extension](https://github.com/grafana/xk6-sql-driver-mysql) are also good examples. 109 | 110 | ## Feedback 111 | 112 | If you find the **xk6-sql** extension useful, please star the repo. The number of stars will affect the time allocated for maintenance. 113 | 114 | [![Stargazers over time](https://starchart.cc/grafana/xk6-sql.svg?variant=adaptive)](https://starchart.cc/grafana/xk6-sql) 115 | 116 | ## Contribute 117 | 118 | If you want to contribute or help with the development of **xk6-sql**, start by reading [CONTRIBUTING.md](CONTRIBUTING.md). 119 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | import sql from "k6/x/sql"; 2 | 3 | // the actual database driver should be used instead of ramsql 4 | import driver from "k6/x/sql/driver/ramsql"; 5 | 6 | const db = sql.open(driver, "roster_db"); 7 | 8 | export function setup() { 9 | db.exec(` 10 | CREATE TABLE IF NOT EXISTS roster 11 | ( 12 | id INTEGER PRIMARY KEY AUTOINCREMENT, 13 | given_name VARCHAR NOT NULL, 14 | family_name VARCHAR NOT NULL 15 | ); 16 | `); 17 | } 18 | 19 | export function teardown() { 20 | db.close(); 21 | } 22 | 23 | export default function () { 24 | let result = db.exec(` 25 | INSERT INTO roster 26 | (given_name, family_name) 27 | VALUES 28 | ('Peter', 'Pan'), 29 | ('Wendy', 'Darling'), 30 | ('Tinker', 'Bell'), 31 | ('James', 'Hook'); 32 | `); 33 | console.log(`${result.rowsAffected()} rows inserted`); 34 | 35 | let rows = db.query("SELECT * FROM roster WHERE given_name = $1;", "Peter"); 36 | for (const row of rows) { 37 | console.log(`${row.family_name}, ${row.given_name}`); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/examples.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | setup() { 4 | cd "$BATS_TEST_DIRNAME" 5 | BASEDIR="$(git rev-parse --show-toplevel)" 6 | EXE="$BASEDIR/k6" 7 | 8 | if [ ! -x "$EXE" ]; then 9 | echo " - building k6" >&3 10 | cd "$BASEDIR" 11 | xk6 build --with github.com/grafana/xk6-sql=. --with github.com/grafana/xk6-sql-driver-ramsql 12 | cd "$BATS_TEST_DIRNAME" 13 | fi 14 | } 15 | 16 | @test 'example.js' { 17 | run $EXE run example.js 18 | [ $status -eq 0 ] 19 | echo "$output" | grep -q 'msg="Pan, Peter"' 20 | } 21 | -------------------------------------------------------------------------------- /examples/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "ES6", 5 | "paths": { 6 | "k6/x/sql": ["./typings/xk6-sql/index.d.ts"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/grafana/xk6-sql 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/grafana/sobek v0.0.0-20250320150027-203dc85b6d98 7 | github.com/proullon/ramsql v0.1.4 8 | github.com/stretchr/testify v1.10.0 9 | go.k6.io/k6 v1.0.0 10 | ) 11 | 12 | require ( 13 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/dlclark/regexp2 v1.11.4 // indirect 16 | github.com/evanw/esbuild v0.25.3 // indirect 17 | github.com/fatih/color v1.18.0 // indirect 18 | github.com/fsnotify/fsnotify v1.6.0 // indirect 19 | github.com/go-logr/logr v1.4.2 // indirect 20 | github.com/go-logr/stdr v1.2.2 // indirect 21 | github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect 22 | github.com/google/pprof v0.0.0-20231127191134-f3a68a39ae15 // indirect 23 | github.com/google/uuid v1.6.0 // indirect 24 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect 25 | github.com/jackc/pgx/v5 v5.5.4 // indirect 26 | github.com/josharian/intern v1.0.0 // indirect 27 | github.com/mailru/easyjson v0.9.0 // indirect 28 | github.com/mattn/go-colorable v0.1.14 // indirect 29 | github.com/mattn/go-isatty v0.0.20 // indirect 30 | github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd // indirect 31 | github.com/mstoykov/k6-taskqueue-lib v0.1.3 // indirect 32 | github.com/pmezard/go-difflib v1.0.0 // indirect 33 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect 34 | github.com/sirupsen/logrus v1.9.3 // indirect 35 | github.com/spf13/afero v1.11.0 // indirect 36 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 37 | go.opentelemetry.io/otel v1.35.0 // indirect 38 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect 39 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect 40 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect 41 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 42 | go.opentelemetry.io/otel/sdk v1.35.0 // indirect 43 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 44 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 45 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect 46 | golang.org/x/net v0.39.0 // indirect 47 | golang.org/x/sys v0.32.0 // indirect 48 | golang.org/x/text v0.24.0 // indirect 49 | golang.org/x/time v0.11.0 // indirect 50 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect 51 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect 52 | google.golang.org/grpc v1.71.1 // indirect 53 | google.golang.org/protobuf v1.36.6 // indirect 54 | gopkg.in/guregu/null.v3 v3.5.0 // indirect 55 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 56 | gopkg.in/yaml.v3 v3.0.1 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | buf.build/gen/go/gogo/protobuf/protocolbuffers/go v1.36.5-20210810001428-4df00b267f94.1 h1:msc7tmde/S7g2xOxfMYsTKjWQsfDI8kNkKCvklq2AhM= 2 | buf.build/gen/go/gogo/protobuf/protocolbuffers/go v1.36.5-20210810001428-4df00b267f94.1/go.mod h1:mUoXP7Apxz01oxVlxIFxqw2LVx60pAp78uBFjUXkmXc= 3 | buf.build/gen/go/prometheus/prometheus/protocolbuffers/go v1.36.5-20240802094132-5b212ab78fb7.1 h1:ImDF97CnEaa0J3KR9nPnE8vJpiIr5wfDjvQijyv3cJ0= 4 | buf.build/gen/go/prometheus/prometheus/protocolbuffers/go v1.36.5-20240802094132-5b212ab78fb7.1/go.mod h1:P36ReP+oIZKZEkukwNbbppm+rwn3QgG9dsfRGcLiy0E= 5 | github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= 6 | github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 7 | github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= 8 | github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 9 | github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= 10 | github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= 11 | github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5 h1:k+1+doEm31k0rRjCjLnGG3YRkuO9ljaEyS2ajZd6GK8= 12 | github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5/go.mod h1:5Q4+CyR7+Q3VMG8f78ou+QSX/BNUNUx5W48eFRat8DQ= 13 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 14 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 15 | github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= 16 | github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= 17 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 18 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 19 | github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= 20 | github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= 21 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 22 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 23 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 24 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 25 | github.com/chromedp/cdproto v0.0.0-20240919203636-12af5e8a671f h1:dEjjp+iN34En5Pl9XIi978DmR2/CMwuOxoPWtiHixKQ= 26 | github.com/chromedp/cdproto v0.0.0-20240919203636-12af5e8a671f/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= 27 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= 28 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 29 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 31 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 33 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 34 | github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= 35 | github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 36 | github.com/evanw/esbuild v0.25.3 h1:4JKyUsm/nHDhpxis4IyWXAi8GiyTwG1WdEp6OhGVE8U= 37 | github.com/evanw/esbuild v0.25.3/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= 38 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 39 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 40 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 41 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 42 | github.com/go-gorp/gorp v2.2.0+incompatible h1:xAUh4QgEeqPPhK3vxZN+bzrim1z5Av6q837gtjUlshc= 43 | github.com/go-gorp/gorp v2.2.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= 44 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 45 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 46 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 47 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 48 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 49 | github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= 50 | github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= 51 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 52 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 53 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 54 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 55 | github.com/google/pprof v0.0.0-20231127191134-f3a68a39ae15 h1:t2sLhFuGXwoomaKLTuoxFfFqqlG1Gp2DpsupXq3UvZ0= 56 | github.com/google/pprof v0.0.0-20231127191134-f3a68a39ae15/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 57 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 58 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 59 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 60 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 61 | github.com/grafana/k6build v0.5.11 h1:nuYV5DOMz5PEU8/XFRcDcfgzxUlLujeYNkU7NVEbdfc= 62 | github.com/grafana/k6build v0.5.11/go.mod h1:SA6/5cnnCLQ99IpFrqVfcuO+SxCRo/yLGtcnj3bfpII= 63 | github.com/grafana/k6deps v0.2.6 h1:T9t5pGnL4697SJ6fCF36fNCNSFSH/kDBI9Do/W+5TqA= 64 | github.com/grafana/k6deps v0.2.6/go.mod h1:y+pBsBemOJHML6ObuJFVgcNgO9RSfRvBklltI194aTE= 65 | github.com/grafana/k6provider v0.1.15 h1:aUStpqDMEnEL9aGCcSKmpcreHRZsr8IELna+ttKMOYI= 66 | github.com/grafana/k6provider v0.1.15/go.mod h1:tnyPNxenfF0G5JWVXQixk7QUJIn2FbZneDfvgocttNs= 67 | github.com/grafana/sobek v0.0.0-20250320150027-203dc85b6d98 h1:DqWI8D/A8GABIIjukZVNr0Sj4sBeewK2TmbTyiqUAZk= 68 | github.com/grafana/sobek v0.0.0-20250320150027-203dc85b6d98/go.mod h1:FmcutBFPLiGgroH42I4/HBahv7GxVjODcVWFTw1ISes= 69 | github.com/grafana/xk6-dashboard v0.7.5 h1:TcILyffT/Ea/XD7xG1jMA5lwtusOPRbEQsQDHmO30Mk= 70 | github.com/grafana/xk6-dashboard v0.7.5/go.mod h1:Y75F8xmgCraKT+pBzFH6me9AyH5PkXD+Bxo1dm6Il/M= 71 | github.com/grafana/xk6-redis v0.3.3 h1:9QiS4OUjYMzvriGzbqrhCn9i/kENmCkibZE+xCJSdPk= 72 | github.com/grafana/xk6-redis v0.3.3/go.mod h1:YL1qfBZXW4o2aAF6/gRa/N9oGoumUg7cJzzCHLxoCOw= 73 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= 74 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= 75 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= 76 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= 77 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 78 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 79 | github.com/influxdata/influxdb1-client v0.0.0-20190402204710-8ff2fc3824fc h1:KpMgaYJRieDkHZJWY3LMafvtqS/U8xX6+lUN+OKpl/Y= 80 | github.com/influxdata/influxdb1-client v0.0.0-20190402204710-8ff2fc3824fc/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= 81 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 82 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 83 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= 84 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 85 | github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= 86 | github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= 87 | github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= 88 | github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 89 | github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= 90 | github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= 91 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 92 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 93 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 94 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 95 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 96 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 97 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 98 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 99 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 100 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 101 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 102 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 103 | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= 104 | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 105 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 106 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 107 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 108 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 109 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 110 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 111 | github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa h1:lx8ZnNPwjkXSzOROz0cg69RlErRXs+L3eDkggASWKLo= 112 | github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I= 113 | github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd h1:AC3N94irbx2kWGA8f/2Ks7EQl2LxKIRQYuT9IJDwgiI= 114 | github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd/go.mod h1:9vRHVuLCjoFfE3GT06X0spdOAO+Zzo4AMjdIwUHBvAk= 115 | github.com/mstoykov/envconfig v1.5.0 h1:E2FgWf73BQt0ddgn7aoITkQHmgwAcHup1s//MsS5/f8= 116 | github.com/mstoykov/envconfig v1.5.0/go.mod h1:vk/d9jpexY2Z9Bb0uB4Ndesss1Sr0Z9ZiGUrg5o9VGk= 117 | github.com/mstoykov/k6-taskqueue-lib v0.1.3 h1:sdiSc5NEK/qpQkTQe505vgRYQocZevdO9ON+yMudFqo= 118 | github.com/mstoykov/k6-taskqueue-lib v0.1.3/go.mod h1:e9R2vtLFHCKT+CMiEjTJVMQiJAi17M1KiXXRs7FYc6w= 119 | github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= 120 | github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= 121 | github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= 122 | github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= 123 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 124 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 125 | github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= 126 | github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= 127 | github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= 128 | github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= 129 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 130 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 131 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= 132 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= 133 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 134 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 135 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= 136 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= 137 | github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= 138 | github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 139 | github.com/proullon/ramsql v0.1.4 h1:yTFRTn46gFH/kPbzCx+mGjuFlyTBUeDr3h2ldwxddl0= 140 | github.com/proullon/ramsql v0.1.4/go.mod h1:CFGqeQHQpdRfWqYmWD3yXqPTEaHkF4zgXy1C6qDWc9E= 141 | github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= 142 | github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= 143 | github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o= 144 | github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= 145 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 146 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 147 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e h1:zWKUYT07mGmVBH+9UgnHXd/ekCK99C8EbDSAt5qsjXE= 148 | github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= 149 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 150 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 151 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 152 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 153 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 154 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 155 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 156 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 157 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 158 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 159 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 160 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 161 | github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= 162 | github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 163 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 164 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 165 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 166 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 167 | go.k6.io/k6 v1.0.0 h1:m03ILf6kubhhM0h8NTc6J6ifqW8ljX6a2PFOiH7WWBw= 168 | go.k6.io/k6 v1.0.0/go.mod h1:7yI5PDHfF68jrSXJnqsaD6muxFt+3Ru+bjBCJW8KAcs= 169 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 170 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 171 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 172 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 173 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= 174 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA= 175 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8= 176 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0/go.mod h1:ChZSJbbfbl/DcRZNc9Gqh6DYGlfjw4PvO1pEOZH1ZsE= 177 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= 178 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= 179 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= 180 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= 181 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= 182 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= 183 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 184 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 185 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= 186 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= 187 | go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= 188 | go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= 189 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 190 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 191 | go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 192 | go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 193 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 194 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 195 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 196 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 197 | golang.org/x/crypto/x509roots/fallback v0.0.0-20250414110644-0091fc8e7c69 h1:yMbJozT8oiQBENoJ9L7TCSZb6wJJAMDUnoIaHHvoEss= 198 | golang.org/x/crypto/x509roots/fallback v0.0.0-20250414110644-0091fc8e7c69/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU= 199 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= 200 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 201 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 202 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 203 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 204 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 205 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 206 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 207 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 208 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 209 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 210 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 211 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 212 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 213 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 214 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 215 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 216 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= 217 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= 218 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= 219 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= 220 | google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= 221 | google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= 222 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 223 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 224 | gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= 225 | gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= 226 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 227 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 228 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 229 | gopkg.in/guregu/null.v3 v3.5.0 h1:xTcasT8ETfMcUHn0zTvIYtQud/9Mx5dJqD554SZct0o= 230 | gopkg.in/guregu/null.v3 v3.5.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= 231 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 232 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 233 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 234 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 235 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 236 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 237 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 238 | gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= 239 | gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= 240 | gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= 241 | gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 242 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * **Use SQL databases from k6 tests.** 3 | * 4 | * xk6-sql is a [Grafana k6 extension](https://grafana.com/docs/k6/latest/extensions/) that enables 5 | * the use of SQL databases in [k6](https://grafana.com/docs/k6/latest/) tests. 6 | * 7 | * In order to use the `xk6-sql` API, in addition to the `k6/x/sql` module, 8 | * it is also necessary to import at least one driver module. 9 | * The default export of the driver module is a driver identifier symbol, 10 | * which should be passed as a parameter of the {@link open} function. 11 | * 12 | * The driver module is typically available at `k6/x/sql/driver/FOO`, 13 | * where `FOO` is the name of the driver. 14 | * 15 | * @example 16 | * ```ts file=examples/example.js 17 | * import sql from "k6/x/sql"; 18 | * 19 | * // the actual database driver should be used instead of ramsql 20 | * import driver from "k6/x/sql/driver/ramsql"; 21 | * 22 | * const db = sql.open(driver, "roster_db"); 23 | * 24 | * export function setup() { 25 | * db.exec(` 26 | * CREATE TABLE IF NOT EXISTS roster 27 | * ( 28 | * id INTEGER PRIMARY KEY AUTOINCREMENT, 29 | * given_name VARCHAR NOT NULL, 30 | * family_name VARCHAR NOT NULL 31 | * ); 32 | * `); 33 | * } 34 | * 35 | * export function teardown() { 36 | * db.close(); 37 | * } 38 | * 39 | * export default function () { 40 | * let result = db.exec(` 41 | * INSERT INTO roster 42 | * (given_name, family_name) 43 | * VALUES 44 | * ('Peter', 'Pan'), 45 | * ('Wendy', 'Darling'), 46 | * ('Tinker', 'Bell'), 47 | * ('James', 'Hook'); 48 | * `); 49 | * console.log(`${result.rowsAffected()} rows inserted`); 50 | * 51 | * let rows = db.query("SELECT * FROM roster WHERE given_name = $1;", "Peter"); 52 | * for (const row of rows) { 53 | * console.log(`${row.family_name}, ${row.given_name}`); 54 | * } 55 | * } 56 | * ``` 57 | * 58 | * @module sql 59 | */ 60 | export as namespace sql; 61 | 62 | /** 63 | * Open a database specified by database driver identifier Symbol and a driver-specific data source name, 64 | * usually consisting of at least a database name and connection information. 65 | * 66 | * @param dirverID driver identification symbol, the default export of the driver module 67 | * @param dataSourceName driver-specific data source name, like a database name 68 | * @param options connection related options 69 | * 70 | * @example 71 | * ```ts file=examples/example.js 72 | * import sql from "k6/x/sql"; 73 | * 74 | * // the actual database driver should be used instead of ramsql 75 | * import driver from "k6/x/sql/driver/ramsql"; 76 | * 77 | * const db = sql.open(driver, "roster_db"); 78 | * ``` 79 | */ 80 | export function open( 81 | dirverID: Symbol, 82 | dataSourceName: String, 83 | options?: Options 84 | ): Database; 85 | 86 | /** 87 | * Connection-related options for the {@link open} function. 88 | * @example 89 | * ```ts 90 | * import sql from "k6/x/sql"; 91 | * 92 | * // the actual database driver should be used instead of ramsql 93 | * import driver from "k6/x/sql/driver/ramsql"; 94 | * 95 | * const db = sql.open(driver, "roster_db", { conn_max_idle_time: "2s" }); 96 | * ``` 97 | */ 98 | export interface Options { 99 | /** 100 | * Sets the maximum amount of time a connection may be idle. 101 | * If 0, connections are not closed due to a connection's idle time. 102 | * A duration string is a possibly signed sequence of decimal numbers, 103 | * each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". 104 | * Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". 105 | * 106 | * @example 107 | * ```ts 108 | * const db = sql.open(driver, "roster_db", { conn_max_idle_time: "1h10m10s" }); 109 | * ``` 110 | */ 111 | conn_max_idle_time: string; 112 | /** 113 | * Sets the maximum amount of time a connection may be reused. 114 | * If 0, connections are not closed due to a connection's age. 115 | * A duration string is a possibly signed sequence of decimal numbers, 116 | * each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". 117 | * Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". 118 | * 119 | * @example 120 | * ```ts 121 | * const db = sql.open(driver, "roster_db", { conn_max_lifetime: "10h" }); 122 | * ``` 123 | */ 124 | conn_max_lifetime: string; 125 | /** 126 | * Sets the maximum number of connections in the idle connection pool. 127 | * If 0, no idle connections are retained. 128 | * The default is currently 2. 129 | * 130 | * @example 131 | * ```ts 132 | * const db = sql.open(driver, "roster_db", { max_idle_conns: 3 }); 133 | * ``` 134 | */ 135 | max_idle_conns: number; 136 | /** 137 | * Sets the maximum number of open connections to the database. 138 | * If 0, then there is no limit on the number of open connections. 139 | * The default is 0 (unlimited). 140 | * 141 | * @example 142 | * ```ts 143 | * const db = sql.open(driver, "roster_db", { max_open_conns: 100 }); 144 | * ``` 145 | */ 146 | max_open_conns: number; 147 | } 148 | 149 | /** 150 | * Database is a database handle representing a pool of zero or more underlying connections. 151 | * 152 | * @example 153 | * ```ts file=examples/example.js 154 | * import sql from "k6/x/sql"; 155 | * 156 | * // the actual database driver should be used instead of ramsql 157 | * import driver from "k6/x/sql/driver/ramsql"; 158 | * 159 | * const db = sql.open(driver, "roster_db"); 160 | * ``` 161 | */ 162 | export interface Database { 163 | /** 164 | * Close the database and prevents new queries from starting. 165 | * 166 | * Close waits for all queries that have started processing on the server to finish. 167 | * 168 | * @example 169 | * ```ts file=examples/example.js 170 | * import sql from "k6/x/sql"; 171 | * 172 | * // the actual database driver should be used instead of ramsql 173 | * import driver from "k6/x/sql/driver/ramsql"; 174 | * 175 | * const db = sql.open(driver, "roster_db"); 176 | * 177 | * export function teardown() { 178 | * db.close(); 179 | * } 180 | * ``` 181 | */ 182 | close(): void; 183 | 184 | /** 185 | * Execute a query without returning any rows. 186 | * 187 | * @param query the query to execute 188 | * @param args placeholder parameters in the query 189 | * @returns summary of the executed SQL commands 190 | * @example 191 | * ```ts file=examples/example.js 192 | * import sql from "k6/x/sql"; 193 | * 194 | * // the actual database driver should be used instead of ramsql 195 | * import driver from "k6/x/sql/driver/ramsql"; 196 | * 197 | * const db = sql.open(driver, "roster_db"); 198 | * 199 | * export function setup() { 200 | * db.exec(` 201 | * CREATE TABLE IF NOT EXISTS roster 202 | * ( 203 | * id INTEGER PRIMARY KEY AUTOINCREMENT, 204 | * given_name VARCHAR NOT NULL, 205 | * family_name VARCHAR NOT NULL 206 | * ); 207 | * `); 208 | * 209 | * let result = db.exec(` 210 | * INSERT INTO roster 211 | * (given_name, family_name) 212 | * VALUES 213 | * ('Peter', 'Pan'), 214 | * ('Wendy', 'Darling'), 215 | * ('Tinker', 'Bell'), 216 | * ('James', 'Hook'); 217 | * `); 218 | * console.log(`${result.rowsAffected()} rows inserted`); 219 | * } 220 | * ``` 221 | */ 222 | exec(query: string, ...args: any[]): Result; 223 | /** 224 | * Execute a query (with a timeout) without returning any rows. 225 | * The timeout parameter is a duration string, a possibly signed sequence of decimal numbers, 226 | * each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". 227 | * Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". 228 | * @param timeout the query timeout as a duration string 229 | * @param query the query to execute 230 | * @param args placeholder parameters in the query 231 | * @returns summary of the executed SQL commands 232 | * @example 233 | * ```ts file=examples/example.js 234 | * import sql from "k6/x/sql"; 235 | * 236 | * // the actual database driver should be used instead of ramsql 237 | * import driver from "k6/x/sql/driver/ramsql"; 238 | * 239 | * const db = sql.open(driver, "roster_db"); 240 | * 241 | * export function setup() { 242 | * db.exec(` 243 | * CREATE TABLE IF NOT EXISTS roster 244 | * ( 245 | * id INTEGER PRIMARY KEY AUTOINCREMENT, 246 | * given_name VARCHAR NOT NULL, 247 | * family_name VARCHAR NOT NULL 248 | * ); 249 | * `); 250 | * 251 | * let result = db.execWithTimeout("10s", ` 252 | * INSERT INTO roster 253 | * (given_name, family_name) 254 | * VALUES 255 | * ('Peter', 'Pan'), 256 | * ('Wendy', 'Darling'), 257 | * ('Tinker', 'Bell'), 258 | * ('James', 'Hook'); 259 | * `); 260 | * console.log(`${result.rowsAffected()} rows inserted`); 261 | * } 262 | * ``` 263 | */ 264 | execWithTimeout(timeout: string, query: string, ...args: any[]): Result; 265 | /** 266 | * Query executes a query that returns rows, typically a SELECT. 267 | * @param query the query to execute 268 | * @param args placeholder parameters in the query 269 | * @returns rows of the query result 270 | * @example 271 | * ```ts file=examples/example.js 272 | * import sql from "k6/x/sql"; 273 | * 274 | * // the actual database driver should be used instead of ramsql 275 | * import driver from "k6/x/sql/driver/ramsql"; 276 | * 277 | * const db = sql.open(driver, "roster_db"); 278 | * 279 | * export default function () { 280 | * let rows = db.query("SELECT * FROM roster WHERE given_name = $1;", "Peter"); 281 | * for (const row of results) { 282 | * console.log(`${row.family_name}, ${row.given_name}`); 283 | * } 284 | * } 285 | * ``` 286 | */ 287 | query(query: string, ...args: any[]): Row[]; 288 | /** 289 | * Query executes a query (with a timeout) that returns rows, typically a SELECT. 290 | * The timeout parameter is a duration string, a possibly signed sequence of decimal numbers, 291 | * each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". 292 | * Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". 293 | * @param timeout the query timeout as a duration string 294 | * @param query the query to execute 295 | * @param args placeholder parameters in the query 296 | * @returns rows of the query result 297 | * @example 298 | * ```ts file=examples/example.js 299 | * import sql from "k6/x/sql"; 300 | * 301 | * // the actual database driver should be used instead of ramsql 302 | * import driver from "k6/x/sql/driver/ramsql"; 303 | * 304 | * const db = sql.open(driver, "roster_db"); 305 | * 306 | * export default function () { 307 | * let rows = db.queryWithTimeout("10s", "SELECT * FROM roster WHERE given_name = $1;", "Peter"); 308 | * for (const row of results) { 309 | * console.log(`${row.family_name}, ${row.given_name}`); 310 | * } 311 | * } 312 | * ``` 313 | */ 314 | queryWithTimeout(timeout: string, query: string, ...args: any[]): Row[]; 315 | } 316 | 317 | /** 318 | * An object containing a single row of the query result. 319 | */ 320 | export interface Row { 321 | /** 322 | * Each column has a property with the same name as the column name. 323 | * The value of the property contains the value of the given column in the current row. 324 | * 325 | * The value of the property is automatically mapped to the corresponding JavaScript type. 326 | * 327 | * @example 328 | * ```ts file=examples/example.js 329 | * import sql from "k6/x/sql"; 330 | * 331 | * // the actual database driver should be used instead of ramsql 332 | * import driver from "k6/x/sql/driver/ramsql"; 333 | * 334 | * const db = sql.open(driver, "roster_db"); 335 | * 336 | * export default function () { 337 | * let rows = db.query("SELECT * FROM roster WHERE given_name = $1;", "Peter"); 338 | * for (const row of results) { 339 | * console.log(`${row.family_name}, ${row.given_name}`); 340 | * } 341 | * } 342 | * ``` 343 | */ 344 | [key: string]: unknown; 345 | } 346 | 347 | /** 348 | * A Result summarizes an executed SQL command. 349 | * @example 350 | * ```ts file=examples/example.js 351 | * import sql from "k6/x/sql"; 352 | * 353 | * // the actual database driver should be used instead of ramsql 354 | * import driver from "k6/x/sql/driver/ramsql"; 355 | * 356 | * const db = sql.open(driver, "roster_db"); 357 | * 358 | * export function setup() { 359 | * let result = db.exec(` 360 | * INSERT INTO roster 361 | * (given_name, family_name) 362 | * VALUES 363 | * ('Peter', 'Pan'), 364 | * ('Wendy', 'Darling'), 365 | * ('Tinker', 'Bell'), 366 | * ('James', 'Hook'); 367 | * `); 368 | * console.log(`${result.rowsAffected()} rows inserted`); 369 | * } 370 | * ``` 371 | */ 372 | export interface Result { 373 | /** 374 | * Returns the integer generated by the database 375 | * in response to a command. Typically this will be from an 376 | * "auto increment" column when inserting a new row. Not all 377 | * databases support this feature, and the syntax of such 378 | * statements varies. 379 | */ 380 | lastInsertId(): number; 381 | /** 382 | * Returns the number of rows affected by an 383 | * update, insert, or delete. Not every database or database 384 | * driver may support this. 385 | */ 386 | rowsAffected(): number; 387 | } 388 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xk6-sql", 3 | "version": "1.0.5", 4 | "description": "Use SQL databases from k6 tests", 5 | "repository": "github:grafana/xk6-sql", 6 | "license": "AGPL-3.0-only", 7 | "devDependencies": { 8 | "@types/k6": "^1.0.0", 9 | "k6": "^0.0.0" 10 | }, 11 | "files": [ 12 | "index.d.ts" 13 | ], 14 | "types": "index.d.ts" 15 | } 16 | -------------------------------------------------------------------------------- /register.go: -------------------------------------------------------------------------------- 1 | // Package sql is the primary go package of the xk6-sql extension. 2 | // Contains the registration of the xk6-sql extension as a k6 extension. 3 | package sql 4 | 5 | import ( 6 | "github.com/grafana/xk6-sql/sql" 7 | "go.k6.io/k6/js/modules" 8 | ) 9 | 10 | func init() { 11 | modules.Register(sql.ImportPath, sql.New()) 12 | } 13 | -------------------------------------------------------------------------------- /register_internal_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/grafana/xk6-sql/sql" 7 | "github.com/stretchr/testify/require" 8 | "go.k6.io/k6/ext" 9 | ) 10 | 11 | func Test_register(t *testing.T) { 12 | t.Parallel() 13 | 14 | require.Contains(t, ext.Get(ext.JSExtension), sql.ImportPath) 15 | } 16 | -------------------------------------------------------------------------------- /releases/v1.0.0.md: -------------------------------------------------------------------------------- 1 | 🎉 xk6-sql `v1.0.0` is here! 2 | 3 | ## Modularization 4 | 5 | This release contains a major refactoring, the modularization of the previously monolithic `xk6-sql`. The database driver integrations have been extracted into separate k6 driver extensions. 6 | 7 | Although modularization means a small API modification, it is basically a **breaking change!** 8 | 9 | ## How it Works 10 | 11 | The SQL database driver integration is implemented in a separate k6 extension. The JavaScript API of this extension contains a single default export whose type is [JavaScript Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) and is used to specify the database driver in the [xk6-sql API](https://sql.x.k6.io). 12 | 13 | The use of the Symbol type is necessary in order to force the import of the driver module. 14 | 15 | ```JavaScript 16 | import sql from "k6/x/sql" 17 | import sqlite3 from "k6/x/sql/sqlite3" 18 | 19 | const db = sql.open(sqlite3, "./test.db") 20 | ``` 21 | 22 | ## Drivers 23 | 24 | For easier discovery, the `xk6-sql-driver` topic is included in the database driver extensions repository. The [xk6-sql-driver GitHub topic search](https://github.com/topics/xk6-sql-driver) therefore lists the available driver extensions. 25 | 26 | During the refactoring, the following k6 SQL database driver extensions were created from the database drivers previously embedded in the xk6-sql extension: 27 | 28 | - https://github.com/grafana/xk6-sql-driver-mysql 29 | - https://github.com/grafana/xk6-sql-driver-postgres 30 | - https://github.com/grafana/xk6-sql-driver-sqlite3 31 | - https://github.com/grafana/xk6-sql-driver-azuresql 32 | - https://github.com/grafana/xk6-sql-driver-sqlserver 33 | - https://github.com/grafana/xk6-sql-driver-clickhouse 34 | 35 | The following template repository can be used to create a new driver extension: https://github.com/grafana/xk6-sql-driver-ramsql 36 | 37 | ## Solved problems 38 | 39 | 1. New SQL database type support (integration of new golang database/sql driver) does not require changes to the source code of `xk6-sql` and the release of `xk6-sql`. 40 | 41 | 2. Supporting additional database drivers does not increase the size of `k6`. Since the database drivers are implemented in a separate `k6` extension, it is sufficient to embed only the drivers you want to use. 42 | 43 | 3. The SQL database driver integration created by the community can be maintained by the community. Since database drivers are implemented as `k6` extensions, the community can create and maintain driver modules independently of Grafana. 44 | 45 | 4. Drivers may have different requirements. For example, cgo (`CGO_ENABLED`) should only be enabled if a driver needs it. (like `sqlite3`). 46 | 47 | 5. From the security perspective, the attack surface is smaller if fewer dependencies are embedded. 48 | 49 | ## Build 50 | 51 | The [xk6](https://github.com/grafana/xk6) build tool can be used to build a k6 that will include **xk6-sql** extension **and database drivers**. 52 | 53 | > [!IMPORTANT] 54 | > In the command line bellow, **xk6-sql-driver-ramsql** is just an example, it should be replaced with the database driver extension you want to use. 55 | > For example use `--with github.com/grafana/xk6-sql-driver-mysql` to access MySQL databases. 56 | 57 | ```bash 58 | xk6 build --with github.com/grafana/xk6-sql@latest --with github.com/grafana/xk6-sql-driver-ramsql@latest 59 | ``` 60 | 61 | ## API Compatibility 62 | 63 | The `xk6-sql` JavaScript API changes in an incompatible way because of the driver parameter type becomes Symbol instead of String. 64 | 65 | ## API documentation 66 | 67 | A TypeScript declaration file was created for the `xk6-sql` API, from which an [API documentation site](https://sql.x.k6.io) is generated. 68 | 69 | ## MySQL TLS support 70 | 71 | MySQL TLS configuration support has been moved to the driver extension: https://github.com/grafana/xk6-sql-driver-mysql 72 | 73 | It is important to note that the MySQL TLS configuration API will change in the future. 74 | -------------------------------------------------------------------------------- /releases/v1.0.1.md: -------------------------------------------------------------------------------- 1 | xk6-sql `v1.0.1` is here 🎉! 2 | 3 | This release includes: 4 | 5 | - A `smoke.test.js` file has been added for smoke testing after the build. The `k6lint`'s `smoke` checker expects a smoke test file. 6 | -------------------------------------------------------------------------------- /releases/v1.0.2.md: -------------------------------------------------------------------------------- 1 | xk6-sql `v1.0.2` is here 🎉! 2 | 3 | This release includes: 4 | 5 | This is a maintenance release, changes: 6 | 7 | - Simplify contribution with support for reproducible development environments ([Development Containers](https://containers.dev/)). 8 | - Updated documentation for typings to work with VSCode 9 | -------------------------------------------------------------------------------- /releases/v1.0.3.md: -------------------------------------------------------------------------------- 1 | xk6-sql `v1.0.3` is here 🎉! 2 | 3 | This release includes: 4 | 5 | ## New features 6 | 7 | - [Connection options in the open function](https://github.com/grafana/xk6-sql/issues/122): An optional options parameter can be used in `open()` to specify database connection-related options. 8 | 9 | ```js 10 | sql.open(driver, "roster_db", opts) 11 | ``` 12 | 13 | Properties: 14 | - `conn_max_idle_time`: Sets the maximum amount of time a connection may be idle. If 0, connections are not closed due to a connection's idle time. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Example: 15 | ```js 16 | const db = sql.open(driver, "roster_db", { conn_max_idle_time: "1h10m10s" }); 17 | ``` 18 | - `conn_max_lifetime`: Sets the maximum amount of time a connection may be reused. If 0, connections are not closed due to a connection's age. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Example: 19 | ```js 20 | const db = sql.open(driver, "roster_db", { conn_max_lifetime: "10h" }); 21 | ``` 22 | - `max_idle_conns`: Sets the maximum number of connections in the idle connection pool. If 0, no idle connections are retained. The default is currently 2. Example: 23 | ```js 24 | const db = sql.open(driver, "roster_db", { max_idle_conns: 3 }); 25 | ``` 26 | - `max_open_conns`: Sets the maximum number of open connections to the database. If 0, then there is no limit on the number of open connections. The default is 0 (unlimited). Example: 27 | ```js 28 | const db = sql.open(driver, "roster_db", { max_open_conns: 100 }); 29 | ``` 30 | 31 | ## Bugfixes 32 | 33 | - [Symbol type driver parameter support](https://github.com/grafana/xk6-sql/issues/120): The `open()` function now accepts `Symbol` (class) type driver ids in addition to the primitive symbol type. This is because when a driver is imported with the `require()` function, it is not a primitive symbol that is imported, but a `Symbol` class type. Also fixes [#115](https://github.com/grafana/xk6-sql/issues/115) 34 | -------------------------------------------------------------------------------- /releases/v1.0.4.md: -------------------------------------------------------------------------------- 1 | xk6-sql `v1.0.4` is here 🎉! 2 | 3 | This release includes: 4 | 5 | ## New features 6 | 7 | - [Timeout support in `execWithTimeout()` and `queryWithTimeout()` functions](https://github.com/grafana/xk6-sql/issues/127): The `exec()` and `query()` functions wait for an unlimited amount of time for the result. This can lead to the test stalling, for example, in the event of a network problem. The API has been extended with timeout-handling counterparts of these functions: `execWithTimeout()` and `queryWithTimeout()`. The first parameter is the timeout. The timeout parameter is a duration string, a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as `300ms`, `-1.5h` or `2h45m`. Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. 8 | 9 | ## Bugfixes 10 | 11 | - [Use VU Context()](https://github.com/grafana/xk6-sql/issues/124): VU Context() is now used in `query()` and `exec()` functions instead of background context. Using background context is a potential problem if SQL operations are still running after the VU context is invalidated. 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /releases/v1.0.5.md: -------------------------------------------------------------------------------- 1 | xk6-sql `v1.0.5` is here 🎉! 2 | 3 | This is a maintenance release, changes: 4 | 5 | - Updated k6 to v1.0.0 6 | - Use shared GitHub workflows from xk6 7 | - Updated tooling dependencies 8 | - `go`: 1.24 9 | - `golangci-lint`: 2.1.6 10 | - `xk6`: 0.19.2 11 | - `gosec`: 2.22.4 12 | - `govulncheck`: 1.1.4 13 | - `bun`: 1.2.12 14 | -------------------------------------------------------------------------------- /smoke.test.js: -------------------------------------------------------------------------------- 1 | import "k6/x/sql"; 2 | 3 | export default function () {} 4 | -------------------------------------------------------------------------------- /sql/drivers.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/grafana/sobek" 7 | "go.k6.io/k6/js/modules" 8 | ) 9 | 10 | type registry struct { 11 | mu sync.RWMutex 12 | drivers map[*sobek.Symbol]struct{} 13 | } 14 | 15 | var driverRegistry *registry //nolint:gochecknoglobals 16 | 17 | func init() { 18 | driverRegistry = ®istry{ 19 | drivers: make(map[*sobek.Symbol]struct{}), 20 | } 21 | } 22 | 23 | func (r *registry) register(driverName string) *sobek.Symbol { 24 | r.mu.Lock() 25 | defer r.mu.Unlock() 26 | 27 | id := sobek.NewSymbol(driverName) 28 | 29 | r.drivers[id] = struct{}{} 30 | 31 | return id 32 | } 33 | 34 | func (r *registry) lookup(id *sobek.Symbol) (bool, string) { 35 | r.mu.RLock() 36 | defer r.mu.RUnlock() 37 | 38 | _, found := r.drivers[id] 39 | if !found { 40 | return false, "" 41 | } 42 | 43 | return true, id.String() 44 | } 45 | 46 | // RegisterDriver registers an SQL database driver. 47 | func RegisterDriver(driverName string) *sobek.Symbol { 48 | return driverRegistry.register(driverName) 49 | } 50 | 51 | // driverImportPathPrefix contains driver module's common JavaScript import path prefix. 52 | const driverImportPathPrefix = "k6/x/sql/driver/" 53 | 54 | // RegisterModule registers an SQL database driver module. 55 | // The module import path will be k6/x/sql/driver/ + driverName. 56 | // The module's default export will be the driver id Symbol. 57 | func RegisterModule(driverName string) modules.Module { 58 | id := RegisterDriver(driverName) 59 | root := &driverRootModule{driverID: id} 60 | 61 | modules.Register(driverImportPathPrefix+driverName, root) 62 | 63 | return root 64 | } 65 | 66 | // lookupDriver search SQL database driver by id in the registry. 67 | // Returns true and the driver name if the driver is registered, 68 | // otherwise returns false and an empty string. 69 | func lookupDriver(id *sobek.Symbol) (bool, string) { 70 | return driverRegistry.lookup(id) 71 | } 72 | 73 | // driverRootModule is the global module object type. It is instantiated once per test 74 | // run and will be used to create `k6/x/sql/driver/XXX` module instances for each VU. 75 | type driverRootModule struct { 76 | driverID *sobek.Symbol 77 | } 78 | 79 | var _ modules.Module = &driverRootModule{} 80 | 81 | // NewModuleInstance implements the modules.Module interface to return 82 | // a new instance for each VU. 83 | func (root *driverRootModule) NewModuleInstance(_ modules.VU) modules.Instance { 84 | instance := &driverModule{ 85 | exports: modules.Exports{ 86 | Default: root.driverID, 87 | }, 88 | } 89 | 90 | return instance 91 | } 92 | 93 | // module represents an instance of the JavaScript module for every VU. 94 | type driverModule struct { 95 | exports modules.Exports 96 | } 97 | 98 | // Exports is representation of ESM exports of a module. 99 | func (mod *driverModule) Exports() modules.Exports { 100 | return mod.exports 101 | } 102 | -------------------------------------------------------------------------------- /sql/module.go: -------------------------------------------------------------------------------- 1 | // Package sql provides a javascript module for performing SQL actions against relational databases. 2 | package sql 3 | 4 | import ( 5 | "context" 6 | "database/sql" 7 | "errors" 8 | "fmt" 9 | "time" 10 | 11 | "github.com/grafana/sobek" 12 | "go.k6.io/k6/js/modules" 13 | ) 14 | 15 | // ImportPath contains module's JavaScript import path. 16 | const ImportPath = "k6/x/sql" 17 | 18 | // New creates a new instance of the extension's JavaScript module. 19 | func New() modules.Module { 20 | return new(rootModule) 21 | } 22 | 23 | // rootModule is the global module object type. It is instantiated once per test 24 | // run and will be used to create `k6/x/sql` module instances for each VU. 25 | type rootModule struct{} 26 | 27 | // NewModuleInstance implements the modules.Module interface to return 28 | // a new instance for each VU. 29 | func (*rootModule) NewModuleInstance(vu modules.VU) modules.Instance { 30 | instance := &module{} 31 | 32 | instance.exports.Default = instance 33 | instance.exports.Named = map[string]interface{}{ 34 | "open": instance.Open, 35 | } 36 | 37 | instance.vu = vu 38 | 39 | return instance 40 | } 41 | 42 | // module represents an instance of the JavaScript module for every VU. 43 | type module struct { 44 | exports modules.Exports 45 | vu modules.VU 46 | } 47 | 48 | // Exports is representation of ESM exports of a module. 49 | func (mod *module) Exports() modules.Exports { 50 | return mod.exports 51 | } 52 | 53 | // KeyValue is a simple key-value pair. 54 | type KeyValue map[string]interface{} 55 | 56 | func asSymbol(value sobek.Value) (*sobek.Symbol, bool) { 57 | sym, ok := value.(*sobek.Symbol) 58 | if ok { 59 | return sym, ok 60 | } 61 | 62 | obj, ok := value.(*sobek.Object) 63 | if !ok { 64 | return nil, false 65 | } 66 | 67 | valueOf, ok := sobek.AssertFunction(obj.Get("valueOf")) 68 | if !ok { 69 | return nil, false 70 | } 71 | 72 | ret, err := valueOf(obj) 73 | if err != nil { 74 | return nil, false 75 | } 76 | 77 | sym, ok = ret.(*sobek.Symbol) 78 | 79 | return sym, ok 80 | } 81 | 82 | // open establishes a connection to the specified database type using 83 | // the provided connection string. 84 | func (mod *module) Open(driverID sobek.Value, connectionString string, opts *options) (*Database, error) { 85 | driverSym, ok := asSymbol(driverID) 86 | 87 | if !ok { 88 | return nil, fmt.Errorf("%w: invalid driver parameter type", errUnsupportedDatabase) 89 | } 90 | 91 | registered, database := lookupDriver(driverSym) 92 | if !registered { 93 | return nil, fmt.Errorf("%w: %s", errUnsupportedDatabase, database) 94 | } 95 | 96 | db, err := sql.Open(database, connectionString) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | if err = opts.apply(db); err != nil { 102 | return nil, err 103 | } 104 | 105 | return &Database{db: db, ctx: mod.vu.Context}, nil 106 | } 107 | 108 | // Database is a database handle representing a pool of zero or more underlying connections. 109 | type Database struct { 110 | db *sql.DB 111 | ctx func() context.Context 112 | } 113 | 114 | // Query executes a query that returns rows, typically a SELECT. 115 | func (dbase *Database) Query(query string, args ...interface{}) ([]KeyValue, error) { 116 | return dbase.query(dbase.ctx(), query, args...) 117 | } 118 | 119 | // QueryWithTimeout executes a query (with a timeout) that returns rows, typically a SELECT. 120 | // The timeout can be specified as a duration string. 121 | func (dbase *Database) QueryWithTimeout(timeout string, query string, args ...interface{}) ([]KeyValue, error) { 122 | ctx, cancel, err := dbase.parseTimeout(timeout) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | defer cancel() 128 | 129 | return dbase.query(ctx, query, args...) 130 | } 131 | 132 | // Exec executes a query without returning any rows. 133 | func (dbase *Database) Exec(query string, args ...interface{}) (sql.Result, error) { 134 | return dbase.exec(dbase.ctx(), query, args...) 135 | } 136 | 137 | // ExecWithTimeout executes a query (with a timeout) without returning any rows. 138 | // The timeout can be specified as a duration string. 139 | func (dbase *Database) ExecWithTimeout(timeout string, query string, args ...interface{}) (sql.Result, error) { 140 | ctx, cancel, err := dbase.parseTimeout(timeout) 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | defer cancel() 146 | 147 | return dbase.exec(ctx, query, args...) 148 | } 149 | 150 | // Close the database and prevents new queries from starting. 151 | func (dbase *Database) Close() error { 152 | return dbase.db.Close() 153 | } 154 | 155 | func (dbase *Database) parseTimeout(timeout string) (context.Context, context.CancelFunc, error) { 156 | dur, err := time.ParseDuration(timeout) 157 | if err != nil { 158 | return nil, nil, err 159 | } 160 | 161 | ctx, cancel := context.WithTimeout(dbase.ctx(), dur) 162 | 163 | return ctx, cancel, nil 164 | } 165 | 166 | func (dbase *Database) query(ctx context.Context, query string, args ...interface{}) ([]KeyValue, error) { 167 | rows, err := dbase.db.QueryContext(ctx, query, args...) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | defer func() { 173 | _ = rows.Close() 174 | }() 175 | 176 | if rows.Err() != nil { 177 | return nil, rows.Err() 178 | } 179 | 180 | cols, err := rows.Columns() 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | values := make([]interface{}, len(cols)) 186 | valuePtrs := make([]interface{}, len(cols)) 187 | result := make([]KeyValue, 0) 188 | 189 | for rows.Next() { 190 | for i := range values { 191 | valuePtrs[i] = &values[i] 192 | } 193 | 194 | err = rows.Scan(valuePtrs...) 195 | if err != nil { 196 | return nil, err 197 | } 198 | 199 | data := make(KeyValue, len(cols)) 200 | for i, colName := range cols { 201 | data[colName] = *valuePtrs[i].(*interface{}) //nolint:forcetypeassert 202 | } 203 | 204 | result = append(result, data) 205 | } 206 | 207 | return result, nil 208 | } 209 | 210 | func (dbase *Database) exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { 211 | return dbase.db.ExecContext(ctx, query, args...) 212 | } 213 | 214 | var errUnsupportedDatabase = errors.New("unsupported database") 215 | -------------------------------------------------------------------------------- /sql/module_internal_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/grafana/sobek" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_asSymbol(t *testing.T) { 11 | t.Parallel() 12 | 13 | symbol := sobek.NewSymbol("foo") 14 | 15 | sym, ok := asSymbol(symbol) 16 | 17 | require.True(t, ok) 18 | require.Same(t, symbol, sym) 19 | 20 | rt := sobek.New() 21 | 22 | obj := symbol.ToObject(rt) 23 | 24 | sym, ok = asSymbol(obj) 25 | 26 | require.True(t, ok) 27 | require.Same(t, symbol, sym) 28 | 29 | sym, ok = asSymbol(sobek.Undefined()) 30 | 31 | require.False(t, ok) 32 | require.Nil(t, sym) 33 | } 34 | -------------------------------------------------------------------------------- /sql/module_test.go: -------------------------------------------------------------------------------- 1 | package sql_test 2 | 3 | import ( 4 | _ "embed" 5 | "testing" 6 | 7 | "github.com/grafana/xk6-sql/sql" 8 | "github.com/grafana/xk6-sql/sqltest" 9 | _ "github.com/proullon/ramsql/driver" 10 | ) 11 | 12 | //go:embed testdata/script.js 13 | var script string 14 | 15 | func TestMain(m *testing.M) { 16 | sql.RegisterModule("ramsql") 17 | 18 | m.Run() 19 | } 20 | 21 | // TestIntegration performs an integration test creating a ramsql database. 22 | func TestIntegration(t *testing.T) { 23 | t.Parallel() 24 | 25 | sqltest.RunScript(t, "ramsql", "testdb", script) 26 | } 27 | 28 | func TestOptions(t *testing.T) { 29 | t.Parallel() 30 | 31 | sqltest.RunScript(t, "ramsql", "testdb", `const db = sql.open(driver, connection, { conn_max_idle_time: "5s" });`) 32 | } 33 | -------------------------------------------------------------------------------- /sql/options.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | 7 | "github.com/grafana/sobek" 8 | ) 9 | 10 | // options represents connection related options for Open(). 11 | type options struct { 12 | ConnMaxIdleTime sobek.Value 13 | ConnMaxLifetime sobek.Value 14 | MaxIdleConns sobek.Value 15 | MaxOpenConns sobek.Value 16 | } 17 | 18 | func (o *options) apply(db *sql.DB) error { 19 | if o == nil { 20 | return nil 21 | } 22 | 23 | if o.ConnMaxIdleTime != nil { 24 | d, err := time.ParseDuration(o.ConnMaxIdleTime.String()) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | db.SetConnMaxIdleTime(d) 30 | } 31 | 32 | if o.ConnMaxLifetime != nil { 33 | d, err := time.ParseDuration(o.ConnMaxLifetime.String()) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | db.SetConnMaxLifetime(d) 39 | } 40 | 41 | if o.MaxIdleConns != nil { 42 | db.SetMaxIdleConns(int(o.MaxIdleConns.ToInteger())) 43 | } 44 | 45 | if o.MaxOpenConns != nil { 46 | db.SetMaxOpenConns(int(o.MaxOpenConns.ToInteger())) 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /sql/options_internal_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | "github.com/grafana/sobek" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_options_apply(t *testing.T) { 12 | t.Parallel() 13 | 14 | db, err := sql.Open("ramsql", "foo") 15 | 16 | require.NoError(t, err) 17 | 18 | rt := sobek.New() 19 | 20 | require.NoError(t, (&options{}).apply(db)) 21 | require.NoError(t, (&options{ConnMaxIdleTime: rt.ToValue("5s")}).apply(db)) 22 | require.NoError(t, (&options{ConnMaxLifetime: rt.ToValue("10m")}).apply(db)) 23 | 24 | require.NoError(t, (&options{MaxIdleConns: rt.ToValue(5)}).apply(db)) 25 | require.NoError(t, (&options{MaxOpenConns: rt.ToValue(10)}).apply(db)) 26 | 27 | require.Error(t, (&options{ConnMaxIdleTime: rt.ToValue("5g")}).apply(db)) 28 | require.Error(t, (&options{ConnMaxLifetime: rt.ToValue("10e")}).apply(db)) 29 | } 30 | -------------------------------------------------------------------------------- /sql/sql_internal_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/grafana/sobek" 9 | "github.com/stretchr/testify/require" 10 | "go.k6.io/k6/js/modulestest" 11 | ) 12 | 13 | func TestOpen(t *testing.T) { //nolint: paralleltest 14 | rt := modulestest.NewRuntime(t) 15 | 16 | mod, ok := New().NewModuleInstance(rt.VU).(*module) 17 | 18 | require.True(t, ok) 19 | 20 | driver := RegisterDriver("ramsql") 21 | require.NotNil(t, driver) 22 | 23 | db, err := mod.Open(driver, "", nil) 24 | 25 | require.NoError(t, err) 26 | require.NotNil(t, db) 27 | 28 | const expr = `CREATE TABLE address (id BIGSERIAL PRIMARY KEY, street TEXT, street_number INT);` 29 | 30 | if runtime.GOOS != "windows" { 31 | _, err = db.ExecWithTimeout("1ns", expr) 32 | require.ErrorIs(t, err, context.DeadlineExceeded) 33 | 34 | _, err = db.QueryWithTimeout("1ns", expr) 35 | require.ErrorIs(t, err, context.DeadlineExceeded) 36 | } 37 | 38 | _, err = mod.Open(sobek.New().ToValue("foo"), "testdb", nil) // not a Symbol 39 | 40 | require.Error(t, err) 41 | 42 | _, err = mod.Open(sobek.NewSymbol("ramsql"), "testdb", nil) // not a registered Symbol 43 | 44 | require.Error(t, err) 45 | } 46 | -------------------------------------------------------------------------------- /sql/testdata/script.js: -------------------------------------------------------------------------------- 1 | const db = sql.open(driver, connection); 2 | 3 | db.exec("CREATE TABLE test_table (id integer PRIMARY KEY AUTOINCREMENT, name varchar NOT NULL, value varchar);"); 4 | 5 | for (let i = 0; i < 5; i++) { 6 | db.exec("INSERT INTO test_table (name, value) VALUES ('name-" + i + "', 'value-" + i + "');"); 7 | } 8 | 9 | let all_rows = db.query("SELECT * FROM test_table;"); 10 | if (all_rows.length != 5) { 11 | throw new Error("Expected all five rows to be returned; got " + all_rows.length); 12 | } 13 | 14 | let one_row = db.queryWithTimeout("10s", "SELECT * FROM test_table WHERE name = $1;", "name-2"); 15 | if (one_row.length != 1) { 16 | throw new Error("Expected single row to be returned; got " + one_row.length); 17 | } 18 | 19 | let no_rows = db.query("SELECT * FROM test_table WHERE name = $1;", "bogus-name"); 20 | if (no_rows.length != 0) { 21 | throw new Error("Expected no rows to be returned; got " + no_rows.length); 22 | } 23 | 24 | db.close(); 25 | -------------------------------------------------------------------------------- /sqltest/sqltest.go: -------------------------------------------------------------------------------- 1 | // Package sqltest contains helper functions for driver integration tests of the xk6-sql extension. 2 | package sqltest 3 | 4 | import ( 5 | "testing" 6 | 7 | "github.com/grafana/sobek" 8 | "github.com/grafana/xk6-sql/sql" 9 | "github.com/stretchr/testify/require" 10 | "go.k6.io/k6/ext" 11 | "go.k6.io/k6/js/modules" 12 | "go.k6.io/k6/js/modulestest" 13 | ) 14 | 15 | // RunScript executes JavaScript code in a specially initialized interpreter. 16 | // The "sql" variable contains the xk6-sql module, 17 | // the "driver" variable contains the Symbol identifying the driver, 18 | // and the "connection" variable contains the database connection string. 19 | func RunScript(t *testing.T, driver string, connection string, script string) sobek.Value { 20 | t.Helper() 21 | 22 | runtime := modulestest.NewRuntime(t) 23 | vu := runtime.VU 24 | 25 | root := sql.New() 26 | m := root.NewModuleInstance(runtime.VU) 27 | 28 | require.NoError(t, vu.RuntimeField.Set("sql", m.Exports().Default)) 29 | 30 | jsext, found := ext.Get(ext.JSExtension)["k6/x/sql/driver/"+driver] 31 | 32 | require.True(t, found, "Driver extension found: "+driver) 33 | 34 | jsmod, ok := jsext.Module.(modules.Module) 35 | 36 | require.True(t, ok, "Driver extension module is JavaScript module") 37 | 38 | m = jsmod.NewModuleInstance(vu) 39 | 40 | require.NoError(t, vu.RuntimeField.Set("driver", m.Exports().Default)) 41 | require.NoError(t, vu.RuntimeField.Set("connection", connection)) 42 | 43 | value, err := runtime.VU.RuntimeField.RunString(script) 44 | 45 | require.NoError(t, err) 46 | 47 | return value 48 | } 49 | -------------------------------------------------------------------------------- /sqltest/sqltest_test.go: -------------------------------------------------------------------------------- 1 | package sqltest_test 2 | 3 | import ( 4 | _ "embed" 5 | "testing" 6 | 7 | "github.com/grafana/xk6-sql/sql" 8 | "github.com/grafana/xk6-sql/sqltest" 9 | _ "github.com/proullon/ramsql/driver" 10 | ) 11 | 12 | //go:embed testdata/script.js 13 | var script string 14 | 15 | func TestRunScript(t *testing.T) { 16 | t.Parallel() 17 | 18 | sql.RegisterModule("ramsql") 19 | 20 | sqltest.RunScript(t, "ramsql", "testdb", script) 21 | } 22 | -------------------------------------------------------------------------------- /sqltest/testdata/script.js: -------------------------------------------------------------------------------- 1 | const db = sql.open(driver, connection); 2 | 3 | db.exec("CREATE TABLE test_table (id integer PRIMARY KEY AUTOINCREMENT, name varchar NOT NULL, value varchar);"); 4 | 5 | for (let i = 0; i < 5; i++) { 6 | db.exec("INSERT INTO test_table (name, value) VALUES ('name-" + i + "', 'value-" + i + "');"); 7 | } 8 | 9 | let all_rows = db.query("SELECT * FROM test_table;"); 10 | if (all_rows.length != 5) { 11 | throw new Error("Expected all five rows to be returned; got " + all_rows.length); 12 | } 13 | 14 | let one_row = db.query("SELECT * FROM test_table WHERE name = $1;", "name-2"); 15 | if (one_row.length != 1) { 16 | throw new Error("Expected single row to be returned; got " + one_row.length); 17 | } 18 | 19 | let no_rows = db.query("SELECT * FROM test_table WHERE name = $1;", "bogus-name"); 20 | if (no_rows.length != 0) { 21 | throw new Error("Expected no rows to be returned; got " + no_rows.length); 22 | } 23 | 24 | db.close(); 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "typedocOptions": { 3 | "entryPoints": ["./index.d.ts"], 4 | "out": "build/docs", 5 | "name": "xk6-sql", 6 | "readme": "none", 7 | "hideGenerator": true, 8 | "disableSources": true, 9 | "navigation": { 10 | "includeCategories": false, 11 | "includeGroups": false, 12 | "includeFolders": false 13 | }, 14 | "categorizeByGroup": false, 15 | "visibilityFilters": {}, 16 | "navigationLinks": { 17 | "GitHub": "https://github.com/grafana/xk6-sql" 18 | } 19 | }, 20 | "compilerOptions": { 21 | "target": "ES6", 22 | "module": "ES6", 23 | "paths": { 24 | "k6/x/sql": ["./index.d.ts"] 25 | }, 26 | "esModuleInterop": true, 27 | "strict": true, 28 | "forceConsistentCasingInFileNames": true 29 | } 30 | } 31 | --------------------------------------------------------------------------------