azurite
Version:
An open source Azure Storage API compatible server
1,136 lines • 108 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const sequelize_1 = require("sequelize");
const v4_1 = tslib_1.__importDefault(require("uuid/v4"));
const constants_1 = require("../../common/utils/constants");
const utils_1 = require("../../common/utils/utils");
const utils_2 = require("../../common/utils/utils");
const ReadConditionalHeadersValidator_1 = require("../conditions/ReadConditionalHeadersValidator");
const WriteConditionalHeadersValidator_1 = require("../conditions/WriteConditionalHeadersValidator");
const StorageErrorFactory_1 = tslib_1.__importDefault(require("../errors/StorageErrorFactory"));
const Models = tslib_1.__importStar(require("../generated/artifacts/models"));
const BlobLeaseAdapter_1 = tslib_1.__importDefault(require("../lease/BlobLeaseAdapter"));
const BlobLeaseSyncer_1 = tslib_1.__importDefault(require("../lease/BlobLeaseSyncer"));
const BlobReadLeaseValidator_1 = tslib_1.__importDefault(require("../lease/BlobReadLeaseValidator"));
const BlobWriteLeaseSyncer_1 = tslib_1.__importDefault(require("../lease/BlobWriteLeaseSyncer"));
const BlobWriteLeaseValidator_1 = tslib_1.__importDefault(require("../lease/BlobWriteLeaseValidator"));
const ContainerDeleteLeaseValidator_1 = tslib_1.__importDefault(require("../lease/ContainerDeleteLeaseValidator"));
const ContainerLeaseAdapter_1 = tslib_1.__importDefault(require("../lease/ContainerLeaseAdapter"));
const ContainerLeaseSyncer_1 = tslib_1.__importDefault(require("../lease/ContainerLeaseSyncer"));
const ContainerReadLeaseValidator_1 = tslib_1.__importDefault(require("../lease/ContainerReadLeaseValidator"));
const LeaseFactory_1 = tslib_1.__importDefault(require("../lease/LeaseFactory"));
const constants_2 = require("../utils/constants");
const BlobReferredExtentsAsyncIterator_1 = tslib_1.__importDefault(require("./BlobReferredExtentsAsyncIterator"));
const PageWithDelimiter_1 = tslib_1.__importDefault(require("./PageWithDelimiter"));
const FilterBlobPage_1 = tslib_1.__importDefault(require("./FilterBlobPage"));
const utils_3 = require("../utils/utils");
const QueryInterpreter_1 = require("./QueryInterpreter/QueryInterpreter");
const NotImplementedError_1 = require("../errors/NotImplementedError");
// tslint:disable: max-classes-per-file
class ServicesModel extends sequelize_1.Model {
}
class ContainersModel extends sequelize_1.Model {
}
class BlobsModel extends sequelize_1.Model {
}
class BlocksModel extends sequelize_1.Model {
}
/**
* A SQL based Blob metadata storage implementation based on Sequelize.
* Refer to CONTRIBUTION.md for how to setup SQL database environment.
*
* @export
* @class SqlBlobMetadataStore
* @implements {IBlobMetadataStore}
*/
class SqlBlobMetadataStore {
/**
* Creates an instance of SqlBlobMetadataStore.
*
* @param {string} connectionURI For example, "postgres://user:pass@example.com:5432/dbname"
* @param {SequelizeOptions} [sequelizeOptions]
* @memberof SqlBlobMetadataStore
*/
constructor(connectionURI, sequelizeOptions) {
this.initialized = false;
this.closed = false;
// Enable encrypt connection for SQL Server
if (connectionURI.startsWith("mssql") && sequelizeOptions) {
sequelizeOptions.dialectOptions = sequelizeOptions.dialectOptions || {};
sequelizeOptions.dialectOptions.options =
sequelizeOptions.dialectOptions.options || {};
sequelizeOptions.dialectOptions.options.encrypt = true;
}
this.sequelize = new sequelize_1.Sequelize(connectionURI, sequelizeOptions);
}
async init() {
await this.sequelize.authenticate();
ServicesModel.init({
accountName: {
type: "VARCHAR(32)",
primaryKey: true
},
defaultServiceVersion: {
type: "VARCHAR(10)"
},
cors: {
type: "VARCHAR(4095)"
},
logging: {
type: "VARCHAR(255)"
},
minuteMetrics: {
type: "VARCHAR(255)"
},
hourMetrics: {
type: "VARCHAR(255)"
},
staticWebsite: {
type: "VARCHAR(1023)"
},
deleteRetentionPolicy: {
type: "VARCHAR(255)"
}
}, {
sequelize: this.sequelize,
modelName: "Services",
tableName: "Services",
timestamps: false
});
ContainersModel.init({
accountName: {
type: "VARCHAR(32)",
unique: "accountname_containername"
},
// tslint:disable-next-line:max-line-length
// https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata
containerName: {
type: "VARCHAR(63)",
unique: "accountname_containername"
},
containerId: {
type: sequelize_1.INTEGER.UNSIGNED,
primaryKey: true,
autoIncrement: true
},
lastModified: {
allowNull: false,
type: (0, sequelize_1.DATE)(6)
},
etag: {
allowNull: false,
type: "VARCHAR(127)"
},
// TODO: Confirm max length of metadata pairs
metadata: {
type: "VARCHAR(4095)"
},
containerAcl: {
type: "VARCHAR(1023)"
},
publicAccess: {
type: "VARCHAR(31)"
},
lease: {
type: "VARCHAR(1023)"
},
hasImmutabilityPolicy: {
type: sequelize_1.BOOLEAN
},
hasLegalHold: {
type: sequelize_1.BOOLEAN
}
}, {
sequelize: this.sequelize,
modelName: "Containers",
tableName: "Containers",
charset: constants_1.DEFAULT_SQL_CHARSET,
collate: constants_1.DEFAULT_SQL_COLLATE,
timestamps: false
});
BlobsModel.init({
accountName: {
type: "VARCHAR(64)",
allowNull: false
},
containerName: {
type: "VARCHAR(255)",
allowNull: false
},
blobName: {
type: "VARCHAR(255)",
allowNull: false
},
snapshot: {
type: "VARCHAR(64)",
allowNull: false,
defaultValue: ""
},
blobId: {
type: sequelize_1.INTEGER.UNSIGNED,
primaryKey: true,
autoIncrement: true
},
lastModified: {
allowNull: false,
type: (0, sequelize_1.DATE)(6)
},
creationTime: {
allowNull: false,
type: (0, sequelize_1.DATE)(6)
},
accessTierChangeTime: {
allowNull: true,
type: (0, sequelize_1.DATE)(6)
},
accessTierInferred: {
type: sequelize_1.BOOLEAN
},
etag: {
allowNull: false,
type: "VARCHAR(127)"
},
blobType: {
allowNull: false,
type: "VARCHAR(31)"
},
blobSequenceNumber: {
type: "VARCHAR(63)"
},
accessTier: {
type: "VARCHAR(31)"
},
contentProperties: {
type: "VARCHAR(1023)"
},
lease: {
type: "VARCHAR(1023)"
},
deleting: {
type: sequelize_1.INTEGER.UNSIGNED,
defaultValue: 0, // 0 means container is not under deleting(gc)
allowNull: false
},
isCommitted: {
type: sequelize_1.BOOLEAN,
allowNull: false
},
persistency: {
type: "VARCHAR(255)"
},
committedBlocksInOrder: {
type: (0, sequelize_1.TEXT)({ length: "medium" })
},
metadata: {
type: "VARCHAR(2047)"
},
blobTags: {
type: "VARCHAR(4096)"
}
}, {
sequelize: this.sequelize,
modelName: "Blobs",
tableName: "Blobs",
timestamps: false,
charset: constants_1.DEFAULT_SQL_CHARSET,
collate: constants_1.DEFAULT_SQL_COLLATE,
indexes: [
{
// name: 'title_index',
// using: 'BTREE',
unique: true,
fields: [
"accountName",
"containerName",
"blobName",
"snapshot",
"deleting"
]
}
]
});
BlocksModel.init({
accountName: {
type: "VARCHAR(64)",
allowNull: false
},
containerName: {
type: "VARCHAR(255)",
allowNull: false
},
blobName: {
type: "VARCHAR(255)",
allowNull: false
},
// TODO: Check max block name length
blockName: {
type: "VARCHAR(64)",
allowNull: false
},
deleting: {
type: sequelize_1.INTEGER.UNSIGNED,
defaultValue: 0, // 0 means container is not under deleting(gc)
allowNull: false
},
size: {
type: sequelize_1.INTEGER.UNSIGNED,
allowNull: false
},
persistency: {
type: "VARCHAR(255)"
}
}, {
sequelize: this.sequelize,
modelName: "Blocks",
tableName: "Blocks",
timestamps: false,
indexes: [
{
unique: true,
fields: ["accountName", "containerName", "blobName", "blockName"]
}
]
});
// TODO: sync() is only for development purpose, use migration for production
await this.sequelize.sync();
this.initialized = true;
}
isInitialized() {
return this.initialized;
}
async close() {
await this.sequelize.close();
this.closed = true;
}
isClosed() {
return this.closed;
}
async clean() {
// TODO: Implement cleanup in database
}
async setServiceProperties(context, serviceProperties) {
return this.sequelize.transaction(async (t) => {
const findResult = await ServicesModel.findByPk(serviceProperties.accountName, {
transaction: t
});
const updateValues = {
defaultServiceVersion: serviceProperties.defaultServiceVersion,
cors: this.serializeModelValue(serviceProperties.cors),
logging: this.serializeModelValue(serviceProperties.logging),
minuteMetrics: this.serializeModelValue(serviceProperties.minuteMetrics),
hourMetrics: this.serializeModelValue(serviceProperties.hourMetrics),
staticWebsite: this.serializeModelValue(serviceProperties.staticWebsite),
deleteRetentionPolicy: this.serializeModelValue(serviceProperties.deleteRetentionPolicy)
};
if (findResult === null) {
await ServicesModel.create({
accountName: serviceProperties.accountName,
...updateValues
}, { transaction: t });
}
else {
const updateResult = await ServicesModel.update(updateValues, {
transaction: t,
where: {
accountName: serviceProperties.accountName
}
});
// Set the exactly equal properties will affect 0 rows.
const updatedRows = updateResult[0];
if (updatedRows > 1) {
throw Error(`SqlBlobMetadataStore:updateServiceProperties() failed. Update operation affect ${updatedRows} rows.`);
}
}
return serviceProperties;
});
}
async getServiceProperties(context, account) {
const findResult = await ServicesModel.findByPk(account);
if (findResult === null) {
return undefined;
}
const logging = this.deserializeModelValue(findResult, "logging");
const hourMetrics = this.deserializeModelValue(findResult, "hourMetrics");
const minuteMetrics = this.deserializeModelValue(findResult, "minuteMetrics");
const cors = this.deserializeModelValue(findResult, "cors");
const deleteRetentionPolicy = this.deserializeModelValue(findResult, "deleteRetentionPolicy");
const staticWebsite = this.deserializeModelValue(findResult, "staticWebsite");
const defaultServiceVersion = this.getModelValue(findResult, "defaultServiceVersion");
const ret = {
accountName: account
};
if (logging !== undefined) {
ret.logging = logging;
}
if (hourMetrics !== undefined) {
ret.hourMetrics = hourMetrics;
}
if (minuteMetrics !== undefined) {
ret.minuteMetrics = minuteMetrics;
}
if (cors !== undefined) {
ret.cors = cors;
}
if (deleteRetentionPolicy !== undefined) {
ret.deleteRetentionPolicy = deleteRetentionPolicy;
}
if (staticWebsite !== undefined) {
ret.staticWebsite = staticWebsite;
}
if (defaultServiceVersion !== undefined) {
ret.defaultServiceVersion = defaultServiceVersion;
}
return ret;
}
async listContainers(context, account, prefix = "", maxResults = constants_2.DEFAULT_LIST_CONTAINERS_MAX_RESULTS, marker) {
const whereQuery = { accountName: account };
if (prefix.length > 0) {
whereQuery.containerName = {
[sequelize_1.Op.like]: `${prefix}%`
};
}
if (marker !== "") {
if (whereQuery.containerName === undefined) {
whereQuery.containerName = {
[sequelize_1.Op.gt]: marker
};
}
else {
whereQuery.containerName[sequelize_1.Op.gt] = marker;
}
}
const findResult = await ContainersModel.findAll({
limit: maxResults + 1,
where: whereQuery,
order: [["containerName", "ASC"]]
});
const leaseUpdateMapper = (model) => {
const containerModel = this.convertDbModelToContainerModel(model);
return LeaseFactory_1.default.createLeaseState(new ContainerLeaseAdapter_1.default(containerModel), context).sync(new ContainerLeaseSyncer_1.default(containerModel));
};
if (findResult.length <= maxResults) {
return [findResult.map(leaseUpdateMapper), undefined];
}
else {
const tail = findResult[findResult.length - 2];
findResult.pop();
const nextMarker = this.getModelValue(tail, "containerName", true);
return [findResult.map(leaseUpdateMapper), nextMarker];
}
}
async createContainer(context, container) {
try {
await ContainersModel.create(this.convertContainerModelToDbModel(container));
return container;
}
catch (err) {
if (err.name === "SequelizeUniqueConstraintError") {
throw StorageErrorFactory_1.default.getContainerAlreadyExists(context.contextId);
}
throw err;
}
}
async getContainerProperties(context, account, container, leaseAccessConditions) {
const findResult = await this.assertContainerExists(context, account, container, undefined, true);
const containerModel = this.convertDbModelToContainerModel(findResult);
return LeaseFactory_1.default.createLeaseState(new ContainerLeaseAdapter_1.default(containerModel), context)
.validate(new ContainerReadLeaseValidator_1.default(leaseAccessConditions))
.sync(new ContainerLeaseSyncer_1.default(containerModel));
}
async deleteContainer(context, account, container, options = {}) {
await this.sequelize.transaction(async (t) => {
/* Transaction starts */
const findResult = await ContainersModel.findOne({
attributes: [
"accountName",
"containerName",
"etag",
"lastModified",
"lease"
],
where: {
accountName: account,
containerName: container
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, options.modifiedAccessConditions, findResult ? this.convertDbModelToContainerModel(findResult) : undefined);
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
LeaseFactory_1.default.createLeaseState(this.convertDbModelToLease(findResult), context).validate(new ContainerDeleteLeaseValidator_1.default(options.leaseAccessConditions));
await ContainersModel.destroy({
where: {
accountName: account,
containerName: container
},
transaction: t
});
// TODO: GC blobs under deleting status
await BlobsModel.update({
deleting: (0, sequelize_1.literal)("deleting + 1")
}, {
where: {
accountName: account,
containerName: container
},
transaction: t
});
// TODO: GC blocks under deleting status
await BlocksModel.update({
deleting: (0, sequelize_1.literal)("deleting + 1")
}, {
where: {
accountName: account,
containerName: container
},
transaction: t
});
/* Transaction ends */
});
}
async setContainerMetadata(context, account, container, lastModified, etag, metadata, leaseAccessConditions, modifiedAccessConditions) {
return this.sequelize.transaction(async (t) => {
/* Transaction starts */
const findResult = await ContainersModel.findOne({
attributes: [
"accountName",
"containerName",
"etag",
"lastModified",
"lease"
],
where: {
accountName: account,
containerName: container
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, modifiedAccessConditions, findResult ? this.convertDbModelToContainerModel(findResult) : undefined);
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
LeaseFactory_1.default.createLeaseState(this.convertDbModelToLease(findResult), context).validate(new ContainerReadLeaseValidator_1.default(leaseAccessConditions));
await ContainersModel.update({
lastModified,
etag,
metadata: this.serializeModelValue(metadata) || null
}, {
where: {
accountName: account,
containerName: container
},
transaction: t
});
/* Transaction ends */
});
}
async getContainerACL(context, account, container, leaseAccessConditions) {
const findResult = await ContainersModel.findOne({
where: {
accountName: account,
containerName: container
}
});
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
const containerModel = this.convertDbModelToContainerModel(findResult);
LeaseFactory_1.default.createLeaseState(new ContainerLeaseAdapter_1.default(containerModel), context)
.validate(new ContainerReadLeaseValidator_1.default(leaseAccessConditions))
.sync(new ContainerLeaseSyncer_1.default(containerModel));
return {
properties: containerModel.properties,
containerAcl: containerModel.containerAcl
};
}
async setContainerACL(context, account, container, setAclModel) {
await this.sequelize.transaction(async (t) => {
const findResult = await ContainersModel.findOne({
attributes: [
"accountName",
"containerName",
"etag",
"lastModified",
"lease"
],
where: {
accountName: account,
containerName: container
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, setAclModel.modifiedAccessConditions, findResult ? this.convertDbModelToContainerModel(findResult) : undefined);
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
const lease = this.convertDbModelToLease(findResult);
LeaseFactory_1.default.createLeaseState(lease, context).validate(new ContainerReadLeaseValidator_1.default(setAclModel.leaseAccessConditions));
const updateResult = await ContainersModel.update({
lastModified: setAclModel.lastModified,
etag: setAclModel.etag,
containerAcl: this.serializeModelValue(setAclModel.containerAcl) || null,
publicAccess: this.serializeModelValue(setAclModel.publicAccess)
}, {
where: {
accountName: account,
containerName: container
},
transaction: t
});
if (updateResult[0] === 0) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
});
}
async acquireContainerLease(context, account, container, options) {
return this.sequelize.transaction(async (t) => {
/* Transaction starts */
const findResult = await ContainersModel.findOne({
where: {
accountName: account,
containerName: container
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, options.modifiedAccessConditions, findResult ? this.convertDbModelToContainerModel(findResult) : undefined);
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
const containerModel = this.convertDbModelToContainerModel(findResult);
LeaseFactory_1.default.createLeaseState(new ContainerLeaseAdapter_1.default(containerModel), context)
.acquire(options.duration, options.proposedLeaseId)
.sync(new ContainerLeaseSyncer_1.default(containerModel));
await ContainersModel.update(this.convertLeaseToDbModel(new ContainerLeaseAdapter_1.default(containerModel)), {
where: {
accountName: account,
containerName: container
},
transaction: t
});
return {
properties: containerModel.properties,
leaseId: containerModel.leaseId
};
/* Transaction ends */
});
}
async releaseContainerLease(context, account, container, leaseId, options = {}) {
return this.sequelize.transaction(async (t) => {
/* Transaction starts */
const findResult = await ContainersModel.findOne({
where: {
accountName: account,
containerName: container
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, options.modifiedAccessConditions, findResult ? this.convertDbModelToContainerModel(findResult) : undefined);
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
const containerModel = this.convertDbModelToContainerModel(findResult);
LeaseFactory_1.default.createLeaseState(new ContainerLeaseAdapter_1.default(containerModel), context)
.release(leaseId)
.sync(new ContainerLeaseSyncer_1.default(containerModel));
await ContainersModel.update(this.convertLeaseToDbModel(new ContainerLeaseAdapter_1.default(containerModel)), {
where: {
accountName: account,
containerName: container
},
transaction: t
});
return containerModel.properties;
/* Transaction ends */
});
}
async renewContainerLease(context, account, container, leaseId, options = {}) {
return this.sequelize.transaction(async (t) => {
/* Transaction starts */
// TODO: Filter out unnecessary fields in select query
const findResult = await ContainersModel.findOne({
where: {
accountName: account,
containerName: container
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, options.modifiedAccessConditions, findResult ? this.convertDbModelToContainerModel(findResult) : undefined);
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
const containerModel = this.convertDbModelToContainerModel(findResult);
LeaseFactory_1.default.createLeaseState(new ContainerLeaseAdapter_1.default(containerModel), context)
.renew(leaseId)
.sync(new ContainerLeaseSyncer_1.default(containerModel));
await ContainersModel.update(this.convertLeaseToDbModel(new ContainerLeaseAdapter_1.default(containerModel)), {
where: {
accountName: account,
containerName: container
},
transaction: t
});
return {
properties: containerModel.properties,
leaseId: containerModel.leaseId
};
/* Transaction ends */
});
}
async breakContainerLease(context, account, container, breakPeriod, options = {}) {
return this.sequelize.transaction(async (t) => {
const findResult = await ContainersModel.findOne({
where: {
accountName: account,
containerName: container
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, options.modifiedAccessConditions, findResult ? this.convertDbModelToContainerModel(findResult) : undefined);
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
const containerModel = this.convertDbModelToContainerModel(findResult);
LeaseFactory_1.default.createLeaseState(new ContainerLeaseAdapter_1.default(containerModel), context)
.break(breakPeriod)
.sync(new ContainerLeaseSyncer_1.default(containerModel));
await ContainersModel.update(this.convertLeaseToDbModel(new ContainerLeaseAdapter_1.default(containerModel)), {
where: {
accountName: account,
containerName: container
},
transaction: t
});
const leaseTimeSeconds = containerModel.properties.leaseState ===
Models.LeaseStateType.Breaking && containerModel.leaseBreakTime
? Math.round((containerModel.leaseBreakTime.getTime() -
context.startTime.getTime()) /
1000)
: 0;
return {
properties: containerModel.properties,
leaseTime: leaseTimeSeconds
};
});
}
async changeContainerLease(context, account, container, leaseId, proposedLeaseId, options = {}) {
return this.sequelize.transaction(async (t) => {
const findResult = await ContainersModel.findOne({
where: {
accountName: account,
containerName: container
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, options.modifiedAccessConditions, findResult ? this.convertDbModelToContainerModel(findResult) : undefined);
if (findResult === null || findResult === undefined) {
throw StorageErrorFactory_1.default.getContainerNotFound(context.contextId);
}
const containerModel = this.convertDbModelToContainerModel(findResult);
LeaseFactory_1.default.createLeaseState(new ContainerLeaseAdapter_1.default(containerModel), context)
.change(leaseId, proposedLeaseId)
.sync(new ContainerLeaseSyncer_1.default(containerModel));
await ContainersModel.update(this.convertLeaseToDbModel(new ContainerLeaseAdapter_1.default(containerModel)), {
where: {
accountName: account,
containerName: container
},
transaction: t
});
return {
properties: containerModel.properties,
leaseId: containerModel.leaseId
};
});
}
async checkContainerExist(context, account, container) {
await this.assertContainerExists(context, account, container, undefined);
}
async createBlob(context, blob, leaseAccessConditions, modifiedAccessConditions) {
return this.sequelize.transaction(async (t) => {
await this.assertContainerExists(context, blob.accountName, blob.containerName, t);
const blobFindResult = await BlobsModel.findOne({
where: {
accountName: blob.accountName,
containerName: blob.containerName,
blobName: blob.name,
snapshot: blob.snapshot,
deleting: 0,
isCommitted: true
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, modifiedAccessConditions, blobFindResult
? this.convertDbModelToBlobModel(blobFindResult)
: undefined);
// Create if not exists
if (modifiedAccessConditions &&
modifiedAccessConditions.ifNoneMatch === "*" &&
blobFindResult) {
throw StorageErrorFactory_1.default.getBlobAlreadyExists(context.contextId);
}
if (blobFindResult) {
const blobModel = this.convertDbModelToBlobModel(blobFindResult);
LeaseFactory_1.default.createLeaseState(new BlobLeaseAdapter_1.default(blobModel), context)
.validate(new BlobWriteLeaseValidator_1.default(leaseAccessConditions))
.sync(new BlobWriteLeaseSyncer_1.default(blob)); // Keep original blob lease;
if (blobModel.properties !== undefined &&
blobModel.properties.accessTier === Models.AccessTier.Archive) {
throw StorageErrorFactory_1.default.getBlobArchived(context.contextId);
}
}
await BlobsModel.upsert(this.convertBlobModelToDbModel(blob), {
transaction: t
});
});
}
async downloadBlob(context, account, container, blob, snapshot = "", leaseAccessConditions, modifiedAccessConditions) {
return this.sequelize.transaction(async (t) => {
await this.assertContainerExists(context, account, container, t);
const blobFindResult = await BlobsModel.findOne({
where: {
accountName: account,
containerName: container,
blobName: blob,
snapshot,
deleting: 0,
isCommitted: true
},
transaction: t
});
(0, ReadConditionalHeadersValidator_1.validateReadConditions)(context, modifiedAccessConditions, blobFindResult
? this.convertDbModelToBlobModel(blobFindResult)
: undefined);
if (blobFindResult === null || blobFindResult === undefined) {
throw StorageErrorFactory_1.default.getBlobNotFound(context.contextId);
}
const blobModel = this.convertDbModelToBlobModel(blobFindResult);
return LeaseFactory_1.default.createLeaseState(new BlobLeaseAdapter_1.default(blobModel), context)
.validate(new BlobReadLeaseValidator_1.default(leaseAccessConditions))
.sync(new BlobLeaseSyncer_1.default(blobModel));
});
}
async filterBlobs(context, account, container, where, maxResults = constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS, marker) {
return this.sequelize.transaction(async (t) => {
if (container) {
await this.assertContainerExists(context, account, container, t);
}
let whereQuery;
if (container) {
whereQuery = {
accountName: account,
containerName: container
};
}
else {
whereQuery = {
accountName: account
};
}
;
if (marker !== undefined) {
if (whereQuery.blobName !== undefined) {
whereQuery.blobName[sequelize_1.Op.gt] = marker;
}
else {
whereQuery.blobName = {
[sequelize_1.Op.gt]: marker
};
}
}
whereQuery.snapshot = "";
whereQuery.deleting = 0;
// fill the page by possibly querying multiple times
const page = new FilterBlobPage_1.default(maxResults);
const nameItem = (item) => {
return this.getModelValue(item, "blobName", true);
};
const filterFunction = (0, QueryInterpreter_1.generateQueryBlobWithTagsWhereFunction)(context, where);
const readPage = async (off) => {
return (await BlobsModel.findAll({
where: whereQuery,
order: [["blobName", "ASC"]],
transaction: t,
limit: maxResults,
offset: off
}));
};
const [blobItems, nextMarker] = await page.fill(readPage, nameItem);
const filterBlobModelMapper = (model) => {
return this.convertDbModelToFilterBlobModel(model);
};
return [blobItems.map(filterBlobModelMapper).filter((blobItem) => {
const tagsMeetConditions = filterFunction(blobItem);
if (tagsMeetConditions.length !== 0) {
blobItem.tags = { blobTagSet: (0, utils_3.toBlobTags)(tagsMeetConditions) };
return true;
}
return false;
}), nextMarker];
});
}
async listBlobs(context, account, container, delimiter, blob, prefix = "", maxResults = constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS, marker, includeSnapshots, includeUncommittedBlobs) {
return this.sequelize.transaction(async (t) => {
await this.assertContainerExists(context, account, container, t);
const whereQuery = {
accountName: account,
containerName: container
};
if (blob !== undefined) {
whereQuery.blobName = blob;
}
else {
if (prefix.length > 0) {
whereQuery.blobName = {
[sequelize_1.Op.like]: `${prefix}%`
};
}
if (marker !== undefined) {
if (whereQuery.blobName !== undefined) {
whereQuery.blobName[sequelize_1.Op.gt] = marker;
}
else {
whereQuery.blobName = {
[sequelize_1.Op.gt]: marker
};
}
}
}
if (!includeSnapshots) {
whereQuery.snapshot = "";
}
if (!includeUncommittedBlobs) {
whereQuery.isCommitted = true;
}
whereQuery.deleting = 0;
const leaseUpdateMapper = (model) => {
const blobModel = this.convertDbModelToBlobModel(model);
return LeaseFactory_1.default.createLeaseState(new BlobLeaseAdapter_1.default(blobModel), context).sync(new BlobLeaseSyncer_1.default(blobModel));
};
// fill the page by possibly querying multiple times
const page = new PageWithDelimiter_1.default(maxResults, delimiter, prefix);
const nameItem = (item) => {
return this.getModelValue(item, "blobName", true);
};
const readPage = async (off) => {
return await BlobsModel.findAll({
where: whereQuery,
order: [["blobName", "ASC"]],
transaction: t,
limit: maxResults,
offset: off
});
};
const [blobItems, blobPrefixes, nextMarker] = await page.fill(readPage, nameItem);
return [blobItems.map(leaseUpdateMapper), blobPrefixes, nextMarker];
});
}
async listAllBlobs(maxResults = constants_2.DEFAULT_LIST_BLOBS_MAX_RESULTS, marker, includeSnapshots, includeUncommittedBlobs) {
const whereQuery = {};
if (marker !== undefined) {
whereQuery.blobName = {
[sequelize_1.Op.gt]: marker
};
}
if (!includeSnapshots) {
whereQuery.snapshot = "";
}
if (!includeUncommittedBlobs) {
whereQuery.isCommitted = true;
}
whereQuery.deleting = 0;
const blobFindResult = await BlobsModel.findAll({
limit: maxResults + 1,
where: whereQuery,
order: [["blobName", "ASC"]]
});
if (blobFindResult.length <= maxResults) {
return [
blobFindResult.map(this.convertDbModelToBlobModel.bind(this)),
undefined
];
}
else {
blobFindResult.pop();
const tail = blobFindResult[blobFindResult.length - 1];
const nextMarker = this.getModelValue(tail, "blobName", true);
return [
blobFindResult.map(this.convertDbModelToBlobModel.bind(this)),
nextMarker
];
}
}
async stageBlock(context, block, leaseAccessConditions) {
await this.sequelize.transaction(async (t) => {
await this.assertContainerExists(context, block.accountName, block.containerName, t);
const blobFindResult = await BlobsModel.findOne({
where: {
accountName: block.accountName,
containerName: block.containerName,
blobName: block.blobName,
snapshot: "",
deleting: 0
},
transaction: t
});
if (blobFindResult !== null && blobFindResult !== undefined) {
const blobModel = this.convertDbModelToBlobModel(blobFindResult);
if (blobModel.isCommitted === true) {
LeaseFactory_1.default.createLeaseState(new BlobLeaseAdapter_1.default(blobModel), context).validate(new BlobWriteLeaseValidator_1.default(leaseAccessConditions));
}
// If the new block ID does not have same length with before uncommitted block ID, return failure.
const existBlock = await BlocksModel.findOne({
attributes: ["blockName"],
where: {
accountName: block.accountName,
containerName: block.containerName,
blobName: block.blobName,
deleting: 0
},
order: [["id", "ASC"]],
transaction: t
});
if (existBlock &&
Buffer.from(this.getModelValue(existBlock, "blockName", true), "base64").length !== Buffer.from(block.name, "base64").length) {
throw StorageErrorFactory_1.default.getInvalidBlobOrBlock(context.contextId);
}
}
else {
const newBlob = {
deleted: false,
accountName: block.accountName,
containerName: block.containerName,
name: block.blobName,
properties: {
creationTime: context.startTime,
lastModified: context.startTime,
etag: (0, utils_2.newEtag)(),
contentLength: 0,
blobType: Models.BlobType.BlockBlob
},
snapshot: "",
isCommitted: false
};
await BlobsModel.upsert(this.convertBlobModelToDbModel(newBlob), {
transaction: t
});
}
await BlocksModel.upsert({
accountName: block.accountName,
containerName: block.containerName,
blobName: block.blobName,
blockName: block.name,
size: block.size,
persistency: this.serializeModelValue(block.persistency)
}, { transaction: t });
});
}
getBlockList(context, account, container, blob, snapshot = "", isCommitted, leaseAccessConditions, modifiedAccessConditions) {
return this.sequelize.transaction(async (t) => {
await this.assertContainerExists(context, account, container, t);
const blobFindResult = await BlobsModel.findOne({
where: {
accountName: account,
containerName: container,
blobName: blob,
snapshot,
deleting: 0
},
transaction: t
});
(0, ReadConditionalHeadersValidator_1.validateReadConditions)(context, modifiedAccessConditions, blobFindResult
? this.convertDbModelToBlobModel(blobFindResult)
: undefined);
if (blobFindResult === null || blobFindResult === undefined) {
throw StorageErrorFactory_1.default.getBlobNotFound(context.contextId);
}
const blobModel = this.convertDbModelToBlobModel(blobFindResult);
LeaseFactory_1.default.createLeaseState(new BlobLeaseAdapter_1.default(blobModel), context).validate(new BlobReadLeaseValidator_1.default(leaseAccessConditions));
const res = {
uncommittedBlocks: [],
committedBlocks: []
};
if (isCommitted !== false) {
res.committedBlocks = blobModel.committedBlocksInOrder || [];
}
if (isCommitted !== true) {
const blocks = await BlocksModel.findAll({
attributes: ["blockName", "size"],
where: {
accountName: account,
containerName: container,
blobName: blob,
deleting: 0
},
order: [["id", "ASC"]],
transaction: t
});
for (const item of blocks) {
const block = {
name: this.getModelValue(item, "blockName", true),
size: this.getModelValue(item, "size", true)
};
res.uncommittedBlocks.push(block);
}
}
return res;
});
}
async commitBlockList(context, blob, blockList, leaseAccessConditions, modifiedAccessConditions) {
await this.sequelize.transaction(async (t) => {
await this.assertContainerExists(context, blob.accountName, blob.containerName, t);
const pCommittedBlocksMap = new Map(); // persistencyCommittedBlocksMap
const pUncommittedBlocksMap = new Map(); // persistencyUncommittedBlocksMap
const badRequestError = StorageErrorFactory_1.default.getInvalidBlockList(context.contextId);
const blobFindResult = await BlobsModel.findOne({
where: {
accountName: blob.accountName,
containerName: blob.containerName,
blobName: blob.name,
snapshot: blob.snapshot,
deleting: 0,
isCommitted: true
},
transaction: t
});
(0, WriteConditionalHeadersValidator_1.validateWriteConditions)(context, modifiedAccessConditions, blobFindResult
? this.convertDbModelToBlobModel(blobFindResult) // TODO: Reduce duplicated convert
: undefined);
let creationTime = blob.properties.creationTime || context.startTime;
if (blobFindResult !== null && blobFindResult !== undefined) {
const blobModel = this.convertDbModelToBlobModel(blobFindResult);
// Create if not exists
if (modifiedAccessConditions &&
modifiedAccessConditions.ifNoneMatch === "*" &&
blobModel &&
blobModel.isCommitted) {
throw StorageErrorFactory_1.default.getBlobAlreadyExists(context.contextId);
}
creationTime = blobModel.properties.creationTime || creationTime;
LeaseFactory_1.default.createLeaseState(new BlobLeaseAdapter_1.default(blobModel), context).validate(new BlobWriteLeaseValidator_1.default(leaseAccessConditions));
const committedBlocksInOrder = blobModel.committedBlocksInOrder;
for (const pBlock of committedBlocksInOrder || []) {
pCommittedBlocksMap.set(pBlock.name, pBlock);
}
}
const blockFindResult = await BlocksModel.findAll({
where: {
accountName: blob.accountName,
containerName: blob.containerName,
blobName: blob.name,
deleting: 0
},
transaction: t
});
for (const item of blockFindResult) {
const block = {
name: this.getModelValue(item, "blockName", true),
size: this.getModelValue(item, "size", true),
persistency: this.deserializeModelValue(item, "persistency")
};
pUncommittedBlocksMap.set(block.name, block);
}
const selectedBlockList = [];
for (const block of blockList) {
switch (block.blockCommi