UNPKG

azurite

Version:

An open source Azure Storage API compatible server

1,136 lines 108 kB
"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