|
|
@ -749,32 +749,20 @@ std::shared_ptr<BlobFile> BlobDBImpl::SelectBlobFileTTL(uint64_t expiration) { |
|
|
|
return bfile; |
|
|
|
return bfile; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Status BlobDBImpl::Put(const WriteOptions& options, |
|
|
|
Status BlobDBImpl::Put(const WriteOptions& options, const Slice& key, |
|
|
|
ColumnFamilyHandle* column_family, const Slice& key, |
|
|
|
|
|
|
|
const Slice& value) { |
|
|
|
const Slice& value) { |
|
|
|
std::string new_value; |
|
|
|
std::string new_value; |
|
|
|
Slice value_slice; |
|
|
|
Slice value_slice; |
|
|
|
uint64_t expiration = ExtractExpiration(key, value, &value_slice, &new_value); |
|
|
|
uint64_t expiration = ExtractExpiration(key, value, &value_slice, &new_value); |
|
|
|
return PutUntil(options, column_family, key, value_slice, expiration); |
|
|
|
return PutUntil(options, key, value_slice, expiration); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Status BlobDBImpl::Delete(const WriteOptions& options, |
|
|
|
Status BlobDBImpl::Delete(const WriteOptions& options, const Slice& key) { |
|
|
|
ColumnFamilyHandle* column_family, const Slice& key) { |
|
|
|
|
|
|
|
SequenceNumber lsn = db_impl_->GetLatestSequenceNumber(); |
|
|
|
SequenceNumber lsn = db_impl_->GetLatestSequenceNumber(); |
|
|
|
Status s = db_->Delete(options, column_family, key); |
|
|
|
Status s = db_->Delete(options, key); |
|
|
|
|
|
|
|
|
|
|
|
// add deleted key to list of keys that have been deleted for book-keeping
|
|
|
|
// add deleted key to list of keys that have been deleted for book-keeping
|
|
|
|
delete_keys_q_.enqueue({column_family, key.ToString(), lsn}); |
|
|
|
delete_keys_q_.enqueue({DefaultColumnFamily(), key.ToString(), lsn}); |
|
|
|
return s; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status BlobDBImpl::SingleDelete(const WriteOptions& wopts, |
|
|
|
|
|
|
|
ColumnFamilyHandle* column_family, |
|
|
|
|
|
|
|
const Slice& key) { |
|
|
|
|
|
|
|
SequenceNumber lsn = db_impl_->GetLatestSequenceNumber(); |
|
|
|
|
|
|
|
Status s = db_->SingleDelete(wopts, column_family, key); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
delete_keys_q_.enqueue({column_family, key.ToString(), lsn}); |
|
|
|
|
|
|
|
return s; |
|
|
|
return s; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -788,10 +776,17 @@ Status BlobDBImpl::Write(const WriteOptions& opts, WriteBatch* updates) { |
|
|
|
std::shared_ptr<BlobFile> last_file_; |
|
|
|
std::shared_ptr<BlobFile> last_file_; |
|
|
|
bool has_put_; |
|
|
|
bool has_put_; |
|
|
|
std::string new_value_; |
|
|
|
std::string new_value_; |
|
|
|
|
|
|
|
uint32_t default_cf_id_; |
|
|
|
|
|
|
|
|
|
|
|
public: |
|
|
|
public: |
|
|
|
explicit BlobInserter(BlobDBImpl* impl, SequenceNumber seq) |
|
|
|
explicit BlobInserter(BlobDBImpl* impl, SequenceNumber seq) |
|
|
|
: impl_(impl), sequence_(seq), has_put_(false) {} |
|
|
|
: impl_(impl), |
|
|
|
|
|
|
|
sequence_(seq), |
|
|
|
|
|
|
|
has_put_(false), |
|
|
|
|
|
|
|
default_cf_id_(reinterpret_cast<ColumnFamilyHandleImpl*>( |
|
|
|
|
|
|
|
impl_->DefaultColumnFamily()) |
|
|
|
|
|
|
|
->cfd() |
|
|
|
|
|
|
|
->GetID()) {} |
|
|
|
|
|
|
|
|
|
|
|
WriteBatch& updates_blob() { return updates_blob_; } |
|
|
|
WriteBatch& updates_blob() { return updates_blob_; } |
|
|
|
|
|
|
|
|
|
|
@ -803,6 +798,11 @@ Status BlobDBImpl::Write(const WriteOptions& opts, WriteBatch* updates) { |
|
|
|
|
|
|
|
|
|
|
|
virtual Status PutCF(uint32_t column_family_id, const Slice& key, |
|
|
|
virtual Status PutCF(uint32_t column_family_id, const Slice& key, |
|
|
|
const Slice& value_slice) override { |
|
|
|
const Slice& value_slice) override { |
|
|
|
|
|
|
|
if (column_family_id != default_cf_id_) { |
|
|
|
|
|
|
|
batch_rewrite_status_ = Status::NotSupported( |
|
|
|
|
|
|
|
"Blob DB doesn't support non-default column family."); |
|
|
|
|
|
|
|
return batch_rewrite_status_; |
|
|
|
|
|
|
|
} |
|
|
|
Slice value_unc; |
|
|
|
Slice value_unc; |
|
|
|
uint64_t expiration = |
|
|
|
uint64_t expiration = |
|
|
|
impl_->ExtractExpiration(key, value_slice, &value_unc, &new_value_); |
|
|
|
impl_->ExtractExpiration(key, value_slice, &value_unc, &new_value_); |
|
|
@ -851,11 +851,28 @@ Status BlobDBImpl::Write(const WriteOptions& opts, WriteBatch* updates) { |
|
|
|
|
|
|
|
|
|
|
|
virtual Status DeleteCF(uint32_t column_family_id, |
|
|
|
virtual Status DeleteCF(uint32_t column_family_id, |
|
|
|
const Slice& key) override { |
|
|
|
const Slice& key) override { |
|
|
|
|
|
|
|
if (column_family_id != default_cf_id_) { |
|
|
|
|
|
|
|
batch_rewrite_status_ = Status::NotSupported( |
|
|
|
|
|
|
|
"Blob DB doesn't support non-default column family."); |
|
|
|
|
|
|
|
return batch_rewrite_status_; |
|
|
|
|
|
|
|
} |
|
|
|
WriteBatchInternal::Delete(&updates_blob_, column_family_id, key); |
|
|
|
WriteBatchInternal::Delete(&updates_blob_, column_family_id, key); |
|
|
|
sequence_++; |
|
|
|
sequence_++; |
|
|
|
return Status::OK(); |
|
|
|
return Status::OK(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
virtual Status DeleteRange(uint32_t column_family_id, |
|
|
|
|
|
|
|
const Slice& begin_key, const Slice& end_key) { |
|
|
|
|
|
|
|
if (column_family_id != default_cf_id_) { |
|
|
|
|
|
|
|
batch_rewrite_status_ = Status::NotSupported( |
|
|
|
|
|
|
|
"Blob DB doesn't support non-default column family."); |
|
|
|
|
|
|
|
return batch_rewrite_status_; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
WriteBatchInternal::DeleteRange(&updates_blob_, column_family_id, |
|
|
|
|
|
|
|
begin_key, end_key); |
|
|
|
|
|
|
|
return Status::OK(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
virtual Status SingleDeleteCF(uint32_t /*column_family_id*/, |
|
|
|
virtual Status SingleDeleteCF(uint32_t /*column_family_id*/, |
|
|
|
const Slice& /*key*/) override { |
|
|
|
const Slice& /*key*/) override { |
|
|
|
batch_rewrite_status_ = |
|
|
|
batch_rewrite_status_ = |
|
|
@ -932,12 +949,11 @@ Status BlobDBImpl::Write(const WriteOptions& opts, WriteBatch* updates) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Status BlobDBImpl::PutWithTTL(const WriteOptions& options, |
|
|
|
Status BlobDBImpl::PutWithTTL(const WriteOptions& options, |
|
|
|
ColumnFamilyHandle* column_family, |
|
|
|
|
|
|
|
const Slice& key, const Slice& value, |
|
|
|
const Slice& key, const Slice& value, |
|
|
|
uint64_t ttl) { |
|
|
|
uint64_t ttl) { |
|
|
|
uint64_t now = EpochNow(); |
|
|
|
uint64_t now = EpochNow(); |
|
|
|
assert(std::numeric_limits<uint64_t>::max() - now > ttl); |
|
|
|
assert(std::numeric_limits<uint64_t>::max() - now > ttl); |
|
|
|
return PutUntil(options, column_family, key, value, now + ttl); |
|
|
|
return PutUntil(options, key, value, now + ttl); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Slice BlobDBImpl::GetCompressedSlice(const Slice& raw, |
|
|
|
Slice BlobDBImpl::GetCompressedSlice(const Slice& raw, |
|
|
@ -952,8 +968,7 @@ Slice BlobDBImpl::GetCompressedSlice(const Slice& raw, |
|
|
|
return *compression_output; |
|
|
|
return *compression_output; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Status BlobDBImpl::PutUntil(const WriteOptions& options, |
|
|
|
Status BlobDBImpl::PutUntil(const WriteOptions& options, const Slice& key, |
|
|
|
ColumnFamilyHandle* column_family, const Slice& key, |
|
|
|
|
|
|
|
const Slice& value_unc, uint64_t expiration) { |
|
|
|
const Slice& value_unc, uint64_t expiration) { |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::PutUntil:Start"); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::PutUntil:Start"); |
|
|
|
MutexLock l(&write_mutex_); |
|
|
|
MutexLock l(&write_mutex_); |
|
|
@ -992,14 +1007,11 @@ Status BlobDBImpl::PutUntil(const WriteOptions& options, |
|
|
|
bfile->PathName().c_str(), key.ToString().c_str(), |
|
|
|
bfile->PathName().c_str(), key.ToString().c_str(), |
|
|
|
value.size(), s.ToString().c_str(), |
|
|
|
value.size(), s.ToString().c_str(), |
|
|
|
bfile->DumpState().c_str()); |
|
|
|
bfile->DumpState().c_str()); |
|
|
|
// Fallback just write to the LSM and get going
|
|
|
|
return s; |
|
|
|
WriteBatch batch; |
|
|
|
|
|
|
|
batch.Put(column_family, key, value); |
|
|
|
|
|
|
|
return db_->Write(options, &batch); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
WriteBatch batch; |
|
|
|
WriteBatch batch; |
|
|
|
batch.Put(column_family, key, index_entry); |
|
|
|
batch.Put(key, index_entry); |
|
|
|
|
|
|
|
|
|
|
|
// this goes to the base db and can be expensive
|
|
|
|
// this goes to the base db and can be expensive
|
|
|
|
s = db_->Write(options, &batch); |
|
|
|
s = db_->Write(options, &batch); |
|
|
@ -1123,7 +1135,6 @@ Status BlobDBImpl::AppendSN(const std::shared_ptr<BlobFile>& bfile, |
|
|
|
|
|
|
|
|
|
|
|
std::vector<Status> BlobDBImpl::MultiGet( |
|
|
|
std::vector<Status> BlobDBImpl::MultiGet( |
|
|
|
const ReadOptions& read_options, |
|
|
|
const ReadOptions& read_options, |
|
|
|
const std::vector<ColumnFamilyHandle*>& column_family, |
|
|
|
|
|
|
|
const std::vector<Slice>& keys, std::vector<std::string>* values) { |
|
|
|
const std::vector<Slice>& keys, std::vector<std::string>* values) { |
|
|
|
// Get a snapshot to avoid blob file get deleted between we
|
|
|
|
// Get a snapshot to avoid blob file get deleted between we
|
|
|
|
// fetch and index entry and reading from the file.
|
|
|
|
// fetch and index entry and reading from the file.
|
|
|
@ -1131,21 +1142,18 @@ std::vector<Status> BlobDBImpl::MultiGet( |
|
|
|
bool snapshot_created = SetSnapshotIfNeeded(&ro); |
|
|
|
bool snapshot_created = SetSnapshotIfNeeded(&ro); |
|
|
|
std::vector<std::string> values_lsm; |
|
|
|
std::vector<std::string> values_lsm; |
|
|
|
values_lsm.resize(keys.size()); |
|
|
|
values_lsm.resize(keys.size()); |
|
|
|
auto statuses = db_->MultiGet(ro, column_family, keys, &values_lsm); |
|
|
|
auto statuses = db_->MultiGet(ro, keys, &values_lsm); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::MultiGet:AfterIndexEntryGet:1"); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::MultiGet:AfterIndexEntryGet:1"); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::MultiGet:AfterIndexEntryGet:2"); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::MultiGet:AfterIndexEntryGet:2"); |
|
|
|
|
|
|
|
|
|
|
|
values->resize(keys.size()); |
|
|
|
values->resize(keys.size()); |
|
|
|
assert(statuses.size() == keys.size()); |
|
|
|
assert(statuses.size() == keys.size()); |
|
|
|
|
|
|
|
assert(values_lsm.size() == keys.size()); |
|
|
|
for (size_t i = 0; i < keys.size(); ++i) { |
|
|
|
for (size_t i = 0; i < keys.size(); ++i) { |
|
|
|
if (!statuses[i].ok()) { |
|
|
|
if (!statuses[i].ok()) { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Status s = CommonGet(keys[i], values_lsm[i], &((*values)[i])); |
|
|
|
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family[i]); |
|
|
|
|
|
|
|
auto cfd = cfh->cfd(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Status s = CommonGet(cfd, keys[i], values_lsm[i], &((*values)[i])); |
|
|
|
|
|
|
|
statuses[i] = s; |
|
|
|
statuses[i] = s; |
|
|
|
} |
|
|
|
} |
|
|
|
if (snapshot_created) { |
|
|
|
if (snapshot_created) { |
|
|
@ -1163,9 +1171,8 @@ bool BlobDBImpl::SetSnapshotIfNeeded(ReadOptions* read_options) { |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Status BlobDBImpl::CommonGet(const ColumnFamilyData* cfd, const Slice& key, |
|
|
|
Status BlobDBImpl::CommonGet(const Slice& key, const std::string& index_entry, |
|
|
|
const std::string& index_entry, std::string* value, |
|
|
|
std::string* value, SequenceNumber* sequence) { |
|
|
|
SequenceNumber* sequence) { |
|
|
|
|
|
|
|
Slice index_entry_slice(index_entry); |
|
|
|
Slice index_entry_slice(index_entry); |
|
|
|
BlobHandle handle; |
|
|
|
BlobHandle handle; |
|
|
|
Status s = handle.DecodeFrom(&index_entry_slice); |
|
|
|
Status s = handle.DecodeFrom(&index_entry_slice); |
|
|
@ -1269,10 +1276,12 @@ Status BlobDBImpl::CommonGet(const ColumnFamilyData* cfd, const Slice& key, |
|
|
|
|
|
|
|
|
|
|
|
if (bdb_options_.compression != kNoCompression) { |
|
|
|
if (bdb_options_.compression != kNoCompression) { |
|
|
|
BlockContents contents; |
|
|
|
BlockContents contents; |
|
|
|
|
|
|
|
auto cfh = |
|
|
|
|
|
|
|
reinterpret_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily()); |
|
|
|
s = UncompressBlockContentsForCompressionType( |
|
|
|
s = UncompressBlockContentsForCompressionType( |
|
|
|
blob_value.data(), blob_value.size(), &contents, |
|
|
|
blob_value.data(), blob_value.size(), &contents, |
|
|
|
kBlockBasedTableVersionFormat, Slice(), bdb_options_.compression, |
|
|
|
kBlockBasedTableVersionFormat, Slice(), bdb_options_.compression, |
|
|
|
*(cfd->ioptions())); |
|
|
|
*(cfh->cfd()->ioptions())); |
|
|
|
*value = contents.data.ToString(); |
|
|
|
*value = contents.data.ToString(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -1299,9 +1308,10 @@ Status BlobDBImpl::CommonGet(const ColumnFamilyData* cfd, const Slice& key, |
|
|
|
Status BlobDBImpl::Get(const ReadOptions& read_options, |
|
|
|
Status BlobDBImpl::Get(const ReadOptions& read_options, |
|
|
|
ColumnFamilyHandle* column_family, const Slice& key, |
|
|
|
ColumnFamilyHandle* column_family, const Slice& key, |
|
|
|
PinnableSlice* value) { |
|
|
|
PinnableSlice* value) { |
|
|
|
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family); |
|
|
|
if (column_family != DefaultColumnFamily()) { |
|
|
|
auto cfd = cfh->cfd(); |
|
|
|
return Status::NotSupported( |
|
|
|
|
|
|
|
"Blob DB doesn't support non-default column family."); |
|
|
|
|
|
|
|
} |
|
|
|
// Get a snapshot to avoid blob file get deleted between we
|
|
|
|
// Get a snapshot to avoid blob file get deleted between we
|
|
|
|
// fetch and index entry and reading from the file.
|
|
|
|
// fetch and index entry and reading from the file.
|
|
|
|
// TODO(yiwu): For Get() retry if file not found would be a simpler strategy.
|
|
|
|
// TODO(yiwu): For Get() retry if file not found would be a simpler strategy.
|
|
|
@ -1310,11 +1320,11 @@ Status BlobDBImpl::Get(const ReadOptions& read_options, |
|
|
|
|
|
|
|
|
|
|
|
Status s; |
|
|
|
Status s; |
|
|
|
std::string index_entry; |
|
|
|
std::string index_entry; |
|
|
|
s = db_->Get(ro, column_family, key, &index_entry); |
|
|
|
s = db_->Get(ro, key, &index_entry); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:1"); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:1"); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:2"); |
|
|
|
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:2"); |
|
|
|
if (s.ok()) { |
|
|
|
if (s.ok()) { |
|
|
|
s = CommonGet(cfd, key, index_entry, value->GetSelf()); |
|
|
|
s = CommonGet(key, index_entry, value->GetSelf()); |
|
|
|
value->PinSelf(); |
|
|
|
value->PinSelf(); |
|
|
|
} |
|
|
|
} |
|
|
|
if (snapshot_created) { |
|
|
|
if (snapshot_created) { |
|
|
@ -1324,15 +1334,11 @@ Status BlobDBImpl::Get(const ReadOptions& read_options, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Slice BlobDBIterator::value() const { |
|
|
|
Slice BlobDBIterator::value() const { |
|
|
|
Slice index_entry = iter_->value(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh_); |
|
|
|
|
|
|
|
auto cfd = cfh->cfd(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_SYNC_POINT("BlobDBIterator::value:BeforeGetBlob:1"); |
|
|
|
TEST_SYNC_POINT("BlobDBIterator::value:BeforeGetBlob:1"); |
|
|
|
TEST_SYNC_POINT("BlobDBIterator::value:BeforeGetBlob:2"); |
|
|
|
TEST_SYNC_POINT("BlobDBIterator::value:BeforeGetBlob:2"); |
|
|
|
Status s = db_impl_->CommonGet(cfd, iter_->key(), index_entry.ToString(false), |
|
|
|
Slice index_entry = iter_->value(); |
|
|
|
&vpart_); |
|
|
|
Status s = |
|
|
|
|
|
|
|
db_impl_->CommonGet(iter_->key(), index_entry.ToString(false), &vpart_); |
|
|
|
return Slice(vpart_); |
|
|
|
return Slice(vpart_); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -2248,14 +2254,13 @@ std::pair<bool, int64_t> BlobDBImpl::RunGC(bool aborted) { |
|
|
|
return std::make_pair(true, -1); |
|
|
|
return std::make_pair(true, -1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Iterator* BlobDBImpl::NewIterator(const ReadOptions& read_options, |
|
|
|
Iterator* BlobDBImpl::NewIterator(const ReadOptions& read_options) { |
|
|
|
ColumnFamilyHandle* column_family) { |
|
|
|
|
|
|
|
// Get a snapshot to avoid blob file get deleted between we
|
|
|
|
// Get a snapshot to avoid blob file get deleted between we
|
|
|
|
// fetch and index entry and reading from the file.
|
|
|
|
// fetch and index entry and reading from the file.
|
|
|
|
ReadOptions ro(read_options); |
|
|
|
ReadOptions ro(read_options); |
|
|
|
bool snapshot_created = SetSnapshotIfNeeded(&ro); |
|
|
|
bool snapshot_created = SetSnapshotIfNeeded(&ro); |
|
|
|
return new BlobDBIterator(db_->NewIterator(ro, column_family), column_family, |
|
|
|
return new BlobDBIterator(db_->NewIterator(ro), this, snapshot_created, |
|
|
|
this, snapshot_created, ro.snapshot); |
|
|
|
ro.snapshot); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Status DestroyBlobDB(const std::string& dbname, const Options& options, |
|
|
|
Status DestroyBlobDB(const std::string& dbname, const Options& options, |
|
|
@ -2299,8 +2304,7 @@ Status BlobDBImpl::TEST_GetSequenceNumber(const Slice& key, |
|
|
|
if (!s.ok()) { |
|
|
|
if (!s.ok()) { |
|
|
|
return s; |
|
|
|
return s; |
|
|
|
} |
|
|
|
} |
|
|
|
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily()); |
|
|
|
return CommonGet(key, index_entry, nullptr, sequence); |
|
|
|
return CommonGet(cfh->cfd(), key, index_entry, nullptr, sequence); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
std::vector<std::shared_ptr<BlobFile>> BlobDBImpl::TEST_GetBlobFiles() const { |
|
|
|
std::vector<std::shared_ptr<BlobFile>> BlobDBImpl::TEST_GetBlobFiles() const { |
|
|
|