Compare commits

..

11 Commits

Author SHA1 Message Date
tristantr
bd1bedf69e nit 2025-12-09 12:38:00 +01:00
tristantr
048368b1f8 Change banner wordings if new tutorial available 2025-12-09 12:37:38 +01:00
tristantr
c92ab37930 Put back banner when new tutorial is available for a user who completed all tutotorials, and never skipped all 2025-12-09 12:10:10 +01:00
tristantr
0bcacc1f10 Create shared Constants for TUTORIAL_DISMISSED_KEY 2025-12-09 11:34:36 +01:00
tristantr
635bb207af Add comments 2025-12-09 11:29:55 +01:00
tristantr
b5029f9a61 Add error handling 2025-12-09 11:27:12 +01:00
tristantr
2033ded4b4 Use derived instead of function for Svelte 5 good practices 2025-12-09 11:24:44 +01:00
tristantr
05099b891a Remove banner if all tutorials marked as completed, by role 2025-12-09 11:09:29 +01:00
tristantr
8ab0ffe2d1 Remove tutorials banner if all tutorials marked as completed 2025-12-09 11:03:31 +01:00
tristantr
3383eaf060 Remove banner if user has skipped all tutorials 2025-12-09 10:50:53 +01:00
tristantr
4be122c6fc Allow new operator so see the tutorial banner 2025-12-09 10:42:34 +01:00
467 changed files with 11429 additions and 38229 deletions

View File

@@ -1,62 +0,0 @@
{
"permissions": {
"allow": [
"Read(**/*.rs)",
"Bash(ls:*)",
"Bash(grep:*)",
"Bash(cat:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(less:*)",
"Bash(more:*)",
"Bash(find:*)",
"Bash(wc:*)",
"Bash(diff:*)",
"Bash(file:*)",
"Bash(stat:*)",
"Bash(tree:*)",
"Bash(pwd)",
"Bash(which:*)",
"Bash(whereis:*)",
"Bash(echo:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git branch:*)",
"Bash(git show:*)",
"Bash(git blame:*)"
],
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/secrets/**)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/credentials.json)",
"Read(**/*secret*)",
"Edit(.env)",
"Edit(.env.*)",
"Edit(**/.env)",
"Edit(**/.env.*)"
],
"ask": [
"Bash(rm:*)",
"Bash(rmdir:*)",
"Bash(mv:*)",
"Bash(chmod:*)",
"Bash(chown:*)",
"Bash(truncate:*)",
"Bash(shred:*)",
"Bash(unlink:*)",
"Bash(git push:*)",
"Bash(git reset:*)",
"Bash(git revert:*)",
"Bash(git checkout:*)",
"Bash(git merge:*)",
"Bash(git rebase:*)"
]
},
"enableAllProjectMcpServers": true
}

View File

@@ -84,7 +84,6 @@ jobs:
RUST_LOG_STYLE: never
CARGO_NET_GIT_FETCH_WITH_CLI: true
WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
run: |
deno --version && bun -v && go version && python3 --version
cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd ..

View File

@@ -64,10 +64,9 @@ jobs:
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
allowed_bots: 'windmill-internal-app[bot]'
trigger_phrase: '/plan'
allowed_bots: "windmill-internal-app[bot]"
trigger_phrase: "/plan"
claude_args: |
--model opus
--system-prompt "# Claude Planning Mode
You are operating in PLANNING MODE ONLY. Your role is to create detailed, structured plans without making any code changes.

View File

