From 512c59e92bb328f67115514cd737cf8eeb379417 Mon Sep 17 00:00:00 2001 From: Xu Shaohua Date: Thu, 1 Aug 2024 16:17:29 +0800 Subject: [PATCH] leetcode: Add 1141 --- .../.env | 1 + .../.gitignore | 1 + .../Cargo.toml | 12 ++ .../diesel.toml | 9 ++ .../docker-compose.yml | 13 ++ .../index.md | 4 + .../migrations/.keep | 0 .../down.sql | 6 + .../up.sql | 36 ++++++ .../2024-08-01-080910_activity/down.sql | 2 + .../2024-08-01-080910_activity/up.sql | 22 ++++ .../query.sql | 6 + .../solution.sql | 6 + .../src/db.rs | 37 ++++++ .../src/error.rs | 117 ++++++++++++++++++ .../src/main.rs | 25 ++++ .../src/schema.rs | 20 +++ 17 files changed, 317 insertions(+) create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/.env create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/.gitignore create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/Cargo.toml create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/diesel.toml create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/docker-compose.yml create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/index.md create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/.keep create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/00000000000000_diesel_initial_setup/down.sql create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/00000000000000_diesel_initial_setup/up.sql create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/2024-08-01-080910_activity/down.sql create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/2024-08-01-080910_activity/up.sql create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/query.sql create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/solution.sql create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/src/db.rs create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/src/error.rs create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/src/main.rs create mode 100644 src/leetcode/1141.user-activity-for-the-past-30-days-i/src/schema.rs diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/.env b/src/leetcode/1141.user-activity-for-the-past-30-days-i/.env new file mode 100644 index 000000000..453c39f85 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/.env @@ -0,0 +1 @@ +DATABASE_URL=postgres://leetcode:leetcode-password@localhost/leetcode diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/.gitignore b/src/leetcode/1141.user-activity-for-the-past-30-days-i/.gitignore new file mode 100644 index 000000000..c2de89b27 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/.gitignore @@ -0,0 +1 @@ +/db diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/Cargo.toml b/src/leetcode/1141.user-activity-for-the-past-30-days-i/Cargo.toml new file mode 100644 index 000000000..0bb751341 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "lc-1141-user-activity-for-the-past-30-days-i" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +diesel = { version = "2.2.0", default-features = false, features = [ "chrono", "postgres", "r2d2" ] } +dotenvy = "0.15.7" +env_logger = "0.11.3" +log = "0.4.21" +r2d2 = "0.8.10" diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/diesel.toml b/src/leetcode/1141.user-activity-for-the-past-30-days-i/diesel.toml new file mode 100644 index 000000000..95078736a --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "/home/shaohua/dev/rust/TheAlgorithms/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations" diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/docker-compose.yml b/src/leetcode/1141.user-activity-for-the-past-30-days-i/docker-compose.yml new file mode 100644 index 000000000..afd296b19 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.0" +services: + leetcode_db: + image: postgres:15.3 + restart: always + ports: + - 127.0.0.1:5432:5432 + environment: + POSTGRES_PASSWORD: leetcode-password + POSTGRES_USER: leetcode + POSTGRES_DB: leetcode + volumes: + - ./db:/var/lib/postgresql diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/index.md b/src/leetcode/1141.user-activity-for-the-past-30-days-i/index.md new file mode 100644 index 000000000..845f89887 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/index.md @@ -0,0 +1,4 @@ + +# + +[问题描述](https://leetcode.com/problems/) diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/.keep b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/00000000000000_diesel_initial_setup/down.sql b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 000000000..a9f526091 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/00000000000000_diesel_initial_setup/up.sql b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 000000000..d68895b1a --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/2024-08-01-080910_activity/down.sql b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/2024-08-01-080910_activity/down.sql new file mode 100644 index 000000000..5be5184da --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/2024-08-01-080910_activity/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE activity; diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/2024-08-01-080910_activity/up.sql b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/2024-08-01-080910_activity/up.sql new file mode 100644 index 000000000..a071ad934 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/migrations/2024-08-01-080910_activity/up.sql @@ -0,0 +1,22 @@ +-- Your SQL goes here +CREATE TYPE ACTIVITY_TYPES AS ENUM('open_session', 'end_session', 'scroll_down', 'send_message'); + +CREATE TABLE IF NOT EXISTS activity ( + id SERIAL PRIMARY KEY, + user_id int NOT NULL, + session_id int NOT NULL, + activity_date date NOT NULL, + activity_type ACTIVITY_TYPES +); + +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('1', '1', '2019-07-20', 'open_session'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('1', '1', '2019-07-20', 'scroll_down'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('1', '1', '2019-07-20', 'end_session'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('2', '4', '2019-07-20', 'open_session'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('2', '4', '2019-07-21', 'send_message'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('2', '4', '2019-07-21', 'end_session'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('3', '2', '2019-07-21', 'open_session'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('3', '2', '2019-07-21', 'send_message'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('3', '2', '2019-07-21', 'end_session'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('4', '3', '2019-06-25', 'open_session'); +INSERT INTO Activity (user_id, session_id, activity_date, activity_type) VALUES ('4', '3', '2019-06-25', 'end_session'); diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/query.sql b/src/leetcode/1141.user-activity-for-the-past-30-days-i/query.sql new file mode 100644 index 000000000..df7120665 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/query.sql @@ -0,0 +1,6 @@ +/* + * Copyright (c) 2024 Xu Shaohua . All rights reserved. + * Use of this source is governed by General Public License that can be found + * in the LICENSE file. + */ + diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/solution.sql b/src/leetcode/1141.user-activity-for-the-past-30-days-i/solution.sql new file mode 100644 index 000000000..1c4a0ca3e --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/solution.sql @@ -0,0 +1,6 @@ + +SELECT activity_date AS day, COUNT(DISTINCT user_id) AS active_users +FROM activity +WHERE activity_date > '2019-06-27' AND activity_date <= '2019-07-27' +GROUP BY activity_date; + diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/db.rs b/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/db.rs new file mode 100644 index 000000000..c8d1e3be3 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/db.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2024 Xu Shaohua . All rights reserved. +// Use of this source is governed by General Public License that can be found +// in the LICENSE file. + +use diesel::pg::PgConnection; +use diesel::r2d2::{ConnectionManager, Pool}; + +use crate::error::{Error, ErrorKind}; + +pub type DbPool = Pool>; + +/// Create postgres database connection pool. +/// +/// # Errors +/// +/// Returns error if: +/// - No `DATABASE_URL` is set in current environment. +/// - Failed to connect to database. +pub fn get_connection_pool() -> Result { + let url = std::env::var("DATABASE_URL").map_err(|err| { + Error::from_string( + ErrorKind::ConfigError, + format!("DATABASE_URL is not set in environment, err: {err:?}"), + ) + })?; + let manager = ConnectionManager::::new(&url); + + Pool::builder() + .test_on_check_out(true) + .build(manager) + .map_err(|err| { + Error::from_string( + ErrorKind::DbConnError, + format!("Failed to create connection pool, url: {url}, err: {err:?}"), + ) + }) +} diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/error.rs b/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/error.rs new file mode 100644 index 000000000..c9baa5181 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/error.rs @@ -0,0 +1,117 @@ +// Copyright (c) 2022 Xu Shaohua . All rights reserved. +// Use of this source is governed by GNU General Public License +// that can be found in the LICENSE file. + +#![allow(clippy::enum_variant_names)] + +use diesel::result::DatabaseErrorKind; +use std::fmt::{Display, Formatter}; +use std::io; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorKind { + ConfigError, + + DbConnError, + DbGeneralError, + DbUniqueViolationError, + DbForeignKeyViolationError, + DbNotFoundError, + + IoError, +} + +unsafe impl Send for ErrorKind {} + +#[derive(Debug, Clone)] +pub struct Error { + kind: ErrorKind, + message: String, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}: {}", self.kind, self.message) + } +} + +impl std::error::Error for Error {} + +#[allow(dead_code)] +impl Error { + #[must_use] + pub fn new(kind: ErrorKind, message: &str) -> Self { + Self { + kind, + message: message.to_owned(), + } + } + + #[must_use] + pub const fn from_string(kind: ErrorKind, message: String) -> Self { + Self { kind, message } + } + + #[must_use] + pub const fn kind(&self) -> ErrorKind { + self.kind + } + + #[must_use] + pub fn message(&self) -> &str { + &self.message + } +} + +impl From for Error { + fn from(err: io::Error) -> Self { + Self::from_string(ErrorKind::IoError, err.to_string()) + } +} + +impl From for Error { + fn from(err: r2d2::Error) -> Self { + Self::from_string(ErrorKind::DbConnError, format!("r2d2 err: {err}")) + } +} + +impl From for Error { + fn from(err: diesel::result::Error) -> Self { + match &err { + diesel::result::Error::DatabaseError(kind, _info) => match kind { + DatabaseErrorKind::UniqueViolation => { + Self::from_string(ErrorKind::DbUniqueViolationError, err.to_string()) + } + DatabaseErrorKind::ForeignKeyViolation => { + Self::from_string(ErrorKind::DbForeignKeyViolationError, err.to_string()) + } + _ => Self::from_string(ErrorKind::DbGeneralError, err.to_string()), + }, + diesel::result::Error::NotFound => { + Self::from_string(ErrorKind::DbNotFoundError, err.to_string()) + } + _ => Self::from_string(ErrorKind::DbGeneralError, err.to_string()), + } + } +} + +impl From for Error { + fn from(err: std::num::ParseIntError) -> Self { + Self::from_string(ErrorKind::ConfigError, err.to_string()) + } +} + +impl From for Error { + fn from(err: std::ffi::OsString) -> Self { + Self::from_string( + ErrorKind::ConfigError, + format!("OsString to String err: {err:?}"), + ) + } +} + +impl From for Error { + fn from(err: dotenvy::Error) -> Self { + Self::from_string(ErrorKind::ConfigError, format!("dotenv err: {err:?}")) + } +} diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/main.rs b/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/main.rs new file mode 100644 index 000000000..8ad13477e --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/main.rs @@ -0,0 +1,25 @@ +// Copyright (c) 2024 Xu Shaohua . All rights reserved. +// Use of this source is governed by General Public License that can be found +// in the LICENSE file. + +use diesel::connection::SimpleConnection; +use std::env; +use std::fs; + +mod db; +mod error; + +use error::Error; + +fn main() -> Result<(), Error> { + dotenvy::dotenv()?; + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + let pool = db::get_connection_pool()?; + let mut conn = pool.get()?; + for arg in env::args().skip(1) { + let sql_content: String = fs::read_to_string(arg)?; + conn.batch_execute(&sql_content)?; + } + Ok(()) +} diff --git a/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/schema.rs b/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/schema.rs new file mode 100644 index 000000000..f6e812735 --- /dev/null +++ b/src/leetcode/1141.user-activity-for-the-past-30-days-i/src/schema.rs @@ -0,0 +1,20 @@ +// @generated automatically by Diesel CLI. + +pub mod sql_types { + #[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "activity_types"))] + pub struct ActivityTypes; +} + +diesel::table! { + use diesel::sql_types::*; + use super::sql_types::ActivityTypes; + + activity (id) { + id -> Int4, + user_id -> Int4, + session_id -> Int4, + activity_date -> Date, + activity_type -> Nullable, + } +}