mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-19 00:03:19 +00:00
Merge branch 'main' into glm/resource-type-edition
This commit is contained in:
@@ -52,8 +52,6 @@ chore: upgrade sqlx to 0.7
|
||||
4. Stage ONLY the modified/relevant files: `git add <file1> <file2> ...`
|
||||
5. Create the commit with conventional format:
|
||||
```bash
|
||||
git commit -m "<type>: <description>
|
||||
|
||||
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
|
||||
git commit -m "<type>: <description>"
|
||||
```
|
||||
6. Run `git status` to verify the commit succeeded
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: update-sqlx
|
||||
description: How to safely update SQLx offline query cache. MUST use when SQL queries change.
|
||||
---
|
||||
|
||||
# SQLx Offline Query Cache
|
||||
|
||||
Windmill uses `SQLX_OFFLINE=true` in CI, which requires all `sqlx::query!` / `sqlx::query_as!` macros to have matching cached query data in `backend/.sqlx/`.
|
||||
|
||||
## When to Run
|
||||
|
||||
Run after any change to SQL queries in Rust source files. Without it, CI will fail with:
|
||||
```
|
||||
error: `SQLX_OFFLINE=true` but there is no cached data for this query
|
||||
```
|
||||
|
||||
## The Problem
|
||||
|
||||
`cargo sqlx prepare --workspace` **deletes all existing cache files** and regenerates only the ones found in the current compilation. If you don't compile with every feature flag (especially `private` for EE files), you will **silently delete EE query caches**, breaking CI for enterprise tests.
|
||||
|
||||
The standard `./update_sqlx.sh` script tries to compile with all features, but it often fails locally because the EE symlinks can be out of sync with `main`.
|
||||
|
||||
## Safe Procedure
|
||||
|
||||
Always preserve the existing EE caches from `origin/main`. Use this workflow:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# 1. Restore the full cache from main (includes EE caches)
|
||||
git checkout origin/main -- .sqlx/
|
||||
|
||||
# 2. Run prepare with OSS features (what compiles locally)
|
||||
# This regenerates OSS caches to match your code changes.
|
||||
cargo sqlx prepare --workspace -- --workspace --features all_sqlx_features
|
||||
|
||||
# 3. Restore any EE caches that were deleted in step 2.
|
||||
# These are files present in origin/main but missing after prepare.
|
||||
git ls-tree origin/main backend/.sqlx/ \
|
||||
| awk '{print $4}' | sed 's|backend/\.sqlx/||' | sort > /tmp/main_files.txt
|
||||
|
||||
find backend/.sqlx -name "*.json" -printf '%P\n' | sort > /tmp/current_files.txt
|
||||
|
||||
comm -23 /tmp/main_files.txt /tmp/current_files.txt > /tmp/missing_files.txt
|
||||
|
||||
while read f; do
|
||||
git show "origin/main:backend/.sqlx/$f" > "backend/.sqlx/$f"
|
||||
done < /tmp/missing_files.txt
|
||||
|
||||
# 4. Verify nothing was lost from main
|
||||
find backend/.sqlx -name "*.json" -printf '%P\n' | sort > /tmp/current_files.txt
|
||||
comm -23 /tmp/main_files.txt /tmp/current_files.txt | wc -l
|
||||
# Should output: 0
|
||||
```
|
||||
|
||||
## If EE Compiles Locally
|
||||
|
||||
If your EE repo happens to be in sync, you can use the full script (faster):
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
./update_sqlx.sh
|
||||
```
|
||||
|
||||
But if it fails with EE compilation errors, use the safe procedure above.
|
||||
|
||||
## What NOT to Do
|
||||
|
||||
- **Never** run `cargo sqlx prepare --workspace` with only OSS features and commit the result — it will delete EE caches.
|
||||
- **Never** set `SQLX_OFFLINE=true` for local `cargo sqlx prepare` — use a live database per CLAUDE.md. (CI runs with `SQLX_OFFLINE=true`, which is why the cache must be complete.)
|
||||
- **Never** skip the verification step (step 4 above).
|
||||
|
||||
## Verification
|
||||
|
||||
After committing, the diff against `origin/main` should show:
|
||||
- A few **new** cache files (for your changed queries)
|
||||
- A few **deleted** cache files (for old queries that no longer exist)
|
||||
- **Zero** net deletions from the EE cache set
|
||||
|
||||
```bash
|
||||
git diff origin/main --stat backend/.sqlx/
|
||||
```
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM group_ WHERE workspace_id = $1 ORDER BY name asc LIMIT $2 OFFSET $3",
|
||||
"query": "SELECT workspace_id, name, summary, extra_perms FROM group_ WHERE workspace_id = $1 ORDER BY name asc LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -38,5 +38,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ba9ab074f466bc2c581f018e2592f5a453e8a766c35dbf919d29c96966d63c75"
|
||||
"hash": "25784f87ccf0bc13b93739d34d416908b75d0d17392c2565e70b4738039b56a1"
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM resource WHERE workspace_id = $1 AND resource_type != 'state' AND resource_type != 'cache'",
|
||||
"query": "SELECT workspace_id, path, value, description, resource_type, extra_perms, created_by, edited_at, labels FROM resource WHERE workspace_id = $1 AND resource_type != 'state' AND resource_type != 'cache'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -35,13 +35,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
@@ -66,5 +66,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "45e4d13f5806122faecdb1d9ab18159555b652869a036b006f4a151e999b17b7"
|
||||
"hash": "27b243e7ff9838a8fc0a4a8e51ac5d33d8617fc75c0d9d08e428db488e0be409"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM group_ WHERE name = $1 AND workspace_id = $2",
|
||||
"query": "SELECT workspace_id, name, summary, extra_perms FROM group_ WHERE name = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -37,5 +37,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d9c8f6ec7bd10e533876526255c15e376ccb4f898b9c0ab8840b2930bda24fdc"
|
||||
"hash": "2c9a56fd46767c15dabc7813cd2b167a2f73a06d661d31c375697a68f7066aa9"
|
||||
}
|
||||
+6
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins')",
|
||||
"query": "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset FROM resource_type WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
@@ -46,7 +46,6 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
@@ -61,5 +60,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "03d63d2e64b012f624d2731b5bcb8849c74a9474777be61edf0ed43ddda07ef3"
|
||||
"hash": "45d5e9ead8193a04fd00c44a488590fdd2f7c4de45117a18360651655d153545"
|
||||
}
|
||||
+3
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT *\n FROM usr\n WHERE workspace_id = $1\n ",
|
||||
"query": "SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via FROM usr\n WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -47,11 +47,6 @@
|
||||
"ordinal": 8,
|
||||
"name": "added_via",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "is_service_account",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -68,9 +63,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c"
|
||||
"hash": "500b68d23314cb0f9edd570e576d5b9822bebe88f90ae007d943f2c761858190"
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
+7
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * from resource_type WHERE (workspace_id = $1 OR workspace_id = 'admins') ORDER BY name",
|
||||
"query": "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
@@ -46,6 +46,7 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
@@ -60,5 +61,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b8d392ccfcccafe0c19511b3567bc11779b1052b0948c410468a8aeba1d26d33"
|
||||
"hash": "623b061ccaa6bb883e95771fde8c911a165c9c430b7db389370361ca74d737f4"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM config WHERE name LIKE 'worker__%'",
|
||||
"query": "SELECT name, config FROM config WHERE name LIKE 'worker__%'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -22,5 +22,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "ce9e56ff451bae10af2c396352f5f93f78658e57b79dc5295553cacc328eb2b7"
|
||||
"hash": "6f95d6927a9be75f6c371c799ebe3755142647d0257b76e9befd0c4f31c332b1"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM usr\n WHERE workspace_id = $1",
|
||||
"query": "\n SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via, is_service_account\n FROM usr\n WHERE workspace_id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -72,5 +72,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2"
|
||||
"hash": "704d873d4585e00aa0a88cc9949d3a6cc806b33a844b8e0e600cb8bfae428e84"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
"query": "SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via, is_service_account FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -73,5 +73,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b"
|
||||
"hash": "8f308f841b2d60a6be2bc4ae6aeae506781295a3bcf036dbbc8600f229e61408"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM config WHERE name = $1",
|
||||
"query": "SELECT name, config FROM config WHERE name = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -24,5 +24,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d233e07d19e8e339e1378c1bfc5d78d592c00ffb6f42c3d072f56305b40e50f9"
|
||||
"hash": "c9064664829d304013920e3f710c4dfab99b263e5271045c542c34216e2b47cb"
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * FROM resource_type WHERE workspace_id = $1",
|
||||
"query": "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type WHERE (workspace_id = $1 OR workspace_id = 'admins') ORDER BY name",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
@@ -60,5 +60,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7b1239ad6460e8f5fb41bfe12f662a779528784ec8cf3f6dcce5545ab90bf234"
|
||||
"hash": "d0a95698b9a2c5e2543e94276d854d7e509c7db2c2ac7d395b7b53ad5dbc25e6"
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT * from resource_type ORDER BY name",
|
||||
"query": "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type ORDER BY name",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
@@ -58,5 +58,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "eb1f7f01461f5a7540c273b37e5d578c31cf151ab3ef813f7aada76533761e12"
|
||||
"hash": "e253b9e7e6450652589d6ee7ffa86d600e449cd399ac781af8b40c1c444972c3"
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
use sqlx::postgres::Postgres;
|
||||
use sqlx::Pool;
|
||||
use windmill_test_utils::{initialize_tracing, ApiServer};
|
||||
|
||||
/// Integration test: exercises every explicit-column query in `tarball_workspace`.
|
||||
///
|
||||
/// Creates one entity of each type (folder, script, resource, resource_type,
|
||||
/// variable, schedule, group) in the test workspace, then calls the tarball
|
||||
/// export endpoint with all include_* flags enabled. Success means every
|
||||
/// `SELECT col1, col2, ...` list matches the database schema.
|
||||
///
|
||||
/// Tables exercised (one explicit-column query each):
|
||||
/// folder, script, resource, resource_type, variable, schedule, usr, group_
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_tarball_export_all_tables(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let base_url = format!("http://localhost:{port}");
|
||||
|
||||
let client = windmill_api_client::create_client(&base_url, "SECRET_TOKEN".to_string());
|
||||
let http = client.client();
|
||||
|
||||
// ---- folder ----
|
||||
sqlx::query(
|
||||
r#"INSERT INTO folder
|
||||
(workspace_id, name, display_name, owners, extra_perms, summary)
|
||||
VALUES ($1, $2, $3, $4, '{}'::jsonb, $5)"#,
|
||||
)
|
||||
.bind("test-workspace")
|
||||
.bind("test_folder")
|
||||
.bind("Test Folder")
|
||||
.bind(vec!["u/test-user"])
|
||||
.bind("a test folder")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// ---- script (exercises the 30-column Script<ScriptRunnableSettingsHandle> query) ----
|
||||
client
|
||||
.create_script(
|
||||
"test-workspace",
|
||||
&windmill_api_client::types::NewScript {
|
||||
content: "export function main() { return 42; }".to_string(),
|
||||
language: windmill_api_client::types::ScriptLang::Bun,
|
||||
path: "f/test_folder/test_script".to_string(),
|
||||
summary: "test script".to_string(),
|
||||
description: "script for export test".to_string(),
|
||||
kind: Some("script".to_string()),
|
||||
tag: Some("test".to_string()),
|
||||
lock: None,
|
||||
parent_hash: None,
|
||||
schema: Default::default(),
|
||||
is_template: None,
|
||||
draft_only: None,
|
||||
dedicated_worker: None,
|
||||
ws_error_handler_muted: None,
|
||||
priority: None,
|
||||
cache_ttl: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
timeout: None,
|
||||
delete_after_secs: None,
|
||||
restart_unless_cancelled: None,
|
||||
visible_to_runner_only: None,
|
||||
auto_kind: None,
|
||||
on_behalf_of_email: None,
|
||||
has_preprocessor: None,
|
||||
codebase: None,
|
||||
envs: vec![],
|
||||
deployment_message: None,
|
||||
assets: vec![],
|
||||
modules: None,
|
||||
concurrency_key: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// ---- resource ----
|
||||
sqlx::query(
|
||||
r#"INSERT INTO resource
|
||||
(workspace_id, path, value, description, resource_type, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)"#,
|
||||
)
|
||||
.bind("test-workspace")
|
||||
.bind("f/test_folder/test_res")
|
||||
.bind(serde_json::json!({"url": "http://example.com"}))
|
||||
.bind("test resource")
|
||||
.bind("http")
|
||||
.bind("test-user")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// ---- resource_type ----
|
||||
sqlx::query(
|
||||
r#"INSERT INTO resource_type
|
||||
(workspace_id, name, schema, description, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5)"#,
|
||||
)
|
||||
.bind("test-workspace")
|
||||
.bind("http")
|
||||
.bind(serde_json::json!({"type": "object"}))
|
||||
.bind("HTTP resource type")
|
||||
.bind("system")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// ---- variable ----
|
||||
sqlx::query(
|
||||
r#"INSERT INTO variable
|
||||
(workspace_id, path, value, is_secret, description, account)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)"#,
|
||||
)
|
||||
.bind("test-workspace")
|
||||
.bind("f/test_folder/test_var")
|
||||
.bind("test_value")
|
||||
.bind(false)
|
||||
.bind("test variable")
|
||||
.bind(None::<i32>)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// ---- schedule ----
|
||||
client
|
||||
.create_schedule(
|
||||
"test-workspace",
|
||||
&windmill_api_client::types::NewSchedule {
|
||||
schedule: "0 0 0 * * *".to_string(),
|
||||
script_path: "f/test_folder/test_script".to_string(),
|
||||
path: "f/test_folder/test_schedule".to_string(),
|
||||
is_flow: false,
|
||||
timezone: "UTC".to_string(),
|
||||
args: Default::default(),
|
||||
enabled: Some(false),
|
||||
description: Some("test schedule".to_string()),
|
||||
summary: Some("test schedule".to_string()),
|
||||
tag: None,
|
||||
cron_version: Some("v2".to_string()),
|
||||
on_failure: None,
|
||||
on_failure_times: None,
|
||||
on_failure_exact: None,
|
||||
on_failure_extra_args: None,
|
||||
on_recovery: None,
|
||||
on_recovery_times: None,
|
||||
on_recovery_extra_args: None,
|
||||
on_success: None,
|
||||
on_success_extra_args: None,
|
||||
ws_error_handler_muted: None,
|
||||
retry: None,
|
||||
no_flow_overlap: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// ---- group_ (base fixture already has "all", create one more for include_groups) ----
|
||||
sqlx::query(
|
||||
"INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES ($1, $2, $3, '{}'::jsonb)",
|
||||
)
|
||||
.bind("test-workspace")
|
||||
.bind("testgroup")
|
||||
.bind("test group")
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// ---- tarball export: hits ALL explicit-column queries at once ----
|
||||
let params = [
|
||||
"archive_type=tar",
|
||||
"include_schedules=true",
|
||||
"include_users=true",
|
||||
"include_groups=true",
|
||||
"include_settings=true",
|
||||
"include_workspace_dependencies=true",
|
||||
"settings_version=v1",
|
||||
];
|
||||
|
||||
let resp = http
|
||||
.get(format!(
|
||||
"{}/api/w/test-workspace/workspaces/tarball?{}",
|
||||
base_url,
|
||||
params.join("&")
|
||||
))
|
||||
.bearer_auth("SECRET_TOKEN")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
200,
|
||||
"tarball export failed: {}",
|
||||
resp.text().await.unwrap_or_default()
|
||||
);
|
||||
|
||||
// Verify we got actual bytes back
|
||||
let body = resp.bytes().await?;
|
||||
assert!(!body.is_empty(), "tarball export returned empty body");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ async fn list_worker_groups(
|
||||
Extension(db): Extension<DB>,
|
||||
) -> error::JsonResult<Vec<Config>> {
|
||||
let mut configs_raw =
|
||||
sqlx::query_as!(Config, "SELECT * FROM config WHERE name LIKE 'worker__%'")
|
||||
sqlx::query_as!(Config, "SELECT name, config FROM config WHERE name LIKE 'worker__%'")
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
// Remove the 'worker__' prefix from all config names
|
||||
@@ -119,7 +119,7 @@ async fn get_config(
|
||||
) -> error::JsonResult<Option<serde_json::Value>> {
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
|
||||
let config = sqlx::query_as!(Config, "SELECT * FROM config WHERE name = $1", name)
|
||||
let config = sqlx::query_as!(Config, "SELECT name, config FROM config WHERE name = $1", name)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.map(|c| c.config);
|
||||
|
||||
@@ -365,7 +365,7 @@ impl EmbeddingsDb {
|
||||
let hub_resource_types = response.json::<Vec<HubResourceType>>().await?;
|
||||
|
||||
let resource_types: Vec<ResourceType> =
|
||||
sqlx::query_as!(ResourceType, "SELECT * from resource_type ORDER BY name",)
|
||||
sqlx::query_as!(ResourceType, "SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type ORDER BY name",)
|
||||
.fetch_all(pg_db)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ async fn list_groups(
|
||||
|
||||
let rows = sqlx::query_as!(
|
||||
Group,
|
||||
"SELECT * FROM group_ WHERE workspace_id = $1 ORDER BY name asc LIMIT $2 OFFSET $3",
|
||||
"SELECT workspace_id, name, summary, extra_perms FROM group_ WHERE workspace_id = $1 ORDER BY name asc LIMIT $2 OFFSET $3",
|
||||
w_id,
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
@@ -574,7 +574,7 @@ pub async fn get_group_opt<'c>(
|
||||
) -> Result<Option<Group>> {
|
||||
let group_opt = sqlx::query_as!(
|
||||
Group,
|
||||
"SELECT * FROM group_ WHERE name = $1 AND workspace_id = $2",
|
||||
"SELECT workspace_id, name, summary, extra_perms FROM group_ WHERE name = $1 AND workspace_id = $2",
|
||||
name,
|
||||
w_id
|
||||
)
|
||||
|
||||
@@ -979,7 +979,10 @@ async fn create_script_internal<'c>(
|
||||
.await?;
|
||||
}
|
||||
let clashing_script = sqlx::query_as::<_, Script<ScriptRunnableSettingsHandle>>(
|
||||
"SELECT * FROM script WHERE path = $1 AND archived = false AND workspace_id = $2",
|
||||
&format!(
|
||||
"SELECT {} FROM script WHERE path = $1 AND archived = false AND workspace_id = $2",
|
||||
windmill_common::scripts::SCRIPT_COLUMNS,
|
||||
),
|
||||
)
|
||||
.bind(&ns.path)
|
||||
.bind(&w_id)
|
||||
@@ -1762,7 +1765,10 @@ async fn get_script_by_path(
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as::<_, ScriptWithStarred<ScriptRunnableSettingsHandle>>(
|
||||
"SELECT *, NULL as starred FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
|
||||
&format!(
|
||||
"SELECT {}, NULL as starred FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1",
|
||||
windmill_common::scripts::SCRIPT_COLUMNS,
|
||||
),
|
||||
)
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
@@ -2277,7 +2283,10 @@ async fn get_script_by_hash_internal<'c>(
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as::<_, ScriptWithStarred<ScriptRunnableSettingsHandle>>(
|
||||
"SELECT *, NULL as starred FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
&format!(
|
||||
"SELECT {}, NULL as starred FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
windmill_common::scripts::SCRIPT_COLUMNS,
|
||||
),
|
||||
)
|
||||
.bind(hash)
|
||||
.bind(workspace_id)
|
||||
|
||||
@@ -385,7 +385,7 @@ async fn list_users(
|
||||
let rows = sqlx::query_as!(
|
||||
User,
|
||||
"
|
||||
SELECT *
|
||||
SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via, is_service_account
|
||||
FROM usr
|
||||
WHERE workspace_id = $1
|
||||
",
|
||||
@@ -1174,7 +1174,7 @@ async fn get_workspace_user(
|
||||
|
||||
let user = sqlx::query_as!(
|
||||
User,
|
||||
"SELECT * FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
"SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via, is_service_account FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
&username_to_update,
|
||||
&w_id
|
||||
)
|
||||
|
||||
@@ -490,7 +490,7 @@ pub(crate) async fn tarball_workspace(
|
||||
Some(t) => Err(Error::BadRequest(format!("Invalid Archive Type {t}"))),
|
||||
}?;
|
||||
{
|
||||
let folders = sqlx::query_as::<_, Folder>("SELECT * FROM folder WHERE workspace_id = $1")
|
||||
let folders = sqlx::query_as::<_, Folder>("SELECT name, workspace_id, display_name, owners, extra_perms, summary, edited_at, created_by, default_permissioned_as FROM folder WHERE workspace_id = $1")
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
@@ -507,10 +507,13 @@ pub(crate) async fn tarball_workspace(
|
||||
|
||||
{
|
||||
let scripts = sqlx::query_as::<_, Script<ScriptRunnableSettingsHandle>>(
|
||||
"SELECT * FROM script as o WHERE workspace_id = $1 AND archived = false
|
||||
AND (draft_only IS NULL OR draft_only = false)
|
||||
AND created_at = (select max(created_at) from script where path = o.path AND \
|
||||
workspace_id = $1)",
|
||||
&format!(
|
||||
"SELECT {} FROM script as o WHERE workspace_id = $1 AND archived = false
|
||||
AND (draft_only IS NULL OR draft_only = false)
|
||||
AND created_at = (select max(created_at) from script where path = o.path AND \
|
||||
workspace_id = $1)",
|
||||
windmill_common::scripts::SCRIPT_COLUMNS,
|
||||
),
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -595,7 +598,7 @@ pub(crate) async fn tarball_workspace(
|
||||
if !skip_resources.unwrap_or(false) {
|
||||
let resources = sqlx::query_as!(
|
||||
Resource,
|
||||
"SELECT * FROM resource WHERE workspace_id = $1 AND resource_type != 'state' AND resource_type != 'cache'",
|
||||
"SELECT workspace_id, path, value, description, resource_type, extra_perms, created_by, edited_at, labels FROM resource WHERE workspace_id = $1 AND resource_type != 'state' AND resource_type != 'cache'",
|
||||
&w_id
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -612,7 +615,7 @@ pub(crate) async fn tarball_workspace(
|
||||
if !skip_resource_types.unwrap_or(false) {
|
||||
let resource_types = sqlx::query_as!(
|
||||
ResourceType,
|
||||
"SELECT * FROM resource_type WHERE workspace_id = $1",
|
||||
"SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset FROM resource_type WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -651,9 +654,9 @@ pub(crate) async fn tarball_workspace(
|
||||
if !skip_variables.unwrap_or(false) {
|
||||
let variables =
|
||||
sqlx::query_as::<_, ExportableListableVariable>(if !skip_secrets.unwrap_or(false) {
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND expires_at IS NULL"
|
||||
"SELECT workspace_id, path, value, is_secret, description, extra_perms, account, is_oauth, expires_at, labels FROM variable WHERE workspace_id = $1 AND expires_at IS NULL"
|
||||
} else {
|
||||
"SELECT * FROM variable WHERE workspace_id = $1 AND is_secret = false AND expires_at IS NULL"
|
||||
"SELECT workspace_id, path, value, is_secret, description, extra_perms, account, is_oauth, expires_at, labels FROM variable WHERE workspace_id = $1 AND is_secret = false AND expires_at IS NULL"
|
||||
})
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -727,7 +730,7 @@ pub(crate) async fn tarball_workspace(
|
||||
|
||||
if include_schedules.unwrap_or(false) {
|
||||
let schedules = sqlx::query_as::<_, Schedule>(
|
||||
"SELECT * FROM schedule
|
||||
"SELECT workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, args, extra_perms, email, permissioned_as, error, on_failure, on_failure_times, on_failure_exact, on_failure_extra_args, on_recovery, on_recovery_times, on_recovery_extra_args, on_success, on_success_extra_args, ws_error_handler_muted, retry, no_flow_overlap, summary, description, tag, paused_until, cron_version, dynamic_skip, labels FROM schedule
|
||||
WHERE workspace_id = $1",
|
||||
)
|
||||
.bind(&w_id)
|
||||
@@ -998,7 +1001,7 @@ pub(crate) async fn tarball_workspace(
|
||||
|
||||
if include_users.unwrap_or(false) {
|
||||
let users = sqlx::query!(
|
||||
"SELECT * FROM usr
|
||||
"SELECT workspace_id, username, email, is_admin, created_at, operator, disabled, role, added_via FROM usr
|
||||
WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
|
||||
@@ -2430,18 +2430,30 @@ WHERE table_schema = current_schema()",
|
||||
Ok(q)
|
||||
}
|
||||
DbType::Mysql => {
|
||||
let db_name = database_name.unwrap_or("");
|
||||
let explicit_db = database_name.filter(|s| !s.is_empty());
|
||||
let table_filter = if let Some(t) = table {
|
||||
let parts: Vec<&str> = t.split('.').collect();
|
||||
let tname = parts[parts.len() - 1];
|
||||
let schema = if parts.len() > 1 { parts[0] } else { db_name };
|
||||
let schema_sql = if parts.len() > 1 {
|
||||
format!("'{}'", escape_sql_literal(parts[0]))
|
||||
} else {
|
||||
explicit_db
|
||||
.map(|dn| format!("'{}'", escape_sql_literal(dn)))
|
||||
.unwrap_or_else(|| "DATABASE()".to_string())
|
||||
};
|
||||
format!(
|
||||
"\nWHERE\n TABLE_NAME = '{}' AND TABLE_SCHEMA = '{}'",
|
||||
"\nWHERE\n TABLE_NAME = '{}' AND TABLE_SCHEMA = {}",
|
||||
escape_sql_literal(tname),
|
||||
escape_sql_literal(schema)
|
||||
schema_sql
|
||||
)
|
||||
} else {
|
||||
"\nWHERE\n TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys', '_vt')".to_string()
|
||||
let schema_predicate = explicit_db
|
||||
.map(|dn| format!("TABLE_SCHEMA = '{}'", escape_sql_literal(dn)))
|
||||
.unwrap_or_else(|| "TABLE_SCHEMA = DATABASE()".to_string());
|
||||
format!(
|
||||
"\nWHERE\n {}\n AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys', '_vt')",
|
||||
schema_predicate
|
||||
)
|
||||
};
|
||||
let extra_col = if table.is_none() {
|
||||
",\n TABLE_NAME as table_name"
|
||||
@@ -4250,10 +4262,28 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_load_table_metadata_mysql_all_tables() {
|
||||
fn test_expand_load_table_metadata_mysql_single_table_uses_session_db() {
|
||||
let marker = r#"-- WM_INTERNAL_DB_LOAD_TABLE_METADATA {"table":"users"}"#;
|
||||
let sql = expand_code(marker, &ScriptLang::Mysql);
|
||||
assert!(sql.contains("TABLE_NAME = 'users'"));
|
||||
assert!(sql.contains("TABLE_SCHEMA = DATABASE()"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_load_table_metadata_mysql_all_tables_uses_session_db() {
|
||||
let marker = r#"-- WM_INTERNAL_DB_LOAD_TABLE_METADATA {}"#;
|
||||
let sql = expand_code(marker, &ScriptLang::Mysql);
|
||||
assert!(sql.contains("TABLE_NAME as table_name"));
|
||||
assert!(sql.contains("TABLE_SCHEMA = DATABASE()"));
|
||||
assert!(sql.contains("TABLE_SCHEMA NOT IN"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_load_table_metadata_mysql_all_tables_with_database_name() {
|
||||
let marker = r#"-- WM_INTERNAL_DB_LOAD_TABLE_METADATA {"databaseName":"mydb"}"#;
|
||||
let sql = expand_code(marker, &ScriptLang::Mysql);
|
||||
assert!(sql.contains("TABLE_NAME as table_name"));
|
||||
assert!(sql.contains("TABLE_SCHEMA = 'mydb'"));
|
||||
assert!(sql.contains("TABLE_SCHEMA NOT IN"));
|
||||
}
|
||||
|
||||
|
||||
@@ -312,52 +312,10 @@ pub async fn fetch_script_for_update<'a>(
|
||||
e: impl sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
) -> crate::error::Result<Option<Script<ScriptRunnableSettingsHandle>>> {
|
||||
sqlx::query_as::<_, Script<ScriptRunnableSettingsHandle>>(
|
||||
"SELECT
|
||||
workspace_id,
|
||||
hash,
|
||||
path,
|
||||
parent_hashes,
|
||||
summary,
|
||||
description,
|
||||
content,
|
||||
created_by,
|
||||
created_at,
|
||||
archived,
|
||||
schema,
|
||||
deleted,
|
||||
is_template,
|
||||
extra_perms,
|
||||
lock,
|
||||
lock_error_logs,
|
||||
language,
|
||||
kind,
|
||||
tag,
|
||||
draft_only,
|
||||
envs,
|
||||
concurrency_key,
|
||||
concurrent_limit,
|
||||
concurrency_time_window_s,
|
||||
debounce_key,
|
||||
debounce_delay_s,
|
||||
dedicated_worker,
|
||||
runnable_settings_handle,
|
||||
ws_error_handler_muted,
|
||||
priority,
|
||||
cache_ttl,
|
||||
cache_ignore_s3_path,
|
||||
timeout,
|
||||
delete_after_use,
|
||||
delete_after_secs,
|
||||
restart_unless_cancelled,
|
||||
visible_to_runner_only,
|
||||
auto_kind,
|
||||
codebase,
|
||||
has_preprocessor,
|
||||
on_behalf_of_email,
|
||||
assets,
|
||||
modules,
|
||||
labels
|
||||
FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1 FOR UPDATE",
|
||||
&format!(
|
||||
"SELECT {} FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1 FOR UPDATE",
|
||||
SCRIPT_COLUMNS,
|
||||
),
|
||||
)
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
|
||||
@@ -122,7 +122,7 @@ pub async fn get_or_refresh_mcp_client(
|
||||
let redirect_uri = format!("{}/api/mcp/oauth/callback", base_url);
|
||||
|
||||
let cached_client: Option<McpOAuthClient> =
|
||||
sqlx::query_as("SELECT * FROM mcp_oauth_client WHERE mcp_server_url = $1")
|
||||
sqlx::query_as("SELECT mcp_server_url, client_id, client_secret, client_secret_expires_at, token_endpoint FROM mcp_oauth_client WHERE mcp_server_url = $1")
|
||||
.bind(mcp_server_url)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
|
||||
@@ -566,7 +566,7 @@ pub async fn get_schedule_opt<'c>(
|
||||
path: &str,
|
||||
) -> Result<Option<Schedule>> {
|
||||
let schedule_opt = sqlx::query_as::<_, Schedule>(
|
||||
"SELECT * FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
"SELECT workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, args, extra_perms, email, permissioned_as, error, on_failure, on_failure_times, on_failure_exact, on_failure_extra_args, on_recovery, on_recovery_times, on_recovery_extra_args, on_success, on_success_extra_args, ws_error_handler_muted, retry, no_flow_overlap, summary, description, tag, paused_until, cron_version, dynamic_skip, labels FROM schedule WHERE path = $1 AND workspace_id = $2",
|
||||
)
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
|
||||
@@ -1510,7 +1510,7 @@ async fn list_resource_types(
|
||||
) -> JsonResult<Vec<ResourceType>> {
|
||||
let rows = sqlx::query_as!(
|
||||
ResourceType,
|
||||
"SELECT * from resource_type WHERE (workspace_id = $1 OR workspace_id = 'admins') ORDER \
|
||||
"SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type WHERE (workspace_id = $1 OR workspace_id = 'admins') ORDER \
|
||||
BY name",
|
||||
&w_id
|
||||
)
|
||||
@@ -1544,7 +1544,7 @@ async fn get_resource_type(
|
||||
|
||||
let resource_type_o = sqlx::query_as!(
|
||||
ResourceType,
|
||||
"SELECT * from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins')",
|
||||
"SELECT workspace_id, name, schema, description, created_by, edited_at, format_extension, is_fileset from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins')",
|
||||
&name,
|
||||
&w_id
|
||||
)
|
||||
|
||||
@@ -318,6 +318,19 @@ pub fn id_to_codebase_info(id: &str) -> CodebaseInfo {
|
||||
CodebaseInfo { is_tar, is_esm }
|
||||
}
|
||||
|
||||
/// Column list for `SELECT ... FROM script` when targeting `Script<ScriptRunnableSettingsHandle>`.
|
||||
/// Shared across all query sites so a schema change only needs one edit.
|
||||
pub const SCRIPT_COLUMNS: &str = concat!(
|
||||
"workspace_id, hash, path, parent_hashes, summary, description, content, ",
|
||||
"created_by, created_at, archived, schema, deleted, is_template, extra_perms, ",
|
||||
"lock, lock_error_logs, language, kind, tag, draft_only, envs, ",
|
||||
"dedicated_worker, ws_error_handler_muted, priority, cache_ttl, cache_ignore_s3_path, ",
|
||||
"timeout, delete_after_use, delete_after_secs, restart_unless_cancelled, ",
|
||||
"visible_to_runner_only, auto_kind, codebase, has_preprocessor, on_behalf_of_email, ",
|
||||
"assets, modules, labels, concurrency_key, concurrent_limit, ",
|
||||
"concurrency_time_window_s, debounce_key, debounce_delay_s, runnable_settings_handle",
|
||||
);
|
||||
|
||||
#[derive(Serialize, sqlx::FromRow, Debug)]
|
||||
pub struct Script<SR> {
|
||||
pub workspace_id: String,
|
||||
|
||||
Reference in New Issue
Block a user