@@ -95,8 +95,8 @@ jobs:
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
allowed_bots: 'windmill-internal-app[bot]'
trigger_phrase: '/ai'
allowed_bots: "windmill-internal-app[bot]"
trigger_phrase: "/ai"
settings: |
{
"env": {
@@ -105,7 +105,6 @@ jobs:
}
claude_args: |
--allowedTools "Bash,WebFetch,WebSearch"
--model opus
--system-prompt "## IMPORTANT INSTRUCTIONS
- Your branch name should be a short description of the requested changes.
- Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a draft PR from that branch to main.

View File

@@ -29,11 +29,6 @@ on:
required: false
default: false
type: boolean
slim:
description: "Build slim image (true, false)"
required: false
default: false
type: boolean
concurrency:
group: ${{ github.ref }}
cancel-in-progress: false
@@ -593,7 +588,7 @@ jobs:
${{ steps.meta-ee-public.outputs.labels }}
build_ee_slim:
if: ${{ startsWith(github.ref, 'refs/tags/v') }} || ((github.event_name != 'workflow_dispatch') || (github.event.inputs.slim))
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
needs: [build_ee]
runs-on: ubicloud
steps:
@@ -613,7 +608,6 @@ jobs:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-slim
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
@@ -628,7 +622,7 @@ jobs:
uses: depot/build-push-action@v1
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64
push: true
file: "./docker/DockerfileSlimEe"
tags: |

View File

@@ -45,4 +45,3 @@ jobs:
At the end of your review, add complete instructions to reproduce the added changes through the app interface. These instructions will be given to a tester so he can verify the changes. It should be a short descriptive text (not a step by step or a list) on how to navigate the app (what page, what action, what input, etc) to see the changes.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
--model opus

View File

@@ -3,7 +3,7 @@ name: Weekly PR Summary
on:
schedule:
# Every Friday at 8:00 AM UTC
- cron: '0 8 * * 5'
- cron: "0 8 * * 5"
workflow_dispatch:
# Allow manual triggering for testing
@@ -112,7 +112,6 @@ jobs:
- Verify the file was created by running: `ls -lh summary.md`
claude_args: |
--allowedTools "Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash"
--model haiku
- name: Send Summary to Windmill
if: hashFiles('summary.md') != ''

View File

@@ -1,135 +1,5 @@
# Changelog
## [1.595.0](https://github.com/windmill-labs/windmill/compare/v1.594.0...v1.595.0) (2025-12-19)
### Features
* email triggers custom cert ([#7415](https://github.com/windmill-labs/windmill/issues/7415)) ([0bf7407](https://github.com/windmill-labs/windmill/commit/0bf74074192d22e3ba28acae65d88464f9958fb8))
### Bug Fixes
* **backend:** put for loop itered in a separate table ([#7419](https://github.com/windmill-labs/windmill/issues/7419)) ([f89fb29](https://github.com/windmill-labs/windmill/commit/f89fb292da320f54d682e8de5ff57acac0405efa))
* do not use unshare for init scripts ([#7418](https://github.com/windmill-labs/windmill/issues/7418)) ([c28e771](https://github.com/windmill-labs/windmill/commit/c28e77110e3a97c597b0781124a97b6d16a34810))
* **frontend:** settings redesign ([#7406](https://github.com/windmill-labs/windmill/issues/7406)) ([210b828](https://github.com/windmill-labs/windmill/commit/210b8285d4d9a693f67b40831d5bb39d6aeffb92))
* Python Enum types generate proper dropdown schemas with descriptions ([#7400](https://github.com/windmill-labs/windmill/issues/7400)) ([da500fc](https://github.com/windmill-labs/windmill/commit/da500fcf3e79f76e14d1724f07dd69e58a6307e8))
* teams, need both guid and thread id format ([#7420](https://github.com/windmill-labs/windmill/issues/7420)) ([8268354](https://github.com/windmill-labs/windmill/commit/8268354889d0eb1fb44c083fd1c6243f08788e2c))
## [1.594.0](https://github.com/windmill-labs/windmill/compare/v1.593.1...v1.594.0) (2025-12-19)
### Features
* restart flow from step with different flow version ([#7409](https://github.com/windmill-labs/windmill/issues/7409)) ([a699382](https://github.com/windmill-labs/windmill/commit/a6993823affeff6baf7b6c2b40bdb35713bbffe5))
### Bug Fixes
* **backend:** correctly apply preprocessor step tag ([#7412](https://github.com/windmill-labs/windmill/issues/7412)) ([0fe7a2a](https://github.com/windmill-labs/windmill/commit/0fe7a2a17e810153bc7628b9278e2926b869c389))
* disable oomgroup by default ([8060244](https://github.com/windmill-labs/windmill/commit/806024403ee6496dfff886d3ecdb53d4a2b646e6))
* improve teams search ux ([#7407](https://github.com/windmill-labs/windmill/issues/7407)) ([96aacee](https://github.com/windmill-labs/windmill/commit/96aaceef951c23a7d5f4af6ad6b95883f5ba8f71))
## [1.593.1](https://github.com/windmill-labs/windmill/compare/v1.593.0...v1.593.1) (2025-12-18)
### Bug Fixes
* fix folder/group history seq id grant issues ([c9a19f1](https://github.com/windmill-labs/windmill/commit/c9a19f12d637ca47c4a9bbfe0e851198111c3e9e))
## [1.593.0](https://github.com/windmill-labs/windmill/compare/v1.592.1...v1.593.0) (2025-12-17)
### Features
* **ai:** support IAM auth for bedrock provider ([#7379](https://github.com/windmill-labs/windmill/issues/7379)) ([8c55f61](https://github.com/windmill-labs/windmill/commit/8c55f61bbad81bc81509660b5d54d3289c1edfca))
* **backend:** stop schedules and cancel jobs when archiving a workspace ([#7377](https://github.com/windmill-labs/windmill/issues/7377)) ([ebc82db](https://github.com/windmill-labs/windmill/commit/ebc82dbe58eef19ca1e049f0b2099b702fe3725e))
* data table schemas ([#7353](https://github.com/windmill-labs/windmill/issues/7353)) ([75fdc2c](https://github.com/windmill-labs/windmill/commit/75fdc2cdc96ae06ee8a7891fe670acec8a58afe3))
* http triggers scopes ([#7385](https://github.com/windmill-labs/windmill/issues/7385)) ([b4eb7c6](https://github.com/windmill-labs/windmill/commit/b4eb7c6ac076261aed2d9c97f3b09ac52f7fe0da))
* **internal:** runnable settings ([#7298](https://github.com/windmill-labs/windmill/issues/7298)) ([fe56191](https://github.com/windmill-labs/windmill/commit/fe5619142228ea5370b64112e3a2e38aed507b66))
* workspace forks merge UI ([#7333](https://github.com/windmill-labs/windmill/issues/7333)) ([9d06c15](https://github.com/windmill-labs/windmill/commit/9d06c152ee5c2ab1f76a631411f3603bb0575f5e))
### Bug Fixes
* add history directly viewable in folder/group viewer ([#7365](https://github.com/windmill-labs/windmill/issues/7365)) ([b3603d8](https://github.com/windmill-labs/windmill/commit/b3603d872090c354a9ee82714a6a0e4e79019428))
* add history to raw app builder ([#7362](https://github.com/windmill-labs/windmill/issues/7362)) ([431074d](https://github.com/windmill-labs/windmill/commit/431074d2493d6e87148806a09f60a7eacef552ff))
* **aiagent:** fix gemini-3.0 usage ([#7382](https://github.com/windmill-labs/windmill/issues/7382)) ([f64d918](https://github.com/windmill-labs/windmill/commit/f64d918af6e1d9c0e5b1c0abfee081625f3410cb))
* **aichat:** fix for azure responses api not available in some region ([#7387](https://github.com/windmill-labs/windmill/issues/7387)) ([e7719d2](https://github.com/windmill-labs/windmill/commit/e7719d2cda1c636f0f0acd7cb9bd52c6b3712ebe))
* **backend:** better trigger listening logs ([#7392](https://github.com/windmill-labs/windmill/issues/7392)) ([3ba361a](https://github.com/windmill-labs/windmill/commit/3ba361ad1ae19130b8bd72a3d940ddc529f0471b))
* **frontend:** http/email triggers UI nits ([#7378](https://github.com/windmill-labs/windmill/issues/7378)) ([75e1e90](https://github.com/windmill-labs/windmill/commit/75e1e902734e755f2979f882dd4b2889ce13dfef))
* **mcp:** fix unresovled schema ([#7383](https://github.com/windmill-labs/windmill/issues/7383)) ([1b86a39](https://github.com/windmill-labs/windmill/commit/1b86a39051df1344718ed868a15714f4cee90680))
* propagate canceled_by in flows ([#7396](https://github.com/windmill-labs/windmill/issues/7396)) ([0454f39](https://github.com/windmill-labs/windmill/commit/0454f392e7d9c77f47252b18c1d7ec2ba2cc8cca))
* **rawapp:** make popup work with runnables ([2f5fdd6](https://github.com/windmill-labs/windmill/commit/2f5fdd6b3f742a614cfba590408b88a64d0c86a3))
* **rawapp:** schema for openai ([#7364](https://github.com/windmill-labs/windmill/issues/7364)) ([37394d6](https://github.com/windmill-labs/windmill/commit/37394d6d532923aa273b50c94799ed7a0161e2af))
* SCIM 2.0 RFC compliance + displayName support ([#7380](https://github.com/windmill-labs/windmill/issues/7380)) ([6ffb80d](https://github.com/windmill-labs/windmill/commit/6ffb80d1e1631385ea1bc2b5ad447431f52d892f))
## [1.592.1](https://github.com/windmill-labs/windmill/compare/v1.592.0...v1.592.1) (2025-12-12)
### Bug Fixes
* **ai:** improve share system prompts ([490114d](https://github.com/windmill-labs/windmill/commit/490114d133a08ef7f61ed216796b01fbec32a677))
## [1.592.0](https://github.com/windmill-labs/windmill/compare/v1.591.4...v1.592.0) (2025-12-12)
### Features
* **ai:** standardize and improve system prompts ([#7346](https://github.com/windmill-labs/windmill/issues/7346)) ([31e002a](https://github.com/windmill-labs/windmill/commit/31e002ad411bfbf08c933700bf5ae12b253ac0b8))
### Bug Fixes
* **bun:** deployment error on workspace dependencies ([#7355](https://github.com/windmill-labs/windmill/issues/7355)) ([6859670](https://github.com/windmill-labs/windmill/commit/68596701f1e9da460d7bd3246dc797fca1e66a62))
* clear datetime input in schedule sets input to null ([#7358](https://github.com/windmill-labs/windmill/issues/7358)) ([8f1343e](https://github.com/windmill-labs/windmill/commit/8f1343e155620b047e746da40653ec627f97a1a3))
## [1.591.4](https://github.com/windmill-labs/windmill/compare/v1.591.3...v1.591.4) (2025-12-12)
### Bug Fixes
* **app:** fix raw scripts forbidden by policy if no args ([857adf5](https://github.com/windmill-labs/windmill/commit/857adf5b63fe243736366c38a7e573678552a99b))
## [1.591.3](https://github.com/windmill-labs/windmill/compare/v1.591.2...v1.591.3) (2025-12-11)
### Bug Fixes
* fix test up to step ([#7348](https://github.com/windmill-labs/windmill/issues/7348)) ([4337a88](https://github.com/windmill-labs/windmill/commit/4337a8810e7db874cd75006aacf4e4a85bc33836))
* S3 SDK nits + Presigned S3 Public URL function ([#7342](https://github.com/windmill-labs/windmill/issues/7342)) ([2ee00b3](https://github.com/windmill-labs/windmill/commit/2ee00b3c7b0f7ba9a5997ec4474949500ff2c67b))
## [1.591.2](https://github.com/windmill-labs/windmill/compare/v1.591.1...v1.591.2) (2025-12-11)
### Bug Fixes
* **backend:** pin reqwest to 0.12.24 for better handling of redirects ([#7336](https://github.com/windmill-labs/windmill/issues/7336)) ([99bf866](https://github.com/windmill-labs/windmill/commit/99bf866f90d96f706a517b7626df999bce9e36ac))
## [1.591.1](https://github.com/windmill-labs/windmill/compare/v1.591.0...v1.591.1) (2025-12-10)
### Bug Fixes
* update git sync to latest cli ([da65ddd](https://github.com/windmill-labs/windmill/commit/da65ddd8f82ae283557526d0e97b1057976ec2ff))
## [1.591.0](https://github.com/windmill-labs/windmill/compare/v1.590.0...v1.591.0) (2025-12-10)
### Features
* **aichat:** add test pipeline ([#7321](https://github.com/windmill-labs/windmill/issues/7321)) ([36cb5bd](https://github.com/windmill-labs/windmill/commit/36cb5bde1a887effee2e2ba83a6ddd85f3e79775))
* **aichat:** use single tool for flow chat ([#7326](https://github.com/windmill-labs/windmill/issues/7326)) ([888a6a4](https://github.com/windmill-labs/windmill/commit/888a6a4e607f814cd92f3f489b40ac886dbafc0d))
* **mcp:** add documentation endpoint ([#7331](https://github.com/windmill-labs/windmill/issues/7331)) ([bd3271f](https://github.com/windmill-labs/windmill/commit/bd3271f967ed1996060c590bac0587a387b7869c))
### Bug Fixes
* alining group name normalization between scim and igroup api ([#7325](https://github.com/windmill-labs/windmill/issues/7325)) ([8159b8e](https://github.com/windmill-labs/windmill/commit/8159b8e017660f3b3aeed6beb97a42e3ee658824))
* **backend:** add presigned url support for object storage ([#7328](https://github.com/windmill-labs/windmill/issues/7328)) ([651681b](https://github.com/windmill-labs/windmill/commit/651681b7efb6793d2939217aafcfb3e44f5b0e84))
* **cli:** do not remove schema for normal apps ([3470c92](https://github.com/windmill-labs/windmill/commit/3470c927aeae1627984fff535ec36ece4349f329))
* **frontend:** do not consider advanced module settings when testing ai agent step ([72a38f3](https://github.com/windmill-labs/windmill/commit/72a38f36fbaf3ceaec03612b9932187fa134755c))
## [1.590.0](https://github.com/windmill-labs/windmill/compare/v1.589.3...v1.590.0) (2025-12-08)

View File

@@ -1,6 +1,16 @@
ARG DEBIAN_IMAGE=debian:bookworm-slim
ARG RUST_IMAGE=rust:1.90-slim-bookworm
# Build libwindmill_duckdb_ffi_internal.so separately
FROM ${RUST_IMAGE} AS windmill_duckdb_ffi_internal_builder
WORKDIR /windmill-duckdb-ffi-internal
RUN apt-get update && apt-get install -y pkg-config clang=1:14.0-55.* libclang-dev=1:14.0-55.* cmake=3.25.* && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY ./backend/windmill-duckdb-ffi-internal .
RUN cargo build --release -p windmill_duckdb_ffi_internal
FROM ${RUST_IMAGE} AS rust_base
RUN apt-get update && apt-get install -y git libssl-dev pkg-config npm
@@ -20,20 +30,6 @@ WORKDIR /windmill
ENV SQLX_OFFLINE=true
# ENV CARGO_INCREMENTAL=1
FROM rust_base AS windmill_duckdb_ffi_internal_builder
WORKDIR /windmill-duckdb-ffi-internal
RUN apt-get update && apt-get install -y clang=1:14.0-55.* libclang-dev=1:14.0-55.* cmake=3.25.* && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY ./backend/windmill-duckdb-ffi-internal .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
cargo build --release -p windmill_duckdb_ffi_internal
FROM node:24-alpine as frontend
# install dependencies
@@ -48,7 +44,6 @@ RUN mkdir /backend
COPY /backend/windmill-api/openapi.yaml /backend/windmill-api/openapi.yaml
COPY /openflow.openapi.yaml /openflow.openapi.yaml
COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.sh
COPY /system_prompts/auto-generated /system_prompts/auto-generated
RUN cd /backend/windmill-api && . ./build_openapi.sh
COPY /backend/parsers/windmill-parser-wasm/pkg/ /backend/parsers/windmill-parser-wasm/pkg/
@@ -234,9 +229,7 @@ COPY ./frontend/src/lib/hubPaths.json ${APP}/hubPaths.json
RUN windmill cache ${APP}/hubPaths.json && rm ${APP}/hubPaths.json && chmod -R 777 /tmp/windmill
# Cr,.eate a non-root user 'windmill' with UID and GID 1000
# Create a non-root user 'windmill' with UID and GID 1000
RUN addgroup --gid 1000 windmill && \
adduser --disabled-password --gecos "" --uid 1000 --gid 1000 windmill

View File

@@ -1,19 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_diff SET has_changes = true, exists_in_source = $5, exists_in_fork = $6\n WHERE path = $3 AND kind = $4 AND (\n (source_workspace_id = $1 AND fork_workspace_id = $2)\n OR (source_workspace_id = $2 AND fork_workspace_id =$1)\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Text",
"Bool",
"Bool"
]
},
"nullable": []
},
"hash": "034a8519198daf30e0eb8a74ed92f896c83bb39e1cb52fe3c29c1a224c3859c2"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE schedule SET enabled = false WHERE workspace_id = $1 AND enabled = true RETURNING path",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "03669873e4e3b22c737d5170821f677925474aad885bf1c0780bdb978225517e"
}

View File

@@ -0,0 +1,156 @@
{
"db_name": "PostgreSQL",
"query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, debounce_key, debounce_delay_s, cache_ttl, cache_ignore_s3_path, language as \"language: ScriptLang\", dedicated_worker, priority, delete_after_use, timeout, has_preprocessor, on_behalf_of_email, created_by, path from script where hash = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "hash",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "tag",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "concurrency_key",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "concurrent_limit",
"type_info": "Int4"
},
{
"ordinal": 4,
"name": "concurrency_time_window_s",
"type_info": "Int4"
},
{
"ordinal": 5,
"name": "debounce_key",
"type_info": "Varchar"
},
{
"ordinal": 6,
"name": "debounce_delay_s",
"type_info": "Int4"
},
{
"ordinal": 7,
"name": "cache_ttl",
"type_info": "Int4"
},
{
"ordinal": 8,
"name": "cache_ignore_s3_path",
"type_info": "Bool"
},
{
"ordinal": 9,
"name": "language: ScriptLang",
"type_info": {
"Custom": {
"name": "script_lang",
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash",
"postgresql",
"nativets",
"bun",
"mysql",
"bigquery",
"snowflake",
"graphql",
"powershell",
"mssql",
"php",
"bunnative",
"rust",
"ansible",
"csharp",
"oracledb",
"nu",
"java",
"duckdb",
"ruby"
]
}
}
}
},
{
"ordinal": 10,
"name": "dedicated_worker",
"type_info": "Bool"
},
{
"ordinal": 11,
"name": "priority",
"type_info": "Int2"
},
{
"ordinal": 12,
"name": "delete_after_use",
"type_info": "Bool"
},
{
"ordinal": 13,
"name": "timeout",
"type_info": "Int4"
},
{
"ordinal": 14,
"name": "has_preprocessor",
"type_info": "Bool"
},
{
"ordinal": 15,
"name": "on_behalf_of_email",
"type_info": "Text"
},
{
"ordinal": 16,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 17,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false,
true,
true,
true,
true,
true,
true,
true,
true,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "05b69dcef0f4f649513e186e73089979c49b4b8113ee832ea7539b56a0415f32"
}

View File

@@ -29,7 +29,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}

View File

@@ -172,11 +172,6 @@
"ordinal": 33,
"name": "datatable",
"type_info": "Jsonb"
},
{
"ordinal": 34,
"name": "teams_team_guid",
"type_info": "Text"
}
],
"parameters": {
@@ -218,7 +213,6 @@
true,
true,
true,
true,
true
]
},

View File

@@ -1,59 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path, kind, ahead, behind, has_changes, exists_in_source, exists_in_fork FROM workspace_diff\n WHERE source_workspace_id = $1 AND fork_workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "kind",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "ahead",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "behind",
"type_info": "Int4"
},
{
"ordinal": 4,
"name": "has_changes",
"type_info": "Bool"
},
{
"ordinal": 5,
"name": "exists_in_source",
"type_info": "Bool"
},
{
"ordinal": 6,
"name": "exists_in_fork",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false,
true,
true,
true
]
},
"hash": "0b8e5fe95f4a2855678ca041b50405b698a368626da42dd9f4ce9d0681d016a1"
}

View File

@@ -1,17 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM workspace_diff WHERE path = $3 AND kind = $4 AND (\n (source_workspace_id = $1 AND fork_workspace_id = $2)\n OR (source_workspace_id = $2 AND fork_workspace_id =$1)\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "0f689b9bd1c9a24f3c6cdafef0215f102122665bc3cc15718831b991052b4caf"
}

View File

@@ -172,11 +172,6 @@
"ordinal": 33,
"name": "datatable",
"type_info": "Jsonb"
},
{
"ordinal": 34,
"name": "teams_team_guid",
"type_info": "Text"
}
],
"parameters": {
@@ -218,7 +213,6 @@
true,
true,
true,
true,
true
]
},

View File

@@ -39,7 +39,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE instance_group SET scim_display_name = $1 where id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "2241ed0c5a47ac715de3ef13a850e514e0fb7b062f4147bffb0e9badfea478d0"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s)\n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, NULL, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s\n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8",
"Text"
]
},
"nullable": []
},
"hash": "23759cb515e926e272bbc8e5d8a0a9d039b99bc2026e381e99ef41cdaf6ea19f"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings\n SET teams_team_id = null, teams_team_name = null, teams_team_guid = null WHERE workspace_id = $1",
"query": "UPDATE workspace_settings\n SET teams_team_id = null, teams_team_name = null WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
@@ -10,5 +10,5 @@
},
"nullable": []
},
"hash": "62a625902ab1507f198bc9b12f2fea8398ec3905699ebf0e28cdfc85c0de4615"
"hash": "23c37d36e16251763fabf194e41de63612a7506cc0671b0eb83e528c1c839db4"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path FROM script\n WHERE workspace_id = $1 AND path = ANY($2) AND archived = false",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"TextArray"
]
},
"nullable": [
false
]
},
"hash": "2d1ba3c92c0385c530934082284cf548a50d533dc1bef58dfd0ecc163c9920f3"
}

View File

@@ -1,35 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value, is_secret, description\n FROM variable\n WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "is_secret",
"type_info": "Bool"
},
{
"ordinal": 2,
"name": "description",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "2d5ff8fc102ae0c452c1f9cd5cd30fd0a3b4e6e746c659da1767edafd139d45e"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO flow_iterator_data (job_id, itered) VALUES ($1, $2)\n ON CONFLICT (job_id) DO UPDATE SET itered = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Jsonb"
]
},
"nullable": []
},
"hash": "389828f43e638c02757ba37da46b03111a9915a16b53f3e29a09de89210d6af1"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT itered as \"itered: Json<Vec<Box<RawValue>>>\" FROM flow_iterator_data WHERE job_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "itered: Json<Vec<Box<RawValue>>>",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false
]
},
"hash": "3c5165992c4b8ad3f91627d1d9f6156d3a6b45a7cb2b37a7c166d36d7caa4d2f"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, cache_ignore_s3_path, runnable_settings_handle) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38)",
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, cache_ignore_s3_path) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37)",
"describe": {
"columns": [],
"parameters": {
@@ -59,9 +59,7 @@
"failure",
"command",
"approval",
"preprocessor",
"schedule_handler_old",
"dynamic_skip"
"preprocessor"
]
}
}
@@ -88,11 +86,10 @@
"Jsonb",
"Varchar",
"Int4",
"Bool",
"Int8"
"Bool"
]
},
"nullable": []
},
"hash": "b4eb72b0274cbdce7490f63c36d0d16ee847294fadc138593a1baa417cbb3652"
"hash": "3d05d9d7e087eb6e1c14c2b8a20598581e6c7493ed99cb9ad1c2ee5d0b212d38"
}

View File

@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO runnable_settings (hash, debouncing_settings, concurrency_settings)\n VALUES ($1, $2, $3)\n ON CONFLICT (hash)\n DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8",
"Int8"
]
},
"nullable": []
},
"hash": "451d9cde90d14071e21ffb5f615052b7ba7fc315fc301ed5c0ff50d9a3ab0d4a"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id FROM workspace WHERE parent_workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "4d04e436d46530f2f4c9dbc5c6c472581b9d90d330957539c0019ec2e7d9a68a"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path FROM app\n WHERE workspace_id = $1 AND path = ANY($2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"TextArray"
]
},
"nullable": [
false
]
},
"hash": "50ef2eac143273c7b7b7414b9be3c89130c709a57aca712e2af996bd4a4fa101"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path FROM resource\n WHERE workspace_id = $1 AND path = ANY($2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"TextArray"
]
},
"nullable": [
false
]
},
"hash": "5169d793f3820095839bb69d6c7c8cbea692ef6c1a838b970f382ee72db492d3"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET teams_team_id = $1, teams_team_name = $2\n WHERE workspace_id = $3\n AND NOT EXISTS (\n SELECT 1 FROM workspace_settings\n WHERE teams_team_id = $1 AND workspace_id <> $2\n )\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "551c78392919e18019bb0a4344fb1bd45853bf5b72e0ab991e0e61fedcfb42fc"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE instance_group SET scim_display_name = $1, name = $2 where id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Text"
]
},
"nullable": []
},
"hash": "574d9a2bb6eceb62f3d2c2be3f18b29ef8bba3d6da1b1e21f2ca307ccbebee89"
}

View File

@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle)\n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, NULL, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, cache_ignore_s3_path, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets, debounce_key, debounce_delay_s, runnable_settings_handle\n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8",
"Text"
]
},
"nullable": []
},
"hash": "5c056ad6cc8967393729288437205c605a24118021fdb2b21b6b61695dc4ff28"
}

View File

@@ -1,35 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT app.summary, app.policy, app_version.value\n FROM app\n JOIN app_version\n ON app_version.id = app.versions[array_upper(app.versions, 1)]\n WHERE app.workspace_id = $1 AND app.path = $2 AND COALESCE(app.draft_only, false) = false",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "summary",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "policy",
"type_info": "Jsonb"
},
{
"ordinal": 2,
"name": "value",
"type_info": "Json"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "66a9cb11fcc4757a3b35154840b703b87daa538cf3a72007649bbb074f56131e"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN runnable_settings rs ON rs.hash = v2_job_queue.runnable_settings_handle LEFT JOIN concurrency_settings cs ON cs.hash = rs.concurrency_settings\n WHERE v2_job.runnable_path = $1 AND v2_job.kind != 'dependencies' AND v2_job_queue.running = true AND v2_job_queue.workspace_id = $2 AND v2_job_queue.canceled_by IS NULL AND COALESCE(cs.concurrent_limit, v2_job.concurrent_limit) > 0), $3) as min_started_at, now() AS now",
"query": "SELECT COALESCE((SELECT MIN(started_at) as min_started_at\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id\n WHERE v2_job.runnable_path = $1 AND v2_job.kind != 'dependencies' AND v2_job_queue.running = true AND v2_job_queue.workspace_id = $2 AND v2_job_queue.canceled_by IS NULL AND v2_job.concurrent_limit > 0), $3) as min_started_at, now() AS now",
"describe": {
"columns": [
{
@@ -26,5 +26,5 @@
null
]
},
"hash": "a2e52f033120a3f0b64e0a5ba125df7ce0d25096a23f0b655846a1c07b41f620"
"hash": "6b6f8f7b4a6b6e7e41a9da8b6dfdbcae842ff252cc355bd91aeeb5e26dcc74f3"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT \n v2_job_queue.workspace_id,\n v2_job_queue.id,\n v2_job.args as \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n v2_job.parent_job,\n v2_job.created_by,\n v2_job_queue.started_at,\n v2_job_queue.runnable_settings_handle,\n scheduled_for,\n runnable_path,\n kind as \"kind: JobKind\",\n runnable_id as \"runnable_id: ScriptHash\",\n canceled_reason,\n canceled_by,\n permissioned_as,\n permissioned_as_email,\n flow_status as \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n v2_job.tag,\n script_lang as \"script_lang: ScriptLang\",\n same_worker,\n pre_run_error,\n concurrent_limit,\n concurrency_time_window_s,\n flow_innermost_root_job,\n root_job,\n timeout,\n flow_step_id,\n cache_ttl,\n cache_ignore_s3_path,\n v2_job_queue.priority,\n preprocessed,\n script_entrypoint_override,\n trigger,\n trigger_kind as \"trigger_kind: JobTriggerKind\",\n visible_to_owner,\n NULL as permissioned_as_end_user_email\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1",
"query": "SELECT \n v2_job_queue.workspace_id,\n v2_job_queue.id,\n v2_job.args as \"args: sqlx::types::Json<HashMap<String, Box<RawValue>>>\",\n v2_job.parent_job,\n v2_job.created_by,\n v2_job_queue.started_at,\n scheduled_for,\n runnable_path,\n kind as \"kind: JobKind\",\n runnable_id as \"runnable_id: ScriptHash\",\n canceled_reason,\n canceled_by,\n permissioned_as,\n permissioned_as_email,\n flow_status as \"flow_status: sqlx::types::Json<Box<RawValue>>\",\n v2_job.tag,\n script_lang as \"script_lang: ScriptLang\",\n same_worker,\n pre_run_error,\n concurrent_limit,\n concurrency_time_window_s,\n flow_innermost_root_job,\n root_job,\n timeout,\n flow_step_id,\n cache_ttl,\n cache_ignore_s3_path,\n v2_job_queue.priority,\n preprocessed,\n script_entrypoint_override,\n trigger,\n trigger_kind as \"trigger_kind: JobTriggerKind\",\n visible_to_owner,\n NULL as permissioned_as_end_user_email\n FROM v2_job_queue INNER JOIN v2_job ON v2_job.id = v2_job_queue.id LEFT JOIN v2_job_status ON v2_job_status.id = v2_job_queue.id WHERE v2_job_queue.id = $1",
"describe": {
"columns": [
{
@@ -35,21 +35,16 @@
},
{
"ordinal": 6,
"name": "runnable_settings_handle",
"type_info": "Int8"
},
{
"ordinal": 7,
"name": "scheduled_for",
"type_info": "Timestamptz"
},
{
"ordinal": 8,
"ordinal": 7,
"name": "runnable_path",
"type_info": "Varchar"
},
{
"ordinal": 9,
"ordinal": 8,
"name": "kind: JobKind",
"type_info": {
"Custom": {
@@ -84,42 +79,42 @@
}
},
{
"ordinal": 10,
"ordinal": 9,
"name": "runnable_id: ScriptHash",
"type_info": "Int8"
},
{
"ordinal": 11,
"ordinal": 10,
"name": "canceled_reason",
"type_info": "Text"
},
{
"ordinal": 12,
"ordinal": 11,
"name": "canceled_by",
"type_info": "Varchar"
},
{
"ordinal": 13,
"ordinal": 12,
"name": "permissioned_as",
"type_info": "Varchar"
},
{
"ordinal": 14,
"ordinal": 13,
"name": "permissioned_as_email",
"type_info": "Varchar"
},
{
"ordinal": 15,
"ordinal": 14,
"name": "flow_status: sqlx::types::Json<Box<RawValue>>",
"type_info": "Jsonb"
},
{
"ordinal": 16,
"ordinal": 15,
"name": "tag",
"type_info": "Varchar"
},
{
"ordinal": 17,
"ordinal": 16,
"name": "script_lang: ScriptLang",
"type_info": {
"Custom": {
@@ -155,77 +150,77 @@
}
},
{
"ordinal": 18,
"ordinal": 17,
"name": "same_worker",
"type_info": "Bool"
},
{
"ordinal": 19,
"ordinal": 18,
"name": "pre_run_error",
"type_info": "Text"
},
{
"ordinal": 20,
"ordinal": 19,
"name": "concurrent_limit",
"type_info": "Int4"
},
{
"ordinal": 21,
"ordinal": 20,
"name": "concurrency_time_window_s",
"type_info": "Int4"
},
{
"ordinal": 22,
"ordinal": 21,
"name": "flow_innermost_root_job",
"type_info": "Uuid"
},
{
"ordinal": 23,
"ordinal": 22,
"name": "root_job",
"type_info": "Uuid"
},
{
"ordinal": 24,
"ordinal": 23,
"name": "timeout",
"type_info": "Int4"
},
{
"ordinal": 25,
"ordinal": 24,
"name": "flow_step_id",
"type_info": "Varchar"
},
{
"ordinal": 26,
"ordinal": 25,
"name": "cache_ttl",
"type_info": "Int4"
},
{
"ordinal": 27,
"ordinal": 26,
"name": "cache_ignore_s3_path",
"type_info": "Bool"
},
{
"ordinal": 28,
"ordinal": 27,
"name": "priority",
"type_info": "Int2"
},
{
"ordinal": 29,
"ordinal": 28,
"name": "preprocessed",
"type_info": "Bool"
},
{
"ordinal": 30,
"ordinal": 29,
"name": "script_entrypoint_override",
"type_info": "Varchar"
},
{
"ordinal": 31,
"ordinal": 30,
"name": "trigger",
"type_info": "Varchar"
},
{
"ordinal": 32,
"ordinal": 31,
"name": "trigger_kind: JobTriggerKind",
"type_info": {
"Custom": {
@@ -244,19 +239,20 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}
}
},
{
"ordinal": 33,
"ordinal": 32,
"name": "visible_to_owner",
"type_info": "Bool"
},
{
"ordinal": 34,
"ordinal": 33,
"name": "permissioned_as_end_user_email",
"type_info": "Text"
}
@@ -273,7 +269,6 @@
true,
false,
true,
true,
false,
true,
false,
@@ -304,5 +299,5 @@
null
]
},
"hash": "5bf200f2c8db25ddf231b564503c6c70f7f3958564a79bb0c6b3863b1ebb0cbf"
"hash": "6c97ab28ab47b75fb3ff39ea70fa3627f08b61bbd33aecb9ea816f8f78a04ec5"
}

View File

@@ -39,7 +39,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT \n j.id, j.workspace_id, j.runnable_id AS \"runnable_id: ScriptHash\", q.scheduled_for, q.started_at, j.parent_job, j.flow_innermost_root_job, j.runnable_path, j.kind as \"kind!: JobKind\", j.permissioned_as, \n j.created_by, j.script_lang AS \"script_lang: ScriptLang\", j.permissioned_as_email, j.flow_step_id, j.trigger_kind AS \"trigger_kind: JobTriggerKind\", j.trigger, j.priority, j.concurrent_limit, j.tag, j.cache_ttl, q.cache_ignore_s3_path, q.runnable_settings_handle\n FROM v2_job j LEFT JOIN v2_job_queue q ON j.id = q.id\n WHERE j.id = $1 AND j.workspace_id = $2",
"query": "SELECT \n j.id, j.workspace_id, j.runnable_id AS \"runnable_id: ScriptHash\", q.scheduled_for, q.started_at, j.parent_job, j.flow_innermost_root_job, j.runnable_path, j.kind as \"kind!: JobKind\", j.permissioned_as, \n j.created_by, j.script_lang AS \"script_lang: ScriptLang\", j.permissioned_as_email, j.flow_step_id, j.trigger_kind AS \"trigger_kind: JobTriggerKind\", j.trigger, j.priority, j.concurrent_limit, j.tag, j.cache_ttl, q.cache_ignore_s3_path\n FROM v2_job j LEFT JOIN v2_job_queue q ON j.id = q.id\n WHERE j.id = $1 AND j.workspace_id = $2",
"describe": {
"columns": [
{
@@ -154,7 +154,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}
@@ -189,11 +190,6 @@
"ordinal": 20,
"name": "cache_ignore_s3_path",
"type_info": "Bool"
},
{
"ordinal": 21,
"name": "runnable_settings_handle",
"type_info": "Int8"
}
],
"parameters": {
@@ -223,9 +219,8 @@
true,
false,
true,
true,
true
]
},
"hash": "a1745a4f525b251d2f5a602ab2b2ede46b4471e21b11f607573a844013911abe"
"hash": "7b5ad10af2a9b34fa86429499ea24c0c09c6e7e9ebfa3af90035570133f7c579"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, debounce_key, debounce_delay_s, cache_ttl, cache_ignore_s3_path, runnable_settings_handle, language as \"language: ScriptLang\", dedicated_worker, priority, timeout, on_behalf_of_email, created_by FROM script\n WHERE path = $1 AND workspace_id = $2 AND archived = false AND (lock IS NOT NULL OR $3 = false)\n ORDER BY created_at DESC LIMIT 1",
"query": "select hash, tag, concurrency_key, concurrent_limit, concurrency_time_window_s, debounce_key, debounce_delay_s, cache_ttl, cache_ignore_s3_path, language as \"language: ScriptLang\", dedicated_worker, priority, timeout, on_behalf_of_email, created_by FROM script\n WHERE path = $1 AND workspace_id = $2 AND archived = false AND (lock IS NOT NULL OR $3 = false)\n ORDER BY created_at DESC LIMIT 1",
"describe": {
"columns": [
{
@@ -50,11 +50,6 @@
},
{
"ordinal": 9,
"name": "runnable_settings_handle",
"type_info": "Int8"
},
{
"ordinal": 10,
"name": "language: ScriptLang",
"type_info": {
"Custom": {
@@ -90,27 +85,27 @@
}
},
{
"ordinal": 11,
"ordinal": 10,
"name": "dedicated_worker",
"type_info": "Bool"
},
{
"ordinal": 12,
"ordinal": 11,
"name": "priority",
"type_info": "Int2"
},
{
"ordinal": 13,
"ordinal": 12,
"name": "timeout",
"type_info": "Int4"
},
{
"ordinal": 14,
"ordinal": 13,
"name": "on_behalf_of_email",
"type_info": "Text"
},
{
"ordinal": 15,
"ordinal": 14,
"name": "created_by",
"type_info": "Varchar"
}
@@ -132,7 +127,6 @@
true,
true,
true,
true,
false,
true,
true,
@@ -141,5 +135,5 @@
false
]
},
"hash": "a33673ebc4d1eb4c3513987dbc43e2c80974598e1d9fe7203145bfc29928ba65"
"hash": "7f9b7ab9bec6a0f745273d0cd5602ceab46a7ec9fd225f7b9d16a2ddb9bad7b3"
}

View File

@@ -1,35 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value, description, resource_type\n FROM resource\n WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Jsonb"
},
{
"ordinal": 1,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "resource_type",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
true,
true,
false
]
},
"hash": "819c233915383e89af1bcf1a56c5f67c4e1fc217f216f609e36a9944a7807b33"
}

View File

@@ -1,17 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_diff (source_workspace_id, fork_workspace_id, path, kind, ahead, behind, has_changes)\n SELECT $1, unnest($2::varchar[]), $3, $4, 0, 1, NULL\n ON CONFLICT (source_workspace_id, fork_workspace_id, path, kind)\n DO UPDATE SET\n behind = workspace_diff.behind + 1,\n has_changes = NULL",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"VarcharArray",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "90a1797f8d5ef7f9b67557c1fb919d1165860c5daf0de76fb2ac2201de11553c"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n jsonb_strip_nulls(jsonb_build_object(\n 'path', asset.path,\n 'kind', asset.kind,\n 'usages', ARRAY_AGG(jsonb_build_object(\n 'path', asset.usage_path,\n 'kind', asset.usage_kind,\n 'access_type', asset.usage_access_type\n )),\n 'metadata', (CASE\n WHEN asset.kind = 'resource' THEN\n jsonb_build_object('resource_type', resource.resource_type)\n ELSE\n NULL\n END\n )\n )) as \"list!: _\"\n FROM asset\n LEFT JOIN resource ON asset.kind = 'resource'\n AND array_to_string((string_to_array(asset.path, '/'))[1:3], '/') = resource.path -- With specific table, asset path can be e.g u/diego/pg_db/table_name\n AND resource.workspace_id = $1\n WHERE asset.workspace_id = $1\n AND (asset.kind <> 'resource' OR resource.path IS NOT NULL)\n AND (asset.usage_kind <> 'flow' OR asset.usage_path = ANY(SELECT path FROM flow WHERE workspace_id = $1))\n AND (asset.usage_kind <> 'script' OR asset.usage_path = ANY(SELECT path FROM script WHERE workspace_id = $1))\n GROUP BY asset.path, asset.kind, resource.resource_type\n ORDER BY asset.path, asset.kind",
"query": "SELECT\n jsonb_strip_nulls(jsonb_build_object(\n 'path', asset.path,\n 'kind', asset.kind,\n 'usages', ARRAY_AGG(jsonb_build_object(\n 'path', asset.usage_path,\n 'kind', asset.usage_kind,\n 'access_type', asset.usage_access_type\n )),\n 'metadata', (CASE\n WHEN asset.kind = 'resource' THEN\n jsonb_build_object('resource_type', resource.resource_type)\n ELSE\n NULL\n END\n )\n )) as \"list!: _\"\n FROM asset\n LEFT JOIN resource ON asset.kind = 'resource' AND asset.path = resource.path AND resource.workspace_id = $1\n WHERE asset.workspace_id = $1\n AND (asset.kind <> 'resource' OR resource.path IS NOT NULL)\n AND (asset.usage_kind <> 'flow' OR asset.usage_path = ANY(SELECT path FROM flow WHERE workspace_id = $1))\n AND (asset.usage_kind <> 'script' OR asset.usage_path = ANY(SELECT path FROM script WHERE workspace_id = $1))\n GROUP BY asset.path, asset.kind, resource.resource_type\n ORDER BY asset.path, asset.kind",
"describe": {
"columns": [
{
@@ -18,5 +18,5 @@
null
]
},
"hash": "c94cd50ff1233025b170efee6489e6637676a8b0435a4612a7efbfff6ea2543d"
"hash": "92f03f4df5e86eb40b255ad0f2cc85e0302c37b0f312366098104cd280a91ef6"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n workspace_id,\n slack_team_id,\n teams_team_id,\n teams_team_name,\n teams_team_guid,\n slack_name,\n slack_command_script,\n teams_command_script,\n slack_email,\n slack_oauth_client_id,\n slack_oauth_client_secret,\n auto_invite_domain,\n auto_invite_operator,\n auto_add,\n customer_id,\n plan,\n webhook,\n deploy_to,\n ai_config,\n error_handler,\n error_handler_extra_args,\n error_handler_muted_on_cancel,\n large_file_storage,\n datatable,\n ducklake,\n git_sync,\n deploy_ui,\n default_app,\n default_scripts,\n mute_critical_alerts,\n color,\n operator_settings,\n git_app_installations,\n auto_add_instance_groups,\n auto_add_instance_groups_roles\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ",
"query": "\n SELECT\n workspace_id,\n slack_team_id,\n teams_team_id,\n teams_team_name,\n slack_name,\n slack_command_script,\n teams_command_script,\n slack_email,\n slack_oauth_client_id,\n slack_oauth_client_secret,\n auto_invite_domain,\n auto_invite_operator,\n auto_add,\n customer_id,\n plan,\n webhook,\n deploy_to,\n ai_config,\n error_handler,\n error_handler_extra_args,\n error_handler_muted_on_cancel,\n large_file_storage,\n datatable,\n ducklake,\n git_sync,\n deploy_ui,\n default_app,\n default_scripts,\n mute_critical_alerts,\n color,\n operator_settings,\n git_app_installations,\n auto_add_instance_groups,\n auto_add_instance_groups_roles\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ",
"describe": {
"columns": [
{
@@ -25,156 +25,151 @@
},
{
"ordinal": 4,
"name": "teams_team_guid",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "slack_name",
"type_info": "Varchar"
},
{
"ordinal": 6,
"ordinal": 5,
"name": "slack_command_script",
"type_info": "Varchar"
},
{
"ordinal": 7,
"ordinal": 6,
"name": "teams_command_script",
"type_info": "Text"
},
{
"ordinal": 8,
"ordinal": 7,
"name": "slack_email",
"type_info": "Varchar"
},
{
"ordinal": 9,
"ordinal": 8,
"name": "slack_oauth_client_id",
"type_info": "Varchar"
},
{
"ordinal": 10,
"ordinal": 9,
"name": "slack_oauth_client_secret",
"type_info": "Varchar"
},
{
"ordinal": 11,
"ordinal": 10,
"name": "auto_invite_domain",
"type_info": "Varchar"
},
{
"ordinal": 12,
"ordinal": 11,
"name": "auto_invite_operator",
"type_info": "Bool"
},
{
"ordinal": 13,
"ordinal": 12,
"name": "auto_add",
"type_info": "Bool"
},
{
"ordinal": 14,
"ordinal": 13,
"name": "customer_id",
"type_info": "Varchar"
},
{
"ordinal": 15,
"ordinal": 14,
"name": "plan",
"type_info": "Varchar"
},
{
"ordinal": 16,
"ordinal": 15,
"name": "webhook",
"type_info": "Text"
},
{
"ordinal": 17,
"ordinal": 16,
"name": "deploy_to",
"type_info": "Varchar"
},
{
"ordinal": 18,
"ordinal": 17,
"name": "ai_config",
"type_info": "Jsonb"
},
{
"ordinal": 19,
"ordinal": 18,
"name": "error_handler",
"type_info": "Varchar"
},
{
"ordinal": 20,
"ordinal": 19,
"name": "error_handler_extra_args",
"type_info": "Json"
},
{
"ordinal": 21,
"ordinal": 20,
"name": "error_handler_muted_on_cancel",
"type_info": "Bool"
},
{
"ordinal": 22,
"ordinal": 21,
"name": "large_file_storage",
"type_info": "Jsonb"
},
{
"ordinal": 23,
"ordinal": 22,
"name": "datatable",
"type_info": "Jsonb"
},
{
"ordinal": 24,
"ordinal": 23,
"name": "ducklake",
"type_info": "Jsonb"
},
{
"ordinal": 25,
"ordinal": 24,
"name": "git_sync",
"type_info": "Jsonb"
},
{
"ordinal": 26,
"ordinal": 25,
"name": "deploy_ui",
"type_info": "Jsonb"
},
{
"ordinal": 27,
"ordinal": 26,
"name": "default_app",
"type_info": "Varchar"
},
{
"ordinal": 28,
"ordinal": 27,
"name": "default_scripts",
"type_info": "Jsonb"
},
{
"ordinal": 29,
"ordinal": 28,
"name": "mute_critical_alerts",
"type_info": "Bool"
},
{
"ordinal": 30,
"ordinal": 29,
"name": "color",
"type_info": "Varchar"
},
{
"ordinal": 31,
"ordinal": 30,
"name": "operator_settings",
"type_info": "Jsonb"
},
{
"ordinal": 32,
"ordinal": 31,
"name": "git_app_installations",
"type_info": "Jsonb"
},
{
"ordinal": 33,
"ordinal": 32,
"name": "auto_add_instance_groups",
"type_info": "TextArray"
},
{
"ordinal": 34,
"ordinal": 33,
"name": "auto_add_instance_groups_roles",
"type_info": "Jsonb"
}
@@ -192,7 +187,6 @@
true,
true,
true,
true,
false,
true,
true,
@@ -222,5 +216,5 @@
true
]
},
"hash": "3c53de373b9f1034b5f43002bf4715e12ddc641f4ca52efe0335719fa9461bb0"
"hash": "95fa60eb45228ff289655fc676991f4e90d237799f6817f292eb1391694164c7"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = EXCLUDED.progress",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Varchar"
]
},
"nullable": []
},
"hash": "99e6bffe177e69448b09e82b30d24af00edc86a9bc498f319c1e5bee55d77a8a"
}

View File

@@ -29,7 +29,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}

View File

@@ -184,7 +184,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}

View File

@@ -1,16 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO password (email, login_type, verified, username, name) VALUES ($1, 'saml', true, $2, $3) ON CONFLICT DO NOTHING",
"query": "INSERT INTO password (email, login_type, verified, username) VALUES ($1, 'saml', true, $2) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "638d3c2ba1198dce5b5b0e47df59a92ff8011e19fbefcc3960d6f0fe167e55b6"
"hash": "a59aac0bc593d99aedd14fd9606f11191590a12ea98a671e172b867306959884"
}

View File

@@ -1,59 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT hash, created_at, content, summary, description, lock, schema\n FROM script\n WHERE workspace_id = $1 AND path = $2 AND archived = false\n ORDER BY created_at DESC\n LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "hash",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 2,
"name": "content",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "summary",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "lock",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "schema",
"type_info": "Json"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false,
true,
true
]
},
"hash": "a6fe41e36e06c88e1387abaea17107ceab2c5b17258958bde03b3e492bb2790e"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT progress::bigint FROM tutorial_progress WHERE email = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "progress",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "a82eec879838b02e3e0722352fba9f537374dc0658733f7e15bfe42f646d22e2"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n id,\n q.runnable_settings_handle,\n q.workspace_id,\n j.runnable_id as \"runnable_id: ScriptHash\",\n scheduled_for,\n parent_job,\n flow_innermost_root_job,\n runnable_path,\n kind as \"kind: JobKind\",\n started_at,\n permissioned_as,\n created_by,\n script_lang as \"script_lang: ScriptLang\",\n permissioned_as_email,\n flow_step_id,\n trigger_kind as \"trigger_kind: JobTriggerKind\",\n trigger,\n q.priority,\n concurrent_limit,\n q.tag,\n cache_ttl,\n cache_ignore_s3_path,\n r.ping as last_ping,\n worker,\n memory_peak,\n running\n FROM v2_job_queue q\n JOIN v2_job j USING (id)\n LEFT JOIN v2_job_runtime r USING (id)\n LEFT JOIN v2_job_status s USING (id)\n WHERE j.id = $1",
"query": "SELECT id, q.workspace_id, j.runnable_id as \"runnable_id: ScriptHash\", scheduled_for, parent_job, flow_innermost_root_job, runnable_path, kind as \"kind: JobKind\", started_at, permissioned_as, created_by, script_lang as \"script_lang: ScriptLang\", \n permissioned_as_email, flow_step_id, trigger_kind as \"trigger_kind: JobTriggerKind\", trigger, q.priority, concurrent_limit, q.tag, cache_ttl, cache_ignore_s3_path, r.ping as last_ping, worker, memory_peak, running\n FROM v2_job_queue q JOIN v2_job j USING (id) LEFT JOIN v2_job_runtime r USING (id) LEFT JOIN v2_job_status s USING (id)\n WHERE j.id = $1",
"describe": {
"columns": [
{
@@ -10,41 +10,36 @@
},
{
"ordinal": 1,
"name": "runnable_settings_handle",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "workspace_id",
"type_info": "Varchar"
},
{
"ordinal": 3,
"ordinal": 2,
"name": "runnable_id: ScriptHash",
"type_info": "Int8"
},
{
"ordinal": 4,
"ordinal": 3,
"name": "scheduled_for",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"ordinal": 4,
"name": "parent_job",
"type_info": "Uuid"
},
{
"ordinal": 6,
"ordinal": 5,
"name": "flow_innermost_root_job",
"type_info": "Uuid"
},
{
"ordinal": 7,
"ordinal": 6,
"name": "runnable_path",
"type_info": "Varchar"
},
{
"ordinal": 8,
"ordinal": 7,
"name": "kind: JobKind",
"type_info": {
"Custom": {
@@ -79,22 +74,22 @@
}
},
{
"ordinal": 9,
"ordinal": 8,
"name": "started_at",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"ordinal": 9,
"name": "permissioned_as",
"type_info": "Varchar"
},
{
"ordinal": 11,
"ordinal": 10,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 12,
"ordinal": 11,
"name": "script_lang: ScriptLang",
"type_info": {
"Custom": {
@@ -130,17 +125,17 @@
}
},
{
"ordinal": 13,
"ordinal": 12,
"name": "permissioned_as_email",
"type_info": "Varchar"
},
{
"ordinal": 14,
"ordinal": 13,
"name": "flow_step_id",
"type_info": "Varchar"
},
{
"ordinal": 15,
"ordinal": 14,
"name": "trigger_kind: JobTriggerKind",
"type_info": {
"Custom": {
@@ -159,59 +154,60 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}
}
},
{
"ordinal": 16,
"ordinal": 15,
"name": "trigger",
"type_info": "Varchar"
},
{
"ordinal": 17,
"ordinal": 16,
"name": "priority",
"type_info": "Int2"
},
{
"ordinal": 18,
"ordinal": 17,
"name": "concurrent_limit",
"type_info": "Int4"
},
{
"ordinal": 19,
"ordinal": 18,
"name": "tag",
"type_info": "Varchar"
},
{
"ordinal": 20,
"ordinal": 19,
"name": "cache_ttl",
"type_info": "Int4"
},
{
"ordinal": 21,
"ordinal": 20,
"name": "cache_ignore_s3_path",
"type_info": "Bool"
},
{
"ordinal": 22,
"ordinal": 21,
"name": "last_ping",
"type_info": "Timestamptz"
},
{
"ordinal": 23,
"ordinal": 22,
"name": "worker",
"type_info": "Varchar"
},
{
"ordinal": 24,
"ordinal": 23,
"name": "memory_peak",
"type_info": "Int4"
},
{
"ordinal": 25,
"ordinal": 24,
"name": "running",
"type_info": "Bool"
}
@@ -223,7 +219,6 @@
},
"nullable": [
false,
true,
false,
true,
false,
@@ -250,5 +245,5 @@
false
]
},
"hash": "b3771b690c5966272b1f42c9965bb6a8f961c119516e4c33dc928cd3b4f4edbc"
"hash": "a84e67035584bbdb02482026b9cc0808086c50f78947d43bb88628a481f41a1d"
}

View File

@@ -1,17 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_diff (source_workspace_id, fork_workspace_id, path, kind, ahead, behind, has_changes)\n VALUES ($1, $2, $3, $4, 1, 0, NULL)\n ON CONFLICT (source_workspace_id, fork_workspace_id, path, kind)\n DO UPDATE SET\n ahead = workspace_diff.ahead + 1,\n has_changes = NULL",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "ac3001bd72248efe36ae3c96af5450f0721f888d18475fae862d675d40779a30"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET git_app_installations = (\n SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb)\n FROM jsonb_array_elements(git_app_installations) AS elem\n WHERE (elem->>'installation_id')::bigint != $1\n )\n WHERE workspace_id = $2\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": []
},
"hash": "ad288f9f242f930fc571d04d5ba217f653437f60c890b6b73059fdaa7eb45302"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM workspace_diff WHERE has_changes = false AND (\n (source_workspace_id = $1 AND fork_workspace_id = $2)\n OR (source_workspace_id = $2 AND fork_workspace_id =$1)\n )",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "af6aea925527c486e56375b6cd215b0e36938a5853285be485647bc8cc846973"
}

View File

@@ -1,49 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, changed_by, changed_at, change_type, member_affected\n FROM group_permission_history\n WHERE workspace_id = $1 AND group_name = $2\n ORDER BY id DESC\n LIMIT $3 OFFSET $4",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "changed_by",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "changed_at",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "change_type",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "member_affected",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
true
]
},
"hash": "b11034489786626184e2f6b7f99469a945a4bbfe0e6a33c5a3e009dadd56d52c"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (\n id, -- 1\n workspace_id, -- 2\n raw_code, -- 3\n raw_lock, -- 4\n raw_flow, -- 5\n tag, -- 6\n parent_job, -- 7\n created_by, -- 8\n permissioned_as, -- 9\n runnable_id, -- 10\n runnable_path, -- 11\n args, -- 12\n kind, -- 13\n trigger, -- 14\n script_lang, -- 15\n same_worker, -- 16\n pre_run_error, -- 17 \n permissioned_as_email, -- 18\n visible_to_owner, -- 19\n flow_innermost_root_job, -- 20\n root_job, -- 38\n concurrent_limit, -- 21\n concurrency_time_window_s, -- 22\n timeout, -- 23\n flow_step_id, -- 24\n cache_ttl, -- 25\n priority, -- 26\n trigger_kind, -- 39\n script_entrypoint_override, -- 12\n preprocessed -- 27,\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = EXCLUDED.email, username = EXCLUDED.username, is_admin = EXCLUDED.is_admin, is_operator = EXCLUDED.is_operator, folders = EXCLUDED.folders, groups = EXCLUDED.groups, workspace_id = EXCLUDED.workspace_id, end_user_email = EXCLUDED.end_user_email\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority, cache_ignore_s3_path, runnable_settings_handle)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31, $42, $43)",
"query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = EXCLUDED.email, username = EXCLUDED.username, is_admin = EXCLUDED.is_admin, is_operator = EXCLUDED.is_operator, folders = EXCLUDED.folders, groups = EXCLUDED.groups, workspace_id = EXCLUDED.workspace_id, end_user_email = EXCLUDED.end_user_email\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority, cache_ignore_s3_path)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31, $42)",
"describe": {
"columns": [],
"parameters": {
@@ -121,18 +121,18 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}
},
"Bool",
"Varchar",
"Bool",
"Int8"
"Bool"
]
},
"nullable": []
},
"hash": "14276a040cb4db88d71fccdc3579e8c0bb132b70668301b535872d1632753e30"
"hash": "b179a3f876ca659bed892d464bf51a733cc86a3204fcd9edccda63fddc97dced"
}

View File

@@ -104,7 +104,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}

View File

@@ -1,49 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, changed_by, changed_at, change_type, affected\n FROM folder_permission_history\n WHERE workspace_id = $1 AND folder_name = $2\n ORDER BY id DESC\n LIMIT $3 OFFSET $4",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "changed_by",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "changed_at",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "change_type",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "affected",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
true
]
},
"hash": "b24eeafc2fc26664ef38d15d3cb47ca19549bcd9372d7a61ec2e28d82897bacf"
}

View File

@@ -18,8 +18,8 @@
"Left": []
},
"nullable": [
true,
false
false,
true
]
},
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id FROM v2_job_queue WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "b9c520fdc29b17977bf1c0b30311c536ebea8a76b75cda188ec91fcc0fc274a7"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO instance_group (name, scim_display_name, id, external_id) VALUES ($1, $2, $3, $4)",
"query": "INSERT INTO instance_group (name, scim_display_name, id, external_id) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING",
"describe": {
"columns": [],
"parameters": {
@@ -13,5 +13,5 @@
},
"nullable": []
},
"hash": "7cb7dcf8b20deb59fb1c3d4ad0ca4f9a209ce0d80682182e56946392f800c24c"
"hash": "bd829646d08f68106211f97c75dce13b6fc7d35bbaf7f503dcc73ae48fd07489"
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path FROM flow\n WHERE workspace_id = $1 AND path = ANY($2) AND archived = false",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"TextArray"
]
},
"nullable": [
false
]
},
"hash": "c5897802334f09596a0297f9b9a1e24f3c6f8b1628965438a9360c073d70924e"
}

View File

@@ -1,41 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value, summary, description, schema\n FROM flow\n WHERE workspace_id = $1 AND path = $2 AND archived = false",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Jsonb"
},
{
"ordinal": 1,
"name": "summary",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "schema",
"type_info": "Json"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
true
]
},
"hash": "dc6ffc9946325e794f3a9d1cf621a9c07d211351195c399f6ff25c1dc7adb01a"
}

View File

@@ -1,20 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM instance_group",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "e1c1e25053ae4b1635780c7e472c9d86806fa8cc762786b7a29bd121837c8ebc"
}

