|
1 | 1 | use assert_cmd::Command;
|
| 2 | +use chrono::NaiveDateTime; |
2 | 3 | use chrono::Utc;
|
3 | 4 | use quary_core::databases::DatabaseConnection;
|
4 | 5 | use quary_databases::databases_duckdb;
|
| 6 | +use quary_databases::databases_redshift; |
5 | 7 | use std::fs;
|
6 | 8 | use tempfile::tempdir;
|
7 | 9 |
|
@@ -331,8 +333,9 @@ async fn test_duckdb_snapshots() {
|
331 | 333 | let current_date = Utc::now().date_naive();
|
332 | 334 | let quary_valid_from_str = &result.rows[0][4];
|
333 | 335 | let quary_valid_from_date = quary_valid_from_str.split_whitespace().next().unwrap();
|
| 336 | + println!("quary_valid_from_date: {}", quary_valid_from_date); |
334 | 337 | let quary_valid_from =
|
335 |
| - chrono::NaiveDate::parse_from_str(quary_valid_from_date, "%Y-%m-%d").unwrap(); |
| 338 | + chrono::NaiveDate::parse_from_str(quary_valid_from_date, "%Y-%m-%dT%H:%M:%SZ").unwrap(); |
336 | 339 | assert_eq!(current_date, quary_valid_from);
|
337 | 340 |
|
338 | 341 | // Update orders.csv data
|
@@ -382,7 +385,191 @@ async fn test_duckdb_snapshots() {
|
382 | 385 | .next()
|
383 | 386 | .unwrap();
|
384 | 387 | let updated_quary_valid_from =
|
385 |
| - chrono::NaiveDate::parse_from_str(updated_quary_valid_from_date, "%Y-%m-%d").unwrap(); |
| 388 | + chrono::NaiveDate::parse_from_str(updated_quary_valid_from_date, "%Y-%m-%dT%H:%M:%SZ") |
| 389 | + .unwrap(); |
386 | 390 | assert_eq!(current_date, updated_quary_valid_from);
|
387 | 391 | }
|
388 | 392 | }
|
| 393 | + |
| 394 | +/// This test simulates a workflow where a model references a snapshot in redshift. |
| 395 | +/// 1. The initial snapshot is taken which builds the orders_snapshot table in the database. |
| 396 | +/// 2. The project is built which references the orders_snapshot table. |
| 397 | +/// 3. The initial state of the snapshot is asserted. |
| 398 | +/// 4. The data is updated and a new snapshot is taken. |
| 399 | +/// 5. The updated state of the snapshot is asserted. (from the stg_orders_snapshot table) |
| 400 | +#[tokio::test] |
| 401 | +#[ignore] |
| 402 | +async fn test_redshift_snapshots() { |
| 403 | + // Prepare the database |
| 404 | + let database = |
| 405 | + databases_redshift::Redshift::new("", None, "", "", "", "", None, None, None, None, None) |
| 406 | + .await |
| 407 | + .ok() |
| 408 | + .unwrap(); |
| 409 | + database |
| 410 | + .exec("DROP TABLE analytics.orders CASCADE") |
| 411 | + .await |
| 412 | + .unwrap(); |
| 413 | + database |
| 414 | + .exec("DROP TABLE transform.orders_snapshot CASCADE") |
| 415 | + .await |
| 416 | + .unwrap(); |
| 417 | + |
| 418 | + database |
| 419 | + .exec( |
| 420 | + " |
| 421 | + CREATE TABLE analytics.orders ( |
| 422 | + order_id character varying(255) ENCODE lzo, |
| 423 | + customer_id character varying(255) ENCODE lzo, |
| 424 | + order_date timestamp without time zone ENCODE az64, |
| 425 | + total_amount numeric(10, 2) ENCODE az64, |
| 426 | + status character varying(255) ENCODE lzo |
| 427 | + ) DISTSTYLE AUTO; |
| 428 | + ", |
| 429 | + ) |
| 430 | + .await |
| 431 | + .unwrap(); |
| 432 | + |
| 433 | + database.exec( |
| 434 | + " |
| 435 | + INSERT INTO analytics.orders (order_id, customer_id, order_date, total_amount, status) VALUES ('1', '1', '2022-01-01 00:00:00', 100, 'in_progress') |
| 436 | + " |
| 437 | + ) |
| 438 | + .await |
| 439 | + .unwrap(); |
| 440 | + |
| 441 | + // Setup |
| 442 | + let name = "quary"; |
| 443 | + let temp_dir = tempdir().unwrap(); |
| 444 | + let project_dir = temp_dir.path(); |
| 445 | + |
| 446 | + // create a .env file |
| 447 | + let env_file_path = project_dir.join(".env"); |
| 448 | + let env_content = |
| 449 | + "REDSHIFT_HOST=\nREDSHIFT_PORT=\nREDSHIFT_USER=\nREDSHIFT_PASSWORD=\nREDSHIFT_DATABASE="; |
| 450 | + fs::write(&env_file_path, env_content).unwrap(); |
| 451 | + |
| 452 | + // Create snapshots directory and orders_snapshot.snapshot.sql file |
| 453 | + let snapshots_dir = project_dir.join("models").join("staging").join("snapshots"); |
| 454 | + fs::create_dir_all(&snapshots_dir).unwrap(); |
| 455 | + let orders_snapshot_file = snapshots_dir.join("orders_snapshot.snapshot.sql"); |
| 456 | + let orders_snapshot_content = |
| 457 | + "SELECT order_id, customer_id, order_date, total_amount, status FROM q.raw_orders"; |
| 458 | + fs::write(&orders_snapshot_file, orders_snapshot_content).unwrap(); |
| 459 | + |
| 460 | + // Create a model which references the snapshot |
| 461 | + let staging_models_dir = project_dir.join("models").join("staging"); |
| 462 | + fs::create_dir_all(&staging_models_dir).unwrap(); |
| 463 | + let stg_orders_snapshot_file = staging_models_dir.join("stg_orders_snapshot.sql"); |
| 464 | + let stg_orders_snapshot_content = "SELECT * FROM q.orders_snapshot"; |
| 465 | + fs::write(&stg_orders_snapshot_file, stg_orders_snapshot_content).unwrap(); |
| 466 | + |
| 467 | + // Create quary.yaml file |
| 468 | + let quary_yaml_content = "redshift:\n schema: transform"; |
| 469 | + let quary_yaml_path = project_dir.join("quary.yaml"); |
| 470 | + fs::write(&quary_yaml_path, quary_yaml_content).unwrap(); |
| 471 | + |
| 472 | + // Create schema.yaml file |
| 473 | + let schema_file = snapshots_dir.join("schema.yaml"); |
| 474 | + let schema_content = r#" |
| 475 | + sources: |
| 476 | + - name: raw_orders |
| 477 | + path: analytics.orders |
| 478 | + snapshots: |
| 479 | + - name: orders_snapshot |
| 480 | + unique_key: order_id |
| 481 | + strategy: |
| 482 | + timestamp: |
| 483 | + updated_at: order_date |
| 484 | + "#; |
| 485 | + fs::write(&schema_file, schema_content).unwrap(); |
| 486 | + |
| 487 | + // Take the initial snapshot and build the project which references the snapshot |
| 488 | + Command::cargo_bin(name) |
| 489 | + .unwrap() |
| 490 | + .current_dir(project_dir) |
| 491 | + .args(vec!["snapshot"]) |
| 492 | + .assert() |
| 493 | + .success(); |
| 494 | + Command::cargo_bin(name) |
| 495 | + .unwrap() |
| 496 | + .current_dir(project_dir) |
| 497 | + .args(vec!["build"]) |
| 498 | + .assert() |
| 499 | + .success(); |
| 500 | + |
| 501 | + { |
| 502 | + let result = database |
| 503 | + .query("SELECT order_id, customer_id, order_date, total_amount, status, quary_valid_from, quary_valid_to, quary_scd_id FROM transform.orders_snapshot") |
| 504 | + .await |
| 505 | + .unwrap(); |
| 506 | + |
| 507 | + assert_eq!(result.rows.len(), 1); |
| 508 | + assert_eq!(result.rows[0][0], "1"); // id |
| 509 | + assert_eq!(result.rows[0][4], "in_progress"); // status |
| 510 | + assert_eq!(result.rows[0][6], "NULL"); // quary_valid_to |
| 511 | + |
| 512 | + // Check that quary_valid_from has the same date as the current date |
| 513 | + let current_date: NaiveDateTime = Utc::now().date_naive().into(); |
| 514 | + let quary_valid_from_str = &result.rows[0][5]; |
| 515 | + let quary_valid_from_date = quary_valid_from_str.split_whitespace().next().unwrap(); |
| 516 | + |
| 517 | + let quary_valid_from = |
| 518 | + chrono::NaiveDateTime::parse_from_str(quary_valid_from_date, "%Y-%m-%dT%H:%M:%S%.f%:z") |
| 519 | + .unwrap(); |
| 520 | + assert_eq!(current_date.date(), quary_valid_from.date()); |
| 521 | + |
| 522 | + database |
| 523 | + .exec( |
| 524 | + " |
| 525 | + UPDATE analytics.orders |
| 526 | + SET order_date = '2099-06-01 00:00:00', status = 'completed' |
| 527 | + WHERE order_id = '1' |
| 528 | + ", |
| 529 | + ) |
| 530 | + .await |
| 531 | + .unwrap(); |
| 532 | + } |
| 533 | + |
| 534 | + // Take updated snapshot |
| 535 | + Command::cargo_bin(name) |
| 536 | + .unwrap() |
| 537 | + .current_dir(project_dir) |
| 538 | + .args(vec!["snapshot"]) |
| 539 | + .assert() |
| 540 | + .success(); |
| 541 | + |
| 542 | + { |
| 543 | + // Assert updated snapshot |
| 544 | + let updated_result = database |
| 545 | + .query("SELECT order_id, customer_id, order_date, total_amount, status, quary_valid_from, quary_valid_to, quary_scd_id FROM transform.stg_orders_snapshot ORDER BY quary_valid_from") |
| 546 | + .await |
| 547 | + .unwrap(); |
| 548 | + |
| 549 | + assert_eq!(updated_result.rows.len(), 2); |
| 550 | + |
| 551 | + // Check the initial row |
| 552 | + assert_eq!(updated_result.rows[0][0], "1"); // id |
| 553 | + assert_eq!(updated_result.rows[0][4], "in_progress"); // status |
| 554 | + assert_ne!(updated_result.rows[0][6], "NULL"); // quary_valid_to should not be NULL |
| 555 | + |
| 556 | + // Check the updated row |
| 557 | + assert_eq!(updated_result.rows[1][0], "1"); // id |
| 558 | + assert_eq!(updated_result.rows[1][4], "completed"); // status |
| 559 | + assert_eq!(updated_result.rows[1][6], "NULL"); // quary_valid_to should be NULL |
| 560 | + |
| 561 | + // Check that quary_valid_from of the updated row has the same date as the current date |
| 562 | + let current_date: NaiveDateTime = Utc::now().date_naive().into(); |
| 563 | + let updated_quary_valid_from_str = &updated_result.rows[1][5]; |
| 564 | + let updated_quary_valid_from_date = updated_quary_valid_from_str |
| 565 | + .split_whitespace() |
| 566 | + .next() |
| 567 | + .unwrap(); |
| 568 | + let updated_quary_valid_from = chrono::NaiveDateTime::parse_from_str( |
| 569 | + updated_quary_valid_from_date, |
| 570 | + "%Y-%m-%dT%H:%M:%S%.f%:z", |
| 571 | + ) |
| 572 | + .unwrap(); |
| 573 | + assert_eq!(current_date.date(), updated_quary_valid_from.date()); |
| 574 | + } |
| 575 | +} |
0 commit comments