opendal 0.1.6.pre.rc.1-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.standard.yml +20 -0
- data/.tool-versions +1 -0
- data/.yardopts +1 -0
- data/DEPENDENCIES.md +9 -0
- data/DEPENDENCIES.rust.tsv +277 -0
- data/Gemfile +35 -0
- data/README.md +159 -0
- data/Rakefile +149 -0
- data/core/CHANGELOG.md +4929 -0
- data/core/CONTRIBUTING.md +61 -0
- data/core/DEPENDENCIES.md +3 -0
- data/core/DEPENDENCIES.rust.tsv +185 -0
- data/core/LICENSE +201 -0
- data/core/README.md +228 -0
- data/core/benches/README.md +18 -0
- data/core/benches/ops/README.md +26 -0
- data/core/benches/types/README.md +9 -0
- data/core/benches/vs_fs/README.md +35 -0
- data/core/benches/vs_s3/README.md +55 -0
- data/core/edge/README.md +3 -0
- data/core/edge/file_write_on_full_disk/README.md +14 -0
- data/core/edge/s3_aws_assume_role_with_web_identity/README.md +18 -0
- data/core/edge/s3_read_on_wasm/.gitignore +3 -0
- data/core/edge/s3_read_on_wasm/README.md +42 -0
- data/core/edge/s3_read_on_wasm/webdriver.json +15 -0
- data/core/examples/README.md +23 -0
- data/core/examples/basic/README.md +15 -0
- data/core/examples/concurrent-upload/README.md +15 -0
- data/core/examples/multipart-upload/README.md +15 -0
- data/core/fuzz/.gitignore +5 -0
- data/core/fuzz/README.md +68 -0
- data/core/src/docs/comparisons/vs_object_store.md +183 -0
- data/core/src/docs/performance/concurrent_write.md +101 -0
- data/core/src/docs/performance/http_optimization.md +124 -0
- data/core/src/docs/rfcs/0000_example.md +74 -0
- data/core/src/docs/rfcs/0000_foyer_integration.md +111 -0
- data/core/src/docs/rfcs/0041_object_native_api.md +185 -0
- data/core/src/docs/rfcs/0044_error_handle.md +198 -0
- data/core/src/docs/rfcs/0057_auto_region.md +160 -0
- data/core/src/docs/rfcs/0069_object_stream.md +145 -0
- data/core/src/docs/rfcs/0090_limited_reader.md +155 -0
- data/core/src/docs/rfcs/0112_path_normalization.md +79 -0
- data/core/src/docs/rfcs/0191_async_streaming_io.md +328 -0
- data/core/src/docs/rfcs/0203_remove_credential.md +96 -0
- data/core/src/docs/rfcs/0221_create_dir.md +89 -0
- data/core/src/docs/rfcs/0247_retryable_error.md +87 -0
- data/core/src/docs/rfcs/0293_object_id.md +67 -0
- data/core/src/docs/rfcs/0337_dir_entry.md +191 -0
- data/core/src/docs/rfcs/0409_accessor_capabilities.md +67 -0
- data/core/src/docs/rfcs/0413_presign.md +154 -0
- data/core/src/docs/rfcs/0423_command_line_interface.md +268 -0
- data/core/src/docs/rfcs/0429_init_from_iter.md +107 -0
- data/core/src/docs/rfcs/0438_multipart.md +163 -0
- data/core/src/docs/rfcs/0443_gateway.md +73 -0
- data/core/src/docs/rfcs/0501_new_builder.md +111 -0
- data/core/src/docs/rfcs/0554_write_refactor.md +96 -0
- data/core/src/docs/rfcs/0561_list_metadata_reuse.md +210 -0
- data/core/src/docs/rfcs/0599_blocking_api.md +157 -0
- data/core/src/docs/rfcs/0623_redis_service.md +300 -0
- data/core/src/docs/rfcs/0627_split_capabilities.md +89 -0
- data/core/src/docs/rfcs/0661_path_in_accessor.md +126 -0
- data/core/src/docs/rfcs/0793_generic_kv_services.md +209 -0
- data/core/src/docs/rfcs/0926_object_reader.md +93 -0
- data/core/src/docs/rfcs/0977_refactor_error.md +151 -0
- data/core/src/docs/rfcs/1085_object_handler.md +73 -0
- data/core/src/docs/rfcs/1391_object_metadataer.md +110 -0
- data/core/src/docs/rfcs/1398_query_based_metadata.md +125 -0
- data/core/src/docs/rfcs/1420_object_writer.md +147 -0
- data/core/src/docs/rfcs/1477_remove_object_concept.md +159 -0
- data/core/src/docs/rfcs/1735_operation_extension.md +117 -0
- data/core/src/docs/rfcs/2083_writer_sink_api.md +106 -0
- data/core/src/docs/rfcs/2133_append_api.md +88 -0
- data/core/src/docs/rfcs/2299_chain_based_operator_api.md +99 -0
- data/core/src/docs/rfcs/2602_object_versioning.md +138 -0
- data/core/src/docs/rfcs/2758_merge_append_into_write.md +79 -0
- data/core/src/docs/rfcs/2774_lister_api.md +66 -0
- data/core/src/docs/rfcs/2779_list_with_metakey.md +143 -0
- data/core/src/docs/rfcs/2852_native_capability.md +58 -0
- data/core/src/docs/rfcs/2884_merge_range_read_into_read.md +80 -0
- data/core/src/docs/rfcs/3017_remove_write_copy_from.md +94 -0
- data/core/src/docs/rfcs/3197_config.md +237 -0
- data/core/src/docs/rfcs/3232_align_list_api.md +69 -0
- data/core/src/docs/rfcs/3243_list_prefix.md +128 -0
- data/core/src/docs/rfcs/3356_lazy_reader.md +111 -0
- data/core/src/docs/rfcs/3526_list_recursive.md +59 -0
- data/core/src/docs/rfcs/3574_concurrent_stat_in_list.md +80 -0
- data/core/src/docs/rfcs/3734_buffered_reader.md +64 -0
- data/core/src/docs/rfcs/3898_concurrent_writer.md +66 -0
- data/core/src/docs/rfcs/3911_deleter_api.md +165 -0
- data/core/src/docs/rfcs/4382_range_based_read.md +213 -0
- data/core/src/docs/rfcs/4638_executor.md +215 -0
- data/core/src/docs/rfcs/5314_remove_metakey.md +120 -0
- data/core/src/docs/rfcs/5444_operator_from_uri.md +162 -0
- data/core/src/docs/rfcs/5479_context.md +140 -0
- data/core/src/docs/rfcs/5485_conditional_reader.md +112 -0
- data/core/src/docs/rfcs/5495_list_with_deleted.md +81 -0
- data/core/src/docs/rfcs/5556_write_returns_metadata.md +121 -0
- data/core/src/docs/rfcs/5871_read_returns_metadata.md +112 -0
- data/core/src/docs/rfcs/6189_remove_native_blocking.md +106 -0
- data/core/src/docs/rfcs/6209_glob_support.md +132 -0
- data/core/src/docs/rfcs/6213_options_api.md +142 -0
- data/core/src/docs/rfcs/README.md +62 -0
- data/core/src/docs/upgrade.md +1556 -0
- data/core/src/services/aliyun_drive/docs.md +61 -0
- data/core/src/services/alluxio/docs.md +45 -0
- data/core/src/services/azblob/docs.md +77 -0
- data/core/src/services/azdls/docs.md +73 -0
- data/core/src/services/azfile/docs.md +65 -0
- data/core/src/services/b2/docs.md +54 -0
- data/core/src/services/cacache/docs.md +38 -0
- data/core/src/services/cloudflare_kv/docs.md +21 -0
- data/core/src/services/cos/docs.md +55 -0
- data/core/src/services/d1/docs.md +48 -0
- data/core/src/services/dashmap/docs.md +38 -0
- data/core/src/services/dbfs/docs.md +57 -0
- data/core/src/services/dropbox/docs.md +64 -0
- data/core/src/services/etcd/docs.md +45 -0
- data/core/src/services/foundationdb/docs.md +42 -0
- data/core/src/services/fs/docs.md +49 -0
- data/core/src/services/ftp/docs.md +42 -0
- data/core/src/services/gcs/docs.md +76 -0
- data/core/src/services/gdrive/docs.md +65 -0
- data/core/src/services/ghac/docs.md +84 -0
- data/core/src/services/github/docs.md +52 -0
- data/core/src/services/gridfs/docs.md +46 -0
- data/core/src/services/hdfs/docs.md +140 -0
- data/core/src/services/hdfs_native/docs.md +35 -0
- data/core/src/services/http/docs.md +45 -0
- data/core/src/services/huggingface/docs.md +61 -0
- data/core/src/services/ipfs/docs.md +45 -0
- data/core/src/services/ipmfs/docs.md +14 -0
- data/core/src/services/koofr/docs.md +51 -0
- data/core/src/services/lakefs/docs.md +62 -0
- data/core/src/services/memcached/docs.md +47 -0
- data/core/src/services/memory/docs.md +36 -0
- data/core/src/services/mini_moka/docs.md +19 -0
- data/core/src/services/moka/docs.md +42 -0
- data/core/src/services/mongodb/docs.md +49 -0
- data/core/src/services/monoiofs/docs.md +46 -0
- data/core/src/services/mysql/docs.md +47 -0
- data/core/src/services/obs/docs.md +54 -0
- data/core/src/services/onedrive/docs.md +115 -0
- data/core/src/services/opfs/docs.md +18 -0
- data/core/src/services/oss/docs.md +74 -0
- data/core/src/services/pcloud/docs.md +51 -0
- data/core/src/services/persy/docs.md +43 -0
- data/core/src/services/postgresql/docs.md +47 -0
- data/core/src/services/redb/docs.md +41 -0
- data/core/src/services/redis/docs.md +43 -0
- data/core/src/services/rocksdb/docs.md +54 -0
- data/core/src/services/s3/compatible_services.md +126 -0
- data/core/src/services/s3/docs.md +244 -0
- data/core/src/services/seafile/docs.md +54 -0
- data/core/src/services/sftp/docs.md +49 -0
- data/core/src/services/sled/docs.md +39 -0
- data/core/src/services/sqlite/docs.md +46 -0
- data/core/src/services/surrealdb/docs.md +54 -0
- data/core/src/services/swift/compatible_services.md +53 -0
- data/core/src/services/swift/docs.md +52 -0
- data/core/src/services/tikv/docs.md +43 -0
- data/core/src/services/upyun/docs.md +51 -0
- data/core/src/services/vercel_artifacts/docs.md +40 -0
- data/core/src/services/vercel_blob/docs.md +45 -0
- data/core/src/services/webdav/docs.md +49 -0
- data/core/src/services/webhdfs/docs.md +90 -0
- data/core/src/services/yandex_disk/docs.md +45 -0
- data/core/tests/behavior/README.md +77 -0
- data/core/tests/data/normal_dir/.gitkeep +0 -0
- data/core/tests/data/normal_file.txt +1041 -0
- data/core/tests/data/special_dir !@#$%^&()_+-=;',/.gitkeep +0 -0
- data/core/tests/data/special_file !@#$%^&()_+-=;',.txt +1041 -0
- data/core/users.md +13 -0
- data/extconf.rb +24 -0
- data/lib/opendal.rb +25 -0
- data/lib/opendal_ruby/entry.rb +35 -0
- data/lib/opendal_ruby/io.rb +70 -0
- data/lib/opendal_ruby/metadata.rb +44 -0
- data/lib/opendal_ruby/opendal_ruby.so +0 -0
- data/lib/opendal_ruby/operator.rb +29 -0
- data/lib/opendal_ruby/operator_info.rb +26 -0
- data/opendal.gemspec +91 -0
- data/test/blocking_op_test.rb +112 -0
- data/test/capability_test.rb +42 -0
- data/test/io_test.rb +172 -0
- data/test/lister_test.rb +77 -0
- data/test/metadata_test.rb +78 -0
- data/test/middlewares_test.rb +46 -0
- data/test/operator_info_test.rb +35 -0
- data/test/test_helper.rb +36 -0
- metadata +240 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
- Proposal Name: `redis_service`
|
|
2
|
+
- Start Date: 2022-08-31
|
|
3
|
+
- RFC PR: [apache/opendal#0623](https://github.com/apache/opendal/pull/0623)
|
|
4
|
+
- Tracking Issue: [apache/opendal#641](https://github.com/apache/opendal/issues/0641)
|
|
5
|
+
|
|
6
|
+
# Summary
|
|
7
|
+
|
|
8
|
+
Use [redis](https://redis.io) as a service of OpenDAL.
|
|
9
|
+
|
|
10
|
+
# Motivation
|
|
11
|
+
|
|
12
|
+
Redis is a fast, in-memory cache with persistent and distributed storage functionalities. It's widely used in production.
|
|
13
|
+
|
|
14
|
+
Users also demand more backend support. Redis is a good candidate.
|
|
15
|
+
|
|
16
|
+
# Guide-level explanation
|
|
17
|
+
|
|
18
|
+
Users only need to provide the network address, username and password to create a Redis Operator. Then everything will act as other operators do.
|
|
19
|
+
|
|
20
|
+
```rust
|
|
21
|
+
use opendal::services::redis::Builder;
|
|
22
|
+
use opendal::Operator;
|
|
23
|
+
|
|
24
|
+
let builder = Builder::default();
|
|
25
|
+
|
|
26
|
+
// set the endpoint of redis server
|
|
27
|
+
builder.endpoint("tcps://domain.to.redis:2333");
|
|
28
|
+
// set the username of redis
|
|
29
|
+
builder.username("example");
|
|
30
|
+
// set the password
|
|
31
|
+
builder.password(&std::env::var("OPENDAL_REDIS_PASSWORD").expect("env OPENDAL_REDIS_PASSWORD not set"));
|
|
32
|
+
// root path
|
|
33
|
+
builder.root("/example/");
|
|
34
|
+
|
|
35
|
+
let op = Operator::new(
|
|
36
|
+
builder.build()? // services::redis::Backend
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// congratulations, you can use `op` just like any other operators!
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
# Reference-level explanation
|
|
43
|
+
|
|
44
|
+
To ease the development, [redis-rs](https://crates.io/crates/redis) will be used.
|
|
45
|
+
|
|
46
|
+
Redis offers a key-value view, so the path of files could be represented as the key of the key-value pair.
|
|
47
|
+
|
|
48
|
+
The content of file will be represented directly as `String`, and metadata will be encoded as [`bincode`](https://github.com/bincode-org/bincode.git) before storing as `String`.
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
+------------------------------------------+
|
|
52
|
+
|Object: /home/monika/ |
|
|
53
|
+
| | SET
|
|
54
|
+
|child: Key: v0:k:/home/monika/ -+---------> 1) /home/monika/poem0.txt
|
|
55
|
+
| |
|
|
56
|
+
|/* directory has no content */ |
|
|
57
|
+
| |
|
|
58
|
+
|metadata: Key: v0:m:/home/monika/ |
|
|
59
|
+
+------------------------------------------+
|
|
60
|
+
|
|
61
|
+
+------------------------------------------+
|
|
62
|
+
|Object: /home/monika/poem0.txt |
|
|
63
|
+
| |
|
|
64
|
+
| /* file has no children */ |
|
|
65
|
+
| |
|
|
66
|
+
|content: Key: v0:c:/home/monika/poem0.txt-+--+
|
|
67
|
+
| | |
|
|
68
|
+
|metadata: Key: v0:m:/home/monika/poem0.txt| |
|
|
69
|
+
| | | |
|
|
70
|
+
+--+---------------------------------------+ |
|
|
71
|
+
| v
|
|
72
|
+
+> STRING STRING
|
|
73
|
+
+----------------------+ +--------------------+
|
|
74
|
+
|\x00\x00\x00\x00\xe6\a| |1JU5T3MON1K413097321|
|
|
75
|
+
|\x00\x00\xf8\x00\a4)!V| |&JU5$T!M0N1K4$%#@#$%|
|
|
76
|
+
|\x81&\x00\x00\x00Q\x00| |3231J)U_ST#MONIKA@#$|
|
|
77
|
+
| ... | |1557(m0N1ka3just4M |
|
|
78
|
+
+----------------------+ | ... |
|
|
79
|
+
+--------------------+
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The [redis-rs](https://crates.io/crates/redis)'s high level APIs is preferred.
|
|
83
|
+
|
|
84
|
+
```rust
|
|
85
|
+
const VERSION: usize = 0;
|
|
86
|
+
|
|
87
|
+
/// meta_key will produce the key to object's metadata
|
|
88
|
+
/// "/path/to/object/" -> "v{VERSION}:m:/path/to/object"
|
|
89
|
+
fn meta_key(path: &str) -> String {
|
|
90
|
+
format!("v{}:m:{}", VERSION, path)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// content_key will produce the key to object's content
|
|
94
|
+
/// "/path/to/object/" -> "v{VERSION}:c:/path/to/object"
|
|
95
|
+
fn content_key(path: &str) -> String {
|
|
96
|
+
format!("v{}:c:{}", VERSION, path)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn connect() -> Result<()> {
|
|
100
|
+
let client = redis::Client::open("redis://localhost:6379")?;
|
|
101
|
+
let con = client.get_async_connection()?;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Forward Compatibility
|
|
106
|
+
|
|
107
|
+
All keys used will have a `v0` prefix, indicating it's using the very first version of `OpenDAL` `Redis` API.
|
|
108
|
+
|
|
109
|
+
When there are changes to the layout, like refactoring the layout of storage, the version number should be updated, too. Further versions should take the compatibility with former implementations into consideration.
|
|
110
|
+
|
|
111
|
+
## Create File
|
|
112
|
+
|
|
113
|
+
If user is creating a file with root `/home/monika/`, and relative path `poem0.txt`.
|
|
114
|
+
|
|
115
|
+
```rust
|
|
116
|
+
// mode: ObjectMode
|
|
117
|
+
// path: relative path string
|
|
118
|
+
let path = get_abs_path(path); // /home/monika/ <> /poem.txt -> /home/monika/poem.txt
|
|
119
|
+
let m_path = meta_key(path); // path of metadata
|
|
120
|
+
let c_path = content_key(path); // path of content
|
|
121
|
+
let last_modified = OffsetDatetime::now_utc().to_string();
|
|
122
|
+
|
|
123
|
+
let mut meta = ObjectMeta::default();
|
|
124
|
+
meta.set_mode(ObjectMode::FILE);
|
|
125
|
+
meta.set_last_modified(OffsetDatetime::now_utc());
|
|
126
|
+
|
|
127
|
+
let encoded = bincode::encode_to_vec(meta)?;
|
|
128
|
+
|
|
129
|
+
con.set(c_path, "".to_string())?;
|
|
130
|
+
con.set(m_path, encoded.as_slice())?;
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
This will create two key-value pair. For object content, its key is `v0:c:/home/monika/poem0.txt`, the value is an empty `String`; For metadata, the key is `v0:m:/home/monika/poem0.txt`, the value is a `bincode` encoded `ObjectMetadata` structure binary string.
|
|
134
|
+
|
|
135
|
+
On creating a file or directory, the backend should also create its all parent directories if not present.
|
|
136
|
+
|
|
137
|
+
```rust
|
|
138
|
+
// create a file under `PATH`
|
|
139
|
+
let mut path = std::path::PathBuf::new(PATH);
|
|
140
|
+
let mut con = client.new_async_connection().await?;
|
|
141
|
+
|
|
142
|
+
while let Some(parent) = path.parent() {
|
|
143
|
+
let (p, c): (String, String) = (parent.display(), path.display());
|
|
144
|
+
let to_children = format!("v0:ch:{}", p);
|
|
145
|
+
con.sadd(to_children, c).await?;
|
|
146
|
+
path = parent;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Read File
|
|
151
|
+
|
|
152
|
+
Opendal empowers users to read with the `path` object, `offset` of the cursor and `size` to read. Redis provided a `GETRANGE` command which perfectly fit into it.
|
|
153
|
+
|
|
154
|
+
```rust
|
|
155
|
+
// path: "poem0.txt"
|
|
156
|
+
// offset: Option<u64>, the offset of reading
|
|
157
|
+
// size: Option<u64>, the size of reading
|
|
158
|
+
let path = get_abs_path(path);
|
|
159
|
+
let c_path = content_key(path);
|
|
160
|
+
let (mut start, mut end) = (0, -1);
|
|
161
|
+
if let Some(offset) = offset {
|
|
162
|
+
start = offset;
|
|
163
|
+
}
|
|
164
|
+
if let Some(size) = size {
|
|
165
|
+
end = start + size;
|
|
166
|
+
}
|
|
167
|
+
let buf: Vec<u8> = con.getrange(c_path, start, end).await?;
|
|
168
|
+
Box::new(buf)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```redis
|
|
172
|
+
GET v0:c:/home/monika/poem0.txt
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Write File
|
|
176
|
+
|
|
177
|
+
Redis ensures the writing of a single entry to be atomic, no locking is required.
|
|
178
|
+
|
|
179
|
+
What needs to take care by opendal, besides the content of object, is its metadata. For example, though offering a `OBJECT IDLETIME` command, redis cannot record the last modified time of a key, so this should be done in opendal.
|
|
180
|
+
|
|
181
|
+
```rust
|
|
182
|
+
use redis::AsyncCommands;
|
|
183
|
+
// args: &OpWrite
|
|
184
|
+
// r: BytesReader
|
|
185
|
+
let mut buf = vec![];
|
|
186
|
+
let content_length: u64 = futures::io::copy(r, &mut buf).await?;
|
|
187
|
+
let last_modified: String = OffsetDateTime::now().to_string();
|
|
188
|
+
|
|
189
|
+
// content md5 will not be offered
|
|
190
|
+
|
|
191
|
+
let mut meta = ObjectMetadata::default();
|
|
192
|
+
meta.set_content_length(content_length);
|
|
193
|
+
meta.set_last_modified(last_modified);
|
|
194
|
+
|
|
195
|
+
// `ObjectMetadata` has implemented the `Serialize` and `Deserialize` trait of `Serde`
|
|
196
|
+
// so bincode could serialize and deserialize it.
|
|
197
|
+
let bytes = bincode::encode_to_vec(&meta)?;
|
|
198
|
+
|
|
199
|
+
let path = get_abs_path(args.path());
|
|
200
|
+
let m_path = meta_key(path);
|
|
201
|
+
let c_path = content_key(path);
|
|
202
|
+
|
|
203
|
+
con.set(c_path, content).await?;
|
|
204
|
+
con.set(m_path, bytes).await?;
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```redis
|
|
208
|
+
SET v0:c:/home/monika/poem.txt content_string
|
|
209
|
+
SET v0:m:/home/monika/poem.txt <bincode encoded metadata>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Stat
|
|
213
|
+
|
|
214
|
+
To get the metadata of an object, using the `GET` command and deserialize from bincode.
|
|
215
|
+
|
|
216
|
+
```rust
|
|
217
|
+
let path = get_abs_path(args.path());
|
|
218
|
+
let meta = meta_key(path);
|
|
219
|
+
let bin: Vec<u8> = con.get(meta).await?;
|
|
220
|
+
let meta: ObjectMeta = bincode::deserialize(bin.as_slice())?;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```redis
|
|
224
|
+
GET v0:m:/home/monika/poem.txt
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## List
|
|
228
|
+
|
|
229
|
+
For listing directories, just `SSCAN` through the child list of the directory, nice and correct.
|
|
230
|
+
|
|
231
|
+
```rust
|
|
232
|
+
// print all sub-directories of `PATH`
|
|
233
|
+
|
|
234
|
+
let s_key = format!("v0:k:{}", PATH);
|
|
235
|
+
let mut con = client.new_async_connection().await?;
|
|
236
|
+
let mut it = con.sscan::<&str, String>(s_key).await?;
|
|
237
|
+
|
|
238
|
+
while let Some(child) = it.next_item().await {
|
|
239
|
+
println!("get sub-dir: {}", child);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Delete
|
|
244
|
+
|
|
245
|
+
All subdirectories of path will be listed and removed.
|
|
246
|
+
|
|
247
|
+
On deleting a file or directory, the backend should remove the entry from its parent's `SET`, and remove all children of entry.
|
|
248
|
+
|
|
249
|
+
This could be done by postorder deleting.
|
|
250
|
+
|
|
251
|
+
```rust
|
|
252
|
+
async fn remove_entry(con: &mut redis::aio::AsyncConnection, entry: String) {
|
|
253
|
+
let skey = format!("v0:ch:{}", entry);
|
|
254
|
+
let it = con.sscan::<&str, String>(skey).await?;
|
|
255
|
+
while let Some(child) = it.next_item().await {
|
|
256
|
+
remove_entry(&mut con, child).await;
|
|
257
|
+
}
|
|
258
|
+
if let Some(parent) = std::PathBuf::new(entry).parent() {
|
|
259
|
+
let p: String = parent.display();
|
|
260
|
+
let parent_skey = format!("v0:ch:{}", p);
|
|
261
|
+
let _ = con.srem(parent_skey, entry).await;
|
|
262
|
+
}
|
|
263
|
+
// remove metadata and content
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Blocking APIs
|
|
268
|
+
|
|
269
|
+
`redis-rs` also offers a synchronous version of API, just port the functions above to its synchronous version.
|
|
270
|
+
|
|
271
|
+
# Drawbacks
|
|
272
|
+
|
|
273
|
+
1. New dependencies is introduced: `redis-rs` and `bincode`;
|
|
274
|
+
2. Some calculations have to be done in client side, this will affect the performance;
|
|
275
|
+
3. Grouping atomic operations together doesn't promise transactional access, this may lead to data racing issues.
|
|
276
|
+
4. Writing large binary strings requiring copying all data from pipe(or `BytesReader` in opendal) to RAM, and then send to redis.
|
|
277
|
+
|
|
278
|
+
# Rationale and alternatives
|
|
279
|
+
|
|
280
|
+
## RedisJSON module
|
|
281
|
+
|
|
282
|
+
The [`RedisJSON`](https://redis.io/docs/stack/json/) module provides JSON support for Redis, and supports depth up to 128. Working on a JSON api could be easier than manually parsing or deserializing from `HASH`.
|
|
283
|
+
|
|
284
|
+
Since `bincode` also offers the ability of deserializing and serializing, `RedisJSON` won't be used.
|
|
285
|
+
|
|
286
|
+
# Prior art
|
|
287
|
+
|
|
288
|
+
None
|
|
289
|
+
|
|
290
|
+
# Unresolved questions
|
|
291
|
+
|
|
292
|
+
None
|
|
293
|
+
|
|
294
|
+
# Future possibilities
|
|
295
|
+
|
|
296
|
+
The implementation proposed here is far from perfect.
|
|
297
|
+
|
|
298
|
+
- The data organization could be optimized to make it acts more like a filesystem
|
|
299
|
+
- Making a customized redis module to calculate metadata on redis side
|
|
300
|
+
- Wait for stable of `bincode` 2.0, and bump to it.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
- Proposal Name: `split-capabilities`
|
|
2
|
+
- Start Date: 2022-09-04
|
|
3
|
+
- RFC PR: [apache/opendal#627](https://github.com/apache/opendal/pull/627)
|
|
4
|
+
- Tracking Issue: [apache/opendal#628](https://github.com/apache/opendal/issues/628)
|
|
5
|
+
|
|
6
|
+
# Summary
|
|
7
|
+
|
|
8
|
+
Split basic operations into `read`, `write`, and `list` capabilities.
|
|
9
|
+
|
|
10
|
+
# Motivation
|
|
11
|
+
|
|
12
|
+
In [RFC-0409: Accessor Capabilities](./0409-accessor-capabilities.md), we introduce the ideas of `Accessor Capabilities`. Services could have different capabilities, and users can check them via:
|
|
13
|
+
|
|
14
|
+
```rust
|
|
15
|
+
let meta = op.metadata();
|
|
16
|
+
let _: bool = meta.can_presign();
|
|
17
|
+
let _: bool = meta.can_multipart();
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If users call not supported capabilities, OpenDAL will return [`io::ErrorKind::Unsupported`](https://doc.rust-lang.org/stable/std/io/enum.ErrorKind.html#variant.Unsupported) instead.
|
|
21
|
+
|
|
22
|
+
Along with that RFC, we also introduce an idea about `Basic Operations`: the operations that all services must support, including:
|
|
23
|
+
|
|
24
|
+
- metadata
|
|
25
|
+
- create
|
|
26
|
+
- read
|
|
27
|
+
- write
|
|
28
|
+
- delete
|
|
29
|
+
- list
|
|
30
|
+
|
|
31
|
+
However, not all storage services support them. In our existing services, exception includes:
|
|
32
|
+
|
|
33
|
+
- HTTP services don't support `write`, `delete`, and `list`.
|
|
34
|
+
- IPFS HTTP gateway doesn't support `write` and `delete`.
|
|
35
|
+
- NOTE: ipfs has a writable HTTP gateway, but there is no available instance.
|
|
36
|
+
- fs could be read-only if mounted as `RO`.
|
|
37
|
+
- object storage like `s3` and `gcs` could not have enough permission.
|
|
38
|
+
- cache services may not support `list`.
|
|
39
|
+
|
|
40
|
+
So in this RFC, we want to remove the idea about `Basic Operations` and convert them into different capabilities:
|
|
41
|
+
|
|
42
|
+
- `read`: `read` and `stat`
|
|
43
|
+
- `write`: `write` and `delete`
|
|
44
|
+
- `list`: `list`
|
|
45
|
+
|
|
46
|
+
# Guide-level explanation
|
|
47
|
+
|
|
48
|
+
No public API changes.
|
|
49
|
+
|
|
50
|
+
# Reference-level explanation
|
|
51
|
+
|
|
52
|
+
This RFC will add three new capabilities:
|
|
53
|
+
|
|
54
|
+
- `read`: `read` and `stat`
|
|
55
|
+
- `write`: `write` and `delete`
|
|
56
|
+
- `list`: `list`
|
|
57
|
+
|
|
58
|
+
After this change, all services must declare the features they support.
|
|
59
|
+
|
|
60
|
+
Most of this RFCs work is to refactor the tests. This RFC will refactor the behavior tests into several parts based on capabilities.
|
|
61
|
+
|
|
62
|
+
# Drawbacks
|
|
63
|
+
|
|
64
|
+
None
|
|
65
|
+
|
|
66
|
+
# Rationale and alternatives
|
|
67
|
+
|
|
68
|
+
None
|
|
69
|
+
|
|
70
|
+
# Prior art
|
|
71
|
+
|
|
72
|
+
None
|
|
73
|
+
|
|
74
|
+
# Unresolved questions
|
|
75
|
+
|
|
76
|
+
None
|
|
77
|
+
|
|
78
|
+
# Future possibilities
|
|
79
|
+
|
|
80
|
+
## Read-only Services
|
|
81
|
+
|
|
82
|
+
OpenDAL can implement read-only services after this change:
|
|
83
|
+
|
|
84
|
+
- HTTP Service
|
|
85
|
+
- IPFS HTTP Gateway
|
|
86
|
+
|
|
87
|
+
## Add new capabilities with Layers
|
|
88
|
+
|
|
89
|
+
We can implement a layer that can add `list` capability for underlying storage services. For example, `IndexLayer` for HTTP services.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
- Proposal Name: `path_in_accessor`
|
|
2
|
+
- Start Date: 2022-09-12
|
|
3
|
+
- RFC PR: [apache/opendal#661](https://github.com/apache/opendal/pull/661)
|
|
4
|
+
- Tracking Issue: [apache/opendal#662](https://github.com/apache/opendal/issues/662)
|
|
5
|
+
|
|
6
|
+
# Summary
|
|
7
|
+
|
|
8
|
+
Move the path from `OpXxx` to `Accessor` directly.
|
|
9
|
+
|
|
10
|
+
# Motivation
|
|
11
|
+
|
|
12
|
+
`Accessor` uses `OpXxx` to carry `path` input:
|
|
13
|
+
|
|
14
|
+
```rust
|
|
15
|
+
impl Accessor {
|
|
16
|
+
async fn read(&self, args: &OpRead) -> Result<BytesReader> {
|
|
17
|
+
let _ = args;
|
|
18
|
+
unimplemented!()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Debug, Clone, Default)]
|
|
23
|
+
pub struct OpRead {
|
|
24
|
+
path: String,
|
|
25
|
+
offset: Option<u64>,
|
|
26
|
+
size: Option<u64>,
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
However, nearly all operation requires a `path`. And the path is represented in `String`, which means we have to clone it:
|
|
31
|
+
|
|
32
|
+
```rust
|
|
33
|
+
impl OpRead {
|
|
34
|
+
pub fn new(path: &str, range: impl RangeBounds<u64>) -> Result<Self> {
|
|
35
|
+
let br = BytesRange::from(range);
|
|
36
|
+
|
|
37
|
+
Ok(Self {
|
|
38
|
+
path: path.to_string(),
|
|
39
|
+
offset: br.offset(),
|
|
40
|
+
size: br.size(),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Besides, we can't expose low-level APIs like:
|
|
47
|
+
|
|
48
|
+
```rust
|
|
49
|
+
impl Object {
|
|
50
|
+
pub async fn read_with(&self, op: OpRead) -> Result<Vec<u8>> {
|
|
51
|
+
..
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Because users can't build the required `OpRead`.
|
|
57
|
+
|
|
58
|
+
# Guide-level explanation
|
|
59
|
+
|
|
60
|
+
With this RFC, users can use low-level APIs can control the `OpXxx` directly:
|
|
61
|
+
|
|
62
|
+
```rust
|
|
63
|
+
impl Object {
|
|
64
|
+
pub async fn read_with(&self, op: OpRead) -> Result<Vec<u8>> {
|
|
65
|
+
..
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pub async fn write_with(&self, op: OpWrite, bs: impl Into<Vec<u8>>) -> Result<()> {
|
|
69
|
+
..
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
So we can add more args in requests like:
|
|
75
|
+
|
|
76
|
+
```rust
|
|
77
|
+
o.write_with(OpWrite::new().with_content_md5("xxxxx"), bs).await;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
# Reference-level explanation
|
|
81
|
+
|
|
82
|
+
All `path` in `OpXxx` will be moved to `Accessor` directly:
|
|
83
|
+
|
|
84
|
+
```rust
|
|
85
|
+
pub trait Accessor: Send + Sync + Debug {
|
|
86
|
+
async fn create(&self, path: &str, args: OpCreate) -> Result<()> {}
|
|
87
|
+
|
|
88
|
+
async fn read(&self, path: &str, args: OpRead) -> Result<BytesReader> {}
|
|
89
|
+
|
|
90
|
+
...
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- All functions that accept `OpXxx` requires ownership instead of reference.
|
|
95
|
+
- All `OpXxx::new()` will introduce breaking changes:
|
|
96
|
+
```diff
|
|
97
|
+
- pub fn new(path: &str, range: impl RangeBounds<u64>) -> Result<Self>
|
|
98
|
+
+ pub fn new(range: impl RangeBounds<u64>) -> Self
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
# Drawbacks
|
|
102
|
+
|
|
103
|
+
## Breaking Changes
|
|
104
|
+
|
|
105
|
+
This RFC may break users' code in the following ways:
|
|
106
|
+
|
|
107
|
+
- Code that depends on `Accessor`:
|
|
108
|
+
- Self-implemented Services
|
|
109
|
+
- Self-implemented Layers
|
|
110
|
+
- Code that depends on `OpXxx`
|
|
111
|
+
|
|
112
|
+
# Rationale and alternatives
|
|
113
|
+
|
|
114
|
+
None.
|
|
115
|
+
|
|
116
|
+
# Prior art
|
|
117
|
+
|
|
118
|
+
None.
|
|
119
|
+
|
|
120
|
+
# Unresolved questions
|
|
121
|
+
|
|
122
|
+
None.
|
|
123
|
+
|
|
124
|
+
# Future possibilities
|
|
125
|
+
|
|
126
|
+
We can add more fields in `OpXxx`.
|