View File

@@ -184,7 +184,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE folder SET owners = array_remove(owners, $1::varchar) WHERE name = $2 AND workspace_id = $3 AND $1 = ANY(owners) RETURNING name",
"query": "UPDATE folder SET owners = array_remove(owners, $1::varchar) WHERE name = $2 AND workspace_id = $3 RETURNING name",
"describe": {
"columns": [
{
@@ -11,7 +11,7 @@
],
"parameters": {
"Left": [
"Text",
"Varchar",
"Text",
"Text"
]
@@ -20,5 +20,5 @@
false
]
},
"hash": "a3ca0af5d84acba93776828c090a79bf6554180b9db4951b69f7070f769b2962"
"hash": "e500a422fa986faf5c612c08f4a139e10b25a3106551e7c92f43dcf9758a5ec7"
}

View File

@@ -1,28 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT concurrency_settings, debouncing_settings FROM runnable_settings WHERE hash = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "concurrency_settings",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "debouncing_settings",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
true,
true
]
},
"hash": "ebbbd069e0f33be9609604025d159fe1ecbefc2e9c11f7c4900b7121d4367e01"
}

View File

@@ -1,17 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET teams_team_id = $1, teams_team_name = $2, teams_team_guid = $3\n WHERE workspace_id = $4\n AND NOT EXISTS (\n SELECT 1 FROM workspace_settings\n WHERE teams_team_id = $1 AND workspace_id <> $4\n )\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "f034f7b0118ad467c7399c5554eb916d5a9716ca0d638e3bc65509b476db378e"
}

