Permalink
Browse files

Implemented non-HTTP based import/export

  • Loading branch information...
1 parent 7795596 commit cc057e70a859f7efcc9a762371378da532cf5e9a @seankearon seankearon committed May 18, 2012
View
5 Raven.Abstractions/Raven.Abstractions.csproj
@@ -158,6 +158,11 @@
<Compile Include="Indexing\SortOptions.cs" />
<Compile Include="Replication\ReplicationDestination.cs" />
<Compile Include="Replication\ReplicationDocument.cs" />
+ <Compile Include="Smuggler\ISmugglerApi.cs" />
+ <Compile Include="Smuggler\SmugglerAction.cs" />
+ <Compile Include="Smuggler\SmugglerApiBase.cs" />
+ <Compile Include="Smuggler\SmugglerOptions.cs" />
+ <Compile Include="Smuggler\TrivialJsonToJsonJsonConverter.cs" />
<Compile Include="SystemTime.cs" />
</ItemGroup>
<ItemGroup>
View
14 Raven.Smuggler/ISmugglerApi.cs → Raven.Abstractions/Smuggler/ISmugglerApi.cs
@@ -1,8 +1,8 @@
-namespace Raven.Smuggler
-{
- public interface ISmugglerApi
- {
- void ExportData(SmugglerOptions options, bool incremental);
- void ImportData(SmugglerOptions options, bool incremental);
- }
+namespace Raven.Abstractions.Smuggler
+{
+ public interface ISmugglerApi
+ {
+ void ExportData(SmugglerOptions options, bool incremental);
+ void ImportData(SmugglerOptions options, bool incremental);
+ }
}
View
14 Raven.Smuggler/SmugglerAction.cs → ...n.Abstractions/Smuggler/SmugglerAction.cs
@@ -1,8 +1,8 @@
-namespace Raven.Smuggler
-{
- public enum SmugglerAction
- {
- Import = 1,
- Export,
- }
+namespace Raven.Abstractions.Smuggler
+{
+ public enum SmugglerAction
+ {
+ Import = 1,
+ Export,
+ }
}
View
320 Raven.Abstractions/Smuggler/SmugglerApiBase.cs
@@ -0,0 +1,320 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using Newtonsoft.Json;
+using Raven.Json.Linq;
+
+namespace Raven.Abstractions.Smuggler
+{
+ public abstract class SmugglerApiBase : ISmugglerApi
+ {
+ protected abstract RavenJArray GetIndexes(int totalCount);
+ protected abstract RavenJArray GetDocuments(Guid lastEtag);
+ protected abstract Guid ExportAttachments(JsonTextWriter jsonWriter, Guid lastEtag);
+
+ protected abstract void PutIndex(string indexName, RavenJToken index);
+ protected abstract void PutAttachment(AttachmentExportInfo attachmentExportInfo);
+ protected abstract void FlushBatch(List<RavenJObject> batch);
+
+ protected abstract void ShowProgress(string format, params object[] args);
+
+ protected bool ensuredDatabaseExists;
+
+ public void ExportData(SmugglerOptions options, bool incremental = false)
+ {
+ var lastDocsEtag = Guid.Empty;
+ var lastAttachmentEtag = Guid.Empty;
+ var folder = options.File;
+ var etagFileLocation = Path.Combine(folder, "IncrementalExport.state.json");
+ if (incremental == true)
+ {
+ if (Directory.Exists(folder) == false)
+ {
+ Directory.CreateDirectory(folder);
+ }
+
+ options.File = Path.Combine(folder, DateTime.Now.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture) + ".ravendb-incremental-dump");
+ if (File.Exists(options.File))
+ {
+ var counter = 1;
+ var found = false;
+ while (found == false)
+ {
+ options.File = Path.Combine(folder, DateTime.Now.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture) + " - " + counter + ".ravendb-incremental-dump");
+
+ if (File.Exists(options.File) == false)
+ found = true;
+ counter++;
+ }
+ }
+
+ if (File.Exists(etagFileLocation))
+ {
+ using (var streamReader = new StreamReader(new FileStream(etagFileLocation, FileMode.Open)))
+ using (var jsonReader = new JsonTextReader(streamReader))
+ {
+ var ravenJObject = RavenJObject.Load(jsonReader);
+ lastDocsEtag = new Guid(ravenJObject.Value<string>("LastDocEtag"));
+ lastAttachmentEtag = new Guid(ravenJObject.Value<string>("LastAttachmentEtag"));
+ }
+ }
+ }
+
+
+ using (var streamWriter = new StreamWriter(new GZipStream(File.Create(options.File), CompressionMode.Compress)))
+ {
+ var jsonWriter = new JsonTextWriter(streamWriter)
+ {
+ Formatting = Formatting.Indented
+ };
+ jsonWriter.WriteStartObject();
+ jsonWriter.WritePropertyName("Indexes");
+ jsonWriter.WriteStartArray();
+ if (options.OperateOnTypes.HasFlag(ItemType.Indexes))
+ {
+ ExportIndexes(jsonWriter);
+ }
+ jsonWriter.WriteEndArray();
+
+ jsonWriter.WritePropertyName("Docs");
+ jsonWriter.WriteStartArray();
+ if (options.OperateOnTypes.HasFlag(ItemType.Documents))
+ {
+ lastDocsEtag = ExportDocuments(options, jsonWriter, lastDocsEtag);
+ }
+ jsonWriter.WriteEndArray();
+
+ jsonWriter.WritePropertyName("Attachments");
+ jsonWriter.WriteStartArray();
+ if (options.OperateOnTypes.HasFlag(ItemType.Attachments))
+ {
+ lastAttachmentEtag = ExportAttachments(jsonWriter, lastAttachmentEtag);
+ }
+ jsonWriter.WriteEndArray();
+
+ jsonWriter.WriteEndObject();
+ streamWriter.Flush();
+ }
+ if (incremental != true)
+ return;
+
+ using (var streamWriter = new StreamWriter(File.Create(etagFileLocation)))
+ {
+ new RavenJObject
+ {
+ {"LastDocEtag", lastDocsEtag.ToString()},
+ {"LastAttachmentEtag", lastAttachmentEtag.ToString()}
+ }.WriteTo(new JsonTextWriter(streamWriter));
+ streamWriter.Flush();
+ }
+ }
+
+ private Guid ExportDocuments(SmugglerOptions options, JsonTextWriter jsonWriter, Guid lastEtag)
+ {
+ int totalCount = 0;
+ while (true)
+ {
+ var documents = GetDocuments(lastEtag);
+ if (documents.Length == 0)
+ {
+ ShowProgress("Done with reading documents, total: {0}", totalCount);
+ return lastEtag;
+ }
+
+ var final = documents.Where(options.MatchFilters).ToList();
+ final.ForEach(item => item.WriteTo(jsonWriter));
+ totalCount += final.Count;
+
+ ShowProgress("Reading batch of {0,3} documents, read so far: {1,10:#,#;;0}", documents.Length, totalCount);
+ lastEtag = new Guid(documents.Last().Value<RavenJObject>("@metadata").Value<string>("@etag"));
+ }
+ }
+
+
+ public void ImportData(SmugglerOptions options, bool incremental = false)
+ {
+ if (incremental == false)
+ {
+ using (FileStream fileStream = File.OpenRead(options.File))
+ {
+ ImportData(fileStream, options);
+ }
+ return;
+ }
+
+ var files = Directory.GetFiles(Path.GetFullPath(options.File))
+ .Where(file => ".ravendb-incremental-dump".Equals(Path.GetExtension(file), StringComparison.CurrentCultureIgnoreCase))
+ .OrderBy(File.GetLastWriteTimeUtc)
+ .ToArray();
+
+ if (files.Length == 0)
+ return;
+
+ var optionsWithoutIndexes = new SmugglerOptions
+ {
+ File = options.File,
+ Filters = options.Filters,
+ OperateOnTypes = options.OperateOnTypes & ~ItemType.Indexes
+ };
+
+ for (var i = 0; i < files.Length - 1; i++)
+ {
+ using (var fileStream = File.OpenRead(Path.Combine(options.File, files[i])))
+ {
+ ImportData(fileStream, optionsWithoutIndexes);
+ }
+ }
+
+ using (var fileStream = File.OpenRead(Path.Combine(options.File, files.Last())))
+ {
+ ImportData(fileStream, options);
+ }
+ }
+
+ protected class AttachmentExportInfo
+ {
+ public byte[] Data { get; set; }
+ public RavenJObject Metadata { get; set; }
+ public string Key { get; set; }
+ }
+
+ protected abstract void EnsureDatabaseExists();
+
+ public void ImportData(Stream stream, SmugglerOptions options, bool importIndexes = true)
+ {
+ EnsureDatabaseExists();
+
+ var sw = Stopwatch.StartNew();
+ // Try to read the stream compressed, otherwise continue uncompressed.
+ JsonTextReader jsonReader;
+ try
+ {
+ var streamReader = new StreamReader(new GZipStream(stream, CompressionMode.Decompress));
+
+ jsonReader = new JsonTextReader(streamReader);
+
+ if (jsonReader.Read() == false)
+ return;
+ }
+ catch (InvalidDataException)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+
+ var streamReader = new StreamReader(stream);
+
+ jsonReader = new JsonTextReader(streamReader);
+
+ if (jsonReader.Read() == false)
+ return;
+ }
+
+ if (jsonReader.TokenType != JsonToken.StartObject)
+ throw new InvalidDataException("StartObject was expected");
+
+ // should read indexes now
+ if (jsonReader.Read() == false)
+ return;
+ if (jsonReader.TokenType != JsonToken.PropertyName)
+ throw new InvalidDataException("PropertyName was expected");
+ if (Equals("Indexes", jsonReader.Value) == false)
+ throw new InvalidDataException("Indexes property was expected");
+ if (jsonReader.Read() == false)
+ return;
+ if (jsonReader.TokenType != JsonToken.StartArray)
+ throw new InvalidDataException("StartArray was expected");
+
+ while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray)
+ {
+ var index = RavenJToken.ReadFrom(jsonReader);
+ if (options.OperateOnTypes.HasFlag(ItemType.Indexes) == false)
+ continue;
+ var indexName = index.Value<string>("name");
+ if (indexName.StartsWith("Raven/") || indexName.StartsWith("Temp/"))
+ continue;
+ PutIndex(indexName, index);
+ }
+
+ // should read documents now
+ if (jsonReader.Read() == false)
+ return;
+ if (jsonReader.TokenType != JsonToken.PropertyName)
+ throw new InvalidDataException("PropertyName was expected");
+ if (Equals("Docs", jsonReader.Value) == false)
+ throw new InvalidDataException("Docs property was expected");
+ if (jsonReader.Read() == false)
+ return;
+ if (jsonReader.TokenType != JsonToken.StartArray)
+ throw new InvalidDataException("StartArray was expected");
+ var batch = new List<RavenJObject>();
+ int totalCount = 0;
+ while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray)
+ {
+ var document = (RavenJObject)RavenJToken.ReadFrom(jsonReader);
+ if (options.OperateOnTypes.HasFlag(ItemType.Documents) == false)
+ continue;
+ if (options.MatchFilters(document) == false)
+ continue;
+
+ totalCount += 1;
+ batch.Add(document);
+ if (batch.Count >= 128)
+ FlushBatch(batch);
+ }
+ FlushBatch(batch);
+
+ var attachmentCount = 0;
+ if (jsonReader.Read() == false || jsonReader.TokenType == JsonToken.EndObject)
+ return;
+ if (jsonReader.TokenType != JsonToken.PropertyName)
+ throw new InvalidDataException("PropertyName was expected");
+ if (Equals("Attachments", jsonReader.Value) == false)
+ throw new InvalidDataException("Attachment property was expected");
+ if (jsonReader.Read() == false)
+ return;
+ if (jsonReader.TokenType != JsonToken.StartArray)
+ throw new InvalidDataException("StartArray was expected");
+ while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray)
+ {
+ attachmentCount += 1;
+ var item = RavenJToken.ReadFrom(jsonReader);
+ if (options.OperateOnTypes.HasFlag(ItemType.Attachments) == false)
+ continue;
+ var attachmentExportInfo =
+ new JsonSerializer
+ {
+ Converters = { new TrivialJsonToJsonJsonConverter() }
+ }.Deserialize<AttachmentExportInfo>(new RavenJTokenReader(item));
+ ShowProgress("Importing attachment {0}", attachmentExportInfo.Key);
+
+ PutAttachment(attachmentExportInfo);
+ }
+ ShowProgress("Imported {0:#,#;;0} documents and {1:#,#;;0} attachments in {2:#,#;;0} ms", totalCount, attachmentCount, sw.ElapsedMilliseconds);
+ }
+
+ protected void ExportIndexes(JsonTextWriter jsonWriter)
+ {
+ int totalCount = 0;
+ while (true)
+ {
+ RavenJArray indexes = GetIndexes(totalCount);
+
+ if (indexes.Length == 0)
+ {
+ ShowProgress("Done with reading indexes, total: {0}", totalCount);
+ break;
+ }
+ totalCount += indexes.Length;
+ ShowProgress("Reading batch of {0,3} indexes, read so far: {1,10:#,#;;0}", indexes.Length, totalCount);
+ foreach (RavenJToken item in indexes)
+ {
+ item.WriteTo(jsonWriter);
+ }
+ }
+ }
+
+ }
+}
View
126 Raven.Smuggler/SmugglerOptions.cs → ....Abstractions/Smuggler/SmugglerOptions.cs
@@ -1,64 +1,64 @@
-//-----------------------------------------------------------------------
-// <copyright file="ExportSpec.cs" company="Hibernating Rhinos LTD">
-// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-using System;
-using System.Collections.Generic;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Raven.Abstractions.Json;
-using Raven.Json.Linq;
-
-namespace Raven.Smuggler
-{
- public class SmugglerOptions
- {
- public SmugglerOptions()
- {
- Filters = new Dictionary<string, string>();
- OperateOnTypes = ItemType.Indexes | ItemType.Documents | ItemType.Attachments;
- }
-
- /// <summary>
- /// A file to write to when doing an export or read from when doing an import.
- /// </summary>
- public string File { get; set; }
-
- public Dictionary<string, string> Filters { get; set; }
-
- /// <summary>
- /// Specify the types to operate on. You can specify more than one type by combining items with the OR parameter.
- /// Default is all items.
- /// Usage example: OperateOnTypes = ItemType.Indexes | ItemType.Documents | ItemType.Attachments.
- /// </summary>
- public ItemType OperateOnTypes { get; set; }
-
- public bool MatchFilters(RavenJToken item)
- {
- foreach (var filter in Filters)
- {
- var copy = filter;
- foreach (var tuple in item.SelectTokenWithRavenSyntaxReturningFlatStructure(copy.Key))
- {
- if (tuple == null || tuple.Item1 == null)
- continue;
- var val = tuple.Item1.Type == JTokenType.String
- ? tuple.Item1.Value<string>()
- : tuple.Item1.ToString(Formatting.None);
- if (string.Equals(val, filter.Value, StringComparison.InvariantCultureIgnoreCase) == false)
- return false;
- }
- }
- return true;
- }
- }
-
- [Flags]
- public enum ItemType
- {
- Documents,
- Indexes,
- Attachments,
- }
+//-----------------------------------------------------------------------
+// <copyright file="ExportSpec.cs" company="Hibernating Rhinos LTD">
+// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Raven.Abstractions.Json;
+using Raven.Json.Linq;
+
+namespace Raven.Abstractions.Smuggler
+{
+ public class SmugglerOptions
+ {
+ public SmugglerOptions()
+ {
+ Filters = new Dictionary<string, string>();
+ OperateOnTypes = ItemType.Indexes | ItemType.Documents | ItemType.Attachments;
+ }
+
+ /// <summary>
+ /// A file to write to when doing an export or read from when doing an import.
+ /// </summary>
+ public string File { get; set; }
+
+ public Dictionary<string, string> Filters { get; set; }
+
+ /// <summary>
+ /// Specify the types to operate on. You can specify more than one type by combining items with the OR parameter.
+ /// Default is all items.
+ /// Usage example: OperateOnTypes = ItemType.Indexes | ItemType.Documents | ItemType.Attachments.
+ /// </summary>
+ public ItemType OperateOnTypes { get; set; }
+
+ public bool MatchFilters(RavenJToken item)
+ {
+ foreach (var filter in Filters)
+ {
+ var copy = filter;
+ foreach (var tuple in item.SelectTokenWithRavenSyntaxReturningFlatStructure(copy.Key))
+ {
+ if (tuple == null || tuple.Item1 == null)
+ continue;
+ var val = tuple.Item1.Type == JTokenType.String
+ ? tuple.Item1.Value<string>()
+ : tuple.Item1.ToString(Formatting.None);
+ if (string.Equals(val, filter.Value, StringComparison.InvariantCultureIgnoreCase) == false)
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ [Flags]
+ public enum ItemType
+ {
+ Documents,
+ Indexes,
+ Attachments,
+ }
}
View
56 ...muggler/TrivialJsonToJsonJsonConverter.cs → ...muggler/TrivialJsonToJsonJsonConverter.cs
@@ -1,29 +1,29 @@
-//-----------------------------------------------------------------------
-// <copyright file="TrivialJsonToJsonJsonConverter.cs" company="Hibernating Rhinos LTD">
-// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-using System;
-using Newtonsoft.Json;
-using Raven.Json.Linq;
-
-namespace Raven.Smuggler
-{
- public class TrivialJsonToJsonJsonConverter : JsonConverter
- {
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
- {
- throw new NotImplementedException();
- }
-
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
- {
- return RavenJObject.Load(reader);
- }
-
- public override bool CanConvert(Type objectType)
- {
- return objectType == typeof(RavenJObject);
- }
- }
+//-----------------------------------------------------------------------
+// <copyright file="TrivialJsonToJsonJsonConverter.cs" company="Hibernating Rhinos LTD">
+// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+using System;
+using Newtonsoft.Json;
+using Raven.Json.Linq;
+
+namespace Raven.Abstractions.Smuggler
+{
+ public class TrivialJsonToJsonJsonConverter : JsonConverter
+ {
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ return RavenJObject.Load(reader);
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(RavenJObject);
+ }
+ }
}
View
1 Raven.Database/Raven.Database.csproj
@@ -466,6 +466,7 @@
<Compile Include="Server\Responders\TransactionPromote.cs" />
<Compile Include="Server\Responders\TransactionRollback.cs" />
<Compile Include="Server\Responders\TransactionStatus.cs" />
+ <Compile Include="Smuggler\DataDumper.cs" />
<Compile Include="Storage\DocumentInTransactionData.cs" />
<Compile Include="Storage\GetMappedResultsParams.cs" />
<Compile Include="Storage\IAttachmentsStorageActions.cs" />
View
146 Raven.Database/Smuggler/DataDumper.cs
@@ -0,0 +1,146 @@
+// -----------------------------------------------------------------------
+// <copyright file="DataDumper.cs" company="Hibernating Rhinos LTD">
+// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using Newtonsoft.Json;
+using Raven.Abstractions.Commands;
+using Raven.Abstractions.Extensions;
+using Raven.Abstractions.Indexing;
+using Raven.Abstractions.Smuggler;
+using Raven.Json.Linq;
+
+namespace Raven.Database.Smuggler
+{
+ public class DataDumper : SmugglerApiBase
+ {
+ public DataDumper(DocumentDatabase database)
+ {
+ _database = database;
+ }
+
+ private readonly DocumentDatabase _database;
+
+ protected override void EnsureDatabaseExists()
+ {
+ ensuredDatabaseExists = true;
+ }
+
+ protected override Guid ExportAttachments(JsonTextWriter jsonWriter, Guid lastEtag)
+ {
+ var totalCount = 0;
+ while (true)
+ {
+ var array = GetAttachments(totalCount, lastEtag);
+ if (array.Length == 0)
+ {
+ ShowProgress("Done with reading attachments, total: {0}", totalCount);
+ return lastEtag;
+ }
+ totalCount += array.Length;
+ ShowProgress("Reading batch of {0,3} attachments, read so far: {1,10:#,#;;0}", array.Length, totalCount);
+ foreach (var item in array)
+ {
+ item.WriteTo(jsonWriter);
+ }
+ lastEtag = new Guid(array.Last().Value<string>("Etag"));
+ }
+ }
+
+ protected override void FlushBatch(List<RavenJObject> batch)
+ {
+ var sw = Stopwatch.StartNew();
+
+ _database.Batch(batch.Select(x =>
+ {
+ var metadata = x.Value<RavenJObject>("@metadata");
+ var key = metadata.Value<string>("@id");
+ x.Remove("@metadata");
+ return new PutCommandData
+ {
+ Document = x,
+ Etag = null,
+ Key = key,
+ Metadata = metadata
+ };
+ }).ToArray());
+
+ ShowProgress("Wrote {0} documents in {1}", batch.Count, sw.ElapsedMilliseconds);
+ ShowProgress(" in {0:#,#;;0} ms", sw.ElapsedMilliseconds);
+ batch.Clear();
+ }
+
+ protected override RavenJArray GetDocuments(Guid lastEtag)
+ {
+ const int dummy = 0;
+ return _database.GetDocuments(dummy, 128, lastEtag);
+ }
+
+ protected override RavenJArray GetIndexes(int totalCount)
+ {
+ return _database.GetIndexes(totalCount, 128);
+ }
+
+ protected override void PutAttachment(AttachmentExportInfo attachmentExportInfo)
+ {
+ // we filter out content length, because getting it wrong will cause errors
+ // in the server side when serving the wrong value for this header.
+ // worse, if we are using http compression, this value is known to be wrong
+ // instead, we rely on the actual size of the data provided for us
+ attachmentExportInfo.Metadata.Remove("Content-Length");
+ _database.PutStatic(attachmentExportInfo.Key, null, new MemoryStream(attachmentExportInfo.Data), attachmentExportInfo.Metadata);
+ }
+
+ protected override void PutIndex(string indexName, RavenJToken index)
+ {
+ _database.PutIndex(indexName, index.Value<RavenJObject>("definition").JsonDeserialization<IndexDefinition>());
+ }
+
+ protected override void ShowProgress(string format, params object[] args)
+ {
+ if (Progress != null)
+ {
+ Progress(string.Format(format, args));
+ }
+ }
+
+ private RavenJArray GetAttachments(int start, Guid? etag)
+ {
+ var array = new RavenJArray();
+ var attachmentInfos = _database.GetAttachments(start, 128, etag);
+
+ foreach (var attachmentInfo in attachmentInfos)
+ {
+ var attachment = _database.GetStatic(attachmentInfo.Key);
+ if (attachment == null)
+ return null;
+ var data = attachment.Data;
+ attachment.Data = () =>
+ {
+ var memoryStream = new MemoryStream();
+ _database.TransactionalStorage.Batch(accessor => data().CopyTo(memoryStream));
+ memoryStream.Position = 0;
+ return memoryStream;
+ };
+
+ var bytes = attachment.Data().ReadData();
+ array.Add(
+ new RavenJObject
+ {
+ {"Data", bytes},
+ {"Metadata", attachmentInfo.Metadata},
+ {"Key", attachmentInfo.Key},
+ {"Etag", new RavenJValue(attachmentInfo.Etag.ToString())}
+ });
+ }
+ return array;
+ }
+
+ public Action<string> Progress { get; set; }
+ }
+}
View
1 Raven.Server/Program.cs
@@ -17,6 +17,7 @@
using NLog.Config;
using Raven.Abstractions;
using Raven.Abstractions.Data;
+using Raven.Abstractions.Smuggler;
using Raven.Database;
using Raven.Database.Config;
using Raven.Database.Server;
View
9 Raven.Server/Raven.Server.csproj
@@ -107,18 +107,9 @@
<Compile Include="..\Raven.Client.Lightweight\Extensions\MultiDatabase.cs">
<Link>Smuggler\Imports\MultiDatabase.cs</Link>
</Compile>
- <Compile Include="..\Raven.Smuggler\ISmugglerApi.cs">
- <Link>Smuggler\ISmugglerApi.cs</Link>
- </Compile>
<Compile Include="..\Raven.Smuggler\SmugglerApi.cs">
<Link>Smuggler\SmugglerApi.cs</Link>
</Compile>
- <Compile Include="..\Raven.Smuggler\SmugglerOptions.cs">
- <Link>Smuggler\SmugglerOptions.cs</Link>
- </Compile>
- <Compile Include="..\Raven.Smuggler\TrivialJsonToJsonJsonConverter.cs">
- <Link>Smuggler\TrivialJsonToJsonJsonConverter.cs</Link>
- </Compile>
<Compile Include="FromMono\Options.cs" />
<Compile Include="RavenDbServer.cs" />
<Compile Include="Program.cs" />
View
1 Raven.Smuggler/Program.cs
@@ -8,6 +8,7 @@
using System.Net;
using NDesk.Options;
using Raven.Abstractions.Data;
+using Raven.Abstractions.Smuggler;
namespace Raven.Smuggler
{
View
4 Raven.Smuggler/Raven.Smuggler.csproj
@@ -65,12 +65,8 @@
<Compile Include="..\Raven.Server\FromMono\Options.cs">
<Link>FromMono\Options.cs</Link>
</Compile>
- <Compile Include="ISmugglerApi.cs" />
<Compile Include="Program.cs" />
- <Compile Include="SmugglerAction.cs" />
<Compile Include="SmugglerApi.cs" />
- <Compile Include="SmugglerOptions.cs" />
- <Compile Include="TrivialJsonToJsonJsonConverter.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="..\Raven.Server\favicon.ico">
View
382 Raven.Smuggler/SmugglerApi.cs
@@ -6,23 +6,36 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using Raven.Abstractions.Connection;
using Raven.Abstractions.Data;
using Raven.Abstractions.Extensions;
+using Raven.Abstractions.Smuggler;
using Raven.Client.Extensions;
using Raven.Json.Linq;
namespace Raven.Smuggler
{
- public class SmugglerApi : ISmugglerApi
+ public class SmugglerApi : SmugglerApiBase
{
+ protected override RavenJArray GetIndexes(int totalCount)
+ {
+ RavenJArray indexes = null;
+ var request = CreateRequest("/indexes?pageSize=128&start=" + totalCount);
+ request.ExecuteRequest(reader => indexes = RavenJArray.Load(new JsonTextReader(reader)));
+ return indexes;
+ }
+
+ private static string StripQuotesIfNeeded(RavenJToken value)
+ {
+ var str = value.ToString(Formatting.None);
+ if (str.StartsWith("\"") && str.EndsWith("\""))
+ return str.Substring(1, str.Length - 2);
+ return str;
+ }
public RavenConnectionStringOptions ConnectionStringOptions { get; private set; }
private readonly HttpRavenRequestFactory httpRavenRequestFactory = new HttpRavenRequestFactory();
@@ -46,143 +59,15 @@ protected HttpRavenRequest CreateRequest(string url, string method = "GET")
return httpRavenRequestFactory.Create(builder.ToString(), method, ConnectionStringOptions);
}
- public void ExportData(SmugglerOptions options, bool incremental = false)
- {
- var lastDocsEtag = Guid.Empty;
- var lastAttachmentEtag = Guid.Empty;
- var folder = options.File;
- var etagFileLocation = Path.Combine(folder, "IncrementalExport.state.json");
- if (incremental == true)
- {
- if (Directory.Exists(folder) == false)
- {
- Directory.CreateDirectory(folder);
- }
-
- options.File = Path.Combine(folder, DateTime.Now.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture) + ".ravendb-incremental-dump");
- if (File.Exists(options.File))
- {
- var counter = 1;
- var found = false;
- while (found == false)
- {
- options.File = Path.Combine(folder, DateTime.Now.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture) + " - " + counter + ".ravendb-incremental-dump");
-
- if (File.Exists(options.File) == false)
- found = true;
- counter++;
- }
- }
-
- if (File.Exists(etagFileLocation))
- {
- using (var streamReader = new StreamReader(new FileStream(etagFileLocation, FileMode.Open)))
- using (var jsonReader = new JsonTextReader(streamReader))
- {
- var ravenJObject = RavenJObject.Load(jsonReader);
- lastDocsEtag = new Guid(ravenJObject.Value<string>("LastDocEtag"));
- lastAttachmentEtag = new Guid(ravenJObject.Value<string>("LastAttachmentEtag"));
- }
- }
- }
-
-
- using (var streamWriter = new StreamWriter(new GZipStream(File.Create(options.File), CompressionMode.Compress)))
- {
- var jsonWriter = new JsonTextWriter(streamWriter)
- {
- Formatting = Formatting.Indented
- };
- jsonWriter.WriteStartObject();
- jsonWriter.WritePropertyName("Indexes");
- jsonWriter.WriteStartArray();
- if (options.OperateOnTypes.HasFlag(ItemType.Indexes))
- {
- ExportIndexes(jsonWriter);
- }
- jsonWriter.WriteEndArray();
-
- jsonWriter.WritePropertyName("Docs");
- jsonWriter.WriteStartArray();
- if (options.OperateOnTypes.HasFlag(ItemType.Documents))
- {
- lastDocsEtag = ExportDocuments(options, jsonWriter, lastDocsEtag);
- }
- jsonWriter.WriteEndArray();
-
- jsonWriter.WritePropertyName("Attachments");
- jsonWriter.WriteStartArray();
- if (options.OperateOnTypes.HasFlag(ItemType.Attachments))
- {
- lastAttachmentEtag = ExportAttachments(jsonWriter, lastAttachmentEtag);
- }
- jsonWriter.WriteEndArray();
-
- jsonWriter.WriteEndObject();
- streamWriter.Flush();
- }
- if (incremental != true)
- return;
-
- using (var streamWriter = new StreamWriter(File.Create(etagFileLocation)))
- {
- new RavenJObject
- {
- {"LastDocEtag", lastDocsEtag.ToString()},
- {"LastAttachmentEtag", lastAttachmentEtag.ToString()}
- }.WriteTo(new JsonTextWriter(streamWriter));
- streamWriter.Flush();
- }
- }
-
- private void ExportIndexes(JsonTextWriter jsonWriter)
+ protected override RavenJArray GetDocuments(Guid lastEtag)
{
- int totalCount = 0;
- while (true)
- {
- RavenJArray indexes = null;
- var request = CreateRequest("/indexes?pageSize=128&start=" + totalCount);
- request.ExecuteRequest(reader => indexes = RavenJArray.Load(new JsonTextReader(reader)));
-
- if (indexes.Length == 0)
- {
- Console.WriteLine("Done with reading indexes, total: {0}", totalCount);
- break;
- }
- totalCount += indexes.Length;
- Console.WriteLine("Reading batch of {0,3} indexes, read so far: {1,10:#,#;;0}", indexes.Length, totalCount);
- foreach (RavenJToken item in indexes)
- {
- item.WriteTo(jsonWriter);
- }
- }
+ RavenJArray documents = null;
+ var request = CreateRequest("/docs?pageSize=128&etag=" + lastEtag);
+ request.ExecuteRequest(reader => documents = RavenJArray.Load(new JsonTextReader(reader)));
+ return documents;
}
- private Guid ExportDocuments(SmugglerOptions options, JsonTextWriter jsonWriter, Guid lastEtag)
- {
- int totalCount = 0;
- while (true)
- {
- RavenJArray documents = null;
- var request = CreateRequest("/docs?pageSize=128&etag=" + lastEtag);
- request.ExecuteRequest(reader => documents = RavenJArray.Load(new JsonTextReader(reader)));
-
- if (documents.Length == 0)
- {
- Console.WriteLine("Done with reading documents, total: {0}", totalCount);
- return lastEtag;
- }
-
- var final = documents.Where(options.MatchFilters).ToList();
- final.ForEach(item => item.WriteTo(jsonWriter));
- totalCount += final.Count;
-
- Console.WriteLine("Reading batch of {0,3} documents, read so far: {1,10:#,#;;0}", documents.Length, totalCount);
- lastEtag = new Guid(documents.Last().Value<RavenJObject>("@metadata").Value<string>("@etag"));
- }
- }
-
- private Guid ExportAttachments(JsonTextWriter jsonWriter, Guid lastEtag)
+ protected override Guid ExportAttachments(JsonTextWriter jsonWriter, Guid lastEtag)
{
int totalCount = 0;
while (true)
@@ -193,15 +78,15 @@ private Guid ExportAttachments(JsonTextWriter jsonWriter, Guid lastEtag)
if (attachmentInfo.Length == 0)
{
- Console.WriteLine("Done with reading attachments, total: {0}", totalCount);
+ ShowProgress("Done with reading attachments, total: {0}", totalCount);
return lastEtag;
}
totalCount += attachmentInfo.Length;
- Console.WriteLine("Reading batch of {0,3} attachments, read so far: {1,10:#,#;;0}", attachmentInfo.Length, totalCount);
+ ShowProgress("Reading batch of {0,3} attachments, read so far: {1,10:#,#;;0}", attachmentInfo.Length, totalCount);
foreach (var item in attachmentInfo)
{
- Console.WriteLine("Downloading attachment: {0}", item.Value<string>("Key"));
+ ShowProgress("Downloading attachment: {0}", item.Value<string>("Key"));
byte[] attachmentData = null;
var requestData = CreateRequest("/static/" + item.Value<string>("Key"));
@@ -220,188 +105,43 @@ private Guid ExportAttachments(JsonTextWriter jsonWriter, Guid lastEtag)
}
}
- public void ImportData(SmugglerOptions options, bool incremental = false)
+ protected override void PutAttachment(AttachmentExportInfo attachmentExportInfo)
{
- if (incremental == false)
+ var request = CreateRequest("/static/" + attachmentExportInfo.Key, "PUT");
+ if (attachmentExportInfo.Metadata != null)
{
- using (FileStream fileStream = File.OpenRead(options.File))
+ foreach (var header in attachmentExportInfo.Metadata)
{
- ImportData(fileStream, options);
+ switch (header.Key)
+ {
+ case "Content-Type":
+ request.WebRequest.ContentType = header.Value.Value<string>();
+ break;
+ default:
+ request.WebRequest.Headers.Add(header.Key, StripQuotesIfNeeded(header.Value));
+ break;
+ }
}
- return;
}
- var files = Directory.GetFiles(Path.GetFullPath(options.File))
- .Where(file => ".ravendb-incremental-dump".Equals(Path.GetExtension(file), StringComparison.CurrentCultureIgnoreCase))
- .OrderBy(File.GetLastWriteTimeUtc)
- .ToArray();
-
- if (files.Length == 0)
- return;
-
- var optionsWithoutIndexes = new SmugglerOptions
- {
- File = options.File,
- Filters = options.Filters,
- OperateOnTypes = options.OperateOnTypes & ~ItemType.Indexes
- };
-
- for (var i = 0; i < files.Length - 1; i++)
- {
- using (var fileStream = File.OpenRead(Path.Combine(options.File, files[i])))
- {
- ImportData(fileStream, optionsWithoutIndexes);
- }
- }
+ request.Write(attachmentExportInfo.Data);
+ request.ExecuteRequest();
- using (var fileStream = File.OpenRead(Path.Combine(options.File, files.Last())))
- {
- ImportData(fileStream, options);
- }
}
- public void ImportData(Stream stream, SmugglerOptions options, bool importIndexes = true)
+ protected override void PutIndex(string indexName, RavenJToken index)
{
- EnsureDatabaseExists();
-
- var sw = Stopwatch.StartNew();
- // Try to read the stream compressed, otherwise continue uncompressed.
- JsonTextReader jsonReader;
- try
- {
- var streamReader = new StreamReader(new GZipStream(stream, CompressionMode.Decompress));
-
- jsonReader = new JsonTextReader(streamReader);
-
- if (jsonReader.Read() == false)
- return;
- }
- catch (InvalidDataException)
- {
- stream.Seek(0, SeekOrigin.Begin);
-
- var streamReader = new StreamReader(stream);
-
- jsonReader = new JsonTextReader(streamReader);
-
- if (jsonReader.Read() == false)
- return;
- }
-
- if (jsonReader.TokenType != JsonToken.StartObject)
- throw new InvalidDataException("StartObject was expected");
-
- // should read indexes now
- if (jsonReader.Read() == false)
- return;
- if (jsonReader.TokenType != JsonToken.PropertyName)
- throw new InvalidDataException("PropertyName was expected");
- if (Equals("Indexes", jsonReader.Value) == false)
- throw new InvalidDataException("Indexes property was expected");
- if (jsonReader.Read() == false)
- return;
- if (jsonReader.TokenType != JsonToken.StartArray)
- throw new InvalidDataException("StartArray was expected");
-
- while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray)
- {
- var index = RavenJToken.ReadFrom(jsonReader);
- if (options.OperateOnTypes.HasFlag(ItemType.Indexes) == false)
- continue;
- var indexName = index.Value<string>("name");
- if (indexName.StartsWith("Raven/") || indexName.StartsWith("Temp/"))
- continue;
-
- var request = CreateRequest("/indexes/" + indexName, "PUT");
- request.Write(index.Value<RavenJObject>("definition"));
- request.ExecuteRequest();
-
- }
-
- // should read documents now
- if (jsonReader.Read() == false)
- return;
- if (jsonReader.TokenType != JsonToken.PropertyName)
- throw new InvalidDataException("PropertyName was expected");
- if (Equals("Docs", jsonReader.Value) == false)
- throw new InvalidDataException("Docs property was expected");
- if (jsonReader.Read() == false)
- return;
- if (jsonReader.TokenType != JsonToken.StartArray)
- throw new InvalidDataException("StartArray was expected");
- var batch = new List<RavenJObject>();
- int totalCount = 0;
- while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray)
- {
- var document = (RavenJObject)RavenJToken.ReadFrom(jsonReader);
- if (options.OperateOnTypes.HasFlag(ItemType.Documents) == false)
- continue;
- if (options.MatchFilters(document) == false)
- continue;
-
- totalCount += 1;
- batch.Add(document);
- if (batch.Count >= 128)
- FlushBatch(batch);
- }
- FlushBatch(batch);
-
- var attachmentCount = 0;
- if (jsonReader.Read() == false || jsonReader.TokenType == JsonToken.EndObject)
- return;
- if (jsonReader.TokenType != JsonToken.PropertyName)
- throw new InvalidDataException("PropertyName was expected");
- if (Equals("Attachments", jsonReader.Value) == false)
- throw new InvalidDataException("Attachment property was expected");
- if (jsonReader.Read() == false)
- return;
- if (jsonReader.TokenType != JsonToken.StartArray)
- throw new InvalidDataException("StartArray was expected");
- while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray)
- {
- attachmentCount += 1;
- var item = RavenJToken.ReadFrom(jsonReader);
- if (options.OperateOnTypes.HasFlag(ItemType.Attachments) == false)
- continue;
- var attachmentExportInfo =
- new JsonSerializer
- {
- Converters = { new TrivialJsonToJsonJsonConverter() }
- }.Deserialize<AttachmentExportInfo>(new RavenJTokenReader(item));
- Console.WriteLine("Importing attachment {0}", attachmentExportInfo.Key);
-
- var request = CreateRequest("/static/" + attachmentExportInfo.Key, "PUT");
- if (attachmentExportInfo.Metadata != null)
- {
- foreach (var header in attachmentExportInfo.Metadata)
- {
- switch (header.Key)
- {
- case "Content-Type":
- request.WebRequest.ContentType = header.Value.Value<string>();
- break;
- default:
- request.WebRequest.Headers.Add(header.Key, StripQuotesIfNeeded(header.Value));
- break;
- }
- }
- }
-
- request.Write(attachmentExportInfo.Data);
- request.ExecuteRequest();
- }
- Console.WriteLine("Imported {0:#,#;;0} documents and {1:#,#;;0} attachments in {2:#,#;;0} ms", totalCount, attachmentCount, sw.ElapsedMilliseconds);
+ var request = CreateRequest("/indexes/" + indexName, "PUT");
+ request.Write(index.Value<RavenJObject>("definition"));
+ request.ExecuteRequest();
}
- private static string StripQuotesIfNeeded(RavenJToken value)
+ protected override void ShowProgress(string format, params object[] args)
{
- var str = value.ToString(Formatting.None);
- if (str.StartsWith("\"") && str.EndsWith("\""))
- return str.Substring(1, str.Length - 2);
- return str;
+ Console.WriteLine(format, args);
}
- private void FlushBatch(List<RavenJObject> batch)
+ protected override void FlushBatch(List<RavenJObject> batch)
{
var sw = Stopwatch.StartNew();
@@ -411,33 +151,25 @@ private void FlushBatch(List<RavenJObject> batch)
var metadata = doc.Value<RavenJObject>("@metadata");
doc.Remove("@metadata");
commands.Add(new RavenJObject
- {
- {"Method", "PUT"},
- {"Document", doc},
- {"Metadata", metadata},
- {"Key", metadata.Value<string>("@id")}
- });
+ {
+ {"Method", "PUT"},
+ {"Document", doc},
+ {"Metadata", metadata},
+ {"Key", metadata.Value<string>("@id")}
+ });
}
var request = CreateRequest("/bulk_docs", "POST");
request.Write(commands);
request.ExecuteRequest();
- Console.Write("Wrote {0} documents in {1}", batch.Count, sw.ElapsedMilliseconds);
+ ShowProgress("Wrote {0} documents in {1}", batch.Count, sw.ElapsedMilliseconds);
- Console.WriteLine(" in {0:#,#;;0} ms", sw.ElapsedMilliseconds);
+ ShowProgress(" in {0:#,#;;0} ms", sw.ElapsedMilliseconds);
batch.Clear();
}
- private class AttachmentExportInfo
- {
- public byte[] Data { get; set; }
- public RavenJObject Metadata { get; set; }
- public string Key { get; set; }
- }
-
- private bool ensuredDatabaseExists;
- private void EnsureDatabaseExists()
+ protected override void EnsureDatabaseExists()
{
if (ensuredDatabaseExists ||
string.IsNullOrWhiteSpace(ConnectionStringOptions.DefaultDatabase))
View
1 Raven.Tests/Bugs/HiLoServerKeysNotExported.cs
@@ -8,6 +8,7 @@
using Raven.Abstractions.Data;
using Raven.Abstractions.Extensions;
using Raven.Abstractions.Indexing;
+using Raven.Abstractions.Smuggler;
using Raven.Client.Document;
using Raven.Database.Config;
using Raven.Database.Extensions;
View
1 Raven.Tests/MailingList/MapReduceIssue/CanPageThroughReduceResults.cs
@@ -1,4 +1,5 @@
using Raven.Abstractions.Data;
+using Raven.Abstractions.Smuggler;
using Raven.Client.Document;
using Raven.Client.Linq;
using Raven.Smuggler;
View
161 Raven.Tests/MailingList/NonHttpBackupRestore.cs
@@ -0,0 +1,161 @@
+// -----------------------------------------------------------------------
+// <copyright file="NonHttpBackupRestore.cs" company="Hibernating Rhinos LTD">
+// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+using System.IO;
+using System.Linq;
+using Raven.Abstractions.Smuggler;
+using Raven.Client;
+using Raven.Client.Embedded;
+using Raven.Client.Indexes;
+using Raven.Database.Smuggler;
+using Raven.Json.Linq;
+using Xunit;
+
+namespace Raven.Tests.MailingList
+{
+ public class NonHttpBackupRestore : RavenTest
+ {
+ [Fact]
+ public void CanImportFromDumpFile()
+ {
+ var options = new SmugglerOptions { File = Path.GetTempFileName() };
+ using (var store = NewDocumentStoreWithData())
+ {
+ var dumper = new DataDumper(store.DocumentDatabase);
+ dumper.ExportData(options);
+ }
+
+ using (var store = NewDocumentStore())
+ {
+ var dumper = new DataDumper(store.DocumentDatabase);
+ dumper.ImportData(options);
+
+ using (var session = store.OpenSession())
+ {
+ // Person imported.
+ Assert.Equal(1, session.Query<Person>().Customize(x => x.WaitForNonStaleResults()).Take(5).Count());
+
+ // Attachment imported.
+ var attachment = store.DatabaseCommands.GetAttachment("Attachments/1");
+ var data = ReadFully(attachment.Data());
+ Assert.Equal(new byte[] { 1, 2, 3 }, data);
+ }
+ }
+ }
+
+ [Fact]
+ public void ImportReplacesAnExistingDatabase()
+ {
+ var options = new SmugglerOptions {File = Path.GetTempFileName()};
+
+ using (var store = NewDocumentStoreWithData())
+ {
+ var dumper = new DataDumper(store.DocumentDatabase);
+ dumper.ExportData(options);
+
+ using (var session = store.OpenSession())
+ {
+ var person = session.Load<Person>(1);
+ person.Name = "Sean Kearon";
+
+ session.Store(new Person {Name = "Gillian"});
+
+ store.DatabaseCommands.DeleteAttachment("Attachments/1", null);
+
+ store.DatabaseCommands.PutAttachment(
+ "Attachments/2",
+ null,
+ new MemoryStream(new byte[] {1, 2, 3, 4, 5, 6}),
+ new RavenJObject {{"Description", "This is another attachment."}});
+
+ session.SaveChanges();
+ }
+
+ new DataDumper(store.DocumentDatabase).ImportData(options);
+ using (var session = store.OpenSession())
+ {
+ // Original attachment has been restored.
+ Assert.NotNull(store.DatabaseCommands.GetAttachment("Attachments/1"));
+
+ // The newly added attachment is still there.
+ Assert.NotNull(store.DatabaseCommands.GetAttachment("Attachments/2"));
+
+ // Original person has been restored.
+ Assert.NotNull(session.Query<Person, PeopleByName>().Customize(x => x.WaitForNonStaleResults()).Single(x => x.Name == "Sean"));
+
+ // The newly added person has not been removed.
+ Assert.True(session.Query<Person, PeopleByName>().Customize(x => x.WaitForNonStaleResults()).Any(x => x.Name == "Gillian"));
+ }
+ }
+ }
+
+ protected override void CreateDefaultIndexes(IDocumentStore documentStore)
+ {
+ base.CreateDefaultIndexes(documentStore);
+ new PeopleByName().Execute(documentStore);
+ }
+
+ protected byte[] ReadFully(Stream input)
+ {
+ var buffer = new byte[16 * 1024];
+ using (var ms = new MemoryStream())
+ {
+ int read;
+ while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ ms.Write(buffer, 0, read);
+ }
+ return ms.ToArray();
+ }
+ }
+
+ private EmbeddableDocumentStore NewDocumentStoreWithData()
+ {
+ var store = NewDocumentStore();
+
+ using (var session = store.OpenSession())
+ {
+ session.Store(new Person { Name = "Sean" });
+ session.SaveChanges();
+
+ store.DatabaseCommands.PutAttachment(
+ "Attachments/1",
+ null,
+ new MemoryStream(new byte[] { 1, 2, 3 }),
+ new RavenJObject { { "Description", "This is an attachment." } });
+ }
+
+ using (var session = store.OpenSession())
+ {
+ // Ensure the index is built.
+ var people = session.Query<Person, PeopleByName>()
+ .Customize(x => x.WaitForNonStaleResults())
+ .Where(x => x.Name == "Sean")
+ .ToArray();
+ Assert.NotEmpty(people);
+ }
+
+ return store;
+ }
+
+ public class PeopleByName : AbstractIndexCreationTask<Person>
+ {
+ public PeopleByName()
+ {
+ Map = (persons => from person in persons
+ select new
+ {
+ person.Name,
+ });
+ }
+ }
+
+ public class Person
+ {
+ public string Id;
+ public string Name;
+ }
+ }
+}
View
3 Raven.Tests/Raven.Tests.csproj
@@ -596,6 +596,7 @@
<Compile Include="MailingList\Maverix.cs" />
<Compile Include="MailingList\NestedIndexDynamic.cs" />
<Compile Include="MailingList\Nick.cs" />
+ <Compile Include="MailingList\NonHttpBackupRestore.cs" />
<Compile Include="MailingList\NSB.cs" />
<Compile Include="MailingList\OrderByValueTypeCast.cs" />
<Compile Include="MailingList\Oregon.cs" />
@@ -929,4 +930,4 @@
<Target Name="AfterBuild">
</Target>
-->
-</Project>
+</Project>

0 comments on commit cc057e7

Please sign in to comment.