1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-22 02:51:33 +01:00

SqlServer cipher repository implementation with bulk copy.

This commit is contained in:
Kyle Spearrin 2016-02-06 11:09:55 -05:00
parent 16507022bd
commit bd6ae4ac17
5 changed files with 227 additions and 48 deletions

View File

@ -9,7 +9,7 @@ namespace Bit.Core.Domains
internal static string TypeValue = "user";
[JsonProperty("id")]
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; private set; } = TypeValue;

View File

@ -0,0 +1,52 @@
using System;
namespace Bit.Core.Repositories.SqlServer
{
public abstract class BaseRepository
{
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
public BaseRepository(string connectionString)
{
if(string.IsNullOrWhiteSpace(connectionString))
{
throw new ArgumentNullException(nameof(connectionString));
}
ConnectionString = connectionString;
}
protected string ConnectionString { get; private set; }
/// <summary>
/// Generate sequential Guid for Sql Server.
/// ref: https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs
/// </summary>
/// <returns>A comb Guid.</returns>
protected Guid GenerateComb()
{
var guidArray = Guid.NewGuid().ToByteArray();
var now = DateTime.UtcNow;
// Get the days and milliseconds which will be used to build the byte string
var days = new TimeSpan(now.Ticks - _baseDateTicks);
var msecs = now.TimeOfDay;
// Convert to a byte array
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333
var daysArray = BitConverter.GetBytes(days.Days);
var msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds / 3.333333));
// Reverse the bytes to match SQL Servers ordering
Array.Reverse(daysArray);
Array.Reverse(msecsArray);
// Copy the bytes into the guid
Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
return new Guid(guidArray);
}
}
}

View File

