Skip to main content

redis_cloud/flexible/
databases.rs

1//! Database management operations for Pro subscriptions
2//!
3//! This module provides comprehensive database management functionality for Redis Cloud
4//! Pro subscriptions, including creation, configuration, backup, import/export, and
5//! monitoring capabilities.
6//!
7//! # Overview
8//!
9//! Pro databases offer the full range of Redis Cloud features including high availability,
10//! auto-scaling, clustering, modules, and advanced data persistence options. They can be
11//! deployed across multiple cloud providers and regions.
12//!
13//! # Key Features
14//!
15//! - **Database Lifecycle**: Create, update, delete, and manage databases
16//! - **Backup & Restore**: Automated and on-demand backup operations
17//! - **Import/Export**: Import data from RDB files or other Redis instances
18//! - **Modules**: Support for `RedisJSON`, `RediSearch`, `RedisGraph`, `RedisTimeSeries`, `RedisBloom`
19//! - **High Availability**: Replication, auto-failover, and clustering support
20//! - **Monitoring**: Metrics, alerts, and performance insights
21//! - **Security**: TLS, password protection, and ACL support
22//!
23//! # Database Configuration Options
24//!
25//! - Memory limits from 250MB to 500GB+
26//! - Support for Redis OSS Cluster API
27//! - Data persistence: AOF, snapshot, or both
28//! - Data eviction policies
29//! - Replication and clustering
30//! - Custom Redis versions
31//!
32//! # Example Usage
33//!
34//! ```no_run
35//! use redis_cloud::{CloudClient, DatabaseHandler};
36//!
37//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
38//! let client = CloudClient::builder()
39//!     .api_key("your-api-key")
40//!     .api_secret("your-api-secret")
41//!     .build()?;
42//!
43//! let handler = DatabaseHandler::new(client);
44//!
45//! // List all databases in a subscription (subscription ID 123)
46//! let databases = handler.get_subscription_databases(123, None, None).await?;
47//!
48//! // Get specific database details
49//! let database = handler.get_subscription_database_by_id(123, 456).await?;
50//! # Ok(())
51//! # }
52//! ```
53
54use crate::types::{Link, ProcessorResponse};
55use crate::{CloudClient, Result};
56use async_stream::try_stream;
57use futures_core::Stream;
58use serde::{Deserialize, Deserializer, Serialize};
59use serde_json::Value;
60use std::collections::HashMap;
61
62// ============================================================================
63// Models
64// ============================================================================
65
66/// `RedisLabs` Account Subscription Databases information
67///
68/// Response from GET /subscriptions/{subscriptionId}/databases
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct AccountSubscriptionDatabases {
72    /// Account ID
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub account_id: Option<i32>,
75
76    /// Subscription information with nested databases array.
77    /// The API returns this as an array, each element containing subscriptionId, databases, and links.
78    #[serde(default, deserialize_with = "deserialize_subscription_info")]
79    pub subscription: Vec<SubscriptionDatabasesInfo>,
80
81    /// HATEOAS links for API navigation
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub links: Option<Vec<Link>>,
84}
85
86/// Subscription databases info returned within `AccountSubscriptionDatabases`
87#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct SubscriptionDatabasesInfo {
90    /// Subscription ID
91    pub subscription_id: i32,
92
93    /// Number of databases (may not always be present)
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub number_of_databases: Option<i32>,
96
97    /// List of databases in this subscription
98    #[serde(default)]
99    pub databases: Vec<Database>,
100
101    /// HATEOAS links
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub links: Option<Vec<Link>>,
104}
105
106/// Custom deserializer that handles both object and array formats for subscription field.
107/// The API returns an array, but some test mocks use an object format.
108fn deserialize_subscription_info<'de, D>(
109    deserializer: D,
110) -> std::result::Result<Vec<SubscriptionDatabasesInfo>, D::Error>
111where
112    D: Deserializer<'de>,
113{
114    let value: Option<Value> = Option::deserialize(deserializer)?;
115
116    match value {
117        None => Ok(Vec::new()),
118        Some(Value::Array(arr)) => {
119            serde_json::from_value(Value::Array(arr)).map_err(serde::de::Error::custom)
120        }
121        Some(Value::Object(obj)) => {
122            // Single object - wrap in array
123            let item: SubscriptionDatabasesInfo =
124                serde_json::from_value(Value::Object(obj)).map_err(serde::de::Error::custom)?;
125            Ok(vec![item])
126        }
127        Some(other) => Err(serde::de::Error::custom(format!(
128            "expected array or object for subscription, got {other:?}"
129        ))),
130    }
131}
132
133/// Optional. Expected read and write throughput for this region.
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct LocalThroughput {
137    /// Specify one of the selected cloud provider regions for the subscription.
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub region: Option<String>,
140
141    /// Write operations for this region per second. Default: 1000 ops/sec
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub write_operations_per_second: Option<i64>,
144
145    /// Read operations for this region per second. Default: 1000 ops/sec
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub read_operations_per_second: Option<i64>,
148}
149
150/// Database tag update request message
151#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct DatabaseTagUpdateRequest {
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub subscription_id: Option<i32>,
156
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub database_id: Option<i32>,
159
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub key: Option<String>,
162
163    /// Database tag value
164    pub value: String,
165
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub command_type: Option<String>,
168}
169
170/// Database tag
171#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct Tag {
174    /// Database tag key.
175    pub key: String,
176
177    /// Database tag value.
178    pub value: String,
179
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub command_type: Option<String>,
182}
183
184/// Active-Active database flush request message
185#[derive(Debug, Clone, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct CrdbFlushRequest {
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub subscription_id: Option<i32>,
190
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub database_id: Option<i32>,
193
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub command_type: Option<String>,
196}
197
198/// Database certificate
199#[derive(Debug, Clone, Serialize, Deserialize)]
200#[serde(rename_all = "camelCase")]
201pub struct DatabaseCertificate {
202    /// An X.509 PEM (base64) encoded server certificate with new line characters replaced by '\n'.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub public_certificate_pem_string: Option<String>,
205}
206
207/// Database tags update request message
208#[derive(Debug, Clone, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct DatabaseTagsUpdateRequest {
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub subscription_id: Option<i32>,
213
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub database_id: Option<i32>,
216
217    /// List of database tags.
218    pub tags: Vec<Tag>,
219
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub command_type: Option<String>,
222}
223
224/// Optional. This database will be a replica of the specified Redis databases, provided as a list of objects with endpoint and certificate details.
225#[derive(Debug, Clone, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase")]
227pub struct DatabaseSyncSourceSpec {
228    /// Redis URI of a source database. Example format: 'redis://user:password@host:port'. If the URI provided is a Redis Cloud database, only host and port should be provided. Example: '<redis://endpoint1:6379>'.
229    pub endpoint: String,
230
231    /// Defines if encryption should be used to connect to the sync source. If not set the source is a Redis Cloud database, it will automatically detect if the source uses encryption.
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub encryption: Option<bool>,
234
235    /// TLS/SSL certificate chain of the sync source. If not set and the source is a Redis Cloud database, it will automatically detect the certificate to use.
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub server_cert: Option<String>,
238}
239
240/// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections. If set to an empty list, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
241#[derive(Debug, Clone, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct DatabaseCertificateSpec {
244    /// Client certificate public key in PEM format, with new line characters replaced with '\n'.
245    pub public_certificate_pem_string: String,
246}
247
248/// Database tag
249#[derive(Debug, Clone, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct CloudTag {
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub key: Option<String>,
254
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub value: Option<String>,
257
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub created_at: Option<String>,
260
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub updated_at: Option<String>,
263
264    /// HATEOAS links
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub links: Option<Vec<Link>>,
267}
268
269/// `BdbVersionUpgradeStatus`
270#[derive(Debug, Clone, Serialize, Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct BdbVersionUpgradeStatus {
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub database_id: Option<i32>,
275
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub target_redis_version: Option<String>,
278
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub progress: Option<f64>,
281
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub upgrade_status: Option<String>,
284}
285
286/// Active-Active database update local properties request message
287#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct CrdbUpdatePropertiesRequest {
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub subscription_id: Option<i32>,
292
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub database_id: Option<i32>,
295
296    /// Optional. Updated database name. Database name is limited to 40 characters or less and must include only letters, digits, and hyphens ('-'). It must start with a letter and end with a letter or digit.
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub name: Option<String>,
299
300    /// Optional. When 'false': Creates a deployment plan and deploys it, updating any resources required by the plan. When 'true': creates a read-only deployment plan and does not update any resources. Default: 'false'
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub dry_run: Option<bool>,
303
304    /// Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub memory_limit_in_gb: Option<f64>,
307
308    /// Optional. The maximum amount of data in the dataset for this database in GB. You cannot set both datasetSizeInGb and totalMemoryInGb. If ‘replication’ is 'true', the database’s total memory will be twice as large as the datasetSizeInGb.If ‘replication’ is false, the database’s total memory will be the datasetSizeInGb value.
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub dataset_size_in_gb: Option<f64>,
311
312    /// Optional. Support Redis [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api). Default: 'false'
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub support_oss_cluster_api: Option<bool>,
315
316    /// Optional. If set to 'true', the database will use the external endpoint for OSS Cluster API. This setting blocks the database's private endpoint. Can only be set if 'supportOSSClusterAPI' is 'true'.
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
319
320    /// Optional. A public key client TLS/SSL certificate with new line characters replaced with '\n'. If specified, mTLS authentication will be required to authenticate user connections if it is not already required. If set to an empty string, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub client_ssl_certificate: Option<String>,
323
324    /// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections. If set to an empty list, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
327
328    /// Optional. When 'true', requires TLS authentication for all connections - mTLS with valid clientTlsCertificates, regular TLS when clientTlsCertificates is not provided. If enableTls is set to 'false' while mTLS is required, it will remove the mTLS requirement and erase previously provided clientTlsCertificates.
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub enable_tls: Option<bool>,
331
332    /// Optional. Type and rate of data persistence in all regions that don't set local 'dataPersistence'.
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub global_data_persistence: Option<String>,
335
336    /// Optional. Changes the password used to access the database in all regions that don't set a local 'password'.
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub global_password: Option<String>,
339
340    /// Optional. List of source IP addresses or subnet masks to allow in all regions that don't set local 'sourceIp' settings. If set, Redis clients will be able to connect to this database only from within the specified source IP addresses ranges. Example: ['192.168.10.0/32', '192.168.12.0/24']
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub global_source_ip: Option<Vec<String>>,
343
344    /// Optional. Redis database alert settings in all regions that don't set local 'alerts'.
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub global_alerts: Option<Vec<DatabaseAlertSpec>>,
347
348    /// Optional. A list of regions and local settings to update.
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub regions: Option<Vec<LocalRegionProperties>>,
351
352    /// Optional. Data eviction policy.
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub data_eviction_policy: Option<String>,
355
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub command_type: Option<String>,
358}
359
360/// Database slowlog entry
361#[derive(Debug, Clone, Serialize, Deserialize)]
362#[serde(rename_all = "camelCase")]
363pub struct DatabaseSlowLogEntry {
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub id: Option<i32>,
366
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub start_time: Option<String>,
369
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub duration: Option<i32>,
372
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub arguments: Option<String>,
375}
376
377/// Database tag
378#[derive(Debug, Clone, Serialize, Deserialize)]
379#[serde(rename_all = "camelCase")]
380pub struct DatabaseTagCreateRequest {
381    /// Database tag key.
382    pub key: String,
383
384    /// Database tag value.
385    pub value: String,
386
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub subscription_id: Option<i32>,
389
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub database_id: Option<i32>,
392
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub command_type: Option<String>,
395}
396
397/// Optional. Throughput measurement method.
398#[derive(Debug, Clone, Serialize, Deserialize)]
399pub struct DatabaseThroughputSpec {
400    /// Throughput measurement method. Use 'operations-per-second' for all new databases.
401    pub by: String,
402
403    /// Throughput value in the selected measurement method.
404    pub value: i64,
405}
406
407/// Optional. Changes Remote backup configuration details.
408#[derive(Debug, Clone, Serialize, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct DatabaseBackupConfig {
411    /// Optional. Determine if backup should be active. Default: null
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub active: Option<bool>,
414
415    /// Required when active is 'true'. Defines the interval between backups. Format: 'every-x-hours', where x is one of 24, 12, 6, 4, 2, or 1. Example: "every-4-hours"
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub interval: Option<String>,
418
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub backup_interval: Option<String>,
421
422    /// Optional. Hour when the backup starts. Available only for "every-12-hours" and "every-24-hours" backup intervals. Specified as an hour in 24-hour UTC time. Example: "14:00" is 2 PM UTC.
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub time_utc: Option<String>,
425
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub database_backup_time_utc: Option<String>,
428
429    /// Required when active is 'true'. Type of storage to host backup files. Can be "aws-s3", "google-blob-storage", "azure-blob-storage", or "ftp". See [Set up backup storage locations](https://redis.io/docs/latest/operate/rc/databases/back-up-data/#set-up-backup-storage-locations) to learn how to set up backup storage locations.
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub storage_type: Option<String>,
432
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub backup_storage_type: Option<String>,
435
436    /// Required when active is 'true'. Path to the backup storage location.
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub storage_path: Option<String>,
439}
440
441/// Optional. Redis advanced capabilities (also known as modules) to be provisioned in the database. Use GET /database-modules to get a list of available advanced capabilities.
442#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct DatabaseModuleSpec {
444    /// Redis advanced capability name. Use GET /database-modules for a list of available capabilities.
445    pub name: String,
446
447    /// Optional. Redis advanced capability parameters. Use GET /database-modules to get the available capabilities and their parameters.
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub parameters: Option<HashMap<String, Value>>,
450}
451
452/// Optional. Changes Replica Of (also known as Active-Passive) configuration details.
453#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(rename_all = "camelCase")]
455pub struct ReplicaOfSpec {
456    /// Optional. This database will be a replica of the specified Redis databases, provided as a list of objects with endpoint and certificate details.
457    pub sync_sources: Vec<DatabaseSyncSourceSpec>,
458}
459
460/// Regex rule for custom hashing policy
461#[derive(Debug, Clone, Serialize, Deserialize)]
462#[serde(rename_all = "camelCase")]
463pub struct RegexRule {
464    /// The ordinal/order of this rule
465    pub ordinal: i32,
466
467    /// The regex pattern for this rule
468    pub pattern: String,
469}
470
471/// Backup configuration status (response)
472#[derive(Debug, Clone, Serialize, Deserialize)]
473#[serde(rename_all = "camelCase")]
474pub struct Backup {
475    /// Whether remote backup is enabled
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub enable_remote_backup: Option<bool>,
478
479    /// Backup time in UTC
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub time_utc: Option<String>,
482
483    /// Backup interval (e.g., "every-24-hours", "every-12-hours")
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub interval: Option<String>,
486
487    /// Backup destination path
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub destination: Option<String>,
490}
491
492/// Security configuration (response)
493#[derive(Debug, Clone, Serialize, Deserialize)]
494#[serde(rename_all = "camelCase")]
495pub struct Security {
496    /// Whether default Redis user is enabled
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub enable_default_user: Option<bool>,
499
500    /// Whether SSL client authentication is enabled
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub ssl_client_authentication: Option<bool>,
503
504    /// Whether TLS client authentication is enabled
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub tls_client_authentication: Option<bool>,
507
508    /// List of source IP addresses allowed to connect
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub source_ips: Option<Vec<String>>,
511
512    /// Database password (masked in responses)
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub password: Option<String>,
515
516    /// Whether TLS is enabled
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub enable_tls: Option<bool>,
519}
520
521/// Clustering configuration (response)
522#[derive(Debug, Clone, Serialize, Deserialize)]
523#[serde(rename_all = "camelCase")]
524pub struct Clustering {
525    /// Number of shards
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub number_of_shards: Option<i32>,
528
529    /// Regex rules for custom hashing
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub regex_rules: Option<Vec<RegexRule>>,
532
533    /// Hashing policy
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub hashing_policy: Option<String>,
536}
537
538/// Active-Active (CRDB) database information
539///
540/// Represents an Active-Active database with global settings and per-region configurations.
541#[derive(Debug, Clone, Serialize, Deserialize)]
542#[serde(rename_all = "camelCase")]
543pub struct ActiveActiveDatabase {
544    /// Database ID
545    #[serde(skip_serializing_if = "Option::is_none")]
546    pub database_id: Option<i32>,
547
548    /// Database name
549    #[serde(skip_serializing_if = "Option::is_none")]
550    pub name: Option<String>,
551
552    /// Database protocol
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub protocol: Option<String>,
555
556    /// Database status
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub status: Option<String>,
559
560    /// Redis version
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub redis_version: Option<String>,
563
564    /// Memory storage type
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub memory_storage: Option<String>,
567
568    /// Whether this is an Active-Active database
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub active_active_redis: Option<bool>,
571
572    /// Timestamp when database was activated
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub activated_on: Option<String>,
575
576    /// Timestamp of last modification
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub last_modified: Option<String>,
579
580    /// Support for OSS Cluster API
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub support_oss_cluster_api: Option<bool>,
583
584    /// Use external endpoint for OSS Cluster API
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
587
588    /// Whether replication is enabled
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub replication: Option<bool>,
591
592    /// Data eviction policy
593    #[serde(skip_serializing_if = "Option::is_none")]
594    pub data_eviction_policy: Option<String>,
595
596    /// Security configuration
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub security: Option<Security>,
599
600    /// Redis modules enabled
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub modules: Option<Vec<DatabaseModuleSpec>>,
603
604    /// Global data persistence setting
605    #[serde(skip_serializing_if = "Option::is_none")]
606    pub global_data_persistence: Option<String>,
607
608    /// Global source IP allowlist
609    #[serde(skip_serializing_if = "Option::is_none")]
610    pub global_source_ip: Option<Vec<String>>,
611
612    /// Global password
613    #[serde(skip_serializing_if = "Option::is_none")]
614    pub global_password: Option<String>,
615
616    /// Global alert configurations
617    #[serde(skip_serializing_if = "Option::is_none")]
618    pub global_alerts: Option<Vec<DatabaseAlertSpec>>,
619
620    /// Global enable default user setting
621    #[serde(skip_serializing_if = "Option::is_none")]
622    pub global_enable_default_user: Option<bool>,
623
624    /// Per-region CRDB database configurations
625    #[serde(skip_serializing_if = "Option::is_none")]
626    pub crdb_databases: Option<Vec<CrdbDatabase>>,
627
628    /// Whether automatic minor version upgrades are enabled
629    #[serde(skip_serializing_if = "Option::is_none")]
630    pub auto_minor_version_upgrade: Option<bool>,
631}
632
633/// Per-region configuration for an Active-Active (CRDB) database
634#[derive(Debug, Clone, Serialize, Deserialize)]
635#[serde(rename_all = "camelCase")]
636pub struct CrdbDatabase {
637    /// Cloud provider
638    #[serde(skip_serializing_if = "Option::is_none")]
639    pub provider: Option<String>,
640
641    /// Cloud region
642    #[serde(skip_serializing_if = "Option::is_none")]
643    pub region: Option<String>,
644
645    /// Redis version compliance
646    #[serde(skip_serializing_if = "Option::is_none")]
647    pub redis_version_compliance: Option<String>,
648
649    /// Public endpoint
650    #[serde(skip_serializing_if = "Option::is_none")]
651    pub public_endpoint: Option<String>,
652
653    /// Private endpoint
654    #[serde(skip_serializing_if = "Option::is_none")]
655    pub private_endpoint: Option<String>,
656
657    /// Memory limit in GB
658    #[serde(skip_serializing_if = "Option::is_none")]
659    pub memory_limit_in_gb: Option<f64>,
660
661    /// Dataset size in GB
662    #[serde(skip_serializing_if = "Option::is_none")]
663    pub dataset_size_in_gb: Option<f64>,
664
665    /// Memory used in MB
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub memory_used_in_mb: Option<f64>,
668
669    /// Read operations per second
670    #[serde(skip_serializing_if = "Option::is_none")]
671    pub read_operations_per_second: Option<i32>,
672
673    /// Write operations per second
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub write_operations_per_second: Option<i32>,
676
677    /// Data persistence setting for this region
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub data_persistence: Option<String>,
680
681    /// Alert configurations for this region
682    #[serde(skip_serializing_if = "Option::is_none")]
683    pub alerts: Option<Vec<DatabaseAlertSpec>>,
684
685    /// Security configuration for this region
686    #[serde(skip_serializing_if = "Option::is_none")]
687    pub security: Option<Security>,
688
689    /// Backup configuration for this region
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub backup: Option<Backup>,
692
693    /// Query performance factor
694    #[serde(skip_serializing_if = "Option::is_none")]
695    pub query_performance_factor: Option<String>,
696}
697
698/// Database backup request message
699#[derive(Debug, Clone, Serialize, Deserialize)]
700#[serde(rename_all = "camelCase")]
701pub struct DatabaseBackupRequest {
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub subscription_id: Option<i32>,
704
705    #[serde(skip_serializing_if = "Option::is_none")]
706    pub database_id: Option<i32>,
707
708    /// Required for Active-Active databases. Name of the cloud provider region to back up. When backing up an Active-Active database, you must back up each region separately.
709    #[serde(skip_serializing_if = "Option::is_none")]
710    pub region_name: Option<String>,
711
712    /// Optional. Manually backs up data to this location, instead of the set 'remoteBackup' location.
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub adhoc_backup_path: Option<String>,
715
716    #[serde(skip_serializing_if = "Option::is_none")]
717    pub command_type: Option<String>,
718}
719
720/// Database
721///
722/// Represents a Redis Cloud database with all known API fields as first-class struct members.
723/// The `extra` field is reserved only for truly unknown/future fields that may be added to the API.
724#[derive(Debug, Clone, Serialize, Deserialize)]
725#[serde(rename_all = "camelCase")]
726pub struct Database {
727    /// Database ID - always present in API responses
728    pub database_id: i32,
729
730    /// Database name
731    #[serde(skip_serializing_if = "Option::is_none")]
732    pub name: Option<String>,
733
734    /// Database status (e.g., "active", "pending", "error", "draft")
735    #[serde(skip_serializing_if = "Option::is_none")]
736    pub status: Option<String>,
737
738    /// Cloud provider (e.g., "AWS", "GCP", "Azure")
739    #[serde(skip_serializing_if = "Option::is_none")]
740    pub provider: Option<String>,
741
742    /// Cloud region (e.g., "us-east-1", "europe-west1")
743    #[serde(skip_serializing_if = "Option::is_none")]
744    pub region: Option<String>,
745
746    /// Redis version (e.g., "7.2", "7.0")
747    #[serde(skip_serializing_if = "Option::is_none")]
748    pub redis_version: Option<String>,
749
750    /// Redis Serialization Protocol version
751    #[serde(skip_serializing_if = "Option::is_none")]
752    pub resp_version: Option<String>,
753
754    /// Total memory limit in GB (including replication and overhead)
755    #[serde(skip_serializing_if = "Option::is_none")]
756    pub memory_limit_in_gb: Option<f64>,
757
758    /// Dataset size in GB (actual data size, excluding replication)
759    #[serde(skip_serializing_if = "Option::is_none")]
760    pub dataset_size_in_gb: Option<f64>,
761
762    /// Memory used in MB
763    #[serde(skip_serializing_if = "Option::is_none")]
764    pub memory_used_in_mb: Option<f64>,
765
766    /// Private endpoint for database connections
767    #[serde(skip_serializing_if = "Option::is_none")]
768    pub private_endpoint: Option<String>,
769
770    /// Public endpoint for database connections (if enabled)
771    #[serde(skip_serializing_if = "Option::is_none")]
772    pub public_endpoint: Option<String>,
773
774    /// TCP port on which the database is available
775    #[serde(skip_serializing_if = "Option::is_none")]
776    pub port: Option<i32>,
777
778    /// Data eviction policy (e.g., "volatile-lru", "allkeys-lru", "noeviction")
779    #[serde(skip_serializing_if = "Option::is_none")]
780    pub data_eviction_policy: Option<String>,
781
782    /// Data persistence setting (e.g., "aof-every-1-sec", "snapshot-every-1-hour", "none")
783    #[serde(skip_serializing_if = "Option::is_none")]
784    pub data_persistence: Option<String>,
785
786    /// Whether replication is enabled
787    #[serde(skip_serializing_if = "Option::is_none")]
788    pub replication: Option<bool>,
789
790    /// Protocol used (e.g., "redis", "memcached")
791    #[serde(skip_serializing_if = "Option::is_none")]
792    pub protocol: Option<String>,
793
794    /// Support for OSS Cluster API
795    #[serde(skip_serializing_if = "Option::is_none")]
796    pub support_oss_cluster_api: Option<bool>,
797
798    /// Use external endpoint for OSS Cluster API
799    #[serde(skip_serializing_if = "Option::is_none")]
800    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
801
802    /// Whether TLS is enabled for connections
803    #[serde(skip_serializing_if = "Option::is_none")]
804    pub enable_tls: Option<bool>,
805
806    /// Throughput measurement configuration
807    #[serde(skip_serializing_if = "Option::is_none")]
808    pub throughput_measurement: Option<DatabaseThroughputSpec>,
809
810    /// Local throughput measurement for Active-Active databases
811    #[serde(skip_serializing_if = "Option::is_none")]
812    pub local_throughput_measurement: Option<Vec<LocalThroughput>>,
813
814    /// Average item size in bytes (for Auto Tiering)
815    #[serde(skip_serializing_if = "Option::is_none")]
816    pub average_item_size_in_bytes: Option<i64>,
817
818    /// Path to periodic backup storage location
819    #[serde(skip_serializing_if = "Option::is_none")]
820    pub periodic_backup_path: Option<String>,
821
822    /// Remote backup configuration
823    #[serde(skip_serializing_if = "Option::is_none")]
824    pub remote_backup: Option<DatabaseBackupConfig>,
825
826    /// List of source IP addresses or subnet masks allowed to connect
827    #[serde(skip_serializing_if = "Option::is_none")]
828    pub source_ip: Option<Vec<String>>,
829
830    /// Client TLS/SSL certificate (deprecated, use `client_tls_certificates`)
831    #[serde(skip_serializing_if = "Option::is_none")]
832    pub client_ssl_certificate: Option<String>,
833
834    /// List of client TLS/SSL certificates for mTLS authentication
835    #[serde(skip_serializing_if = "Option::is_none")]
836    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
837
838    /// Database password (masked in responses for security)
839    #[serde(skip_serializing_if = "Option::is_none")]
840    pub password: Option<String>,
841
842    /// Memcached SASL username
843    #[serde(skip_serializing_if = "Option::is_none")]
844    pub sasl_username: Option<String>,
845
846    /// Memcached SASL password (masked in responses)
847    #[serde(skip_serializing_if = "Option::is_none")]
848    pub sasl_password: Option<String>,
849
850    /// Database alert configurations
851    #[serde(skip_serializing_if = "Option::is_none")]
852    pub alerts: Option<Vec<DatabaseAlertSpec>>,
853
854    /// Redis modules/capabilities enabled on this database
855    #[serde(skip_serializing_if = "Option::is_none")]
856    pub modules: Option<Vec<DatabaseModuleSpec>>,
857
858    /// Database hashing policy for clustering
859    #[serde(skip_serializing_if = "Option::is_none")]
860    pub sharding_type: Option<String>,
861
862    /// Query performance factor (for search and query databases)
863    #[serde(skip_serializing_if = "Option::is_none")]
864    pub query_performance_factor: Option<String>,
865
866    /// List of databases this database is a replica of
867    #[serde(skip_serializing_if = "Option::is_none")]
868    pub replica_of: Option<Vec<String>>,
869
870    /// Replica configuration
871    #[serde(skip_serializing_if = "Option::is_none")]
872    pub replica: Option<ReplicaOfSpec>,
873
874    /// Whether default Redis user is enabled
875    #[serde(skip_serializing_if = "Option::is_none")]
876    pub enable_default_user: Option<bool>,
877
878    /// Whether this is an Active-Active (CRDB) database
879    #[serde(skip_serializing_if = "Option::is_none")]
880    pub active_active_redis: Option<bool>,
881
882    /// Memory storage type: "ram" or "ram-and-flash" (Auto Tiering)
883    #[serde(skip_serializing_if = "Option::is_none")]
884    pub memory_storage: Option<String>,
885
886    /// Redis version compliance status
887    #[serde(skip_serializing_if = "Option::is_none")]
888    pub redis_version_compliance: Option<String>,
889
890    /// Whether automatic minor version upgrades are enabled
891    #[serde(skip_serializing_if = "Option::is_none")]
892    pub auto_minor_version_upgrade: Option<bool>,
893
894    /// Number of shards in the database cluster
895    #[serde(skip_serializing_if = "Option::is_none")]
896    pub number_of_shards: Option<i32>,
897
898    /// Regex rules for custom hashing policy
899    #[serde(skip_serializing_if = "Option::is_none")]
900    pub regex_rules: Option<Vec<RegexRule>>,
901
902    /// Whether SSL client authentication is enabled
903    #[serde(skip_serializing_if = "Option::is_none")]
904    pub ssl_client_authentication: Option<bool>,
905
906    /// Whether TLS client authentication is enabled
907    #[serde(skip_serializing_if = "Option::is_none")]
908    pub tls_client_authentication: Option<bool>,
909
910    /// Timestamp when database was activated
911    #[serde(skip_serializing_if = "Option::is_none")]
912    pub activated: Option<String>,
913
914    /// Timestamp of last modification
915    #[serde(skip_serializing_if = "Option::is_none")]
916    pub last_modified: Option<String>,
917
918    /// HATEOAS links for API navigation
919    #[serde(skip_serializing_if = "Option::is_none")]
920    pub links: Option<Vec<Link>>,
921}
922
923/// Optional. Changes Redis database alert details.
924#[derive(Debug, Clone, Serialize, Deserialize)]
925pub struct DatabaseAlertSpec {
926    /// Alert type. Available options depend on Plan type. See [Configure alerts](https://redis.io/docs/latest/operate/rc/databases/monitor-performance/#configure-metric-alerts) for more information.
927    pub name: String,
928
929    /// Value over which an alert will be sent. Default values and range depend on the alert type. See [Configure alerts](https://redis.io/docs/latest/operate/rc/databases/monitor-performance/#configure-metric-alerts) for more information.
930    pub value: i32,
931}
932
933/// Request structure for creating a new Pro database
934///
935/// Contains all configuration options for creating a database in a Pro subscription,
936/// including memory settings, replication, persistence, modules, and networking.
937#[derive(Debug, Clone, Serialize, Deserialize)]
938#[serde(rename_all = "camelCase")]
939pub struct DatabaseCreateRequest {
940    #[serde(skip_serializing_if = "Option::is_none")]
941    pub subscription_id: Option<i32>,
942
943    /// Optional. When 'false': Creates a deployment plan and deploys it, creating any resources required by the plan. When 'true': creates a read-only deployment plan and does not create any resources. Default: 'false'
944    #[serde(skip_serializing_if = "Option::is_none")]
945    pub dry_run: Option<bool>,
946
947    /// Name of the database. Database name is limited to 40 characters or less and must include only letters, digits, and hyphens ('-'). It must start with a letter and end with a letter or digit.
948    pub name: String,
949
950    /// Optional. Database protocol. Only set to 'memcached' if you have a legacy application. Default: 'redis'
951    #[serde(skip_serializing_if = "Option::is_none")]
952    pub protocol: Option<String>,
953
954    /// Optional. TCP port on which the database is available (10000-19999). Generated automatically if not set.
955    #[serde(skip_serializing_if = "Option::is_none")]
956    pub port: Option<i32>,
957
958    /// Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
959    #[serde(skip_serializing_if = "Option::is_none")]
960    pub memory_limit_in_gb: Option<f64>,
961
962    /// Optional. The maximum amount of data in the dataset for this database in GB. You cannot set both datasetSizeInGb and totalMemoryInGb. If ‘replication’ is 'true', the database’s total memory will be twice as large as the datasetSizeInGb. If ‘replication’ is false, the database’s total memory will be the datasetSizeInGb value.
963    #[serde(skip_serializing_if = "Option::is_none")]
964    pub dataset_size_in_gb: Option<f64>,
965
966    /// Optional. If specified, redisVersion defines the Redis database version. If omitted, the Redis version will be set to the default version (available in 'GET /subscriptions/redis-versions')
967    #[serde(skip_serializing_if = "Option::is_none")]
968    pub redis_version: Option<String>,
969
970    /// Optional. Redis Serialization Protocol version. Must be compatible with Redis version.
971    #[serde(skip_serializing_if = "Option::is_none")]
972    pub resp_version: Option<String>,
973
974    /// Optional. Support [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api). Default: 'false'
975    #[serde(skip_serializing_if = "Option::is_none")]
976    pub support_oss_cluster_api: Option<bool>,
977
978    /// Optional. If set to 'true', the database will use the external endpoint for OSS Cluster API. This setting blocks the database's private endpoint. Can only be set if 'supportOSSClusterAPI' is 'true'. Default: 'false'
979    #[serde(skip_serializing_if = "Option::is_none")]
980    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
981
982    /// Optional. Type and rate of data persistence in persistent storage. Default: 'none'
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub data_persistence: Option<String>,
985
986    /// Optional. Data eviction policy. Default: 'volatile-lru'
987    #[serde(skip_serializing_if = "Option::is_none")]
988    pub data_eviction_policy: Option<String>,
989
990    /// Optional. Sets database replication. Default: 'true'
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub replication: Option<bool>,
993
994    /// Optional. This database will be a replica of the specified Redis databases provided as one or more URI(s). Example: 'redis://user:password@host:port'. If the URI provided is a Redis Cloud database, only host and port should be provided. Example: ['<redis://endpoint1:6379>', '<redis://endpoint2:6380>'].
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub replica_of: Option<Vec<String>>,
997
998    #[serde(skip_serializing_if = "Option::is_none")]
999    pub replica: Option<ReplicaOfSpec>,
1000
1001    #[serde(skip_serializing_if = "Option::is_none")]
1002    pub throughput_measurement: Option<DatabaseThroughputSpec>,
1003
1004    /// Optional. Expected throughput per region for an Active-Active database. Default: 1000 read and write ops/sec for each region
1005    #[serde(skip_serializing_if = "Option::is_none")]
1006    pub local_throughput_measurement: Option<Vec<LocalThroughput>>,
1007
1008    /// Optional. Relevant only to ram-and-flash (also known as Auto Tiering) subscriptions. Estimated average size in bytes of the items stored in the database. Default: 1000
1009    #[serde(skip_serializing_if = "Option::is_none")]
1010    pub average_item_size_in_bytes: Option<i64>,
1011
1012    /// Optional. The path to a backup storage location. If specified, the database will back up every 24 hours to this location, and you can manually back up the database to this location at any time.
1013    #[serde(skip_serializing_if = "Option::is_none")]
1014    pub periodic_backup_path: Option<String>,
1015
1016    #[serde(skip_serializing_if = "Option::is_none")]
1017    pub remote_backup: Option<DatabaseBackupConfig>,
1018
1019    /// Optional. List of source IP addresses or subnet masks to allow. If specified, Redis clients will be able to connect to this database only from within the specified source IP addresses ranges. Example: '['192.168.10.0/32', '192.168.12.0/24']'
1020    #[serde(skip_serializing_if = "Option::is_none")]
1021    pub source_ip: Option<Vec<String>>,
1022
1023    /// Optional. A public key client TLS/SSL certificate with new line characters replaced with '\n'. If specified, mTLS authentication will be required to authenticate user connections. Default: 'null'
1024    #[serde(skip_serializing_if = "Option::is_none")]
1025    pub client_ssl_certificate: Option<String>,
1026
1027    /// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections.
1028    #[serde(skip_serializing_if = "Option::is_none")]
1029    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
1030
1031    /// Optional. When 'true', requires TLS authentication for all connections - mTLS with valid clientTlsCertificates, regular TLS when clientTlsCertificates is not provided. Default: 'false'
1032    #[serde(skip_serializing_if = "Option::is_none")]
1033    pub enable_tls: Option<bool>,
1034
1035    /// Optional. Password to access the database. If not set, a random 32-character alphanumeric password will be automatically generated. Can only be set if 'protocol' is 'redis'.
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    pub password: Option<String>,
1038
1039    /// Optional. Memcached (SASL) Username to access the database. If not set, the username will be set to a 'mc-' prefix followed by a random 5 character long alphanumeric. Can only be set if 'protocol' is 'memcached'.
1040    #[serde(skip_serializing_if = "Option::is_none")]
1041    pub sasl_username: Option<String>,
1042
1043    /// Optional. Memcached (SASL) Password to access the database. If not set, a random 32 character long alphanumeric password will be automatically generated. Can only be set if 'protocol' is 'memcached'.
1044    #[serde(skip_serializing_if = "Option::is_none")]
1045    pub sasl_password: Option<String>,
1046
1047    /// Optional. Redis database alert details.
1048    #[serde(skip_serializing_if = "Option::is_none")]
1049    pub alerts: Option<Vec<DatabaseAlertSpec>>,
1050
1051    /// Optional. Redis advanced capabilities (also known as modules) to be provisioned in the database. Use GET /database-modules to get a list of available advanced capabilities.
1052    #[serde(skip_serializing_if = "Option::is_none")]
1053    pub modules: Option<Vec<DatabaseModuleSpec>>,
1054
1055    /// Optional. Database [Hashing policy](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#manage-the-hashing-policy).
1056    #[serde(skip_serializing_if = "Option::is_none")]
1057    pub sharding_type: Option<String>,
1058
1059    #[serde(skip_serializing_if = "Option::is_none")]
1060    pub command_type: Option<String>,
1061
1062    /// Optional. The query performance factor adds extra compute power specifically for search and query databases. You can increase your queries per second by the selected factor.
1063    #[serde(skip_serializing_if = "Option::is_none")]
1064    pub query_performance_factor: Option<String>,
1065}
1066
1067/// Database import request
1068#[derive(Debug, Clone, Serialize, Deserialize)]
1069#[serde(rename_all = "camelCase")]
1070pub struct DatabaseImportRequest {
1071    #[serde(skip_serializing_if = "Option::is_none")]
1072    pub subscription_id: Option<i32>,
1073
1074    #[serde(skip_serializing_if = "Option::is_none")]
1075    pub database_id: Option<i32>,
1076
1077    /// Type of storage from which to import the database RDB file or Redis data.
1078    pub source_type: String,
1079
1080    /// One or more paths to source data files or Redis databases, as appropriate to specified source type.
1081    pub import_from_uri: Vec<String>,
1082
1083    #[serde(skip_serializing_if = "Option::is_none")]
1084    pub command_type: Option<String>,
1085}
1086
1087/// Redis list of database tags
1088#[derive(Debug, Clone, Serialize, Deserialize)]
1089#[serde(rename_all = "camelCase")]
1090pub struct CloudTags {
1091    #[serde(skip_serializing_if = "Option::is_none")]
1092    pub account_id: Option<i32>,
1093
1094    /// HATEOAS links
1095    #[serde(skip_serializing_if = "Option::is_none")]
1096    pub links: Option<Vec<Link>>,
1097}
1098
1099/// Upgrades the specified Pro database to a later Redis version.
1100#[derive(Debug, Clone, Serialize, Deserialize)]
1101#[serde(rename_all = "camelCase")]
1102pub struct DatabaseUpgradeRedisVersionRequest {
1103    #[serde(skip_serializing_if = "Option::is_none")]
1104    pub database_id: Option<i32>,
1105
1106    #[serde(skip_serializing_if = "Option::is_none")]
1107    pub subscription_id: Option<i32>,
1108
1109    /// The target Redis version the database will be upgraded to. Use GET /subscriptions/redis-versions to get a list of available Redis versions.
1110    pub target_redis_version: String,
1111
1112    #[serde(skip_serializing_if = "Option::is_none")]
1113    pub command_type: Option<String>,
1114}
1115
1116/// `DatabaseSlowLogEntries`
1117#[derive(Debug, Clone, Serialize, Deserialize)]
1118pub struct DatabaseSlowLogEntries {
1119    #[serde(skip_serializing_if = "Option::is_none")]
1120    pub entries: Option<Vec<DatabaseSlowLogEntry>>,
1121
1122    /// HATEOAS links
1123    #[serde(skip_serializing_if = "Option::is_none")]
1124    pub links: Option<Vec<Link>>,
1125}
1126
1127/// Optional. A list of regions and local settings to update.
1128#[derive(Debug, Clone, Serialize, Deserialize)]
1129#[serde(rename_all = "camelCase")]
1130pub struct LocalRegionProperties {
1131    /// Required. Name of the region to update.
1132    #[serde(skip_serializing_if = "Option::is_none")]
1133    pub region: Option<String>,
1134
1135    #[serde(skip_serializing_if = "Option::is_none")]
1136    pub remote_backup: Option<DatabaseBackupConfig>,
1137
1138    #[serde(skip_serializing_if = "Option::is_none")]
1139    pub local_throughput_measurement: Option<LocalThroughput>,
1140
1141    /// Optional. Type and rate of data persistence for this region. If set, 'globalDataPersistence' will not apply to this region.
1142    #[serde(skip_serializing_if = "Option::is_none")]
1143    pub data_persistence: Option<String>,
1144
1145    /// Optional. Changes the password used to access the database in this region. If set, 'globalPassword' will not apply to this region.
1146    #[serde(skip_serializing_if = "Option::is_none")]
1147    pub password: Option<String>,
1148
1149    /// Optional. List of source IP addresses or subnet masks to allow in this region. If set, Redis clients will be able to connect to the database in this region only from within the specified source IP addresses ranges, and 'globalSourceIp' will not apply to this region. Example: ['192.168.10.0/32', '192.168.12.0/24']
1150    #[serde(skip_serializing_if = "Option::is_none")]
1151    pub source_ip: Option<Vec<String>>,
1152
1153    /// Optional. Redis database alert settings for this region. If set, 'glboalAlerts' will not apply to this region.
1154    #[serde(skip_serializing_if = "Option::is_none")]
1155    pub alerts: Option<Vec<DatabaseAlertSpec>>,
1156
1157    /// Optional. Redis Serialization Protocol version for this region. Must be compatible with Redis version.
1158    #[serde(skip_serializing_if = "Option::is_none")]
1159    pub resp_version: Option<String>,
1160}
1161
1162/// `TaskStateUpdate`
1163#[derive(Debug, Clone, Serialize, Deserialize)]
1164#[serde(rename_all = "camelCase")]
1165pub struct TaskStateUpdate {
1166    #[serde(skip_serializing_if = "Option::is_none")]
1167    pub task_id: Option<String>,
1168
1169    #[serde(skip_serializing_if = "Option::is_none")]
1170    pub command_type: Option<String>,
1171
1172    #[serde(skip_serializing_if = "Option::is_none")]
1173    pub status: Option<String>,
1174
1175    #[serde(skip_serializing_if = "Option::is_none")]
1176    pub description: Option<String>,
1177
1178    #[serde(skip_serializing_if = "Option::is_none")]
1179    pub timestamp: Option<String>,
1180
1181    #[serde(skip_serializing_if = "Option::is_none")]
1182    pub response: Option<ProcessorResponse>,
1183
1184    /// HATEOAS links
1185    #[serde(skip_serializing_if = "Option::is_none")]
1186    pub links: Option<Vec<Link>>,
1187}
1188
1189/// Database update request
1190#[derive(Debug, Clone, Serialize, Deserialize)]
1191#[serde(rename_all = "camelCase")]
1192pub struct DatabaseUpdateRequest {
1193    #[serde(skip_serializing_if = "Option::is_none")]
1194    pub subscription_id: Option<i32>,
1195
1196    #[serde(skip_serializing_if = "Option::is_none")]
1197    pub database_id: Option<i32>,
1198
1199    /// Optional. When 'false': Creates a deployment plan and deploys it, updating any resources required by the plan. When 'true': creates a read-only deployment plan and does not update any resources. Default: 'false'
1200    #[serde(skip_serializing_if = "Option::is_none")]
1201    pub dry_run: Option<bool>,
1202
1203    /// Optional. Updated database name.
1204    #[serde(skip_serializing_if = "Option::is_none")]
1205    pub name: Option<String>,
1206
1207    /// Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
1208    #[serde(skip_serializing_if = "Option::is_none")]
1209    pub memory_limit_in_gb: Option<f64>,
1210
1211    /// Optional. The maximum amount of data in the dataset for this database in GB. You cannot set both datasetSizeInGb and totalMemoryInGb. If ‘replication’ is 'true', the database’s total memory will be twice as large as the datasetSizeInGb.If ‘replication’ is false, the database’s total memory will be the datasetSizeInGb value.
1212    #[serde(skip_serializing_if = "Option::is_none")]
1213    pub dataset_size_in_gb: Option<f64>,
1214
1215    /// Optional. Redis Serialization Protocol version. Must be compatible with Redis version.
1216    #[serde(skip_serializing_if = "Option::is_none")]
1217    pub resp_version: Option<String>,
1218
1219    #[serde(skip_serializing_if = "Option::is_none")]
1220    pub throughput_measurement: Option<DatabaseThroughputSpec>,
1221
1222    /// Optional. Type and rate of data persistence in persistent storage.
1223    #[serde(skip_serializing_if = "Option::is_none")]
1224    pub data_persistence: Option<String>,
1225
1226    /// Optional. Data eviction policy.
1227    #[serde(skip_serializing_if = "Option::is_none")]
1228    pub data_eviction_policy: Option<String>,
1229
1230    /// Optional. Turns database replication on or off.
1231    #[serde(skip_serializing_if = "Option::is_none")]
1232    pub replication: Option<bool>,
1233
1234    /// Optional. Hashing policy Regex rules. Used only if 'shardingType' is 'custom-regex-rules'.
1235    #[serde(skip_serializing_if = "Option::is_none")]
1236    pub regex_rules: Option<Vec<String>>,
1237
1238    /// Optional. This database will be a replica of the specified Redis databases provided as one or more URI(s). Example: 'redis://user:password@host:port'. If the URI provided is a Redis Cloud database, only host and port should be provided. Example: ['<redis://endpoint1:6379>', '<redis://endpoint2:6380>'].
1239    #[serde(skip_serializing_if = "Option::is_none")]
1240    pub replica_of: Option<Vec<String>>,
1241
1242    #[serde(skip_serializing_if = "Option::is_none")]
1243    pub replica: Option<ReplicaOfSpec>,
1244
1245    /// Optional. Support Redis [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api).
1246    #[serde(skip_serializing_if = "Option::is_none")]
1247    pub support_oss_cluster_api: Option<bool>,
1248
1249    /// Optional. If set to 'true', the database will use the external endpoint for OSS Cluster API. This setting blocks the database's private endpoint. Can only be set if 'supportOSSClusterAPI' is 'true'.
1250    #[serde(skip_serializing_if = "Option::is_none")]
1251    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
1252
1253    /// Optional. Changes the password used to access the database with the 'default' user. Can only be set if 'protocol' is 'redis'.
1254    #[serde(skip_serializing_if = "Option::is_none")]
1255    pub password: Option<String>,
1256
1257    /// Optional. Changes the Memcached (SASL) username to access the database. Can only be set if 'protocol' is 'memcached'.
1258    #[serde(skip_serializing_if = "Option::is_none")]
1259    pub sasl_username: Option<String>,
1260
1261    /// Optional. Changes the Memcached (SASL) password to access the database. Can only be set if 'protocol' is 'memcached'.
1262    #[serde(skip_serializing_if = "Option::is_none")]
1263    pub sasl_password: Option<String>,
1264
1265    /// Optional. List of source IP addresses or subnet masks to allow. If specified, Redis clients will be able to connect to this database only from within the specified source IP addresses ranges. Example: '['192.168.10.0/32', '192.168.12.0/24']'
1266    #[serde(skip_serializing_if = "Option::is_none")]
1267    pub source_ip: Option<Vec<String>>,
1268
1269    /// Optional. A public key client TLS/SSL certificate with new line characters replaced with '\n'. If specified, mTLS authentication will be required to authenticate user connections if it is not already required. If set to an empty string, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
1270    #[serde(skip_serializing_if = "Option::is_none")]
1271    pub client_ssl_certificate: Option<String>,
1272
1273    /// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections. If set to an empty list, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
1274    #[serde(skip_serializing_if = "Option::is_none")]
1275    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
1276
1277    /// Optional. When 'true', requires TLS authentication for all connections - mTLS with valid clientTlsCertificates, regular TLS when clientTlsCertificates is not provided. If enableTls is set to 'false' while mTLS is required, it will remove the mTLS requirement and erase previously provided clientTlsCertificates.
1278    #[serde(skip_serializing_if = "Option::is_none")]
1279    pub enable_tls: Option<bool>,
1280
1281    /// Optional. When 'true', allows connecting to the database with the 'default' user. When 'false', only defined access control users can connect to the database. Can only be set if 'protocol' is 'redis'.
1282    #[serde(skip_serializing_if = "Option::is_none")]
1283    pub enable_default_user: Option<bool>,
1284
1285    /// Optional. Changes the backup location path. If specified, the database will back up every 24 hours to this location, and you can manually back up the database to this location at any time. If set to an empty string, the backup path will be removed.
1286    #[serde(skip_serializing_if = "Option::is_none")]
1287    pub periodic_backup_path: Option<String>,
1288
1289    #[serde(skip_serializing_if = "Option::is_none")]
1290    pub remote_backup: Option<DatabaseBackupConfig>,
1291
1292    /// Optional. Changes Redis database alert details.
1293    #[serde(skip_serializing_if = "Option::is_none")]
1294    pub alerts: Option<Vec<DatabaseAlertSpec>>,
1295
1296    #[serde(skip_serializing_if = "Option::is_none")]
1297    pub command_type: Option<String>,
1298
1299    /// Optional. Changes the query performance factor. The query performance factor adds extra compute power specifically for search and query databases. You can increase your queries per second by the selected factor.
1300    #[serde(skip_serializing_if = "Option::is_none")]
1301    pub query_performance_factor: Option<String>,
1302}
1303
1304// ============================================================================
1305// Handler
1306// ============================================================================
1307
1308/// Handler for Pro database operations
1309///
1310/// Manages database lifecycle, configuration, backup/restore, import/export,
1311/// and monitoring for Redis Cloud Pro subscriptions.
1312pub struct DatabaseHandler {
1313    client: CloudClient,
1314}
1315
1316impl DatabaseHandler {
1317    /// Create a new handler
1318    #[must_use]
1319    pub fn new(client: CloudClient) -> Self {
1320        Self { client }
1321    }
1322
1323    /// Get all databases in a Pro subscription
1324    ///
1325    /// Gets a list of all databases in the specified Pro subscription.
1326    ///
1327    /// GET /subscriptions/{subscriptionId}/databases
1328    ///
1329    /// # Arguments
1330    ///
1331    /// * `subscription_id` - The subscription ID
1332    /// * `offset` - Optional offset for pagination
1333    /// * `limit` - Optional limit for pagination
1334    ///
1335    /// # Example
1336    ///
1337    /// ```no_run
1338    /// use redis_cloud::CloudClient;
1339    ///
1340    /// # async fn example() -> redis_cloud::Result<()> {
1341    /// let client = CloudClient::builder()
1342    ///     .api_key("your-api-key")
1343    ///     .api_secret("your-api-secret")
1344    ///     .build()?;
1345    ///
1346    /// // Get all databases in subscription 123
1347    /// let databases = client.databases().get_subscription_databases(123, None, None).await?;
1348    ///
1349    /// // With pagination
1350    /// let page = client.databases().get_subscription_databases(123, Some(0), Some(10)).await?;
1351    /// # Ok(())
1352    /// # }
1353    /// ```
1354    pub async fn get_subscription_databases(
1355        &self,
1356        subscription_id: i32,
1357        offset: Option<i32>,
1358        limit: Option<i32>,
1359    ) -> Result<AccountSubscriptionDatabases> {
1360        let mut query = Vec::new();
1361        if let Some(v) = offset {
1362            query.push(format!("offset={v}"));
1363        }
1364        if let Some(v) = limit {
1365            query.push(format!("limit={v}"));
1366        }
1367        let query_string = if query.is_empty() {
1368            String::new()
1369        } else {
1370            format!("?{}", query.join("&"))
1371        };
1372        self.client
1373            .get(&format!(
1374                "/subscriptions/{subscription_id}/databases{query_string}"
1375            ))
1376            .await
1377    }
1378
1379    /// Create Pro database in existing subscription
1380    /// Creates a new database in an existing Pro subscription.
1381    ///
1382    /// POST /subscriptions/{subscriptionId}/databases
1383    pub async fn create_database(
1384        &self,
1385        subscription_id: i32,
1386        request: &DatabaseCreateRequest,
1387    ) -> Result<TaskStateUpdate> {
1388        self.client
1389            .post(
1390                &format!("/subscriptions/{subscription_id}/databases"),
1391                request,
1392            )
1393            .await
1394    }
1395
1396    /// Delete Pro database
1397    /// Deletes a database from a Pro subscription.
1398    ///
1399    /// DELETE /subscriptions/{subscriptionId}/databases/{databaseId}
1400    pub async fn delete_database_by_id(
1401        &self,
1402        subscription_id: i32,
1403        database_id: i32,
1404    ) -> Result<TaskStateUpdate> {
1405        let response = self
1406            .client
1407            .delete_raw(&format!(
1408                "/subscriptions/{subscription_id}/databases/{database_id}"
1409            ))
1410            .await?;
1411        serde_json::from_value(response).map_err(Into::into)
1412    }
1413
1414    /// Get a single Pro database
1415    ///
1416    /// Gets details and settings of a single database in a Pro subscription.
1417    ///
1418    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}
1419    ///
1420    /// # Example
1421    ///
1422    /// ```no_run
1423    /// use redis_cloud::CloudClient;
1424    ///
1425    /// # async fn example() -> redis_cloud::Result<()> {
1426    /// let client = CloudClient::builder()
1427    ///     .api_key("your-api-key")
1428    ///     .api_secret("your-api-secret")
1429    ///     .build()?;
1430    ///
1431    /// let database = client.databases().get_subscription_database_by_id(123, 456).await?;
1432    ///
1433    /// println!("Database: {} (status: {:?})",
1434    ///     database.name.unwrap_or_default(),
1435    ///     database.status);
1436    /// # Ok(())
1437    /// # }
1438    /// ```
1439    pub async fn get_subscription_database_by_id(
1440        &self,
1441        subscription_id: i32,
1442        database_id: i32,
1443    ) -> Result<Database> {
1444        self.client
1445            .get(&format!(
1446                "/subscriptions/{subscription_id}/databases/{database_id}"
1447            ))
1448            .await
1449    }
1450
1451    /// Update Pro database
1452    /// Updates an existing Pro database.
1453    ///
1454    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}
1455    pub async fn update_database(
1456        &self,
1457        subscription_id: i32,
1458        database_id: i32,
1459        request: &DatabaseUpdateRequest,
1460    ) -> Result<TaskStateUpdate> {
1461        self.client
1462            .put(
1463                &format!("/subscriptions/{subscription_id}/databases/{database_id}"),
1464                request,
1465            )
1466            .await
1467    }
1468
1469    /// Get Pro database backup status
1470    /// Gets information on the latest backup attempt for this Pro database.
1471    ///
1472    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/backup
1473    pub async fn get_database_backup_status(
1474        &self,
1475        subscription_id: i32,
1476        database_id: i32,
1477        region_name: Option<String>,
1478    ) -> Result<TaskStateUpdate> {
1479        let mut query = Vec::new();
1480        if let Some(v) = region_name {
1481            query.push(format!("regionName={v}"));
1482        }
1483        let query_string = if query.is_empty() {
1484            String::new()
1485        } else {
1486            format!("?{}", query.join("&"))
1487        };
1488        self.client
1489            .get(&format!(
1490                "/subscriptions/{subscription_id}/databases/{database_id}/backup{query_string}"
1491            ))
1492            .await
1493    }
1494
1495    /// Back up Pro database
1496    /// Manually back up the specified Pro database to a backup path. By default, backups will be stored in the 'remoteBackup' location for this database.
1497    ///
1498    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/backup
1499    pub async fn backup_database(
1500        &self,
1501        subscription_id: i32,
1502        database_id: i32,
1503        request: &DatabaseBackupRequest,
1504    ) -> Result<TaskStateUpdate> {
1505        self.client
1506            .post(
1507                &format!("/subscriptions/{subscription_id}/databases/{database_id}/backup"),
1508                request,
1509            )
1510            .await
1511    }
1512
1513    /// Get Pro database TLS certificate
1514    /// Gets the X.509 PEM (base64) encoded server certificate for TLS connection to the database. Requires 'enableTLS' to be 'true' for the database.
1515    ///
1516    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/certificate
1517    pub async fn get_subscription_database_certificate(
1518        &self,
1519        subscription_id: i32,
1520        database_id: i32,
1521    ) -> Result<DatabaseCertificate> {
1522        self.client
1523            .get(&format!(
1524                "/subscriptions/{subscription_id}/databases/{database_id}/certificate"
1525            ))
1526            .await
1527    }
1528
1529    /// Flush Pro database
1530    /// Deletes all data from the specified Pro database.
1531    ///
1532    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/flush
1533    pub async fn flush_crdb(
1534        &self,
1535        subscription_id: i32,
1536        database_id: i32,
1537        request: &CrdbFlushRequest,
1538    ) -> Result<TaskStateUpdate> {
1539        self.client
1540            .put(
1541                &format!("/subscriptions/{subscription_id}/databases/{database_id}/flush"),
1542                request,
1543            )
1544            .await
1545    }
1546
1547    /// Get Pro database import status
1548    /// Gets information on the latest import attempt for this Pro database.
1549    ///
1550    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/import
1551    pub async fn get_database_import_status(
1552        &self,
1553        subscription_id: i32,
1554        database_id: i32,
1555    ) -> Result<TaskStateUpdate> {
1556        self.client
1557            .get(&format!(
1558                "/subscriptions/{subscription_id}/databases/{database_id}/import"
1559            ))
1560            .await
1561    }
1562
1563    /// Import data to a Pro database
1564    /// Imports data from an RDB file or from a different Redis database into this Pro database. WARNING: Importing data into a database removes all existing data from the database.
1565    ///
1566    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/import
1567    pub async fn import_database(
1568        &self,
1569        subscription_id: i32,
1570        database_id: i32,
1571        request: &DatabaseImportRequest,
1572    ) -> Result<TaskStateUpdate> {
1573        self.client
1574            .post(
1575                &format!("/subscriptions/{subscription_id}/databases/{database_id}/import"),
1576                request,
1577            )
1578            .await
1579    }
1580
1581    /// Update Active-Active database
1582    /// (Active-Active databases only) Updates database properties for an Active-Active database.
1583    ///
1584    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/regions
1585    pub async fn update_crdb_local_properties(
1586        &self,
1587        subscription_id: i32,
1588        database_id: i32,
1589        request: &CrdbUpdatePropertiesRequest,
1590    ) -> Result<TaskStateUpdate> {
1591        self.client
1592            .put(
1593                &format!("/subscriptions/{subscription_id}/databases/{database_id}/regions"),
1594                request,
1595            )
1596            .await
1597    }
1598
1599    /// Get database slowlog
1600    /// Gets the slowlog for a specific database.
1601    ///
1602    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/slow-log
1603    pub async fn get_slow_log(
1604        &self,
1605        subscription_id: i32,
1606        database_id: i32,
1607        region_name: Option<String>,
1608    ) -> Result<DatabaseSlowLogEntries> {
1609        let mut query = Vec::new();
1610        if let Some(v) = region_name {
1611            query.push(format!("regionName={v}"));
1612        }
1613        let query_string = if query.is_empty() {
1614            String::new()
1615        } else {
1616            format!("?{}", query.join("&"))
1617        };
1618        self.client
1619            .get(&format!(
1620                "/subscriptions/{subscription_id}/databases/{database_id}/slow-log{query_string}"
1621            ))
1622            .await
1623    }
1624
1625    /// Get database tags
1626    /// Gets a list of all database tags.
1627    ///
1628    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/tags
1629    pub async fn get_tags(&self, subscription_id: i32, database_id: i32) -> Result<CloudTags> {
1630        self.client
1631            .get(&format!(
1632                "/subscriptions/{subscription_id}/databases/{database_id}/tags"
1633            ))
1634            .await
1635    }
1636
1637    /// Add a database tag
1638    /// Adds a single database tag to a database.
1639    ///
1640    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/tags
1641    pub async fn create_tag(
1642        &self,
1643        subscription_id: i32,
1644        database_id: i32,
1645        request: &DatabaseTagCreateRequest,
1646    ) -> Result<CloudTag> {
1647        self.client
1648            .post(
1649                &format!("/subscriptions/{subscription_id}/databases/{database_id}/tags"),
1650                request,
1651            )
1652            .await
1653    }
1654
1655    /// Overwrite database tags
1656    /// Overwrites all tags on the database.
1657    ///
1658    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/tags
1659    pub async fn update_tags(
1660        &self,
1661        subscription_id: i32,
1662        database_id: i32,
1663        request: &DatabaseTagsUpdateRequest,
1664    ) -> Result<CloudTags> {
1665        self.client
1666            .put(
1667                &format!("/subscriptions/{subscription_id}/databases/{database_id}/tags"),
1668                request,
1669            )
1670            .await
1671    }
1672
1673    /// Delete database tag
1674    /// Removes the specified tag from the database.
1675    ///
1676    /// DELETE /subscriptions/{subscriptionId}/databases/{databaseId}/tags/{tagKey}
1677    pub async fn delete_tag(
1678        &self,
1679        subscription_id: i32,
1680        database_id: i32,
1681        tag_key: String,
1682    ) -> Result<HashMap<String, Value>> {
1683        let response = self
1684            .client
1685            .delete_raw(&format!(
1686                "/subscriptions/{subscription_id}/databases/{database_id}/tags/{tag_key}"
1687            ))
1688            .await?;
1689        serde_json::from_value(response).map_err(Into::into)
1690    }
1691
1692    /// Update database tag value
1693    /// Updates the value of the specified database tag.
1694    ///
1695    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/tags/{tagKey}
1696    pub async fn update_tag(
1697        &self,
1698        subscription_id: i32,
1699        database_id: i32,
1700        tag_key: String,
1701        request: &DatabaseTagUpdateRequest,
1702    ) -> Result<CloudTag> {
1703        self.client
1704            .put(
1705                &format!("/subscriptions/{subscription_id}/databases/{database_id}/tags/{tag_key}"),
1706                request,
1707            )
1708            .await
1709    }
1710
1711    /// Get Pro database version upgrade status
1712    /// Gets information on the latest upgrade attempt for this Pro database.
1713    ///
1714    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/upgrade
1715    pub async fn get_database_redis_version_upgrade_status(
1716        &self,
1717        subscription_id: i32,
1718        database_id: i32,
1719    ) -> Result<BdbVersionUpgradeStatus> {
1720        self.client
1721            .get(&format!(
1722                "/subscriptions/{subscription_id}/databases/{database_id}/upgrade"
1723            ))
1724            .await
1725    }
1726
1727    /// Upgrade Pro database version
1728    ///
1729    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/upgrade
1730    pub async fn upgrade_database_redis_version(
1731        &self,
1732        subscription_id: i32,
1733        database_id: i32,
1734        request: &DatabaseUpgradeRedisVersionRequest,
1735    ) -> Result<TaskStateUpdate> {
1736        self.client
1737            .post(
1738                &format!("/subscriptions/{subscription_id}/databases/{database_id}/upgrade"),
1739                request,
1740            )
1741            .await
1742    }
1743
1744    /// Get available target Redis versions for upgrade
1745    /// Gets a list of Redis versions that the database can be upgraded to.
1746    ///
1747    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/available-target-versions
1748    pub async fn get_available_target_versions(
1749        &self,
1750        subscription_id: i32,
1751        database_id: i32,
1752    ) -> Result<Value> {
1753        self.client
1754            .get_raw(&format!(
1755                "/subscriptions/{subscription_id}/databases/{database_id}/available-target-versions"
1756            ))
1757            .await
1758    }
1759
1760    /// Flush Pro database (standard, non-Active-Active)
1761    /// Deletes all data from the specified Pro database.
1762    ///
1763    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/flush
1764    pub async fn flush_database(
1765        &self,
1766        subscription_id: i32,
1767        database_id: i32,
1768    ) -> Result<TaskStateUpdate> {
1769        // Empty body for standard flush
1770        self.client
1771            .put_raw(
1772                &format!("/subscriptions/{subscription_id}/databases/{database_id}/flush"),
1773                serde_json::json!({}),
1774            )
1775            .await
1776            .and_then(|v| serde_json::from_value(v).map_err(Into::into))
1777    }
1778
1779    // ========================================================================
1780    // Pagination Helpers
1781    // ========================================================================
1782
1783    /// Stream all databases in a Pro subscription
1784    ///
1785    /// Returns an async stream that automatically handles pagination, yielding
1786    /// individual [`Database`] items. This is useful when you need to process
1787    /// a large number of databases without loading them all into memory at once.
1788    ///
1789    /// # Arguments
1790    ///
1791    /// * `subscription_id` - The subscription ID
1792    ///
1793    /// # Example
1794    ///
1795    /// ```no_run
1796    /// use redis_cloud::CloudClient;
1797    /// use futures::StreamExt;
1798    /// use std::pin::pin;
1799    ///
1800    /// # async fn example() -> redis_cloud::Result<()> {
1801    /// let client = CloudClient::builder()
1802    ///     .api_key("your-api-key")
1803    ///     .api_secret("your-api-secret")
1804    ///     .build()?;
1805    ///
1806    /// let handler = client.databases();
1807    /// let mut stream = pin!(handler.stream_databases(123));
1808    /// while let Some(result) = stream.next().await {
1809    ///     let database = result?;
1810    ///     println!("Database: {} (ID: {})", database.name.unwrap_or_default(), database.database_id);
1811    /// }
1812    /// # Ok(())
1813    /// # }
1814    /// ```
1815    pub fn stream_databases(
1816        &self,
1817        subscription_id: i32,
1818    ) -> impl Stream<Item = Result<Database>> + '_ {
1819        self.stream_databases_with_page_size(subscription_id, 100)
1820    }
1821
1822    /// Stream all databases with custom page size
1823    ///
1824    /// Like [`stream_databases`](Self::stream_databases), but allows specifying
1825    /// the page size for API requests.
1826    ///
1827    /// # Arguments
1828    ///
1829    /// * `subscription_id` - The subscription ID
1830    /// * `page_size` - Number of databases to fetch per API request
1831    pub fn stream_databases_with_page_size(
1832        &self,
1833        subscription_id: i32,
1834        page_size: i32,
1835    ) -> impl Stream<Item = Result<Database>> + '_ {
1836        try_stream! {
1837            let mut offset = 0;
1838
1839            loop {
1840                let response = self
1841                    .get_subscription_databases(subscription_id, Some(offset), Some(page_size))
1842                    .await?;
1843
1844                // Extract databases from the response
1845                let databases = Self::extract_databases_from_response(&response);
1846
1847                if databases.is_empty() {
1848                    break;
1849                }
1850
1851                let count = databases.len();
1852                for db in databases {
1853                    yield db;
1854                }
1855
1856                // If we got fewer than page_size, we've reached the end
1857                #[allow(clippy::cast_sign_loss)]
1858                if count < page_size as usize {
1859                    break;
1860                }
1861
1862                offset += page_size;
1863            }
1864        }
1865    }
1866
1867    /// Get all databases in a subscription (collected)
1868    ///
1869    /// Fetches all databases by automatically handling pagination and returns
1870    /// them as a single vector. Use [`stream_databases`](Self::stream_databases)
1871    /// if you prefer to process databases one at a time.
1872    ///
1873    /// # Arguments
1874    ///
1875    /// * `subscription_id` - The subscription ID
1876    ///
1877    /// # Example
1878    ///
1879    /// ```no_run
1880    /// use redis_cloud::CloudClient;
1881    ///
1882    /// # async fn example() -> redis_cloud::Result<()> {
1883    /// let client = CloudClient::builder()
1884    ///     .api_key("your-api-key")
1885    ///     .api_secret("your-api-secret")
1886    ///     .build()?;
1887    ///
1888    /// let all_databases = client.databases().get_all_databases(123).await?;
1889    /// println!("Total databases: {}", all_databases.len());
1890    /// # Ok(())
1891    /// # }
1892    /// ```
1893    pub async fn get_all_databases(&self, subscription_id: i32) -> Result<Vec<Database>> {
1894        let mut databases = Vec::new();
1895        let mut offset = 0;
1896        let page_size = 100;
1897
1898        loop {
1899            let response = self
1900                .get_subscription_databases(subscription_id, Some(offset), Some(page_size))
1901                .await?;
1902
1903            let page = Self::extract_databases_from_response(&response);
1904            let count = page.len();
1905            databases.extend(page);
1906
1907            #[allow(clippy::cast_sign_loss)]
1908            if count < page_size as usize {
1909                break;
1910            }
1911            offset += page_size;
1912        }
1913
1914        Ok(databases)
1915    }
1916
1917    /// Extract databases from an `AccountSubscriptionDatabases` response
1918    fn extract_databases_from_response(response: &AccountSubscriptionDatabases) -> Vec<Database> {
1919        response
1920            .subscription
1921            .first()
1922            .map(|sub| sub.databases.clone())
1923            .unwrap_or_default()
1924    }
1925}