View File

@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO folder_permission_history\n (workspace_id, folder_name, changed_by, change_type, affected)\n VALUES ($1, $2, $3, $4, $5)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "f1206b00c4b81e59943385646d653330efba9cd1e731621f7efc91f04567841f"
}

View File

@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id FROM instance_group WHERE name = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true
]
},
"hash": "f2eb05fe3581772d985e2ace82706d824b208ee9a1cb65a85be389e14672620c"
}

View File

@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO group_permission_history\n (workspace_id, group_name, changed_by, change_type, member_affected)\n VALUES ($1, $2, $3, $4, $5)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "f95358255e55d68dd453d173e23ab3cb1f1c2ba5b1cfc78f706b0b014f045477"
}

View File

@@ -104,7 +104,8 @@
"postgres",
"sqs",
"gcp",
"mqtt"
"mqtt",
"nextcloud"
]
}
}

View File

@@ -1,23 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT path FROM variable\n WHERE workspace_id = $1 AND path = ANY($2)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "path",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"TextArray"
]
},
"nullable": [
false
]
},
"hash": "ff24cfb1f603b4ea612e48cee5880fc516207c8a474446a1ee09e8324f85ba09"
}

249
backend/Cargo.lock generated
View File

@@ -788,9 +788,9 @@ dependencies = [
[[package]]
name = "aws-credential-types"
version = "1.2.11"
version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3"
checksum = "b01c9521fa01558f750d183c8c68c81b0155b9d193a4ba7f84c36bd1b6d04a06"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -800,9 +800,9 @@ dependencies = [
[[package]]
name = "aws-lc-rs"
version = "1.15.2"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288"
checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f"
dependencies = [
"aws-lc-sys",
"zeroize",
@@ -810,9 +810,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.35.0"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1"
checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6"
dependencies = [
"cc",
"cmake",
@@ -822,9 +822,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
version = "1.5.17"
version = "1.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d81b5b2898f6798ad58f484856768bca817e3cd9de0974c24ae0f1113fe88f1b"
checksum = "7ce527fb7e53ba9626fc47824f25e256250556c40d8f81d27dd92aa38239d632"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -895,9 +895,9 @@ dependencies = [
[[package]]
name = "aws-sdk-rds"
version = "1.121.0"
version = "1.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15640ecd44797fa9396c66793bd06e57f63f66419117f83518f4d51057b01945"
checksum = "70fbf24cf9f84d18f6296857982dd970dd1c23a54a3bac208c7e9abc13a35c1b"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1009,9 +1009,9 @@ dependencies = [
[[package]]
name = "aws-sigv4"
version = "1.3.7"
version = "1.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e523e1c4e8e7e8ff219d732988e22bfeae8a1cafdbe6d9eca1546fa080be7c"
checksum = "c35452ec3f001e1f2f6db107b6373f1f48f05ec63ba2c5c9fa91f07dad32af11"
dependencies = [
"aws-credential-types",
"aws-smithy-eventstream",
@@ -1032,9 +1032,9 @@ dependencies = [
[[package]]
name = "aws-smithy-async"
version = "1.2.7"
version = "1.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ee19095c7c4dda59f1697d028ce704c24b2d33c6718790c7f1d5a3015b4107c"
checksum = "127fcfad33b7dfc531141fda7e1c402ac65f88aca5511a4d31e2e3d2cd01ce9c"
dependencies = [
"futures-util",
"pin-project-lite",
@@ -1043,9 +1043,9 @@ dependencies = [
[[package]]
name = "aws-smithy-eventstream"
version = "0.60.14"
version = "0.60.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc12f8b310e38cad85cf3bef45ad236f470717393c613266ce0a89512286b650"
checksum = "e29a304f8319781a39808847efb39561351b1bb76e933da7aa90232673638658"
dependencies = [
"aws-smithy-types",
"bytes",
@@ -1054,9 +1054,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http"
version = "0.62.6"
version = "0.62.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b"
checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca"
dependencies = [
"aws-smithy-eventstream",
"aws-smithy-runtime-api",
@@ -1076,9 +1076,9 @@ dependencies = [
[[package]]
name = "aws-smithy-http-client"
version = "1.1.5"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59e62db736db19c488966c8d787f52e6270be565727236fd5579eaa301e7bc4a"
checksum = "623254723e8dfd535f566ee7b2381645f8981da086b5c4aa26c0c41582bb1d2c"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -1106,27 +1106,27 @@ dependencies = [
[[package]]
name = "aws-smithy-json"
version = "0.61.9"
version = "0.61.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551"
checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54"
dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-observability"
version = "0.1.5"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f616c3f2260612fe44cede278bafa18e73e6479c4e393e2c4518cf2a9a228a"
checksum = "2d1881b1ea6d313f9890710d65c158bdab6fb08c91ea825f74c1c8c357baf4cc"
dependencies = [
"aws-smithy-runtime-api",
]
[[package]]
name = "aws-smithy-query"
version = "0.60.9"
version = "0.60.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae5d689cf437eae90460e944a58b5668530d433b4ff85789e69d2f2a556e057d"
checksum = "d28a63441360c477465f80c7abac3b9c4d075ca638f982e605b7dc2a2c7156c9"
dependencies = [
"aws-smithy-types",
"urlencoding",
@@ -1134,9 +1134,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
version = "1.9.6"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fda37911905ea4d3141a01364bc5509a0f32ae3f3b22d6e330c0abfb62d247"
checksum = "0bbe9d018d646b96c7be063dd07987849862b0e6d07c778aad7d93d1be6c1ef0"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -1158,9 +1158,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime-api"
version = "1.9.3"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab0d43d899f9e508300e587bf582ba54c27a452dd0a9ea294690669138ae14a2"
checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
@@ -1175,9 +1175,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "1.3.5"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "905cb13a9895626d49cf2ced759b062d913834c7482c38e49557eac4e6193f01"
checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e"
dependencies = [
"base64-simd 0.8.0",
"bytes",
@@ -1201,9 +1201,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types-convert"
version = "0.60.11"
version = "0.60.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b70bc27e41d5ed80b376602ff4becdab6ea8489403fad3abbfea2c9c825c1e1e"
checksum = "c99945c7033e37bfe8dc76c19862ebd7544b14b4f34a8551ccf2690b433eba0e"
dependencies = [
"aws-smithy-types",
"chrono",
@@ -1211,18 +1211,18 @@ dependencies = [
[[package]]
name = "aws-smithy-xml"
version = "0.60.13"
version = "0.60.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57"
checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-types"
version = "1.3.11"
version = "1.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164"
checksum = "d79fb68e3d7fe5d4833ea34dc87d2e97d26d3086cb3da660bb6b1f76d98680b6"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
@@ -1793,9 +1793,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.19.1"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
dependencies = [
"allocator-api2",
]
@@ -2008,9 +2008,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.50"
version = "1.2.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c"
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -2166,9 +2166,9 @@ dependencies = [
[[package]]
name = "cmake"
version = "0.1.57"
version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
dependencies = [
"cc",
]
@@ -2822,7 +2822,7 @@ dependencies = [
"parquet",
"rand 0.8.5",
"regex",
"sqlparser 0.55.0",
"sqlparser",
"tempfile",
"tokio",
"url",
@@ -2899,7 +2899,7 @@ dependencies = [
"parquet",
"paste",
"recursive",
"sqlparser 0.55.0",
"sqlparser",
"tokio",
"web-time",
]
@@ -3075,7 +3075,7 @@ dependencies = [
"paste",
"recursive",
"serde_json",
"sqlparser 0.55.0",
"sqlparser",
]
[[package]]
@@ -3371,7 +3371,7 @@ dependencies = [
"log",
"recursive",
"regex",
"sqlparser 0.55.0",
"sqlparser",
]
[[package]]
@@ -3404,7 +3404,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"serde",
"sourcemap 9.3.1",
"sourcemap 9.3.0",
"swc_atoms",
"swc_common",
"swc_config",
@@ -6922,9 +6922,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.1.2"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"icu_collections",
"icu_locale_core",
@@ -6936,9 +6936,9 @@ dependencies = [
[[package]]
name = "icu_properties_data"
version = "2.1.2"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
@@ -7723,13 +7723,13 @@ dependencies = [
[[package]]
name = "libredox"
version = "0.1.11"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags 2.9.4",
"libc",
"redox_syscall 0.6.0",
"redox_syscall 0.5.18",
]
[[package]]
@@ -7766,9 +7766,9 @@ dependencies = [
[[package]]
name = "libz-rs-sys"
version = "0.5.4"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c"
checksum = "8b484ba8d4f775eeca644c452a56650e544bf7e617f1d170fe7298122ead5222"
dependencies = [
"zlib-rs",
]
@@ -8602,6 +8602,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]]
name = "notify"
version = "6.1.1"
@@ -9801,9 +9810,9 @@ dependencies = [
[[package]]
name = "portable-atomic"
version = "1.12.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "postgres-native-tls"
@@ -10570,15 +10579,6 @@ dependencies = [
"bitflags 2.9.4",
]
[[package]]
name = "redox_syscall"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
dependencies = [
"bitflags 2.9.4",
]
[[package]]
name = "redox_users"
version = "0.4.6"
@@ -11233,9 +11233,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.13.2"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
dependencies = [
"web-time",
"zeroize",
@@ -12175,9 +12175,9 @@ dependencies = [
[[package]]
name = "sourcemap"
version = "9.3.1"
version = "9.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccaaa78a0ca68b20f8f711eaa2522a00131c48a3de5b892ca5c36cec1ce9bb"
checksum = "c8131753a3c444a6177b92fc9e1bd0d2ecaf1c8953e6a41cc599e8e7ca317ef2"
dependencies = [
"base64-simd 0.8.0",
"bitvec",
@@ -12264,17 +12264,6 @@ dependencies = [
"sqlparser_derive",
]
[[package]]
name = "sqlparser"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4591acadbcf52f0af60eafbb2c003232b2b4cd8de5f0e9437cb8b1b59046cc0f"
dependencies = [
"log",
"recursive",
"sqlparser_derive",
]
[[package]]
name = "sqlparser_derive"
version = "0.3.0"
@@ -12636,9 +12625,9 @@ dependencies = [
[[package]]
name = "supports-hyperlinks"
version = "3.2.0"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b"
[[package]]
name = "supports-unicode"
@@ -12702,7 +12691,7 @@ dependencies = [
"rustc-hash 1.1.0",
"serde",
"siphasher 0.3.11",
"sourcemap 9.3.1",
"sourcemap 9.3.0",
"swc_allocator",
"swc_atoms",
"swc_eq_ignore_macros",
@@ -12766,7 +12755,7 @@ dependencies = [
"num-bigint",
"once_cell",
"serde",
"sourcemap 9.3.1",
"sourcemap 9.3.0",
"swc_allocator",
"swc_atoms",
"swc_common",
@@ -13949,9 +13938,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.6+spec-1.1.0"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
dependencies = [
"winnow 0.7.14",
]
@@ -14085,9 +14074,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.44"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
dependencies = [
"log",
"pin-project-lite",
@@ -14120,9 +14109,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.36"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
dependencies = [
"once_cell",
"valuable",
@@ -14253,9 +14242,9 @@ dependencies = [
[[package]]
name = "tree-sitter-language"
version = "0.1.6"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae62f7eae5eb549c71b76658648b72cc6111f2d87d24a1e31fa907f4943e3ce"
checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8"
[[package]]
name = "tree-sitter-ruby"
@@ -15174,7 +15163,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"aws-sdk-config",
@@ -15195,6 +15184,7 @@ dependencies = [
"lazy_static",
"libloading 0.8.9",
"memchr",
"nom 8.0.0",
"object_store",
"once_cell",
"pep440_rs",
@@ -15210,7 +15200,6 @@ dependencies = [
"sha1",
"sha2 0.10.9",
"size",
"sql-builder",
"sqlx",
"strum 0.27.2",
"systemstat",
@@ -15237,7 +15226,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"argon2",
@@ -15248,7 +15237,6 @@ dependencies = [
"async-trait",
"async_zip",
"aws-config",
"aws-credential-types",
"aws-sdk-config",
"aws-sdk-sqs",
"aws-sdk-sso",
@@ -15359,7 +15347,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -15374,7 +15362,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"chrono",
"lazy_static",
@@ -15388,7 +15376,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"axum",
@@ -15407,7 +15395,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15445,7 +15433,6 @@ dependencies = [
"lazy_static",
"magic-crypt",
"mail-send",
"native-tls",
"object_store",
"once_cell",
"openidconnect",
@@ -15457,7 +15444,6 @@ dependencies = [
"pep440_rs",
"phf 0.11.3",
"pin-project-lite",
"postgres-native-tls 0.5.1",
"prometheus",
"quick_cache",
"rand 0.9.0",
@@ -15482,7 +15468,6 @@ dependencies = [
"thiserror 2.0.17",
"tikv-jemalloc-ctl",
"tokio",
"tokio-postgres 0.7.13",
"tokio-stream",
"tokio-util",
"tonic",
@@ -15503,7 +15488,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"regex",
"serde",
@@ -15518,7 +15503,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"bytes",
@@ -15542,7 +15527,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -15558,7 +15543,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -15567,7 +15552,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15579,7 +15564,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15591,7 +15576,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"gosyn",
@@ -15603,7 +15588,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15615,7 +15600,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"serde_json",
@@ -15627,7 +15612,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"nu-parser",
@@ -15638,7 +15623,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15649,7 +15634,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -15657,12 +15642,11 @@ dependencies = [
"rustpython-parser",
"serde_json",
"windmill-parser",
"windmill-parser-sql",
]
[[package]]
name = "windmill-parser-py-imports"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15686,7 +15670,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15700,7 +15684,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -15717,21 +15701,21 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"lazy_static",
"nom 8.0.0",
"regex",
"regex-lite",
"serde",
"serde_json",
"sqlparser 0.59.0",
"windmill-parser",
]
[[package]]
name = "windmill-parser-ts"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -15745,12 +15729,11 @@ dependencies = [
"triomphe",
"wasm-bindgen",
"windmill-parser",
"windmill-parser-sql",
]
[[package]]
name = "windmill-parser-yaml"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"serde",
@@ -15761,7 +15744,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -15798,7 +15781,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -15808,7 +15791,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.595.0"
version = "1.590.0"
dependencies = [
"anyhow",
"async-once-cell",
@@ -16714,9 +16697,9 @@ dependencies = [
[[package]]
name = "zlib-rs"
version = "0.5.4"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235"
checksum = "36134c44663532e6519d7a6dfdbbe06f6f8192bde8ae9ed076e9b213f0e31df7"
[[package]]
name = "zstd"

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.595.0"
version = "1.590.0"
authors.workspace = true
edition.workspace = true
@@ -33,7 +33,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal"]
[workspace.package]
version = "1.595.0"
version = "1.590.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -125,7 +125,6 @@ windmill-autoscaling = { workspace = true, optional = true }
futures.workspace = true
tracing.workspace = true
sqlx.workspace = true
sql-builder.workspace = true
rand.workspace = true
chrono.workspace = true
git-version.workspace = true
@@ -159,6 +158,7 @@ kube.workspace = true
k8s-openapi.workspace = true
libloading.workspace = true
bitflags.workspace = true
nom.workspace = true
globset.workspace = true
@@ -256,7 +256,7 @@ mail-send = { version = "0.4.0", features = ["builder"], default-features=false
urlencoding = "^2"
url = { version = "^2" , features = ["serde"]}
async-oauth2 = "0.5.1"
reqwest = { version = "=0.12.24", features = ["json", "stream", "gzip", "multipart"] }
reqwest = { version = "^0.12", features = ["json", "stream", "gzip", "multipart"] }
eventsource-stream = "0.2.3"
time = "^0"
serde_urlencoded = "^0"
@@ -378,6 +378,7 @@ pg_escape = "0.1.1"
async-nats = "0.38.0"
nkeys = "0.4.4"
nu-parser = { version = "0.101.0", default-features = false }
nom = "8.0.0"
globset = "0.4.16"
process-wrap = { version = "8.2.1", features = ["tokio1"] }

View File

@@ -1 +1 @@
576abf6519d1aa12a2b989a58a123501206284fb
0d72102809e766d5285a162dcc30055847700ee2

View File

@@ -43,7 +43,7 @@ def load_openapi_spec(file_path: str) -> Dict[str, Any]:
print(f"Error loading OpenAPI spec: {e}", file=sys.stderr)
sys.exit(1)
def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Optional[Dict[str, Any]], spec: Dict[str, Any], required_fields: Optional[List[str]] = None, base_path: str = "") -> tuple:
def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Optional[Dict[str, Any]], spec: Dict[str, Any], required_fields: Optional[List[str]] = None) -> tuple:
"""Extract separate schemas for path parameters, query parameters, and request body."""
path_params_schema = {
"type": "object",
@@ -63,16 +63,16 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt
for param in parameters:
# Resolve $ref if present
if '$ref' in param:
param = resolve_schema_refs(param, spec, base_path)
param = resolve_schema_refs(param, spec)
param_name = param.get('name', '')
param_schema = param.get('schema', {'type': 'string'})
param_required = param.get('required', False)
param_description = param.get('description', '')
param_in = param.get('in', 'query')
# Resolve any refs in the parameter schema
param_schema = resolve_schema_refs(param_schema, spec, base_path)
param_schema = resolve_schema_refs(param_schema, spec)
# Add description if available
if param_description:
@@ -93,7 +93,7 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt
# Process request body if present
if request_body:
body_schema = extract_request_body_schema(request_body, spec, base_path)
body_schema = extract_request_body_schema(request_body, spec)
# If we have required fields specified and a body schema, update the required array
if body_schema and required_fields:
@@ -115,109 +115,68 @@ def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Opt
return (path_params_schema, query_params_schema, body_schema)
# Cache for loaded external files
_external_file_cache: Dict[str, Dict[str, Any]] = {}
def load_external_file(file_path: str, base_path: str) -> Optional[Dict[str, Any]]:
"""Load an external YAML file relative to the base path."""
if file_path in _external_file_cache:
return _external_file_cache[file_path]
try:
import yaml
from pathlib import Path
# Resolve the path relative to the base file
base_dir = Path(base_path).parent
full_path = (base_dir / file_path).resolve()
with open(full_path, 'r', encoding='utf-8') as f:
content = yaml.safe_load(f)
_external_file_cache[file_path] = content
return content
except Exception as e:
print(f"Warning: Could not load external file {file_path}: {e}", file=sys.stderr)
return None
def resolve_ref(ref_path: str, spec: Dict[str, Any], base_path: str = "") -> tuple:
"""Resolve a $ref path to the actual schema definition.
Handles both internal refs (#/...) and external file refs (file.yaml#/...).
Returns a tuple of (resolved_schema, resolved_spec) where resolved_spec is the spec
that should be used for resolving any nested refs within the resolved schema.
"""
# Check if this is an external file reference
if '#' in ref_path and not ref_path.startswith('#'):
# External file reference: "../../openflow.openapi.yaml#/components/schemas/Retry"
file_part, fragment = ref_path.split('#', 1)
external_spec = load_external_file(file_part, base_path)
if external_spec is None:
return None, spec
# Resolve the fragment within the external file, and return external_spec for nested refs
resolved, _ = resolve_ref('#' + fragment, external_spec, base_path)
return resolved, external_spec
def resolve_ref(ref_path: str, spec: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Resolve a $ref path to the actual schema definition."""
if not ref_path.startswith('#/'):
return None, spec
return None
# Remove the '#/' prefix and split by '/'
path_parts = ref_path[2:].split('/')
# Navigate through the spec following the path
current = spec
for part in path_parts:
if isinstance(current, dict) and part in current:
current = current[part]
else:
return None, spec
return None
return current if isinstance(current, dict) else None
return (current if isinstance(current, dict) else None), spec
def resolve_schema_refs(schema: Dict[str, Any], spec: Dict[str, Any], base_path: str = "") -> Dict[str, Any]:
def resolve_schema_refs(schema: Dict[str, Any], spec: Dict[str, Any]) -> Dict[str, Any]:
"""Recursively resolve all $ref references in a schema."""
if not isinstance(schema, dict):
return schema
# If this is a $ref, resolve it
if '$ref' in schema:
ref_path = schema['$ref']
resolved, resolved_spec = resolve_ref(ref_path, spec, base_path)
resolved = resolve_ref(ref_path, spec)
if resolved:
# Recursively resolve any refs in the resolved schema using the appropriate spec
return resolve_schema_refs(resolved, resolved_spec, base_path)
# Recursively resolve any refs in the resolved schema
return resolve_schema_refs(resolved, spec)
else:
print(f"Warning: Could not resolve $ref: {ref_path}")
return schema
# Recursively process all values in the schema
resolved_schema = {}
for key, value in schema.items():
if isinstance(value, dict):
resolved_schema[key] = resolve_schema_refs(value, spec, base_path)
resolved_schema[key] = resolve_schema_refs(value, spec)
elif isinstance(value, list):
resolved_schema[key] = [
resolve_schema_refs(item, spec, base_path) if isinstance(item, dict) else item
resolve_schema_refs(item, spec) if isinstance(item, dict) else item
for item in value
]
else:
resolved_schema[key] = value
return resolved_schema
def extract_request_body_schema(request_body: Dict[str, Any], spec: Dict[str, Any], base_path: str = "") -> Optional[Dict[str, Any]]:
def extract_request_body_schema(request_body: Dict[str, Any], spec: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Extract request body schema from OpenAPI requestBody definition and resolve refs."""
if not request_body:
return None
content = request_body.get('content', {})
json_content = content.get('application/json', {})
schema = json_content.get('schema', {})
if schema:
# Resolve any $ref references in the schema
return resolve_schema_refs(schema, spec, base_path)
return resolve_schema_refs(schema, spec)
return None
def http_method_to_rust(method: str) -> str:
@@ -262,7 +221,7 @@ def find_mcp_tools(spec: Dict[str, Any]) -> List[Dict[str, Any]]:
return tools
def generate_typescript_code(tools: List[Dict[str, Any]], spec: Dict[str, Any], base_path: str = "") -> str:
def generate_typescript_code(tools: List[Dict[str, Any]], spec: Dict[str, Any]) -> str:
"""Generate TypeScript code with MCP endpoint tools."""
if not tools:
return """// Auto-generated MCP tools from OpenAPI specification
@@ -293,7 +252,7 @@ export const mcpEndpointTools: EndpointTool[] = [];
# Generate separate schemas
path_params_schema, query_params_schema, body_schema = extract_separate_schemas(
tool['parameters'], tool['requestBody'], spec, tool['required_fields'], base_path
tool['parameters'], tool['requestBody'], spec, tool['required_fields']
)
# Convert schemas to TypeScript - use 'as const' for better type inference
@@ -338,7 +297,7 @@ export const mcpEndpointTools: EndpointTool[] = [
return typescript_code
def generate_rust_code(tools: List[Dict[str, Any]], spec: Dict[str, Any], base_path: str = "") -> str:
def generate_rust_code(tools: List[Dict[str, Any]], spec: Dict[str, Any]) -> str:
"""Generate the complete Rust code with MCP tools."""
if not tools:
return """// No MCP tools found in the OpenAPI specification
@@ -361,7 +320,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
# Generate separate schemas
path_params_schema, query_params_schema, body_schema = extract_separate_schemas(
tool['parameters'], tool['requestBody'], spec, tool['required_fields'], base_path
tool['parameters'], tool['requestBody'], spec, tool['required_fields']
)
path_params_rust = schema_to_rust_value(path_params_schema)
@@ -427,7 +386,7 @@ def main():
# Generate and write Rust code
print(f"Generating Rust code...")
rust_code = generate_rust_code(tools, spec, str(openapi_file))
rust_code = generate_rust_code(tools, spec)
print(f"Writing Rust code to: {rust_output_file}")
rust_output_file.parent.mkdir(parents=True, exist_ok=True)
@@ -436,7 +395,7 @@ def main():
# Generate and write TypeScript code
print(f"Generating TypeScript code...")
typescript_code = generate_typescript_code(tools, spec, str(openapi_file))
typescript_code = generate_typescript_code(tools, spec)
print(f"Writing TypeScript code to: {ts_output_file}")
ts_output_file.parent.mkdir(parents=True, exist_ok=True)

View File

@@ -1,6 +0,0 @@
-- Add down migration script here
DROP INDEX IF EXISTS idx_group_perm_history_workspace_group;
DROP TABLE IF EXISTS group_permission_history;
DROP INDEX IF EXISTS idx_folder_perm_history_workspace_folder;
DROP TABLE IF EXISTS folder_permission_history;

View File

@@ -1,90 +0,0 @@
-- Add up migration script here
-- Folder permission changes history
CREATE TABLE IF NOT EXISTS folder_permission_history (
id BIGSERIAL PRIMARY KEY,
workspace_id VARCHAR(50) NOT NULL,
folder_name VARCHAR(255) NOT NULL,
changed_by VARCHAR(50) NOT NULL,
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
change_type VARCHAR(50) NOT NULL,
affected VARCHAR(100),
FOREIGN KEY (workspace_id, folder_name) REFERENCES folder(workspace_id, name) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_folder_perm_history_workspace_folder
ON folder_permission_history(workspace_id, folder_name, id DESC);
-- Group permission changes history
CREATE TABLE IF NOT EXISTS group_permission_history (
id BIGSERIAL PRIMARY KEY,
workspace_id VARCHAR(50) NOT NULL,
group_name VARCHAR(255) NOT NULL,
changed_by VARCHAR(50) NOT NULL,
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
change_type VARCHAR(50) NOT NULL,
member_affected VARCHAR(100),
FOREIGN KEY (workspace_id, group_name) REFERENCES group_(workspace_id, name) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_group_perm_history_workspace_group
ON group_permission_history(workspace_id, group_name, id DESC);
GRANT ALL ON TABLE folder_permission_history TO windmill_user;
GRANT ALL ON TABLE group_permission_history TO windmill_user;
GRANT ALL ON TABLE folder_permission_history TO windmill_admin;
GRANT ALL ON TABLE group_permission_history TO windmill_admin;
-- Enable RLS on folder_permission_history
ALTER TABLE folder_permission_history ENABLE ROW LEVEL SECURITY;
-- Admin policies (windmill_admin can always do everything)
CREATE POLICY admin_all ON folder_permission_history FOR ALL TO windmill_admin USING (true) WITH CHECK (true);
CREATE POLICY admin_all ON group_permission_history FOR ALL TO windmill_admin USING (true) WITH CHECK (true);
-- Enable RLS on group_permission_history
ALTER TABLE group_permission_history ENABLE ROW LEVEL SECURITY;
-- RLS policies for folder_permission_history
-- Anyone can insert
CREATE POLICY allow_insert ON folder_permission_history FOR INSERT TO windmill_user WITH CHECK (true);
-- Select requires being in extra_perms (as user or via group)
CREATE POLICY see_extra_perms_user ON folder_permission_history FOR SELECT TO windmill_user
USING (EXISTS (
SELECT 1 FROM folder f
WHERE f.workspace_id = folder_permission_history.workspace_id
AND f.name = folder_permission_history.folder_name
AND f.extra_perms ? CONCAT('u/', current_setting('session.user'))
));
CREATE POLICY see_extra_perms_groups ON folder_permission_history FOR SELECT TO windmill_user
USING (EXISTS (
SELECT 1 FROM folder f
WHERE f.workspace_id = folder_permission_history.workspace_id
AND f.name = folder_permission_history.folder_name
AND f.extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]
));
-- RLS policies for group_permission_history
-- Anyone can insert
CREATE POLICY allow_insert ON group_permission_history FOR INSERT TO windmill_user WITH CHECK (true);
-- Select requires being in extra_perms (as user or via group)
CREATE POLICY see_extra_perms_user ON group_permission_history FOR SELECT TO windmill_user
USING (EXISTS (
SELECT 1 FROM group_ g
WHERE g.workspace_id = group_permission_history.workspace_id
AND g.name = group_permission_history.group_name
AND (g.extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean
));
CREATE POLICY see_extra_perms_groups ON group_permission_history FOR SELECT TO windmill_user
USING (EXISTS (
SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f
WHERE g.workspace_id = group_permission_history.workspace_id
AND g.name = group_permission_history.group_name
AND SPLIT_PART(f.key, '/', 1) = 'g'
AND f.key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
AND f.value::boolean
));

View File

@@ -1,3 +0,0 @@
-- Add down migration script here
DROP TABLE IF EXISTS skip_workspace_diff_tally;
DROP TABLE IF EXISTS workspace_diff;

View File

@@ -1,24 +0,0 @@
-- Add up migration script here
CREATE TABLE workspace_diff (
source_workspace_id VARCHAR(50) NOT NULL,
fork_workspace_id VARCHAR(50) NOT NULL,
path VARCHAR(255) NOT NULL,
kind VARCHAR(50) NOT NULL,
ahead INTEGER NOT NULL DEFAULT 0,
behind INTEGER NOT NULL DEFAULT 0,
has_changes BOOLEAN DEFAULT NULL,
exists_in_source BOOLEAN DEFAULT NULL,
exists_in_fork BOOLEAN DEFAULT NULL,
PRIMARY KEY (source_workspace_id, fork_workspace_id, path, kind)
);
-- Create table to track workspaces that should be excluded from diff tallying
-- Old workspaces that are linked but have already diverged need to be skipped
CREATE TABLE skip_workspace_diff_tally (
workspace_id VARCHAR(50) PRIMARY KEY,
added_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Populate with all existing workspaces to exclude them from new tallying logic
INSERT INTO skip_workspace_diff_tally (workspace_id)
SELECT id FROM workspace;

View File

@@ -1,2 +1 @@
-- Add down migration script here
ALTER TABLE email_trigger DROP COLUMN enabled;

View File

@@ -1,11 +0,0 @@
ALTER TABLE v2_job_queue
DROP COLUMN runnable_settings_handle;
ALTER TABLE script
DROP COLUMN runnable_settings_handle;
DROP TABLE IF EXISTS job_settings;
DROP TABLE IF EXISTS runnable_settings;
DROP TABLE IF EXISTS concurrency_settings;
DROP TABLE IF EXISTS debouncing_settings;

View File

@@ -1,42 +0,0 @@
CREATE TABLE IF NOT EXISTS concurrency_settings(
hash BIGINT PRIMARY KEY,
concurrency_key VARCHAR(255),
concurrent_limit INTEGER,
concurrency_time_window_s INTEGER
);
CREATE TABLE IF NOT EXISTS debouncing_settings(
hash BIGINT PRIMARY KEY,
debounce_key VARCHAR(255),
debounce_delay_s INTEGER,
max_total_debouncing_time INTEGER,
max_total_debounces_amount INTEGER,
debounce_args_to_accumulate TEXT[]
);
CREATE TABLE IF NOT EXISTS runnable_settings(
hash BIGINT PRIMARY KEY,
debouncing_settings BIGINT DEFAULT NULL,
concurrency_settings BIGINT DEFAULT NULL
);
CREATE TABLE IF NOT EXISTS job_settings(
job_id UUID PRIMARY KEY,
runnable_settings BIGINT DEFAULT NULL
);
ALTER TABLE script
ADD COLUMN runnable_settings_handle BIGINT DEFAULT NULL;
ALTER TABLE v2_job_queue
ADD COLUMN runnable_settings_handle BIGINT DEFAULT NULL;
GRANT ALL ON concurrency_settings TO windmill_admin;
GRANT ALL ON concurrency_settings TO windmill_user;
GRANT ALL ON debouncing_settings TO windmill_admin;
GRANT ALL ON debouncing_settings TO windmill_user;
GRANT ALL ON runnable_settings TO windmill_admin;
GRANT ALL ON runnable_settings TO windmill_user;
GRANT ALL ON job_settings TO windmill_admin;
GRANT ALL ON job_settings TO windmill_user;

View File

@@ -1,38 +0,0 @@
-- Grant CREATE privilege on all databases where custom_instance_user has CONNECT
-- This allows custom_instance_user to create schemas in databases it can already access
DO $$
DECLARE
db_record RECORD;
grant_command TEXT;
BEGIN
-- Find all databases where custom_instance_user has CONNECT privilege
-- We check if the datacl array contains an entry for custom_instance_user with 'c' (CONNECT) privilege
FOR db_record IN
SELECT d.datname
FROM pg_database d
WHERE d.datname NOT IN ('template0', 'template1') -- Skip template databases
AND d.datallowconn = true -- Only consider databases that allow connections
AND d.datacl IS NOT NULL -- Has ACL entries
AND EXISTS (
SELECT 1
FROM unnest(d.datacl) AS acl_entry
WHERE acl_entry::text LIKE 'custom_instance_user=c/%' -- 'c' is the privilege code for CONNECT
)
LOOP
BEGIN
-- Grant CREATE privilege on the database
EXECUTE format('GRANT CREATE ON DATABASE %I TO custom_instance_user', db_record.datname);
RAISE NOTICE 'Granted CREATE on database % to custom_instance_user', db_record.datname;
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Failed to grant CREATE on database %: %', db_record.datname, SQLERRM;
END;
END LOOP;
RAISE NOTICE 'Completed granting CREATE privileges to custom_instance_user on all accessible databases';
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Error in custom_instance_user CREATE privilege migration: %', SQLERRM;
-- Continue without failing the migration
END
$$;

View File

@@ -1 +0,0 @@
-- Add down migration script here

View File

@@ -1,6 +0,0 @@
-- Add up migration script here
GRANT ALL ON SEQUENCE folder_permission_history_id_seq TO windmill_user;
GRANT ALL ON SEQUENCE folder_permission_history_id_seq TO windmill_admin;
GRANT ALL ON SEQUENCE group_permission_history_id_seq TO windmill_user;
GRANT ALL ON SEQUENCE group_permission_history_id_seq TO windmill_admin;

View File

@@ -1,2 +0,0 @@
-- Drop flow_iterator_data table
DROP TABLE IF EXISTS flow_iterator_data;

View File

@@ -1,9 +0,0 @@
-- Create separate table for storing flow iterator data (itered arrays)
-- This avoids expensive JSONB_SET operations on large itered arrays during parallel loop execution
CREATE TABLE IF NOT EXISTS flow_iterator_data (
job_id UUID PRIMARY KEY REFERENCES v2_job_queue (id) ON DELETE CASCADE NOT NULL,
itered JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Index not needed beyond primary key since all lookups are by job_id

View File

@@ -1 +0,0 @@
ALTER TABLE workspace_settings DROP COLUMN teams_team_guid;

View File

@@ -1,3 +0,0 @@
-- Add teams_team_guid column to store the GUID (used for MS Graph API calls)
-- The existing teams_team_id column stores the internal_id (used for webhook matching)
ALTER TABLE workspace_settings ADD COLUMN teams_team_guid TEXT;

View File

@@ -10,7 +10,6 @@ path = "./src/lib.rs"
[dependencies]
windmill-parser.workspace = true
windmill-parser-sql.workspace = true
rustpython-parser.workspace = true
itertools.workspace = true
serde_json.workspace = true

View File

@@ -2,12 +2,12 @@ use rustpython_ast::{Constant, Expr, ExprConstant, Visitor};
use rustpython_parser::{ast::Suite, Parse};
use std::collections::HashMap;
use windmill_parser::asset_parser::{
asset_was_used, merge_assets, parse_asset_syntax, AssetKind, AssetUsageAccessType,
detect_sql_access_type, merge_assets, parse_asset_syntax, AssetKind, AssetUsageAccessType,
ParseAssetsResult,
};
use AssetUsageAccessType::*;
pub fn parse_assets(input: &str) -> anyhow::Result<Vec<ParseAssetsResult>> {
pub fn parse_assets(input: &str) -> anyhow::Result<Vec<ParseAssetsResult<String>>> {
let ast = Suite::parse(input, "main.py")
.map_err(|e| anyhow::anyhow!("Error parsing code: {}", e.to_string()))?;
@@ -15,51 +15,67 @@ pub fn parse_assets(input: &str) -> anyhow::Result<Vec<ParseAssetsResult>> {
ast.into_iter()
.for_each(|stmt| assets_finder.visit_stmt(stmt));
for (kind, path, _) in assets_finder.var_identifiers.into_values() {
for (kind, name) in assets_finder.var_identifiers.into_values() {
// if a db = wmill.datatable() was never used (e.g db.query(...)),
// we still want to register the asset as unknown access type
if asset_was_used(&assets_finder.assets, (kind, &path)) == false {
if assets_finder
.assets
.iter()
.all(|a| !(a.kind == kind && a.path == name))
{
assets_finder
.assets
.push(ParseAssetsResult { kind, access_type: None, path });
.push(ParseAssetsResult { kind, access_type: None, path: name });
}
}
Ok(merge_assets(assets_finder.assets))
}
type VarAssetName = String;
type VarAssetSchema = Option<String>;
struct AssetsFinder {
assets: Vec<ParseAssetsResult>,
var_identifiers: HashMap<String, (AssetKind, VarAssetName, VarAssetSchema)>,
assets: Vec<ParseAssetsResult<String>>,
var_identifiers: HashMap<String, (AssetKind, String)>,
}
impl Visitor for AssetsFinder {
// Handle assignment statements like: x = wmill.datatable('name')
fn visit_stmt_assign(&mut self, node: rustpython_ast::StmtAssign) {
// Check if the value is a call to a tracked function
if let Some(Expr::Name(expr_name)) = node.targets.first() {
// Remove any tracked variables with that name in case of reassignment
let Ok(var_name) = expr_name.id.parse::<String>();
let removed = self.var_identifiers.remove(&var_name);
// if a db = wmill.datatable() or similar was removed, but never used (e.g db.query(...)),
// we still want to register the asset as unknown access type
match removed {
Some((kind, path, _)) => {
if !asset_was_used(&self.assets, (kind, &path)) {
self.assets
.push(ParseAssetsResult { kind, access_type: None, path });
if let Some((kind, name)) = self.extract_asset_from_call(&node.value) {
// Track all target variables
for target in &node.targets {
if let Expr::Name(name_expr) = target {
let Ok(var_name) = name_expr.id.parse::<String>();
self.var_identifiers
.insert(var_name, (kind.clone(), name.clone()));
}
}
} else {
// If not wmill.datatable or similar, remove any tracked variables
// It means the identifier is no longer refering to an asset
for target in &node.targets {
if let Expr::Name(name_expr) = target {
let Ok(var_name) = name_expr.id.parse::<String>();
let removed = self.var_identifiers.remove(&var_name);
// if a db = wmill.datatable() or similar was removed, but never used (e.g db.query(...)),
// we still want to register the asset as unknown access type
match removed {
Some((kind, name)) => {
if self
.assets
.iter()
.all(|a| !(a.kind == kind && a.path == name))
{
self.assets.push(ParseAssetsResult {
kind,
access_type: None,
path: name,
});
}
}
None => {}
}
}
None => {}
}
if let Some((kind, name, schema)) = self.extract_asset_from_call(&node.value) {
// Track target variable
let Ok(var_name) = expr_name.id.parse::<String>();
self.var_identifiers
.insert(var_name, (kind.clone(), name.clone(), schema.clone()));
}
}
// Continue with generic visit to catch any other assets in the expression
@@ -71,7 +87,7 @@ impl Visitor for AssetsFinder {
fn visit_expr_constant(&mut self, node: ExprConstant) {
match node.value {
Constant::Str(s) => {
if let Some((kind, path)) = parse_asset_syntax(&s, false) {
if let Some((kind, path)) = parse_asset_syntax(&s) {
self.assets.push(ParseAssetsResult {
kind,
path: path.to_string(),
@@ -92,7 +108,7 @@ impl Visitor for AssetsFinder {
if let Expr::Constant(ExprConstant { value: Constant::Str(s), .. }) =
&keyword.value
{
if let Some((kind, path)) = parse_asset_syntax(s, false) {
if let Some((kind, path)) = parse_asset_syntax(s) {
self.assets.push(ParseAssetsResult {
kind,
path: path.to_string(),
@@ -109,10 +125,7 @@ impl Visitor for AssetsFinder {
impl AssetsFinder {
/// Extract asset info from calls like wmill.datatable('name'), wmill.ducklake('name'), etc.
fn extract_asset_from_call(
&self,
expr: &Expr,
) -> Option<(AssetKind, VarAssetName, VarAssetSchema)> {
fn extract_asset_from_call(&self, expr: &Expr) -> Option<(AssetKind, String)> {
let call = expr.as_call_expr()?;
// Check for wmill.datatable, wmill.ducklake pattern
@@ -148,21 +161,10 @@ impl AssetsFinder {
} else {
None
}
});
let (name, schema) = match name {
None => ("main".to_string(), None),
Some(name) => {
if let Some((name, s)) = name.split_once(':') {
let schema = Some(s.to_string());
let name = if name.is_empty() { "main" } else { name };
(name.to_string(), schema)
} else {
(name, None)
}
}
};
})
.unwrap_or_else(|| "main".to_string());
Some((kind, name, schema))
Some((kind, name))
}
fn visit_expr_call_inner(&mut self, node: &rustpython_ast::ExprCall) -> Result<(), ()> {
@@ -182,8 +184,8 @@ impl AssetsFinder {
value, ..
}) = node.func.as_ref()
{
if let Expr::Name(expr_name) = value.as_ref() {
expr_name.id.parse().map_err(|_| ())?
if let Expr::Name(name_expr) = value.as_ref() {
name_expr.id.parse().map_err(|_| ())?
} else {
return Err(());
}
@@ -193,45 +195,28 @@ impl AssetsFinder {
if obj_name == "wmill" {
// Continue
} else if let Some((kind, ref path, ref schema)) = self.var_identifiers.get(&obj_name) {
} else if let Some((kind, ref path)) = self.var_identifiers.get(&obj_name) {
if ident == "query" {
let expr_name = node.args.get(0).or_else(|| {
let name_expr = node.args.get(0).or_else(|| {
node.keywords
.iter()
.find(|kw| kw.arg.as_deref() == Some("name"))
.map(|kw| &kw.value)
});
let sql = match expr_name {
Some(Expr::Constant(ExprConstant { value: Constant::Str(sql), .. })) => sql,
match name_expr {
Some(Expr::Constant(ExprConstant {
value: Constant::Str(sql_query), ..
})) => {
let access_type = detect_sql_access_type(&sql_query);
self.assets.push(ParseAssetsResult {
kind: *kind,
path: path.to_string(),
access_type,
});
return Ok(());
}
_ => return Err(()),
};
let duckdb_conn_prefix = match kind {
AssetKind::DataTable => "datatable",
AssetKind::Ducklake => "ducklake",
_ => return Ok(()),
};
let sql = format!("ATTACH '{duckdb_conn_prefix}://{path}' AS dt; USE dt; {sql}");
// We use the SQL parser to detect if it's a read or write query
match windmill_parser_sql::parse_assets(&sql) {
Ok(mut sql_assets) => {
if let Some(schema_name) = schema {
for asset in &mut sql_assets {
if asset.kind == *kind && asset.path.starts_with(path.as_str()) {
asset.path = format!(
"{}/{}.{}",
path,
schema_name,
&asset.path[path.len() + 1..]
);
}
}
}
self.assets.extend(sql_assets);
}
_ => {}
}
return Ok(());
} else {
return Err(());
}
@@ -264,9 +249,7 @@ impl AssetsFinder {
match arg_val {
Some(Expr::Constant(ExprConstant { value: Constant::Str(value), .. })) => {
let path = parse_asset_syntax(&value, false)
.map(|(_, p)| p)
.unwrap_or(&value);
let path = parse_asset_syntax(&value).map(|(_, p)| p).unwrap_or(&value);
self.assets
.push(ParseAssetsResult { kind, path: path.to_string(), access_type });
}
@@ -276,233 +259,5 @@ impl AssetsFinder {
}
}
// Positional arguments in python can also be used by their name
struct Arg(usize, &'static str);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_py_asset_parser_load_s3() {
let input = r#"
import wmill
def main():
wmill.load_s3_file('s3:///test.csv')
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::S3Object,
path: "/test.csv".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_py_asset_parser_unused_sql() {
let input = r#"
import wmill
def main():
db = wmill.datatable()
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "main".to_string(),
access_type: None
},])
);
}
#[test]
fn test_py_asset_parser_sql_read() {
let input = r#"
import wmill
def main(x: int):
db = wmill.datatable('dt')
return db.query('SELECT * FROM friends WHERE age = $1', x).fetch()
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/friends".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_py_asset_parser_sql_read_write() {
let input = r#"
import wmill
def main(x: int):
db = wmill.datatable('dt')
db.query('UPDATE friends SET x = $1', x).fetch()
db.query('SELECT * FROM friends WHERE age = $1', x).fetch_one()
db.query('SELECT * FROM analytics').fetch()
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/analytics".to_string(),
access_type: Some(R)
},
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/friends".to_string(),
access_type: Some(RW)
},
])
);
}
#[test]
fn test_py_asset_parser_multiple_sql_scopes() {
let input = r#"
import wmill
def main():
def f(x: int):
db = wmill.datatable()
return db.query('SELECT * FROM friends WHERE age = $1', x)
db = wmill.datatable('another1')
return db.query('INSERT INTO customers VALUES ($1)', 0)
def g():
db = wmill.ducklake('another2')
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "another1/customers".to_string(),
access_type: Some(W)
},
ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "another2".to_string(),
access_type: None
},
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "main/friends".to_string(),
access_type: Some(R)
},
])
);
}
#[test]
fn test_py_asset_parser_overriden_var_identifier() {
let input = r#"
import wmill
def main():
db = wmill.datatable('another1')
def g():
db = wmill.ducklake()
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "another1".to_string(),
access_type: None
},
ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main".to_string(),
access_type: None
},
])
);
}
#[test]
fn test_py_asset_parser_datatable_with_schema() {
let input = r#"
import wmill
def main(x: int):
db = wmill.datatable('dt:public')
return db.query('SELECT * FROM friends WHERE age = $1', x).fetch()
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/public.friends".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_py_asset_parser_ducklake_with_schema() {
let input = r#"
import wmill
def main():
db = wmill.ducklake('lake1:analytics')
return db.query('SELECT * FROM metrics').fetch()
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "lake1/analytics.metrics".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_py_asset_parser_schema_with_write() {
let input = r#"
import wmill
def main(x: int):
db = wmill.datatable('dt:public')
db.query('INSERT INTO users VALUES ($1)', x).fetch()
return db.query('SELECT * FROM users').fetch()
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/public.users".to_string(),
access_type: Some(RW)
},])
);
}
#[test]
fn test_py_asset_parser_unused_datatable_with_schema() {
let input = r#"
import wmill
def main():
db = wmill.datatable('dt:public')
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt".to_string(),
access_type: None
},])
);
}
}
// Positional arguments in python can also be used by their name

View File

@@ -15,7 +15,7 @@ use windmill_parser::{json_to_typ, Arg, MainArgSignature, ObjectType, Typ};
use rustpython_parser::{
ast::{
Constant, Expr, ExprAttribute, ExprConstant, ExprDict, ExprList, ExprName, Stmt, StmtAssign, StmtClassDef, StmtFunctionDef, Suite,
Constant, Expr, ExprConstant, ExprDict, ExprList, ExprName, Stmt, StmtFunctionDef, Suite,
},
Parse,
};
@@ -60,166 +60,6 @@ fn filter_non_main(code: &str, main_name: &str) -> String {
return filtered_code;
}
/// Data extracted from parsing the Python code
struct CodeMetadata {
enums: HashMap<String, EnumInfo>,
descriptions: HashMap<String, String>,
}
/// Information about an Enum class
struct EnumInfo {
values: Vec<String>,
members: HashMap<String, String>,
}
fn has_enum_keyword(code: &str) -> bool {
code.contains("Enum")
}
/// Extract only class and function definitions from code (prepass filtering)
fn filter_relevant_statements(code: &str) -> String {
let mut result = Vec::new();
let mut lines = code.lines().peekable();
while let Some(line) = lines.next() {
let trimmed = line.trim_start();
if trimmed.starts_with("class ") || trimmed.starts_with("def ") {
result.push(line);
let base_indent = line.len() - trimmed.len();
while let Some(&next_line) = lines.peek() {
let next_trimmed = next_line.trim_start();
let next_indent = next_line.len() - next_trimmed.len();
if next_trimmed.is_empty() || next_indent > base_indent {
result.push(lines.next().unwrap());
} else {
break;
}
}
}
}
result.join("\n")
}
/// Extract Enum definitions and docstring descriptions lazily.
/// Only parses AST if relevant keywords are present.
fn extract_code_metadata(code: &str, main_name: &str) -> CodeMetadata {
let mut enums = HashMap::new();
let mut descriptions = HashMap::new();
let has_enum = has_enum_keyword(code);
let has_docstring = code.contains("Args:");
if !has_enum && !has_docstring {
return CodeMetadata { enums, descriptions };
}
let filtered_code = filter_relevant_statements(code);
let ast = match Suite::parse(&filtered_code, "main.py") {
Ok(ast) => ast,
Err(_) => return CodeMetadata { enums, descriptions },
};
for stmt in ast {
match stmt {
Stmt::ClassDef(StmtClassDef { name, body, bases, .. }) if has_enum => {
let is_enum = bases.iter().any(|base| {
matches!(base, Expr::Name(ExprName { id, .. })
if id == "Enum" || id == "IntEnum" || id == "StrEnum"
|| id == "Flag" || id == "IntFlag")
});
if is_enum {
let mut values = Vec::new();
let mut members = HashMap::new();
for item in body {
if let Stmt::Assign(StmtAssign { targets, value, .. }) = item {
if let Some(Expr::Name(ExprName { id: target_name, .. })) = targets.first() {
if !target_name.starts_with('_') {
if let Expr::Constant(ExprConstant { value: Constant::Str(val), .. }) = value.as_ref() {
values.push(val.to_string());
members.insert(target_name.to_string(), val.to_string());
}
}
}
}
}
if !values.is_empty() {
enums.insert(name.to_string(), EnumInfo { values, members });
}
}
},
Stmt::FunctionDef(StmtFunctionDef { name: func_name, body, .. }) if has_docstring => {
if &func_name == main_name {
if let Some(Stmt::Expr(expr_stmt)) = body.first() {
if let Expr::Constant(ExprConstant { value: Constant::Str(docstring), .. }) = expr_stmt.value.as_ref() {
descriptions = parse_docstring_args(docstring);
}
}
}
},
_ => {}
}
}
CodeMetadata { enums, descriptions }
}
/// Parse docstring Args: section (format: "param_name (type): Description")
fn parse_docstring_args(docstring: &str) -> HashMap<String, String> {
let mut descriptions = HashMap::new();
let mut in_args_section = false;
let mut base_indent: Option<usize> = None;
for line in docstring.lines() {
let trimmed = line.trim();
if trimmed == "Args:" {
in_args_section = true;
base_indent = None;
continue;
}
if in_args_section {
if trimmed.is_empty() {
continue;
}
let indent = line.len() - line.trim_start().len();
if base_indent.is_none() && !trimmed.is_empty() {
base_indent = Some(indent);
}
if let Some(base) = base_indent {
if indent < base && trimmed.ends_with(':') {
break;
}
}
if let Some(colon_pos) = trimmed.find(':') {
let before_colon = &trimmed[..colon_pos];
let description = trimmed[colon_pos + 1..].trim();
if let Some(paren_pos) = before_colon.find('(') {
let param_name = before_colon[..paren_pos].trim();
descriptions.insert(param_name.to_string(), description.to_string());
} else {
descriptions.insert(before_colon.trim().to_string(), description.to_string());
}
}
}
}
descriptions
}
/// skip_params is a micro optimization for when we just want to find the main
/// function without parsing all the params.
pub fn parse_python_signature(
@@ -251,61 +91,27 @@ pub fn parse_python_signature(
if !skip_params && params.is_some() {
let params = params.unwrap();
//println!("{:?}", params);
let def_arg_start = params.args.len() - params.defaults().count();
// Two-pass approach for lazy metadata extraction:
// Pass 1: Parse types without enum info to determine if metadata is needed
// Pass 2: Re-parse unknown types with metadata only if necessary
// This ensures zero overhead for scripts without enums/docstrings
let empty_enums = HashMap::new();
let args_first_pass: Vec<_> = params
.args
.iter()
.enumerate()
.map(|(i, x)| {
let arg_name = x.as_arg().arg.to_string();
let (typ, has_default) = x
.as_arg()
.annotation
.as_ref()
.map_or((Typ::Unknown, false), |e| parse_expr(e, &empty_enums));
(i, arg_name, typ, has_default)
})
.collect();
// Determine if we need to extract metadata from the code
let has_potential_enums = args_first_pass
.iter()
.any(|(_, _, typ, _)| matches!(typ, Typ::Resource(_)));
let metadata = if has_potential_enums || code.contains("Args:") {
extract_code_metadata(code, &main_name)
} else {
CodeMetadata {
enums: HashMap::new(),
descriptions: HashMap::new(),
}
};
// Build final args, re-parsing Resource types as enums if metadata was extracted
Ok(MainArgSignature {
star_args: params.vararg.is_some(),
star_kwargs: params.kwarg.is_some(),
args: args_first_pass
.into_iter()
.map(|(i, arg_name, mut typ, mut has_default)| {
if matches!(typ, Typ::Resource(_)) && !metadata.enums.is_empty() {
if let Some(annotation) = params.args[i].as_arg().annotation.as_ref() {
(typ, has_default) = parse_expr(annotation, &metadata.enums);
}
}
args: params
.args
.iter()
.enumerate()
.map(|(i, x)| {
let (mut typ, has_default) = x
.as_arg()
.annotation
.as_ref()
.map_or((Typ::Unknown, false), |e| parse_expr(e));
let default = if i >= def_arg_start {
params
.defaults()
.nth(i - def_arg_start)
.map(|expr| to_value(expr, &metadata.enums))
.map(to_value)
.flatten()
} else {
None
@@ -334,8 +140,8 @@ pub fn parse_python_signature(
}
Arg {
otyp: metadata.descriptions.get(&arg_name).map(|d| d.to_string()),
name: arg_name,
otyp: None,
name: x.as_arg().arg.to_string(),
typ,
has_default: has_default || default.is_some(),
default,
@@ -357,15 +163,15 @@ pub fn parse_python_signature(
}
}
fn parse_expr(e: &Box<Expr>, enums: &HashMap<String, EnumInfo>) -> (Typ, bool) {
fn parse_expr(e: &Box<Expr>) -> (Typ, bool) {
match e.as_ref() {
Expr::Name(ExprName { id, .. }) => (parse_typ(id.as_ref(), enums), false),
Expr::Name(ExprName { id, .. }) => (parse_typ(id.as_ref()), false),
Expr::Attribute(x) => {
if x.value
.as_name_expr()
.is_some_and(|x| x.id.as_str() == "wmill")
{
(parse_typ(x.attr.as_str(), enums), false)
(parse_typ(x.attr.as_str()), false)
} else {
(Typ::Unknown, false)
}
@@ -375,7 +181,7 @@ fn parse_expr(e: &Box<Expr>, enums: &HashMap<String, EnumInfo>) -> (Typ, bool) {
x.right.as_ref(),
Expr::Constant(ExprConstant { value: Constant::None, .. })
) {
(parse_expr(&x.left, enums).0, true)
(parse_expr(&x.left).0, true)
} else {
(Typ::Unknown, false)
}
@@ -404,8 +210,8 @@ fn parse_expr(e: &Box<Expr>, enums: &HashMap<String, EnumInfo>) -> (Typ, bool) {
};
(Typ::Str(values), false)
}
"List" | "list" => (Typ::List(Box::new(parse_expr(&x.slice, enums).0)), false),
"Optional" => (parse_expr(&x.slice, enums).0, true),
"List" | "list" => (Typ::List(Box::new(parse_expr(&x.slice).0)), false),
"Optional" => (parse_expr(&x.slice).0, true),
_ => (Typ::Unknown, false),
},
_ => (Typ::Unknown, false),
@@ -414,11 +220,7 @@ fn parse_expr(e: &Box<Expr>, enums: &HashMap<String, EnumInfo>) -> (Typ, bool) {
}
}
fn parse_typ(id: &str, enums: &HashMap<String, EnumInfo>) -> Typ {
if let Some(enum_info) = enums.get(id) {
return Typ::Str(Some(enum_info.values.clone()));
}
fn parse_typ(id: &str) -> Typ {
match id {
"str" => Typ::Str(None),
"float" => Typ::Float,
@@ -447,7 +249,7 @@ fn map_resource_name(x: &str) -> String {
}
}
fn to_value<R>(et: &Expr<R>, enums: &HashMap<String, EnumInfo>) -> Option<serde_json::Value> {
fn to_value<R>(et: &Expr<R>) -> Option<serde_json::Value> {
match et {
Expr::Constant(ExprConstant { value, .. }) => Some(constant_to_value(value)),
Expr::Dict(ExprDict { keys, values, .. }) => {
@@ -457,35 +259,22 @@ fn to_value<R>(et: &Expr<R>, enums: &HashMap<String, EnumInfo>) -> Option<serde_
.map(|(k, v)| {
let key = k
.as_ref()
.map(|e| to_value(e, enums))
.map(to_value)
.flatten()
.and_then(|x| match x {
serde_json::Value::String(s) => Some(s),
_ => None,
})
.unwrap_or_else(|| "no_key".to_string());
(key, to_value(&v, enums))
(key, to_value(&v))
})
.collect::<HashMap<String, _>>();
Some(json!(v))
}
Expr::List(ExprList { elts, .. }) => {
let v = elts.into_iter().map(|x| to_value(&x, enums)).collect::<Vec<_>>();
let v = elts.into_iter().map(|x| to_value(&x)).collect::<Vec<_>>();
Some(json!(v))
}
Expr::Attribute(ExprAttribute { value, attr, .. }) => {
// Handle Enum.MEMBER: returns enum value ("red") not member name ("RED")
if let Expr::Name(ExprName { id: enum_name, .. }) = value.as_ref() {
if let Some(enum_info) = enums.get(enum_name.as_str()) {
if let Some(enum_value) = enum_info.members.get(attr.as_str()) {
return Some(json!(enum_value));
}
}
Some(json!(attr.as_str()))
} else {
None
}
}
Expr::Call { .. } => Some(json!(FUNCTION_CALL)),
_ => None,
}
@@ -962,35 +751,4 @@ def main(a: str, b: Optional[str], c: str | None): return
Ok(())
}
#[test]
fn test_parse_python_sig_enum() -> anyhow::Result<()> {
let code = r#"
from enum import Enum
class Color(str, Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
def main(color: Color = Color.RED):
"""
Test enum parsing
Args:
color (Color): Color selection from Color enum
"""
return {"color": color}
"#;
let result = parse_python_signature(code, None, false)?;
assert_eq!(result.args.len(), 1);
assert_eq!(result.args[0].name, "color");
assert_eq!(
result.args[0].typ,
Typ::Str(Some(vec!["red".to_string(), "green".to_string(), "blue".to_string()]))
);
assert_eq!(result.args[0].default, Some(json!("red")));
assert_eq!(result.args[0].otyp, Some("Color selection from Color enum".to_string()));
Ok(())
}
}

View File

@@ -20,4 +20,4 @@ anyhow.workspace = true
lazy_static.workspace = true
serde_json.workspace = true
serde.workspace = true
sqlparser = { version = "0.59.0", features = ["visitor"] }
nom.workspace = true

View File

@@ -1,676 +1,151 @@
use std::collections::HashMap;
use sqlparser::{
ast::{
CopyTarget, Expr, ObjectName, TableFactor, TableObject, Value, ValueWithSpan, Visit,
Visitor,
},
dialect::DuckDbDialect,
parser::Parser,
};
use windmill_parser::asset_parser::{
asset_was_used, merge_assets, parse_asset_syntax, AssetKind, AssetUsageAccessType,
ParseAssetsResult,
merge_assets, AssetKind, AssetUsageAccessType, ParseAssetsResult,
};
use AssetUsageAccessType::*;
pub fn parse_assets(input: &str) -> anyhow::Result<Vec<ParseAssetsResult>> {
let statements = Parser::parse_sql(&DuckDbDialect, input)?;
use nom::{
branch::alt,
bytes::complete::{tag, tag_no_case, take_while},
character::complete::{char, multispace0},
combinator::opt,
sequence::preceded,
IResult, Parser,
};
let mut collector = AssetCollector::new();
for statement in statements {
let _ = statement.visit(&mut collector);
}
pub fn parse_assets<'a>(input: &str) -> anyhow::Result<Vec<ParseAssetsResult<&str>>> {
let mut assets = Vec::new();
let mut remaining = input;
for (_, (kind, path)) in collector.var_identifiers {
if !asset_was_used(&collector.assets, (kind, &path)) {
collector
.assets
.push(ParseAssetsResult { kind, access_type: None, path: path });
while !remaining.trim().is_empty() {
if let Ok((rest, _)) = parse_comment(remaining) {
remaining = rest; // skip comment
}
}
Ok(merge_assets(collector.assets))
}
/// Visitor that collects S3 asset literals from SQL statements
struct AssetCollector {
assets: Vec<ParseAssetsResult>,
// e.g set to Read when we are inside a SELECT ... FROM ... statement
current_access_type_stack: Vec<AssetUsageAccessType>,
// e.g ATTACH 'ducklake://a' AS dl; => { "dl": (Ducklake, "a") }
var_identifiers: HashMap<String, (AssetKind, String)>,
// e.g USE dl;
currently_used_asset: Option<(AssetKind, String)>,
}
impl AssetCollector {
fn new() -> Self {
Self {
assets: Vec::new(),
current_access_type_stack: Vec::with_capacity(8),
var_identifiers: HashMap::new(),
currently_used_asset: None,
}
}
// Detect when we do 'a.b' and 'a' is associated with an asset in var_identifiers
// Or when we access 'b' and we did USE a;
fn get_associated_asset_from_obj_name(&self, name: &ObjectName) -> Option<ParseAssetsResult> {
let access_type = self.current_access_type_stack.last().copied();
if let Some((kind, path)) = &self.currently_used_asset {
// We don't want to infer that any simple identifier refers to an asset if
// we are not in a known R/W context
if access_type.is_none() {
return None;
}
if name.0.len() == 1 || name.0.len() == 2 {
if name
.0
.iter()
.any(|id| id.as_ident().and_then(|id| id.quote_style).is_some())
{
return None;
}
let specific_table = &name
.0
.iter()
.map(|id| id.as_ident().map(|id| id.value.clone()))
.collect::<Option<Vec<String>>>()?
.join(".");
let path = format!("{}/{}", path, specific_table);
return Some(ParseAssetsResult { kind: *kind, access_type, path });
}
}
// Check if the first part of the name (the a in a.b) is associated with an asset
if name.0.len() < 2 {
return None;
}
let ident = name.0.first()?.as_ident()?;
let (kind, path) = self.var_identifiers.get(&ident.value)?;
let path = if name.0.len() == 2 || name.0.len() == 3 {
let specific_table = &name.0[1..]
.iter()
.map(|id| id.as_ident().map(|id| id.value.clone()))
.collect::<Option<Vec<String>>>()?
.join(".");
format!("{}/{}", path, specific_table)
if let Ok((rest, res)) = parse_asset(remaining) {
assets.push(res);
remaining = rest;
} else {
path.clone()
};
Some(ParseAssetsResult { kind: *kind, access_type, path })
}
fn handle_string_literal(&mut self, s: &str) {
// Check if the string matches our asset syntax patterns
if let Some((kind, path)) = parse_asset_syntax(s, false) {
if kind == AssetKind::S3Object {
self.assets.push(ParseAssetsResult {
kind,
path: path.to_string(),
access_type: self.current_access_type_stack.last().copied(),
});
}
remaining = &remaining[1..]; // skip 1 char and continue
}
}
fn handle_obj_name_pre(&mut self, name: &ObjectName) {
if let Some(fname) = get_trivial_obj_name(name) {
if is_read_fn(fname) {
self.current_access_type_stack.push(R);
}
}
if let Some(str_lit) = get_str_lit_from_obj_name(name) {
self.handle_string_literal(str_lit);
}
// Writes to tables should be handled directly when visiting the statement
if self.current_access_type_stack.last() == Some(&R) {
if let Some(asset) = self.get_associated_asset_from_obj_name(name) {
self.assets.push(asset);
}
}
}
fn handle_obj_name_post(&mut self, name: &ObjectName) {
if self.current_access_type_stack.is_empty() {
return;
}
if let Some(fname) = get_trivial_obj_name(name) {
if is_read_fn(fname) {
self.current_access_type_stack.pop();
}
}
}
fn handle_table_with_joins(&mut self, table_with_joins: &sqlparser::ast::TableWithJoins) {
if let TableFactor::Table { name, .. } = &table_with_joins.relation {
if let Some(asset) = self.get_associated_asset_from_obj_name(name) {
self.assets.push(asset);
}
}
for join in &table_with_joins.joins {
if let TableFactor::Table { name, .. } = &join.relation {
if let Some(asset) = self.get_associated_asset_from_obj_name(name) {
self.assets.push(asset);
}
}
}
}
Ok(merge_assets(assets))
}
impl Visitor for AssetCollector {
type Break = ();
fn pre_visit_table_factor(
&mut self,
table_factor: &TableFactor,
) -> std::ops::ControlFlow<Self::Break> {
match table_factor {
TableFactor::Table { name, args, .. } => {
if args.is_none() {
// Avoid Table Functions
self.handle_obj_name_pre(name);
}
}
_ => {}
}
std::ops::ControlFlow::Continue(())
}
fn post_visit_table_factor(
&mut self,
table_factor: &TableFactor,
) -> std::ops::ControlFlow<Self::Break> {
match table_factor {
TableFactor::Table { name, .. } => self.handle_obj_name_post(name),
_ => {}
}
std::ops::ControlFlow::Continue(())
}
fn pre_visit_expr(&mut self, expr: &Expr) -> std::ops::ControlFlow<Self::Break> {
match expr {
Expr::Value(ValueWithSpan { value: Value::SingleQuotedString(s), .. }) => {
self.handle_string_literal(s)
}
Expr::Value(ValueWithSpan { value: Value::DoubleQuotedString(s), .. }) => {
self.handle_string_literal(s);
}
_ => {}
}
std::ops::ControlFlow::Continue(())
}
fn post_visit_expr(&mut self, expr: &Expr) -> std::ops::ControlFlow<Self::Break> {
match expr {
Expr::Function(func) => self.handle_obj_name_post(&func.name),
_ => {}
}
std::ops::ControlFlow::Continue(())
}
fn pre_visit_statement(
&mut self,
statement: &sqlparser::ast::Statement,
) -> std::ops::ControlFlow<Self::Break> {
match statement {
sqlparser::ast::Statement::Query(_) => {
// don't forget pop() in post_visit_statement
self.current_access_type_stack.push(R);
}
sqlparser::ast::Statement::Insert(insert) => {
let access_type = if insert.returning.is_some() { RW } else { W };
self.current_access_type_stack.push(access_type);
match insert.table {
TableObject::TableName(ref name) => {
if let Some(asset) = self.get_associated_asset_from_obj_name(name) {
self.assets.push(asset);
}
}
_ => {}
}
self.current_access_type_stack.pop();
}
sqlparser::ast::Statement::Update { returning, table, from, .. } => {
if let Some(from_tables) = from {
let from_tables = match from_tables {
sqlparser::ast::UpdateTableFromKind::AfterSet(tables) => tables,
sqlparser::ast::UpdateTableFromKind::BeforeSet(tables) => tables,
};
self.current_access_type_stack.push(R);
for table_with_joins in from_tables {
self.handle_table_with_joins(table_with_joins);
}
self.current_access_type_stack.pop();
}
let access_type = if returning.is_some() { RW } else { W };
self.current_access_type_stack.push(access_type);
self.handle_table_with_joins(table);
self.current_access_type_stack.pop();
}
sqlparser::ast::Statement::Delete(delete) => {
let access_type = if delete.returning.is_some() { RW } else { W };
self.current_access_type_stack.push(access_type);
for name in &delete.tables {
if let Some(asset) = self.get_associated_asset_from_obj_name(name) {
self.assets.push(asset);
}
}
let tables = match &delete.from {
sqlparser::ast::FromTable::WithFromKeyword(tables) => tables,
sqlparser::ast::FromTable::WithoutKeyword(tables) => tables,
};
for table_with_joins in tables {
self.handle_table_with_joins(table_with_joins);
}
self.current_access_type_stack.pop();
}
sqlparser::ast::Statement::CreateTable(create_table) => {
self.current_access_type_stack.push(W);
if let Some(asset) = self.get_associated_asset_from_obj_name(&create_table.name) {
self.assets.push(asset);
}
self.current_access_type_stack.pop();
}
sqlparser::ast::Statement::CreateView { name, .. } => {
self.current_access_type_stack.push(W);
if let Some(asset) = self.get_associated_asset_from_obj_name(name) {
self.assets.push(asset);
}
self.current_access_type_stack.pop();
}
sqlparser::ast::Statement::Copy { target: CopyTarget::File { filename }, .. } => {
self.current_access_type_stack.push(W);
self.handle_string_literal(filename);
self.current_access_type_stack.pop();
}
sqlparser::ast::Statement::AttachDuckDBDatabase {
database_path,
database_alias,
..
} => {
if let Some((kind, path)) = parse_asset_syntax(&database_path.value, true) {
if kind == AssetKind::Ducklake
|| kind == AssetKind::DataTable
|| kind == AssetKind::Resource
{
if let Some(database_alias) = database_alias {
self.var_identifiers
.insert(database_alias.value.clone(), (kind, path.to_string()));
}
}
}
}
sqlparser::ast::Statement::DetachDuckDBDatabase { database_alias, .. } => {
let asset = self.var_identifiers.remove(&database_alias.value);
if self.currently_used_asset == asset {
self.currently_used_asset = None;
}
}
sqlparser::ast::Statement::Use(sqlparser::ast::Use::Object(obj_name)) => {
if let Some((kind, path)) = self.var_identifiers.get(&obj_name.to_string()) {
self.currently_used_asset = Some((*kind, path.clone()));
} else {
self.currently_used_asset = None;
}
}
_ => {}
}
std::ops::ControlFlow::Continue(())
}
fn post_visit_statement(
&mut self,
statement: &sqlparser::ast::Statement,
) -> std::ops::ControlFlow<Self::Break> {
match statement {
sqlparser::ast::Statement::Query(_) => {
self.current_access_type_stack.pop();
}
_ => {}
}
std::ops::ControlFlow::Continue(())
}
fn pre_visit_query(
&mut self,
_query: &sqlparser::ast::Query,
) -> std::ops::ControlFlow<Self::Break> {
self.current_access_type_stack.push(R);
std::ops::ControlFlow::Continue(())
}
fn post_visit_query(
&mut self,
_query: &sqlparser::ast::Query,
) -> std::ops::ControlFlow<Self::Break> {
self.current_access_type_stack.pop();
std::ops::ControlFlow::Continue(())
}
// We do not use pre_visit_relation because we cannot know if an ObjectName is a table or a function
fn parse_asset(input: &str) -> IResult<&str, ParseAssetsResult<&str>> {
alt((
parse_s3_object_read.map(|path| ParseAssetsResult {
path,
kind: AssetKind::S3Object,
access_type: Some(R),
}),
parse_s3_object_write.map(|path| ParseAssetsResult {
path,
kind: AssetKind::S3Object,
access_type: Some(W),
}),
// Parse ambiguous access_types at the end if we could not find precisely read or copy
parse_s3_object_lit.map(|path| ParseAssetsResult {
path,
kind: AssetKind::S3Object,
access_type: None,
}),
parse_resource_lit.map(|path| ParseAssetsResult {
path,
kind: AssetKind::Resource,
access_type: None,
}),
parse_ducklake_lit.map(|path| ParseAssetsResult {
path,
kind: AssetKind::Ducklake,
access_type: None,
}),
parse_datatable_lit.map(|path| ParseAssetsResult {
path,
kind: AssetKind::DataTable,
access_type: None,
}),
))
.parse(input)
}
fn is_read_fn(fname: &str) -> bool {
fname.eq_ignore_ascii_case("read_parquet")
|| fname.eq_ignore_ascii_case("read_csv")
|| fname.eq_ignore_ascii_case("read_json")
/// Any expression that reads an s3 asset
fn parse_s3_object_read(input: &str) -> IResult<&str, &str> {
alt((parse_s3_object_read_fn, parse_s3_object_select_from)).parse(input)
}
fn get_trivial_obj_name(name: &sqlparser::ast::ObjectName) -> Option<&str> {
if name.0.len() != 1 {
return None;
}
Some(name.0.first()?.as_ident()?.value.as_str())
/// Any expression that writes to an s3 asset
fn parse_s3_object_write(input: &str) -> IResult<&str, &str> {
// COPY (...) TO 's3://...'
let (input, _) = (tag_no_case("TO"), multispace0).parse(input)?;
let (input, path) = parse_s3_object_lit(input)?;
Ok((input, path))
}
fn get_str_lit_from_obj_name(name: &ObjectName) -> Option<&str> {
if name.0.len() != 1 {
return None;
}
let ident = name.0.first()?.as_ident()?;
if ident.quote_style != Some('\'') {
return None;
}
Some(ident.value.as_str())
/// read_parquet('s3://...')
fn parse_s3_object_read_fn(input: &str) -> IResult<&str, &str> {
let (input, _) = alt((
tag_no_case("read_parquet"),
tag_no_case("read_csv"),
tag_no_case("read_json"),
))
.parse(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char('(')(input)?;
let (input, _) = multispace0(input)?;
let (input, path) = parse_s3_object_lit(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = char(')')(input)?;
Ok((input, path))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sql_asset_parser_s3_literals() {
let input = r#"
SELECT * FROM read_parquet('s3:///a.parquet');
COPY (SELECT * FROM 's3://snd/b.parquet') TO 's3:///c.parquet';
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![
ParseAssetsResult {
kind: AssetKind::S3Object,
path: "/a.parquet".to_string(),
access_type: Some(R)
},
ParseAssetsResult {
kind: AssetKind::S3Object,
path: "/c.parquet".to_string(),
access_type: Some(W)
},
ParseAssetsResult {
kind: AssetKind::S3Object,
path: "snd/b.parquet".to_string(),
access_type: Some(R)
},
])
);
}
#[test]
fn test_sql_asset_parser_attach_no_usage_is_registered_as_unknown() {
let input = r#"
ATTACH 'ducklake://my_dl' AS dl;
SELECT 2;
USE dl;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "my_dl".to_string(),
access_type: None
},])
);
}
#[test]
fn test_sql_asset_parser_attach_dot_notation_read() {
let input = r#"
ATTACH 'ducklake://my_dl' AS dl;
SELECT * FROM dl.table1;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "my_dl/table1".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_sql_asset_parser_attach_dot_notation_write() {
let input = r#"
ATTACH 'datatable://my_dt' AS dt;
SELECT dt.read_bait FROM unrelated_table; -- dt. doesn't access the asset
INSERT INTO dt.table1 VALUES ('test');
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "my_dt/table1".to_string(),
access_type: Some(W)
},])
);
}
#[test]
fn test_sql_asset_parser_detach() {
let input = r#"
ATTACH 'ducklake://my_dl' AS dl;
DETACH dl;
SELECT * FROM dl.table1;
"#;
let s = parse_assets(input);
assert_eq!(s.map_err(|e| e.to_string()), Ok(vec![]));
}
#[test]
fn test_sql_asset_parser_implicit_use_asset() {
let input = r#"
ATTACH 'ducklake://my_dl' AS dl;
USE dl;
INSERT INTO table1 VALUES ('test');
USE memory;
SELECT * FROM table1;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "my_dl/table1".to_string(),
access_type: Some(W)
},])
);
}
#[test]
fn test_sql_asset_parser_default_main() {
let input = r#"
ATTACH 'datatable' AS dl;
INSERT INTO dl.table1 VALUES ('test');
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "main/table1".to_string(),
access_type: Some(W)
},])
);
}
#[test]
fn test_sql_asset_parser_create_table() {
let input = r#"
ATTACH 'ducklake' AS dl; USE dl;
CREATE TABLE friends (
name text,
age int
);
INSERT INTO friends VALUES ($name, $age);
SELECT * FROM friends;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main/friends".to_string(),
access_type: Some(RW)
},])
);
}
// Make sure a_function is not detected as main/a_function
#[test]
fn test_sql_asset_parser_function_table() {
let input = r#"
ATTACH 'ducklake' AS dl; USE dl;
SELECT * FROM a_function('');
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main".to_string(),
access_type: None
},])
);
}
#[test]
fn test_sql_asset_parser_delete() {
let input = r#"
ATTACH 'ducklake' AS dl;
USE dl;
DELETE FROM table1;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main/table1".to_string(),
access_type: Some(W)
},])
);
}
#[test]
fn test_sql_asset_parser_update() {
let input = r#"
ATTACH 'ducklake' AS dl;
USE dl;
UPDATE table1 SET id = NULL;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main/table1".to_string(),
access_type: Some(W)
},])
);
}
#[test]
fn test_sql_asset_parser_resource() {
let input = r#"
ATTACH 'res://u/user/pg_resource' AS db (TYPE postgres);
USE db;
SELECT * FROM table1;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Resource,
path: "u/user/pg_resource/table1".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_sql_asset_parser_update_with_dot_notation() {
let input = r#"
ATTACH 'ducklake' AS dl;
UPDATE dl.table1 SET id = NULL;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main/table1".to_string(),
access_type: Some(W)
},])
);
}
#[test]
fn test_sql_asset_parser_table_with_schema() {
let input = r#"
ATTACH 'ducklake' AS dl;
UPDATE dl.sch.table1 SET id = NULL;
SELECT * FROM dl.sch.table1;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main/sch.table1".to_string(),
access_type: Some(RW)
},])
);
}
#[test]
fn test_sql_asset_parser_table_with_schema_implicit() {
let input = r#"
ATTACH 'ducklake' AS dl;
USE dl;
UPDATE sch.table1 SET id = NULL;
SELECT * FROM sch.table1;
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main/sch.table1".to_string(),
access_type: Some(RW)
},])
);
}
/// SELECT ... FROM 's3://...'
fn parse_s3_object_select_from(input: &str) -> IResult<&str, &str> {
let (input, _) = tag_no_case("FROM").parse(input)?;
let (input, _) = multispace0(input)?;
let (input, path) = parse_s3_object_lit(input)?;
Ok((input, path))
}
/// 's3://...'
fn parse_s3_object_lit(input: &str) -> IResult<&str, &str> {
let (input, _) = quote(input)?;
let (input, _) = tag("s3://").parse(input)?;
let (input, path) = take_while(|c| c != '\'' && c != '"')(input)?;
let (input, _) = quote(input)?;
Ok((input, path))
}
fn quote(input: &str) -> IResult<&str, char> {
alt((char('\''), char('\"'))).parse(input)
}
fn parse_resource_lit(input: &str) -> IResult<&str, &str> {
let (input, _) = quote(input)?;
let (input, _) = alt((tag("$res:"), tag("res://"))).parse(input)?;
let (input, path) = take_while(|c| c != '\'' && c != '"')(input)?;
let (input, _) = quote(input)?;
Ok((input, path))
}
fn parse_ducklake_lit(input: &str) -> IResult<&str, &str> {
let (input, _) = quote(input)?;
let (input, _) = tag("ducklake").parse(input)?;
let (input, path) =
opt(preceded(tag("://"), take_while(|c| c != '\'' && c != '"'))).parse(input)?;
let (input, _) = quote(input)?;
Ok((input, path.unwrap_or("main")))
}
fn parse_datatable_lit(input: &str) -> IResult<&str, &str> {
let (input, _) = quote(input)?;
let (input, _) = tag("datatable").parse(input)?;
let (input, path) =
opt(preceded(tag("://"), take_while(|c| c != '\'' && c != '"'))).parse(input)?;
let (input, _) = quote(input)?;
Ok((input, path.unwrap_or("main")))
}
fn parse_comment(input: &str) -> IResult<&str, &str> {
let (input, _) = tag("--").parse(input)?;
let (input, comment) = take_while(|c| c != '\n')(input)?;
Ok((input, comment))
}

View File

@@ -15,7 +15,6 @@ serde-wasm-bindgen.workspace = true
[dependencies]
windmill-parser.workspace = true
windmill-parser-sql.workspace = true
swc_common.workspace = true
triomphe.workspace = true
swc_ecma_parser.workspace = true

View File

@@ -5,12 +5,12 @@ use swc_ecma_ast::{CallExpr, Expr, Lit, MemberExpr, MemberProp, Str};
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsSyntax};
use swc_ecma_visit::{Visit, VisitWith};
use windmill_parser::asset_parser::{
asset_was_used, merge_assets, parse_asset_syntax, AssetKind, AssetUsageAccessType,
detect_sql_access_type, merge_assets, parse_asset_syntax, AssetKind, AssetUsageAccessType,
ParseAssetsResult,
};
use AssetUsageAccessType::*;
pub fn parse_assets(code: &str) -> anyhow::Result<Vec<ParseAssetsResult>> {
pub fn parse_assets(code: &str) -> anyhow::Result<Vec<ParseAssetsResult<String>>> {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.into());
let lexer = Lexer::new(
@@ -40,10 +40,8 @@ pub fn parse_assets(code: &str) -> anyhow::Result<Vec<ParseAssetsResult>> {
Ok(merge_assets(assets_finder.assets))
}
type VarAssetName = String;
type VarAssetSchema = Option<String>;
struct AssetsFinder {
assets: Vec<ParseAssetsResult>,
assets: Vec<ParseAssetsResult<String>>,
// The user will write code like:
// let sql = wmill.datatable('main')
@@ -51,62 +49,16 @@ struct AssetsFinder {
// The goal is to remember that the identifier "sql" corresponds to the datatable "main"
// so that when we see a tagged template expression with tag "sql" we know which datatable it
// corresponds to. This allows us to infer if a datatable is Read or Write based on the SQL query.
var_identifiers: HashMap<String, (AssetKind, VarAssetName, VarAssetSchema)>,
var_identifiers: HashMap<String, (AssetKind, String)>,
}
/// Helper function to extract wmill.datatable() or wmill.ducklake() calls,
/// Returns (AssetKind, asset_name, optional_schema_name)
fn extract_wmill_datatable_call(expr: &Expr) -> Option<(AssetKind, String, Option<String>)> {
if let Expr::Call(call_expr) = expr {
if let Some(Expr::Member(member)) = call_expr.callee.as_expr().map(AsRef::as_ref) {
// Check if object is "wmill"
let is_wmill = matches!(
member.obj.as_ref(),
Expr::Ident(ident) if ident.sym.as_str() == "wmill"
);
if is_wmill {
if let MemberProp::Ident(prop) = &member.prop {
// Get the asset name from first arg, default to "main"
let asset_name = call_expr
.args
.first()
.and_then(|arg| match arg.expr.as_ref() {
Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()),
_ => None,
})
.unwrap_or_else(|| "main".to_string());
let (asset_name, schema_name) = asset_name.split_once(':').map_or_else(
|| (asset_name.clone(), None),
|(name, schema)| {
(
(if name.is_empty() { "main" } else { name }).to_string(),
Some(schema.to_string()),
)
},
);
let kind = match prop.sym.as_str() {
"datatable" => Some(AssetKind::DataTable),
"ducklake" => Some(AssetKind::Ducklake),
_ => None,
};
return kind.map(|k| (k, asset_name, schema_name));
}
}
}
}
None
}
impl Visit for AssetsFinder {
// visit_call_expr will not recurse if it detects an asset,
// so this will only be called when no further context was found
fn visit_lit(&mut self, node: &swc_ecma_ast::Lit) {
match node {
swc_ecma_ast::Lit::Str(str) => {
if let Some((kind, path)) = parse_asset_syntax(str.value.as_str(), false) {
if let Some((kind, path)) = parse_asset_syntax(str.value.as_str()) {
self.assets.push(ParseAssetsResult {
kind,
path: path.to_string(),
@@ -125,37 +77,6 @@ impl Visit for AssetsFinder {
}
}
fn visit_assign_expr(&mut self, node: &swc_ecma_ast::AssignExpr) {
// Handle reassignments like: sql = wmill.datatable('main')
// Extract the variable name from the left side
let var_name = match &node.left {
swc_ecma_ast::AssignTarget::Simple(simple_target) => match simple_target {
swc_ecma_ast::SimpleAssignTarget::Ident(ident_binding) => {
ident_binding.id.sym.as_str().to_string()
}
_ => {
node.visit_children_with(self);
return;
}
},
_ => {
node.visit_children_with(self);
return;
}
};
// Check if right side is a wmill.datatable() or wmill.ducklake() call
if let Some((kind, asset_name, schema)) = extract_wmill_datatable_call(node.right.as_ref())
{
self.var_identifiers
.insert(var_name, (kind, asset_name, schema));
return;
}
// Default: visit children
node.visit_children_with(self);
}
fn visit_block_stmt(&mut self, node: &swc_ecma_ast::BlockStmt) {
// Save current state before entering the block
let saved_var_identifiers = self.var_identifiers.clone();
@@ -163,12 +84,19 @@ impl Visit for AssetsFinder {
// Visit children (this may add new identifiers)
node.visit_children_with(self);
// If we find 'let sql = wmill.datatable(...)',
// but no sql`` tagged templates were used, we add
// the asset with unknown access type
for var in self.var_identifiers.keys() {
if saved_var_identifiers.contains_key(var) {
continue;
}
let (kind, ref path, _) = self.var_identifiers[var];
if asset_was_used(&self.assets, (kind, path)) {
let (kind, ref path) = self.var_identifiers[var];
if self
.assets
.iter()
.any(|a| a.kind == kind && &a.path == path)
{
continue;
}
self.assets
@@ -190,12 +118,43 @@ impl Visit for AssetsFinder {
};
// Check if init is a call to wmill.datatable(...) or wmill.ducklake(...)
// optionally with .schema() chained
if let Some(init) = &node.init {
if let Some((kind, asset_name, schema)) = extract_wmill_datatable_call(init.as_ref()) {
self.var_identifiers
.insert(var_name, (kind, asset_name, schema));
return;
if let Expr::Call(call_expr) = init.as_ref() {
if let Some(Expr::Member(member)) = call_expr.callee.as_expr().map(AsRef::as_ref) {
// Check if object is "wmill"
let is_wmill = matches!(
member.obj.as_ref(),
Expr::Ident(ident) if ident.sym.as_str() == "wmill"
);
if is_wmill {
if let MemberProp::Ident(prop) = &member.prop {
// Get the asset name from first arg, default to "main"
let asset_name = call_expr
.args
.first()
.and_then(|arg| match arg.expr.as_ref() {
Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()),
_ => None,
})
.unwrap_or_else(|| "main".to_string());
match prop.sym.as_str() {
"datatable" => {
self.var_identifiers
.insert(var_name, (AssetKind::DataTable, asset_name));
return;
}
"ducklake" => {
self.var_identifiers
.insert(var_name, (AssetKind::Ducklake, asset_name));
return;
}
_ => {}
}
}
}
}
}
}
@@ -214,7 +173,9 @@ impl Visit for AssetsFinder {
};
// Check if it's a known identifier
let Some((kind, asset_name, schema)) = self.var_identifiers.get(tag_name) else {
let (kind, asset_name) = if let Some((kind, name)) = self.var_identifiers.get(tag_name) {
(*kind, name.clone())
} else {
node.visit_children_with(self);
return;
};
@@ -226,34 +187,13 @@ impl Visit for AssetsFinder {
.iter()
.map(|quasi| quasi.raw.as_str())
.collect::<Vec<_>>()
.join("$1"); // placeholder for expressions
.join(" ");
let duckdb_conn_prefix = match kind {
AssetKind::DataTable => "datatable",
AssetKind::Ducklake => "ducklake",
_ => return,
};
let sql = format!("ATTACH '{duckdb_conn_prefix}://{asset_name}' AS dt; USE dt; {sql}");
// Determine access type based on SQL keywords
let access_type = detect_sql_access_type(&sql);
// We use the SQL parser to detect if it's a read or write query
match windmill_parser_sql::parse_assets(&sql) {
Ok(mut sql_assets) => {
if let Some(schema) = schema {
for asset in &mut sql_assets {
if asset.kind == *kind && asset.path.starts_with(asset_name) {
asset.path = format!(
"{}/{}.{}",
asset_name,
schema,
&asset.path[asset_name.len() + 1..]
);
}
}
}
self.assets.extend(sql_assets);
}
_ => {}
}
self.assets
.push(ParseAssetsResult { kind, path: asset_name, access_type });
}
}
@@ -281,9 +221,7 @@ impl AssetsFinder {
match arg_value.map(|e| e.expr.as_ref()) {
Some(Expr::Lit(Lit::Str(Str { value, .. }))) => {
let path = parse_asset_syntax(&value, false)
.map(|(_, p)| p)
.unwrap_or(&value);
let path = parse_asset_syntax(&value).map(|(_, p)| p).unwrap_or(&value);
self.assets
.push(ParseAssetsResult { kind, path: path.to_string(), access_type });
}
@@ -292,274 +230,3 @@ impl AssetsFinder {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ts_asset_parser_load_s3() {
let input = r#"
import * as wmill from "windmill-client"
export async function main() {
wmill.loadS3File('s3:///test.csv')
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::S3Object,
path: "/test.csv".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_ts_asset_parser_unused_sql() {
let input = r#"
import * as wmill from "windmill-client"
export async function main() {
let sql = wmill.datatable('dt')
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt".to_string(),
access_type: None
},])
);
}
#[test]
fn test_ts_asset_parser_sql_read() {
let input = r#"
import * as wmill from "windmill-client"
export async function main(x: number) {
let sql = wmill.datatable('dt')
return await sql`SELECT * FROM friends WHERE age = ${x}`.fetch()
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/friends".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_ts_asset_parser_sql_read_write() {
let input = r#"
import * as wmill from "windmill-client"
export async function main(x: number) {
let sql = wmill.datatable('dt')
await sql`UPDATE friends SET name = 'Pierre' WHERE age = ${x}`.fetch()
let pierre = await sql`SELECT * FROM friends WHERE age = ${x}`.fetchOne()
return await sql`SELECT * FROM analytics`.fetch()
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/analytics".to_string(),
access_type: Some(R)
},
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/friends".to_string(),
access_type: Some(RW)
},
])
);
}
#[test]
fn test_ts_asset_parser_multiple_sql_scopes() {
let input = r#"
import * as wmill from "windmill-client"
export async function main() {
async function f(x: number) {
let sql = wmill.datatable()
return await sql`SELECT * FROM friends WHERE age = ${x}`.fetch()
}
let sql = wmill.datatable('another1')
return await sql`INSERT INTO customers VALUES (${0})`.fetch()
}
function unused() {
let sql = wmill.ducklake('another2')
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "another1/customers".to_string(),
access_type: Some(W)
},
ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "another2".to_string(),
access_type: None
},
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "main/friends".to_string(),
access_type: Some(R)
},
])
);
}
#[test]
fn test_ts_asset_parser_overriden_var_identifier() {
let input = r#"
import * as wmill from "windmill-client"
export async function main() {
let sql = wmill.datatable('another1')
}
function g() {
let sql = wmill.ducklake()
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "another1".to_string(),
access_type: None
},
ParseAssetsResult {
kind: AssetKind::Ducklake,
path: "main".to_string(),
access_type: None
},
])
);
}
#[test]
fn test_ts_asset_parser_datatable_with_schema() {
let input = r#"
import * as wmill from "windmill-client"
export async function main(x: number) {
let sql = wmill.datatable(':myschema')
return await sql`SELECT * FROM friends WHERE age = ${x}`.fetch()
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "main/myschema.friends".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_ts_asset_parser_schema_with_write() {
let input = r#"
import * as wmill from "windmill-client"
export async function main(x: number) {
let sql = wmill.datatable('dt:public')
await sql`INSERT INTO users VALUES (${x})`.fetch()
return await sql`SELECT * FROM users`.fetch()
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/public.users".to_string(),
access_type: Some(RW)
},])
);
}
#[test]
fn test_ts_asset_parser_unused_datatable_with_schema() {
let input = r#"
import * as wmill from "windmill-client"
export async function main() {
let sql = wmill.datatable('dt:myschema')
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt".to_string(),
access_type: None
},])
);
}
#[test]
fn test_ts_asset_parser_reassignment() {
let input = r#"
import * as wmill from "windmill-client"
export async function main(x: number) {
let sql;
sql = wmill.datatable('dt')
return await sql`SELECT * FROM users WHERE id = ${x}`.fetch()
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/users".to_string(),
access_type: Some(R)
},])
);
}
#[test]
fn test_ts_asset_parser_reassignment_with_schema() {
let input = r#"
import * as wmill from "windmill-client"
export async function main(x: number) {
let sql = wmill.datatable('dt')
await sql`INSERT INTO test VALUES ('')`.fetch()
sql = wmill.datatable('dt:private')
return await sql`SELECT * FROM users WHERE id = ${x}`.fetch()
}
"#;
let s = parse_assets(input);
assert_eq!(
s.map_err(|e| e.to_string()),
Ok(vec![
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/private.users".to_string(),
access_type: Some(R)
},
ParseAssetsResult {
kind: AssetKind::DataTable,
path: "dt/test".to_string(),
access_type: Some(W)
},
])
);
}
}

View File

@@ -190,36 +190,41 @@ pub fn parse_ruby(code: &str) -> String {
#[cfg(feature = "sql-parser")]
#[wasm_bindgen]
pub fn parse_assets_sql(code: &str) -> String {
match windmill_parser_sql::parse_assets(code) {
Ok(r) => serde_json::to_string(&r).unwrap(),
Err(err) => format!("err: {:?}", err),
if let Ok(r) = windmill_parser_sql::parse_assets(code) {
return serde_json::to_string(&r).unwrap();
} else {
return "Invalid".to_string();
}
}
#[cfg(feature = "ts-parser")]
#[wasm_bindgen]
pub fn parse_assets_ts(code: &str) -> String {
match windmill_parser_ts::parse_assets(code) {
Ok(r) => serde_json::to_string(&r).unwrap(),
Err(err) => format!("err: {:?}", err),
if let Ok(r) = windmill_parser_ts::parse_assets(code) {
return serde_json::to_string(&r).unwrap();
} else {
return "Invalid".to_string();
}
}
#[cfg(feature = "py-parser")]
#[wasm_bindgen]
pub fn parse_assets_py(code: &str) -> String {
match windmill_parser_py::parse_assets(code) {
Ok(r) => serde_json::to_string(&r).unwrap(),
Err(err) => format!("err: {:?}", err),
if let Ok(r) = windmill_parser_py::parse_assets(code) {
return serde_json::to_string(&r).unwrap();
} else {
return "Invalid".to_string();
}
}
#[cfg(feature = "ansible-parser")]
#[wasm_bindgen]
pub fn parse_assets_ansible(code: &str) -> String {
match windmill_parser_yaml::parse_assets(code) {
Ok(r) => serde_json::to_string(&r).unwrap(),
Err(err) => format!("err: {:?}", err),
let o = windmill_parser_yaml::parse_assets(code);
if let Ok(r) = o {
return serde_json::to_string(&r).unwrap();
} else {
return format!("err: {:?}", o.err().unwrap());
}
}

View File

@@ -1,10 +1,10 @@
use windmill_parser::asset_parser::{
merge_assets, AssetKind, AssetUsageAccessType, ParseAssetsResult,
};
merge_assets, AssetKind, AssetUsageAccessType, ParseAssetsResult,
};
use crate::{parse_ansible_reqs, ResourceOrVariablePath};
pub fn parse_assets(input: &str) -> anyhow::Result<Vec<ParseAssetsResult>> {
pub fn parse_assets(input: &str) -> anyhow::Result<Vec<ParseAssetsResult<String>>> {
let mut assets = vec![];
if let (_, Some(ansible_reqs), _) = parse_ansible_reqs(input)? {
if let Some(delegate_to_git_repo_details) = ansible_reqs.delegate_to_git_repo {
@@ -38,3 +38,4 @@ pub fn parse_assets(input: &str) -> anyhow::Result<Vec<ParseAssetsResult>> {
Ok(merge_assets(assets))
}

Some files were not shown because too many files have changed in this diff Show More