@ -1,27 +1,193 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Threading.Tasks;
using Bit.Core.Repositories.SqlServer.Models;
using DataTableProxy;
using Bit.Core.Domains;
namespace Bit.Core.Repositories.SqlServer
{
public class CipherRepository : ICipherRepository
public class CipherRepository : BaseRepository, ICipherRepository
{
public CipherRepository(string connectionString)
: base(connectionString)
{ }
public Task DirtyCiphersAsync(string userId)
{
throw new NotImplementedException();
return Task.FromResult(0);
}
public Task UpdateDirtyCiphersAsync(IEnumerable<dynamic> ciphers)
{
throw new NotImplementedException();
var cleanedCiphers = ciphers.Where(c => c is Cipher);
if(cleanedCiphers.Count() == 0)
{
return Task.FromResult(0);
}
using(var connection = new SqlConnection(ConnectionString))
{
connection.Open();
using(var transaction = connection.BeginTransaction())
{
try
{
// 1. Create temp tables to bulk copy into.
var sqlCreateTemp = @"
SELECT TOP 0 *
INTO #TempFolder
FROM [dbo].[Folder]
SELECT TOP 0 *
INTO #TempSite
FROM [dbo].[Site]";
using(var cmd = new SqlCommand(sqlCreateTemp, connection, transaction))
{
cmd.ExecuteNonQuery();
}
// 2. Bulk bopy into temp tables.
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
{
bulkCopy.DestinationTableName = "#TempFolder";
var dataTable = cleanedCiphers
.Where(c => c is Folder)
.Select(c => new FolderTableModel(c as Folder))
.ToTable(new ClassMapping<FolderTableModel>().AddAllPropertiesAsColumns());
bulkCopy.WriteToServer(dataTable);
}
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
{
bulkCopy.DestinationTableName = "#TempSite";
var dataTable = cleanedCiphers
.Where(c => c is Site)
.Select(c => new SiteTableModel(c as Site))
.ToTable(new ClassMapping<SiteTableModel>().AddAllPropertiesAsColumns());
bulkCopy.WriteToServer(dataTable);
}
// 3. Insert into real tables from temp tables and clean up.
var sqlUpdate = @"
UPDATE
[dbo].[Folder]
SET
[UserId] = TF.[UserId],
[Name] = TF.[Name],
[CreationDate] = TF.[CreationDate],
[RevisionDate] = TF.[RevisionDate]
FROM
[dbo].[Folder] F
INNER JOIN
#TempFolder TF ON F.Id = TF.Id
UPDATE
[dbo].[Site]
SET
[UserId] = TS.[UserId],
[FolderId] = TS.[FolderId],
[Name] = TS.[Name],
[Uri] = TS.[Uri],
[Username] = TS.[Username],
[Password] = TS.[Password],
[Notes] = TS.[Notes],
[CreationDate] = TS.[CreationDate],
[RevisionDate] = TS.[RevisionDate]
FROM
[dbo].[Site] S
INNER JOIN
#TempSite TS ON S.Id = TS.Id
DROP TABLE #TempFolder
DROP TABLE #TempSite";
using(var cmd = new SqlCommand(sqlUpdate, connection, transaction))
{
cmd.ExecuteNonQuery();
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
return Task.FromResult(0);
}
public Task CreateAsync(IEnumerable<dynamic> ciphers)
{
throw new NotImplementedException();
var cleanedCiphers = ciphers.Where(c => c is Cipher);
if(cleanedCiphers.Count() == 0)
{
return Task.FromResult(0);
}
// Generate new Ids for these new ciphers
foreach(var cipher in cleanedCiphers)
{
cipher.Id = GenerateComb().ToString();
}
using(var connection = new SqlConnection(ConnectionString))
{
connection.Open();
using(var transaction = connection.BeginTransaction())
{
try
{
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
{
bulkCopy.DestinationTableName = "[dbo].[Folder]";
var dataTable = cleanedCiphers
.Where(c => c is Folder)
.Select(c => new FolderTableModel(c as Folder))
.ToTable(new ClassMapping<FolderTableModel>().AddAllPropertiesAsColumns());
bulkCopy.WriteToServer(dataTable);
}
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
{
bulkCopy.DestinationTableName = "[dbo].[Site]";
var dataTable = cleanedCiphers
.Where(c => c is Site)
.Select(c => new SiteTableModel(c as Site))
.ToTable(new ClassMapping<SiteTableModel>().AddAllPropertiesAsColumns());
bulkCopy.WriteToServer(dataTable);
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
return Task.FromResult(0);
}
}
}

View File

@ -8,19 +8,11 @@ using Dapper;
namespace Bit.Core.Repositories.SqlServer
{
public abstract class Repository<T, TModel> : IRepository<T> where T : IDataObject where TModel : ITableModel<T>
public abstract class Repository<T, TModel> : BaseRepository, IRepository<T> where T : IDataObject where TModel : ITableModel<T>
{
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
public Repository(string connectionString, string schema = null, string table = null)
: base(connectionString)
{
if(string.IsNullOrWhiteSpace(connectionString))
{
throw new ArgumentNullException(nameof(connectionString));
}
ConnectionString = connectionString;
if(!string.IsNullOrWhiteSpace(table))
{
Table = table;
@ -32,7 +24,6 @@ namespace Bit.Core.Repositories.SqlServer
}
}
protected string ConnectionString { get; private set; }
protected string Schema { get; private set; } = "dbo";
protected string Table { get; private set; } = typeof(T).Name;
@ -109,36 +100,5 @@ namespace Bit.Core.Repositories.SqlServer
commandType: CommandType.StoredProcedure);
}
}
/// <summary>
/// Generate sequential Guid for Sql Server.
/// ref: https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs
/// </summary>
/// <returns>A comb Guid.</returns>
protected Guid GenerateComb()
{
var guidArray = Guid.NewGuid().ToByteArray();
var now = DateTime.UtcNow;
// Get the days and milliseconds which will be used to build the byte string
var days = new TimeSpan(now.Ticks - _baseDateTicks);
var msecs = now.TimeOfDay;
// Convert to a byte array
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333
var daysArray = BitConverter.GetBytes(days.Days);
var msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds / 3.333333));
// Reverse the bytes to match SQL Servers ordering
Array.Reverse(daysArray);
Array.Reverse(msecsArray);
// Copy the bytes into the guid
Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
return new Guid(guidArray);
}
}
}

View File

@ -15,7 +15,8 @@
"Microsoft.AspNet.DataProtection.Extensions": "1.0.0-rc1-final",
"Microsoft.Azure.DocumentDB": "1.5.2",
"Newtonsoft.Json": "8.0.1",
"Dapper": "1.42.0"
"Dapper": "1.42.0",
"DataTableProxy": "1.2.0"
},
"frameworks": {