├── .editorconfig ├── .github └── workflows │ └── dotnet-core.yml ├── .gitignore ├── LICENSE ├── Packaging └── couchbase-logo.png ├── README.md ├── Src ├── Couchbase.Linq.IntegrationTests │ ├── AnyAllTests.cs │ ├── AverageTests.cs │ ├── BeerSample.cs │ ├── BucketContextTests.cs │ ├── ConsistencyTests.cs │ ├── Couchbase.Linq.IntegrationTests.csproj │ ├── CountQueryTests.cs │ ├── Documents │ │ ├── Airline.cs │ │ ├── Airport.cs │ │ ├── Beer.cs │ │ ├── BeerFiltered.cs │ │ ├── BeerStyle.cs │ │ ├── BeerWithEnum.cs │ │ ├── Brewery.cs │ │ ├── BreweryFilter.cs │ │ ├── Geo.cs │ │ ├── Route.cs │ │ ├── RouteInCollection.cs │ │ └── Schedule.cs │ ├── FirstQueryTests.cs │ ├── MinMaxTests.cs │ ├── N1QLTestBase.cs │ ├── QueryTests.cs │ ├── SingleQueryTests.cs │ ├── SumTests.cs │ ├── TestConfigurations.cs │ ├── TestSetup.cs │ ├── TravelSample.cs │ └── config.json ├── Couchbase.Linq.UnitTests │ ├── BeerSample.cs │ ├── BucketContextTests.cs │ ├── Clauses │ │ ├── NestClauseTests.cs │ │ └── UseKeysClauseTests.cs │ ├── ClusterQueryExecutorEmulator.cs │ ├── Couchbase.Linq.UnitTests.csproj │ ├── Documents │ │ ├── Address.cs │ │ ├── Airline.cs │ │ ├── Airport.cs │ │ ├── Beer.cs │ │ ├── BeerFiltered.cs │ │ ├── Brewery.cs │ │ ├── BreweryFilter.cs │ │ ├── Car.cs │ │ ├── Child.cs │ │ ├── ChildWithContract.cs │ │ ├── Contact.cs │ │ ├── Geo.cs │ │ ├── Route.cs │ │ ├── RouteInCollection.cs │ │ └── Schedule.cs │ ├── Extensions │ │ └── QueryExtensionTests.cs │ ├── Filters │ │ └── CollectionMetadataCacheTests.cs │ ├── Metadata │ │ ├── ContextMetadataTests.cs │ │ └── DocumentSetMetadataTests.cs │ ├── N1QLTestBase.cs │ ├── QueryFactory.cs │ ├── QueryGeneration │ │ ├── AggregateTests.cs │ │ ├── AnyAllTests.cs │ │ ├── ArrayIndexTests.cs │ │ ├── ArrayOperatorTests.cs │ │ ├── BinaryExpressionTests.cs │ │ ├── ConditionalExpressionTests.cs │ │ ├── ConstantExpressionTests.cs │ │ ├── DictionaryTests.cs │ │ ├── DistinctTests.cs │ │ ├── EnumTests.cs │ │ ├── ExplainTests.cs │ │ ├── ExpressionTransformers │ │ │ └── StringComparisonExpressionTransformerTests.cs │ │ ├── FirstTests.cs │ │ ├── HintTests.cs │ │ ├── IsMissingTests.cs │ │ ├── JoinTests.cs │ │ ├── MathTests.cs │ │ ├── MemberNameResolutionTests.cs │ │ ├── MetaTests.cs │ │ ├── MethodCallTranslators │ │ │ ├── ContainsMethodCallTranslatorTests.cs │ │ │ ├── DateMethodCallTranslatorTests.cs │ │ │ ├── KeyMethodCallTranslatorTests.cs │ │ │ └── N1QlFunctionMethodCallTranslatorTests.cs │ │ ├── N1QlHelpersTests.cs │ │ ├── NestTests.cs │ │ ├── NullHandlingTests.cs │ │ ├── OrderByClauseTests.cs │ │ ├── SelectDocumentMetadataTests.cs │ │ ├── SelectTests.cs │ │ ├── SingleTests.cs │ │ ├── StringTests.cs │ │ ├── SubqueryTests.cs │ │ ├── TakeAndSkipTests.cs │ │ ├── UnaryExpressionTests.cs │ │ ├── UnionTests.cs │ │ └── WhereClauseTests.cs │ └── Serialization │ │ └── DefaultSerializationConverterProviderTests.cs ├── Couchbase.Linq │ ├── BucketContext.cs │ ├── BucketQueryOptions.cs │ ├── CallerArgumentExpressionAttribute.cs │ ├── Clauses │ │ ├── AllAsyncExpressionNode.cs │ │ ├── AnyAsyncExpressionNode.cs │ │ ├── ArrayGeneratingFunctionExpressionNode.cs │ │ ├── AverageAsyncExpressionNode.cs │ │ ├── ConsistentWithClause.cs │ │ ├── ConsistentWithExpressionNode.cs │ │ ├── CountAsyncExpressionNode.cs │ │ ├── ExplainAsyncExpressionNode.cs │ │ ├── ExplainExpressionNode.cs │ │ ├── FirstAsyncExpressionNode.cs │ │ ├── HintClause.cs │ │ ├── LongCountAsyncExpressionNode.cs │ │ ├── MaxAsyncExpressionNode.cs │ │ ├── MinAsyncExpressionNode.cs │ │ ├── NestClause.cs │ │ ├── NestExpressionNode.cs │ │ ├── ScanConsistencyClause.cs │ │ ├── ScanConsistencyExpressionNode.cs │ │ ├── SingleAsyncExpressionNode.cs │ │ ├── SumAsyncExpressionNode.cs │ │ ├── UseHashClause.cs │ │ ├── UseHashExpressionNode.cs │ │ ├── UseIndexClause.cs │ │ ├── UseIndexExpressionNode.cs │ │ ├── UseKeysClause.cs │ │ └── UseKeysExpressionNode.cs │ ├── CollectionQueryable.cs │ ├── Couchbase.Linq.csproj │ ├── Couchbase.snk │ ├── CouchbaseCollectionAttribute.cs │ ├── CouchbaseLinqConfiguration.cs │ ├── CouchbaseLinqConfigurationExtensions.cs │ ├── CouchbaseQueryable.cs │ ├── DocumentSet`1.cs │ ├── Execution │ │ ├── ClusterQueryExecutor.cs │ │ ├── ClusterQueryProvider.cs │ │ ├── DelayedFilterQueryProvider.cs │ │ ├── IAsyncQueryExecutor.cs │ │ ├── IAsyncQueryProvider.cs │ │ ├── ScalarResult.cs │ │ ├── ScalarResultBehavior.cs │ │ └── StreamedData │ │ │ ├── AsyncStreamedScalarValueInfo.cs │ │ │ ├── AsyncStreamedSingleValueInfo.cs │ │ │ ├── AsyncStreamedValue.cs │ │ │ └── AsyncStreamedValueInfo.cs │ ├── Extensions │ │ ├── EnumerableExtensionMethods.cs │ │ ├── EnumerableExtensions.cs │ │ ├── QueryExtensionMethods.cs │ │ ├── QueryExtensions.AnyAll.cs │ │ ├── QueryExtensions.Average.cs │ │ ├── QueryExtensions.Consistency.cs │ │ ├── QueryExtensions.Count.cs │ │ ├── QueryExtensions.Explain.cs │ │ ├── QueryExtensions.First.cs │ │ ├── QueryExtensions.MinMax.cs │ │ ├── QueryExtensions.Nest.cs │ │ ├── QueryExtensions.Single.cs │ │ ├── QueryExtensions.Sum.cs │ │ ├── QueryExtensions.Use.cs │ │ └── QueryExtensions.cs │ ├── Filters │ │ ├── AttributeDocumentFilterSetGenerator.cs │ │ ├── CollectionMetadataCache.cs │ │ ├── DocumentFilterAttribute.cs │ │ ├── DocumentFilterManager.cs │ │ ├── DocumentFilterSet.cs │ │ ├── DocumentTypeFilterAttribute.cs │ │ ├── IDocumentFilter.cs │ │ └── IDocumentFilterSetGenerator.cs │ ├── HashHintType.cs │ ├── IBucketContext.cs │ ├── ICollectionQueryable.cs │ ├── IDocumentSet.cs │ ├── IDocumentSet`1.cs │ ├── LinqClusterOptionsExtensions.cs │ ├── Metadata │ │ ├── ContextMetadata.cs │ │ ├── ContextMetadataCache.cs │ │ ├── DocumentMetadata.cs │ │ ├── DocumentSetMetadata.cs │ │ └── IDocumentMetadataProvider.cs │ ├── N1QlDatePart.cs │ ├── N1QlFunctionAttribute.cs │ ├── N1QlFunctions.Core.cs │ ├── N1QlFunctions.DateTime.cs │ ├── N1QlFunctions.Metadata.cs │ ├── N1QlFunctions.Missing.cs │ ├── N1QlIndexType.cs │ ├── NullableAttributes.cs │ ├── Operators │ │ ├── AllAsyncResultOperator.cs │ │ ├── AnyAsyncResultOperator.cs │ │ ├── AsyncValueFromSequenceResultOperatorBase.cs │ │ ├── AverageAsyncResultOperator.cs │ │ ├── ChoiceAsyncResultOperatorBase.cs │ │ ├── CountAsyncResultOperator.cs │ │ ├── ExplainAsyncResultOperator.cs │ │ ├── ExplainResultOperator.cs │ │ ├── FirstAsyncResultOperator.cs │ │ ├── LongCountAsyncResultOperator.cs │ │ ├── MaxAsyncResultOperator.cs │ │ ├── MinAsyncResultOperator.cs │ │ ├── SingleAsyncResultOperator.cs │ │ └── SumAsyncResultOperator.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── QueryGeneration │ │ ├── DefaultMethodCallTranslatorProvider.cs │ │ ├── EnhancedPartialEvaluatingExpressionTreeProcessor.cs │ │ ├── EnhancedPartialEvaluatingExpressionVisitor.cs │ │ ├── ExpressionTransformers │ │ │ ├── DateTimeComparisonExpressionTransformer.cs │ │ │ ├── DateTimeSortExpressionTransformer.cs │ │ │ ├── EnumComparisonExpressionTransformer.cs │ │ │ ├── KeyExpressionTransfomer.cs │ │ │ ├── MultiKeyExpressionTransfomer.cs │ │ │ └── StringComparisonExpressionTransformer.cs │ │ ├── Expressions │ │ │ └── StringComparisonExpression.cs │ │ ├── FromParts │ │ │ ├── AnsiJoinPart.cs │ │ │ ├── ExtentPart.cs │ │ │ ├── FromPart.cs │ │ │ ├── JoinPart.cs │ │ │ └── JoinTypes.cs │ │ ├── IMethodCallTranslator.cs │ │ ├── IMethodCallTranslatorProvider.cs │ │ ├── IN1QlExpressionTreeVisitor.cs │ │ ├── IN1QlQueryModelVisitor.cs │ │ ├── InnerNestDetectingExpressionVisitor.cs │ │ ├── LinqQueryOptions.cs │ │ ├── MemberNameResolvers │ │ │ ├── ExtendedTypeSerializerMemberNameResolver.cs │ │ │ ├── IMemberNameResolver.cs │ │ │ └── JsonNetMemberNameResolver.cs │ │ ├── MethodCallTranslators │ │ │ ├── ConcatMethodCallTranslator.cs │ │ │ ├── ContainsMethodCallTranslator.cs │ │ │ ├── DateMethodCallTranslator.cs │ │ │ ├── DictionaryContainsKeyMethodCallTranslator .cs │ │ │ ├── DictionaryItemMethodCallTranslator.cs │ │ │ ├── DictionaryKeysMethodCallTranslator.cs │ │ │ ├── DictionaryValuesMethodCallTranslator.cs │ │ │ ├── IsMissingMethodCallTranslator.cs │ │ │ ├── IsValuedMethodCallTranslator.cs │ │ │ ├── KeyMethodCallTranslator.cs │ │ │ ├── ListIndexMethodCallTranslator.cs │ │ │ ├── MathMethodCallTranslator.cs │ │ │ ├── MetaMethodCallTranslator.cs │ │ │ ├── N1QlFunctionMethodCallTranslator.cs │ │ │ ├── SerializationConverterMethodCallTranslator.cs │ │ │ ├── StringCapitalizationMethodCallTranslator.cs │ │ │ ├── StringIndexOfMethodCallTranslator.cs │ │ │ ├── StringLengthMethodCallTranslator.cs │ │ │ ├── StringReplaceMethodCallTranslator.cs │ │ │ ├── StringSplitMethodCallTranslator.cs │ │ │ ├── StringTrimMethodCallTranslator.cs │ │ │ ├── SubqueryMethodCallTranslator.cs │ │ │ ├── SubstringMethodCallTranslator.cs │ │ │ └── ToStringMethodCallTranslator.cs │ │ ├── N1QLExpressionTreeVisitor.cs │ │ ├── N1QLLetQueryPart.cs │ │ ├── N1QLQueryModelVisitor.cs │ │ ├── N1QLQueryType.cs │ │ ├── N1QlExtentNameProvider.cs │ │ ├── N1QlHelpers.cs │ │ ├── N1QlQueryGenerationContext.cs │ │ ├── NamedParameter.cs │ │ ├── ParameterAggregator.cs │ │ └── QueryPartsAggregator.cs │ ├── QueryParserHelper.cs │ ├── Serialization │ │ ├── Converters │ │ │ ├── SerializationConverterBase.cs │ │ │ ├── StringEnumSerializationConverter.cs │ │ │ └── UnixMillisecondsSerializationConverter.cs │ │ ├── DefaultSerializationConverterProvider.cs │ │ ├── IJsonNetSerializationConverterRegistry.cs │ │ ├── ISerializationConverter.cs │ │ ├── ISerializationConverterProvider.cs │ │ ├── ISerializationConverterT.cs │ │ ├── SerializationExpressionTreeProcessor.cs │ │ ├── SerializationExpressionTreeVisitor.cs │ │ └── TypeBasedSerializationConverterRegistry.cs │ ├── Utils │ │ ├── ExceptionMsgs.cs │ │ ├── ExcludeSerializationConversionEvaluatableExpressionFilter.cs │ │ ├── ReflectionUtils.cs │ │ ├── ServiceProviderExtensions.cs │ │ └── ThrowHelpers.cs │ ├── Versioning │ │ └── FeatureVersions.cs │ └── couchbase.png ├── Directory.Build.props ├── couchbase-net-linq.sln └── global.json └── docs ├── any-all.md ├── array-filtering-projections.md ├── async-queries.md ├── bucket-context.md ├── custom-serializers.md ├── date-handling.md ├── document-filters.md ├── enum.md ├── grouping-aggregation.md ├── joins.md ├── math-functions.md ├── meta-keyword.md ├── nest.md ├── null-missing-valued.md ├── poco-mapping.md ├── query-hints.md ├── ryow.md ├── scopes-collections.md ├── serialization-converters.md ├── simple-select.md ├── sorting-take-limit.md ├── string-handling.md ├── unnest.md ├── use-keys.md └── where-clause.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.md] 4 | indent_size = 4 5 | indent_style = space 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v3 18 | with: 19 | dotnet-version: '8.0.x' 20 | - name: Install dependencies 21 | run: dotnet restore Src/couchbase-net-linq.sln 22 | - name: Build 23 | run: dotnet build --configuration Debug --no-restore Src/couchbase-net-linq.sln 24 | - name: Test 25 | run: dotnet test --no-restore --verbosity normal Src/Couchbase.Linq.UnitTests/Couchbase.Linq.UnitTests.csproj 26 | -------------------------------------------------------------------------------- /Packaging/couchbase-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/Linq2Couchbase/6b63364d429079531b3bedeb5d7adbb100b898fc/Packaging/couchbase-logo.png -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/AverageTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Couchbase.Linq.Extensions; 5 | using Couchbase.Linq.IntegrationTests.Documents; 6 | using NUnit.Framework; 7 | 8 | namespace Couchbase.Linq.IntegrationTests 9 | { 10 | [TestFixture] 11 | public class AverageTests : N1QlTestBase 12 | { 13 | [Test] 14 | public void Average_WithSelector() 15 | { 16 | var context = new BucketContext(TestSetup.Bucket); 17 | 18 | var avg = context.Query() 19 | .Where(p => p.Type == "beer" && N1QlFunctions.IsValued(p.Abv)) 20 | .Average(p => p.Abv); 21 | 22 | Assert.Greater(avg, 0); 23 | Console.WriteLine("Average ABV of all beers is {0}", avg); 24 | } 25 | 26 | [Test] 27 | public async Task AverageAsync_NoSelector() 28 | { 29 | var context = new BucketContext(TestSetup.Bucket); 30 | 31 | var avg = await context.Query() 32 | .Where(p => p.Type == "beer" && N1QlFunctions.IsValued(p.Abv)) 33 | .Select(p => p.Abv) 34 | .AverageAsync(); 35 | 36 | Assert.Greater(avg, 0); 37 | Console.WriteLine("Average ABV of all beers is {0}", avg); 38 | } 39 | 40 | [Test] 41 | public async Task AverageAsync_WithSelector() 42 | { 43 | var context = new BucketContext(TestSetup.Bucket); 44 | 45 | var avg = await context.Query() 46 | .Where(p => p.Type == "beer" && N1QlFunctions.IsValued(p.Abv)) 47 | .AverageAsync(p => p.Abv); 48 | 49 | Assert.Greater(avg, 0); 50 | Console.WriteLine("Average ABV of all beers is {0}", avg); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/BeerSample.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Couchbase.KeyValue; 3 | using Couchbase.Linq.Filters; 4 | 5 | using Couchbase.Linq.IntegrationTests.Documents; 6 | using Couchbase.Linq.Utils; 7 | 8 | namespace Couchbase.Linq.IntegrationTests 9 | { 10 | /// 11 | /// A concrete DbContext for the beer-sample example bucket. 12 | /// 13 | public class BeerSample : BucketContext 14 | { 15 | public BeerSample() 16 | : this(TestSetup.Bucket) 17 | { 18 | } 19 | 20 | public BeerSample(IBucket bucket) : base(bucket) 21 | { 22 | //Two ways of applying a filter are included in this example. 23 | //This is by implementing IDocumentFilter and then adding explicitly. 24 | //adding it to the DocumentFilterManager 25 | 26 | bucket.Cluster.ClusterServices.GetRequiredService().SetFilter(new BreweryFilter()); 27 | } 28 | 29 | public IDocumentSet Beers { get; set; } = null!; 30 | 31 | public IQueryable Breweries 32 | { 33 | get { return Query(); } 34 | } 35 | } 36 | } 37 | 38 | #region [ License information ] 39 | 40 | /* ************************************************************ 41 | * 42 | * @author Couchbase 43 | * @copyright 2015 Couchbase, Inc. 44 | * 45 | * Licensed under the Apache License, Version 2.0 (the "License"); 46 | * you may not use this file except in compliance with the License. 47 | * You may obtain a copy of the License at 48 | * 49 | * http://www.apache.org/licenses/LICENSE-2.0 50 | * 51 | * Unless required by applicable law or agreed to in writing, software 52 | * distributed under the License is distributed on an "AS IS" BASIS, 53 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 54 | * See the License for the specific language governing permissions and 55 | * limitations under the License. 56 | * 57 | * ************************************************************/ 58 | 59 | #endregion 60 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Couchbase.Linq.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | true 6 | 7 | 8 | 9 | 10 | PreserveNewest 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/Airline.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Couchbase.Linq.IntegrationTests.Documents 4 | { 5 | /* 6 | * { 7 | "id": 24, 8 | "type": "airline", 9 | "name": "American Airlines", 10 | "iata": "AA", 11 | "icao": "AAL", 12 | "callsign": "AMERICAN", 13 | "country": "United States" 14 | } 15 | * */ 16 | public class Airline 17 | { 18 | [JsonProperty("id")] 19 | public string Id { get; set; } 20 | 21 | [JsonProperty("type")] 22 | public string Type { get; set; } 23 | 24 | [JsonProperty("name")] 25 | public string Name { get; set; } 26 | 27 | [JsonProperty("iata")] 28 | public string Iata { get; set; } 29 | 30 | [JsonProperty("icao")] 31 | public string Icao { get; set; } 32 | 33 | [JsonProperty("callsign")] 34 | public string Callsign { get; set; } 35 | 36 | [JsonProperty("country")] 37 | public string Country { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/Airport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Newtonsoft.Json; 4 | 5 | namespace Couchbase.Linq.IntegrationTests.Documents 6 | { 7 | public class Airport 8 | { 9 | [Key] 10 | [JsonProperty("id")] 11 | public virtual int Id { get; set; } 12 | 13 | [JsonProperty("airportname")] 14 | public virtual string AirportName { get; set; } 15 | 16 | [JsonProperty("faa")] 17 | public virtual string Faa { get; set; } 18 | 19 | [JsonProperty("type")] 20 | public virtual string Type { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/Beer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Core.IO.Serializers; 3 | using Newtonsoft.Json; 4 | 5 | namespace Couchbase.Linq.IntegrationTests.Documents 6 | { 7 | public class Beer 8 | { 9 | [JsonProperty("name")] 10 | public virtual string Name { get; set; } 11 | 12 | [JsonProperty("abv")] 13 | public virtual decimal Abv { get; set; } 14 | 15 | [JsonProperty("ibu")] 16 | public virtual decimal Ibu { get; set; } 17 | 18 | [JsonProperty("srm")] 19 | public virtual decimal Srm { get; set; } 20 | 21 | [JsonProperty("upc")] 22 | public virtual decimal Upc { get; set; } 23 | 24 | [JsonProperty("type")] 25 | public virtual string Type { get; set; } 26 | 27 | [JsonProperty("brewery_id")] 28 | public virtual string BreweryId { get; set; } 29 | 30 | [JsonProperty("description")] 31 | public virtual string Description { get; set; } 32 | 33 | [JsonProperty("style")] 34 | public virtual string Style { get; set; } 35 | 36 | [JsonProperty("category")] 37 | public virtual string Category { get; set; } 38 | 39 | [JsonProperty("updated")] 40 | public virtual DateTime Updated { get; set; } 41 | 42 | [JsonProperty("updated_offset")] 43 | public virtual DateTimeOffset UpdatedOffset { get; set; } 44 | 45 | // This property isn't normally on beers in the beer-sample bucket 46 | // But we need it for some integration tests so we'll add it 47 | [JsonProperty("updatedUnixMillis")] 48 | [JsonConverter(typeof(UnixMillisecondsConverter))] 49 | public virtual DateTime? UpdatedUnixMillis { get; set; } 50 | } 51 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/BeerFiltered.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Newtonsoft.Json; 4 | 5 | namespace Couchbase.Linq.IntegrationTests.Documents 6 | { 7 | [Filters.DocumentTypeFilter("beer")] 8 | public class BeerFiltered 9 | { 10 | [JsonProperty("name")] 11 | public virtual string Name { get; set; } 12 | 13 | [JsonProperty("abv")] 14 | public virtual decimal Abv { get; set; } 15 | 16 | [JsonProperty("ibu")] 17 | public virtual decimal Ibu { get; set; } 18 | 19 | [JsonProperty("srm")] 20 | public decimal Srm { get; set; } 21 | 22 | [JsonProperty("upc")] 23 | public virtual decimal Upc { get; set; } 24 | 25 | [JsonProperty("type")] 26 | public virtual string Type { get; set; } 27 | 28 | [JsonProperty("brewery_id")] 29 | public virtual string BreweryId { get; set; } 30 | 31 | [JsonProperty("description")] 32 | public virtual string Description { get; set; } 33 | 34 | [JsonProperty("style")] 35 | public virtual string Style { get; set; } 36 | 37 | [JsonProperty("category")] 38 | public virtual string Category { get; set; } 39 | 40 | [JsonProperty("updated")] 41 | public virtual DateTime Updated { get; set; } 42 | 43 | [Key] 44 | public string Key { get { return BreweryId + "-" + Name.ToLower().Replace(' ', '_'); } } 45 | } 46 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/BeerStyle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Converters; 9 | 10 | namespace Couchbase.Linq.IntegrationTests.Documents 11 | { 12 | [JsonConverter(typeof(StringEnumConverter))] 13 | public enum BeerStyle 14 | { 15 | Porter, 16 | 17 | [EnumMember(Value="Oatmeal Stout")] 18 | OatmealStout, 19 | 20 | [EnumMember(Value="Pumpkin Beer")] 21 | PumpkinBeer 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/BeerWithEnum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Newtonsoft.Json; 4 | 5 | namespace Couchbase.Linq.IntegrationTests.Documents 6 | { 7 | public class BeerWithEnum 8 | { 9 | [Key] 10 | [JsonProperty("name")] 11 | public string Name { get; set; } 12 | 13 | [JsonProperty("abv")] 14 | public decimal Abv { get; set; } 15 | 16 | [JsonProperty("ibu")] 17 | public decimal Ibu { get; set; } 18 | 19 | [JsonProperty("srm")] 20 | public decimal Srm { get; set; } 21 | 22 | [JsonProperty("upc")] 23 | public decimal Upc { get; set; } 24 | 25 | [JsonProperty("type")] 26 | public string Type { get; set; } 27 | 28 | [JsonProperty("brewery_id")] 29 | public string BreweryId { get; set; } 30 | 31 | [JsonProperty("description")] 32 | public string Description { get; set; } 33 | 34 | [JsonProperty("style")] 35 | public BeerStyle? Style { get; set; } 36 | 37 | [JsonProperty("category")] 38 | public string Category { get; set; } 39 | 40 | [JsonProperty("updated")] 41 | public DateTime Updated { get; set; } 42 | } 43 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/Brewery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace Couchbase.Linq.IntegrationTests.Documents 6 | { 7 | public class Brewery 8 | { 9 | [JsonProperty("name")] 10 | public virtual string Name { get; set; } 11 | 12 | [JsonProperty("city")] 13 | public virtual string City { get; set; } 14 | 15 | [JsonProperty("state")] 16 | public virtual string State { get; set; } 17 | 18 | [JsonProperty("code")] 19 | public virtual string Code { get; set; } 20 | 21 | [JsonProperty("country")] 22 | public virtual string Country { get; set; } 23 | 24 | [JsonProperty("phone")] 25 | public virtual string Phone { get; set; } 26 | 27 | [JsonProperty("website")] 28 | public virtual string Website { get; set; } 29 | 30 | [JsonProperty("type")] 31 | public virtual string Type { get; set; } 32 | 33 | [JsonProperty("updated")] 34 | public virtual DateTime Updated { get; set; } 35 | 36 | [JsonProperty("description")] 37 | public virtual string Description { get; set; } 38 | 39 | [JsonProperty("address")] 40 | public virtual IList Address { get; set; } 41 | 42 | [JsonProperty("geo")] 43 | public virtual Geo Geo { get; set; } 44 | 45 | /// 46 | /// Note: This property doesn't exist in the default beer-sample. For tests we're acting as if it exists, 47 | /// and is a list of keys for all beers made by the brewery. 48 | /// 49 | [JsonProperty("beers")] 50 | public virtual IList Beers { get; set; } 51 | } 52 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/BreweryFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Couchbase.Linq.Filters; 4 | 5 | namespace Couchbase.Linq.IntegrationTests.Documents 6 | { 7 | class BreweryFilter : IDocumentFilter 8 | { 9 | public int Priority { get; set; } 10 | 11 | public IQueryable ApplyFilter(IQueryable source) 12 | { 13 | return source.Where(p => p.Type == "brewery"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/Geo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Couchbase.Linq.IntegrationTests.Documents 4 | { 5 | public class Geo 6 | { 7 | [JsonProperty("accuracy")] 8 | public virtual string Accuracy { get; set; } 9 | 10 | [JsonProperty("lat")] 11 | public virtual decimal Latitude { get; set; } 12 | 13 | [JsonProperty("lon")] 14 | public virtual decimal Longitude { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/Route.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Couchbase.Linq.IntegrationTests.Documents 5 | { 6 | public class Route 7 | { 8 | public string Id { get; set; } 9 | 10 | [JsonProperty("type")] 11 | public string Type { get; set; } 12 | 13 | [JsonProperty("airline")] 14 | public string Airline { get; set; } 15 | 16 | [JsonProperty("airlineid")] 17 | public string AirlineId { get; set; } 18 | 19 | [JsonProperty("sourceairport")] 20 | public string SourceAirport { get; set; } 21 | 22 | [JsonProperty("destinationairport")] 23 | public string DestinationAirport { get; set; } 24 | 25 | [JsonProperty("stops")] 26 | public uint Stops { get; set; } 27 | 28 | [JsonProperty("equipment")] 29 | public string Equipment { get; set; } 30 | 31 | [JsonProperty("schedule")] 32 | public List Schedule { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/RouteInCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Couchbase.Linq.IntegrationTests.Documents 4 | { 5 | [CouchbaseCollection("inventory", "route")] 6 | public class RouteInCollection : Route 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/Documents/Schedule.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Couchbase.Linq.IntegrationTests.Documents 4 | { 5 | public class Schedule 6 | { 7 | [JsonProperty("day")] 8 | public uint Day { get; set; } 9 | 10 | [JsonProperty("utc")] 11 | public string Utc { get; set; } 12 | 13 | [JsonProperty("flight")] 14 | public string Flight { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/SumTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Couchbase.Linq.Extensions; 5 | using Couchbase.Linq.IntegrationTests.Documents; 6 | using NUnit.Framework; 7 | 8 | namespace Couchbase.Linq.IntegrationTests 9 | { 10 | [TestFixture] 11 | public class SumTests : N1QlTestBase 12 | { 13 | [Test] 14 | public void Sum_NoSelector() 15 | { 16 | var context = new BucketContext(TestSetup.Bucket); 17 | 18 | var beers = from beer in context.Query() 19 | where beer.Type == "beer" 20 | select beer.Ibu; 21 | 22 | Console.WriteLine(beers.Sum()); 23 | } 24 | 25 | [Test] 26 | public async Task SumAsync_NoSelector() 27 | { 28 | var context = new BucketContext(TestSetup.Bucket); 29 | 30 | var beers = from beer in context.Query() 31 | where beer.Type == "beer" 32 | select beer.Ibu; 33 | 34 | Console.WriteLine(await beers.SumAsync()); 35 | } 36 | 37 | [Test] 38 | public async Task SumAsync_WithSelector() 39 | { 40 | var context = new BucketContext(TestSetup.Bucket); 41 | 42 | var beers = from beer in context.Query() 43 | where beer.Type == "beer" 44 | select beer; 45 | 46 | Console.WriteLine(await beers.SumAsync(p => p.Ibu)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/TestConfigurations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | 4 | namespace Couchbase.Linq.IntegrationTests 5 | { 6 | public class TestConfigurations 7 | { 8 | private static IConfigurationRoot _jsonConfiguration; 9 | 10 | public static ClusterOptions DefaultConfig(Action setupAction = null) 11 | { 12 | return DefaultLocalhostConfig(setupAction); 13 | } 14 | 15 | private static ClusterOptions DefaultLocalhostConfig(Action setupAction = null) 16 | { 17 | EnsureConfigurationLoaded(); 18 | 19 | var options = new ClusterOptions(); 20 | _jsonConfiguration.GetSection("couchbase").Bind(options); 21 | 22 | options.AddLinq(setupAction); 23 | 24 | return options; 25 | } 26 | 27 | private static void EnsureConfigurationLoaded() 28 | { 29 | if (_jsonConfiguration == null) 30 | { 31 | var builder = new ConfigurationBuilder(); 32 | builder.AddJsonFile("config.json"); 33 | _jsonConfiguration = builder.Build(); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/TestSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | 5 | namespace Couchbase.Linq.IntegrationTests 6 | { 7 | [SetUpFixture] 8 | public class TestSetup : N1QlTestBase 9 | { 10 | public static ICluster Cluster { get; private set; } 11 | 12 | public static IBucket Bucket { get; private set; } 13 | 14 | [OneTimeSetUp] 15 | public async Task SetUp() 16 | { 17 | Cluster = await Couchbase.Cluster.ConnectAsync(TestConfigurations.DefaultConfig()); 18 | await Cluster.WaitUntilReadyAsync(TimeSpan.FromSeconds(10)); 19 | 20 | var bucket = await Cluster.BucketAsync("beer-sample"); 21 | await EnsurePrimaryIndexExists(bucket); 22 | 23 | Bucket = bucket; 24 | } 25 | 26 | [OneTimeTearDown] 27 | public void TearDown() 28 | { 29 | Cluster.Dispose(); 30 | 31 | Cluster = null; 32 | Bucket = null; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/TravelSample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Linq.IntegrationTests.Documents; 3 | 4 | namespace Couchbase.Linq.IntegrationTests 5 | { 6 | public class TravelSample : BucketContext 7 | { 8 | public IDocumentSet Airlines { get; set; } 9 | public IDocumentSet Routes { get; set; } 10 | 11 | public TravelSample(IBucket bucket) 12 | : base(bucket) 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.IntegrationTests/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "couchbase": { 3 | "connectionString": "couchbase://localhost", 4 | "username": "Administrator", 5 | "password": "password" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Couchbase.Linq.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Address.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | 8 | namespace Couchbase.Linq.UnitTests.Documents 9 | { 10 | class Address 11 | { 12 | [JsonProperty("address1")] 13 | public string AddressLine1 { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Airline.cs: -------------------------------------------------------------------------------- 1 |  2 | using Newtonsoft.Json; 3 | 4 | namespace Couchbase.Linq.UnitTests.Documents 5 | { 6 | /* 7 | * { 8 | "id": 24, 9 | "type": "airline", 10 | "name": "American Airlines", 11 | "iata": "AA", 12 | "icao": "AAL", 13 | "callsign": "AMERICAN", 14 | "country": "United States" 15 | } 16 | * */ 17 | public class Airline 18 | { 19 | [JsonIgnore] 20 | public string Id { get; set; } 21 | 22 | [JsonProperty("type")] 23 | public string Type { get; set; } 24 | 25 | [JsonProperty("name")] 26 | public string Name { get; set; } 27 | 28 | [JsonProperty("iata")] 29 | public string Iata { get; set; } 30 | 31 | [JsonProperty("icao")] 32 | public string Icao { get; set; } 33 | 34 | [JsonProperty("callsign")] 35 | public string Callsign { get; set; } 36 | 37 | [JsonProperty("country")] 38 | public string Country { get; set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Airport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Newtonsoft.Json; 4 | 5 | namespace Couchbase.Linq.UnitTests.Documents 6 | { 7 | public class Airport 8 | { 9 | [Key] 10 | [JsonProperty("id")] 11 | public virtual int Id { get; set; } 12 | 13 | [JsonProperty("airportname")] 14 | public virtual string AirportName { get; set; } 15 | 16 | [JsonProperty("faa")] 17 | public virtual string Faa { get; set; } 18 | 19 | [JsonProperty("type")] 20 | public virtual string Type { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Beer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using Couchbase.Core.IO.Serializers; 4 | using Newtonsoft.Json; 5 | 6 | namespace Couchbase.Linq.UnitTests.Documents 7 | { 8 | public class Beer 9 | { 10 | [Key] 11 | [JsonProperty("name")] 12 | public virtual string Name { get; set; } 13 | 14 | [JsonProperty("abv")] 15 | public virtual decimal Abv { get; set; } 16 | 17 | [JsonProperty("ibu")] 18 | public virtual decimal Ibu { get; set; } 19 | 20 | [JsonProperty("srm")] 21 | public virtual decimal Srm { get; set; } 22 | 23 | [JsonProperty("upc")] 24 | public virtual decimal Upc { get; set; } 25 | 26 | [JsonProperty("type")] 27 | public virtual string Type { get; set; } 28 | 29 | [JsonProperty("brewery_id")] 30 | public virtual string BreweryId { get; set; } 31 | 32 | [JsonProperty("description")] 33 | public virtual string Description { get; set; } 34 | 35 | [JsonProperty("style")] 36 | public virtual string Style { get; set; } 37 | 38 | [JsonProperty("category")] 39 | public virtual string Category { get; set; } 40 | 41 | [JsonProperty("updated")] 42 | public virtual DateTime Updated { get; set; } 43 | 44 | [JsonProperty("updatedOffset")] 45 | public virtual DateTimeOffset UpdatedOffset { get; set; } 46 | 47 | [JsonProperty("updatedUnixMillis")] 48 | [JsonConverter(typeof(UnixMillisecondsConverter))] 49 | public virtual DateTime? UpdatedUnixMillis { get; set; } 50 | } 51 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/BeerFiltered.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Couchbase.Linq.UnitTests.Documents 5 | { 6 | [Couchbase.Linq.Filters.DocumentTypeFilter("beer")] 7 | public class BeerFiltered 8 | { 9 | [JsonProperty("name")] 10 | public string Name { get; set; } 11 | 12 | [JsonProperty("abv")] 13 | public decimal Abv { get; set; } 14 | 15 | [JsonProperty("ibu")] 16 | public decimal Ibu { get; set; } 17 | 18 | [JsonProperty("srm")] 19 | public decimal Srm { get; set; } 20 | 21 | [JsonProperty("upc")] 22 | public decimal Upc { get; set; } 23 | 24 | [JsonProperty("type")] 25 | public string Type { get; set; } 26 | 27 | [JsonProperty("brewery_id")] 28 | public string BreweryId { get; set; } 29 | 30 | [JsonProperty("description")] 31 | public string Description { get; set; } 32 | 33 | [JsonProperty("style")] 34 | public string Style { get; set; } 35 | 36 | [JsonProperty("category")] 37 | public string Category { get; set; } 38 | 39 | [JsonProperty("updated")] 40 | public DateTime Updated { get; set; } 41 | } 42 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Brewery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace Couchbase.Linq.UnitTests.Documents 6 | { 7 | public class Brewery 8 | { 9 | [JsonProperty("name")] 10 | public string Name { get; set; } 11 | 12 | [JsonProperty("city")] 13 | public string City { get; set; } 14 | 15 | [JsonProperty("state")] 16 | public string State { get; set; } 17 | 18 | [JsonProperty("code")] 19 | public string Code { get; set; } 20 | 21 | [JsonProperty("country")] 22 | public string Country { get; set; } 23 | 24 | [JsonProperty("phone")] 25 | public string Phone { get; set; } 26 | 27 | [JsonProperty("website")] 28 | public string Website { get; set; } 29 | 30 | [JsonProperty("type")] 31 | public string Type { get; set; } 32 | 33 | [JsonProperty("updated")] 34 | public DateTime Updated { get; set; } 35 | 36 | [JsonProperty("description")] 37 | public string Description { get; set; } 38 | 39 | [JsonProperty("address")] 40 | public List Address { get; set; } 41 | 42 | [JsonProperty("geo")] 43 | public Geo Geo { get; set; } 44 | 45 | /// 46 | /// Note: This property doesn't exist in the default beer-sample. For tests we're acting as if it exists, 47 | /// and is a list of keys for all beers made by the brewery. 48 | /// 49 | [JsonProperty("beers")] 50 | public List Beers { get; set; } 51 | } 52 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/BreweryFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Couchbase.Linq.Filters; 4 | 5 | namespace Couchbase.Linq.UnitTests.Documents 6 | { 7 | class BreweryFilter : IDocumentFilter 8 | { 9 | public int Priority { get; set; } 10 | 11 | public IQueryable ApplyFilter(IQueryable source) 12 | { 13 | return source.Where(p => p.Type == "brewery"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Car.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Couchbase.Linq.UnitTests.Documents 4 | { 5 | class Car 6 | { 7 | public Guid Id { get; set; } 8 | public string Name { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Child.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Couchbase.Linq.UnitTests.Documents 4 | { 5 | public sealed class Child 6 | { 7 | [JsonProperty("age")] 8 | public string Age { get; set; } 9 | 10 | [JsonProperty("fname")] 11 | public string FirstName { get; set; } 12 | 13 | [JsonProperty("gender")] 14 | public string Gender { get; set; } 15 | 16 | [JsonProperty("contactId")] 17 | public string ContactId { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/ChildWithContract.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace Couchbase.Linq.UnitTests.Documents 4 | { 5 | [DataContract] 6 | public class ChildWithContract 7 | { 8 | [DataMember(Name = "age")] 9 | public int Age { get; set; } 10 | 11 | [DataMember(Name = "fname")] 12 | public string FirstName { get; set; } 13 | 14 | public string Gender { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Contact.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Couchbase.Linq.UnitTests.Documents 5 | { 6 | public sealed class Contact 7 | { 8 | [JsonProperty("age")] 9 | public int Age { get; set; } 10 | 11 | [JsonProperty("fname")] 12 | public string FirstName { get; set; } 13 | 14 | [JsonProperty("lname")] 15 | public string LastName { get; set; } 16 | 17 | [JsonProperty("email")] 18 | public string Email { get; set; } 19 | 20 | [JsonProperty("hobbies")] 21 | public List Hobbies { get; set; } 22 | 23 | [JsonProperty("relation")] 24 | public string Relation { get; set; } 25 | 26 | [JsonProperty("title")] 27 | public string Title { get; set; } 28 | 29 | [JsonProperty("type")] 30 | public string Type { get; set; } 31 | 32 | [JsonProperty("children")] 33 | public List Children { get; set; } 34 | } 35 | 36 | 37 | /* 38 | * { 39 | "age": 46, 40 | "children": [ 41 | { 42 | "age": 17, 43 | "fname": "Aiden", 44 | "gender": "m" 45 | }, 46 | { 47 | "age": 2, 48 | "fname": "Bill", 49 | "gender": "f" 50 | } 51 | ], 52 | "email": "dave@gmail.com", 53 | "fname": "Dave", 54 | "hobbies": [ 55 | "golf", 56 | "surfing" 57 | ], 58 | "lname": "Smith", 59 | "relation": "friend", 60 | "title": "Mr.", 61 | "type": "contact" 62 | },*/ 63 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Geo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Couchbase.Linq.UnitTests.Documents 4 | { 5 | public class Geo 6 | { 7 | [JsonProperty("accuracy")] 8 | public string Accuracy { get; set; } 9 | 10 | [JsonProperty("lat")] 11 | public decimal Latitude { get; set; } 12 | 13 | [JsonProperty("lon")] 14 | public decimal Longitude { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Documents/Schedule.cs: -------------------------------------------------------------------------------- 1 |  2 | using Newtonsoft.Json; 3 | 4 | namespace Couchbase.Linq.UnitTests.Documents 5 | { 6 | public class Schedule 7 | { 8 | [JsonProperty("day")] 9 | public uint Day { get; set; } 10 | 11 | [JsonProperty("utc")] 12 | public string Utc { get; set; } 13 | 14 | [JsonProperty("flight")] 15 | public string Flight { get; set; } 16 | } 17 | 18 | /*"day": 0, 19 | "utc": "22:03:00", 20 | "flight": "AA138" 21 | * */ 22 | } 23 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Filters/CollectionMetadataCacheTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Linq.Filters; 3 | using NUnit.Framework; 4 | 5 | namespace Couchbase.Linq.UnitTests.Filters 6 | { 7 | public class CollectionMetadataCacheTests 8 | { 9 | [Test] 10 | public void GetCollection_Annotated_AnnotationValues() 11 | { 12 | // Arrange 13 | 14 | var cache = new CollectionMetadataCache(); 15 | 16 | // Act 17 | 18 | var (scope, collection) = cache.GetCollection(); 19 | 20 | // Assert 21 | 22 | Assert.AreEqual("my_scope", scope); 23 | Assert.AreEqual("my_collection", collection); 24 | } 25 | 26 | [Test] 27 | public void GetCollection_Unannotated_DefaultValues() 28 | { 29 | // Arrange 30 | 31 | var cache = new CollectionMetadataCache(); 32 | 33 | // Act 34 | 35 | var (scope, collection) = cache.GetCollection(); 36 | 37 | // Assert 38 | 39 | Assert.AreEqual("_default", scope); 40 | Assert.AreEqual("_default", collection); 41 | } 42 | 43 | #region Helpers 44 | 45 | [CouchbaseCollection("my_scope", "my_collection")] 46 | public class Annotated 47 | { 48 | } 49 | 50 | public class Unannotated 51 | { 52 | } 53 | 54 | #endregion 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/Metadata/ContextMetadataTests.cs: -------------------------------------------------------------------------------- 1 | using Couchbase.Linq.Metadata; 2 | using Couchbase.Linq.UnitTests.Documents; 3 | using NUnit.Framework; 4 | 5 | namespace Couchbase.Linq.UnitTests.Metadata 6 | { 7 | public class ContextMetadataTests 8 | { 9 | #region ctor 10 | 11 | [Test] 12 | public void ctor_GetsAllPublicDocumentSets() 13 | { 14 | // Act 15 | 16 | var result = new ContextMetadata(typeof(TestContext)); 17 | 18 | // Assert 19 | 20 | Assert.AreEqual(3, result.Properties.Length); 21 | } 22 | 23 | [Test] 24 | public void ctor_ValidInitializer() 25 | { 26 | // Arrange 27 | 28 | var context = new TestContext(); 29 | var metadata = new ContextMetadata(typeof(TestContext)); 30 | 31 | // Act 32 | 33 | metadata.Fill(context); 34 | 35 | // Assert 36 | 37 | Assert.NotNull(context.Beers); 38 | Assert.NotNull(context.Routes); 39 | Assert.NotNull(context.RoutesOverridden); 40 | } 41 | 42 | #endregion 43 | 44 | #region Helpers 45 | 46 | private class TestContext : BucketContext 47 | { 48 | public TestContext() : base(QueryFactory.CreateMockBucket("default")) 49 | { 50 | } 51 | 52 | public IDocumentSet Beers { get; set; } 53 | 54 | public IDocumentSet Routes { get; set; } 55 | 56 | [CouchbaseCollection("my", "override")] 57 | public IDocumentSet RoutesOverridden { get; set; } 58 | 59 | private IDocumentSet PrivateBeers { get; set; } 60 | 61 | private IDocumentSet NoSetterBeers => null; 62 | 63 | private int OtherType { get; set; } 64 | } 65 | 66 | #endregion 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/QueryGeneration/ConditionalExpressionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Couchbase.Core; 7 | using Couchbase.Linq.UnitTests.Documents; 8 | using Moq; 9 | using NUnit.Framework; 10 | 11 | namespace Couchbase.Linq.UnitTests.QueryGeneration 12 | { 13 | [TestFixture] 14 | class ConditionalExpressionTests : N1QLTestBase 15 | { 16 | 17 | [Test] 18 | public void Test_IfThenElse() 19 | { 20 | var mockBucket = new Mock(); 21 | mockBucket.SetupGet(e => e.Name).Returns("default"); 22 | 23 | var query = 24 | QueryFactory.Queryable(mockBucket.Object) 25 | .Select(e => new { e.FirstName, Value = e.Age < 10 ? null : e.LastName }); 26 | 27 | const string expected = 28 | "SELECT `Extent1`.`fname` as `FirstName`, CASE WHEN (`Extent1`.`age` < 10) THEN NULL ELSE `Extent1`.`lname` END as `Value` FROM `default` as `Extent1`"; 29 | 30 | var n1QlQuery = CreateN1QlQuery(mockBucket.Object, query.Expression); 31 | 32 | Assert.AreEqual(expected, n1QlQuery); 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/QueryGeneration/DistinctTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Couchbase.Core; 7 | using Couchbase.Core.Version; 8 | using Couchbase.Linq.UnitTests.Documents; 9 | using Moq; 10 | using NUnit.Framework; 11 | 12 | namespace Couchbase.Linq.UnitTests.QueryGeneration 13 | { 14 | [TestFixture] 15 | public class DistinctTests : N1QLTestBase 16 | { 17 | [Test] 18 | public void Test_Distinct_Keyword() 19 | { 20 | var mockBucket = new Mock(); 21 | mockBucket.SetupGet(e => e.Name).Returns("default"); 22 | 23 | var query = QueryFactory.Queryable(mockBucket.Object) 24 | .Select(c => new {age = c.Age}) 25 | .Distinct(); 26 | 27 | const string expected = "SELECT DISTINCT `Extent1`.`age` as `age` FROM `default` as `Extent1`"; 28 | 29 | var n1QlQuery = CreateN1QlQuery(mockBucket.Object, query.Expression); 30 | 31 | Assert.AreEqual(expected, n1QlQuery); 32 | } 33 | 34 | [Test] 35 | public void Test_DistinctRaw_CorrectOrdering() 36 | { 37 | var mockBucket = new Mock(); 38 | mockBucket.SetupGet(e => e.Name).Returns("default"); 39 | 40 | var query = QueryFactory.Queryable(mockBucket.Object) 41 | .Select(c => c.Age) 42 | .Distinct(); 43 | 44 | const string expected = "SELECT DISTINCT RAW `Extent1`.`age` FROM `default` as `Extent1`"; 45 | 46 | var n1QlQuery = CreateN1QlQuery(mockBucket.Object, query.Expression, 47 | new ClusterVersion(new Version(5, 5))); 48 | 49 | Assert.AreEqual(expected, n1QlQuery); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/QueryGeneration/ExplainTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Couchbase.Core; 9 | using Couchbase.Linq.Extensions; 10 | using Couchbase.Linq.UnitTests.Documents; 11 | using Moq; 12 | using NUnit.Framework; 13 | 14 | namespace Couchbase.Linq.UnitTests.QueryGeneration 15 | { 16 | [TestFixture] 17 | public class ExplainTests : N1QLTestBase 18 | { 19 | [Test] 20 | public void Test_Explain_Keyword() 21 | { 22 | var query = CreateQueryable("default") 23 | .Select(c => new {age = c.Age}); 24 | 25 | _ = query.Explain(); 26 | var n1QlQuery = QueryExecutor.Query; 27 | 28 | const string expected = "EXPLAIN SELECT `Extent1`.`age` as `age` FROM `default` as `Extent1`"; 29 | 30 | Assert.AreEqual(expected, n1QlQuery); 31 | } 32 | 33 | [Test] 34 | public async Task Test_ExplainAsync_Keyword() 35 | { 36 | var query = CreateQueryable("default") 37 | .Select(c => new {age = c.Age}); 38 | 39 | _ = await query.ExplainAsync(); 40 | var n1QlQuery = QueryExecutor.Query; 41 | 42 | const string expected = "EXPLAIN SELECT `Extent1`.`age` as `age` FROM `default` as `Extent1`"; 43 | 44 | Assert.AreEqual(expected, n1QlQuery); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/QueryGeneration/SelectDocumentMetadataTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Couchbase.Core; 5 | using Couchbase.Linq.UnitTests.Documents; 6 | using Moq; 7 | using Newtonsoft.Json.Serialization; 8 | using NUnit.Framework; 9 | 10 | namespace Couchbase.Linq.UnitTests.QueryGeneration 11 | { 12 | [TestFixture] 13 | public class SelectDocumentMetadataTests : N1QLTestBase 14 | { 15 | [Test] 16 | public void Test_SelectDocumentMetadata_Basic() 17 | { 18 | SetContractResolver(new DefaultContractResolver()); 19 | 20 | var mockBucket = new Mock(); 21 | mockBucket.SetupGet(e => e.Name).Returns("default"); 22 | 23 | var query = 24 | QueryFactory.Queryable(mockBucket.Object); 25 | 26 | const string expected = "SELECT `Extent1`.*, META(`Extent1`) as `__metadata` FROM `default` as `Extent1`"; 27 | 28 | var n1QlQuery = CreateN1QlQuery(mockBucket.Object, query.Expression, true); 29 | 30 | Assert.AreEqual(expected, n1QlQuery); 31 | } 32 | 33 | [Test] 34 | public void Test_SelectDocumentMetadata_WithPlainSelectProjection() 35 | { 36 | SetContractResolver(new DefaultContractResolver()); 37 | 38 | var mockBucket = new Mock(); 39 | mockBucket.SetupGet(e => e.Name).Returns("default"); 40 | 41 | var query = 42 | QueryFactory.Queryable(mockBucket.Object) 43 | .Select(p => p); 44 | 45 | const string expected = "SELECT `Extent1`.*, META(`Extent1`) as `__metadata` FROM `default` as `Extent1`"; 46 | 47 | var n1QlQuery = CreateN1QlQuery(mockBucket.Object, query.Expression, true); 48 | 49 | Assert.AreEqual(expected, n1QlQuery); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq.UnitTests/QueryGeneration/UnaryExpressionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Couchbase.Core; 7 | using Couchbase.Linq.UnitTests.Documents; 8 | using Moq; 9 | using NUnit.Framework; 10 | 11 | namespace Couchbase.Linq.UnitTests.QueryGeneration 12 | { 13 | [TestFixture] 14 | class UnaryExpressionTests : N1QLTestBase 15 | { 16 | 17 | #region Logical Operators 18 | 19 | [Test] 20 | public void Test_BooleanNot() 21 | { 22 | var mockBucket = new Mock(); 23 | mockBucket.SetupGet(e => e.Name).Returns("default"); 24 | 25 | var query = 26 | QueryFactory.Queryable(mockBucket.Object) 27 | // ReSharper disable once NegativeEqualityExpression 28 | .Where(e => !(e.Age == 10)) 29 | .Select(e => new { age = e.Age }); 30 | 31 | const string expected = 32 | "SELECT `Extent1`.`age` as `age` FROM `default` as `Extent1` WHERE NOT (`Extent1`.`age` = 10)"; 33 | 34 | var n1QlQuery = CreateN1QlQuery(mockBucket.Object, query.Expression); 35 | 36 | Assert.AreEqual(expected, n1QlQuery); 37 | } 38 | 39 | #endregion 40 | 41 | #region Arithmetic Operators 42 | 43 | [Test] 44 | public void Test_Negation() 45 | { 46 | var mockBucket = new Mock(); 47 | mockBucket.SetupGet(e => e.Name).Returns("default"); 48 | 49 | var query = 50 | QueryFactory.Queryable(mockBucket.Object) 51 | .Select(e => new {age = -e.Age}); 52 | 53 | const string expected = 54 | "SELECT -`Extent1`.`age` as `age` FROM `default` as `Extent1`"; 55 | 56 | var n1QlQuery = CreateN1QlQuery(mockBucket.Object, query.Expression); 57 | 58 | Assert.AreEqual(expected, n1QlQuery); 59 | } 60 | 61 | #endregion 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/BucketQueryOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Linq.Filters; 3 | 4 | namespace Couchbase.Linq 5 | { 6 | /// 7 | /// Options to control queries against an . 8 | /// 9 | [Flags] 10 | public enum BucketQueryOptions 11 | { 12 | /// 13 | /// No special options, use default behavior. 14 | /// 15 | None = 0, 16 | 17 | /// 18 | /// Suppress all registered filters in the . 19 | /// 20 | SuppressFilters = 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/CallerArgumentExpressionAttribute.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | // From https://github.com/dotnet/runtime/blob/5da4a9e919dcee35f831ab69b6e475baaf798875/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs 5 | 6 | namespace System.Runtime.CompilerServices; 7 | 8 | #if !NETCOREAPP3_0_OR_GREATER 9 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] 10 | internal sealed class CallerArgumentExpressionAttribute : Attribute 11 | { 12 | public CallerArgumentExpressionAttribute(string parameterName) 13 | { 14 | ParameterName = parameterName; 15 | } 16 | 17 | public string ParameterName { get; } 18 | } 19 | #endif -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/AnyAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for AnyAsync. 14 | /// 15 | internal class AnyAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.AnyAsyncNoPredicate, 23 | QueryExtensionMethods.AnyAsyncWithPredicate 24 | }; 25 | 26 | /// 27 | /// Creates a new AnyAsyncExpressionNode. 28 | /// 29 | /// Method parse info. 30 | /// Optional predicate which filters the results. 31 | public AnyAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalPredicate) 32 | : base(parseInfo, optionalPredicate, null) 33 | { 34 | } 35 | 36 | /// 37 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 38 | ClauseGenerationContext clauseGenerationContext) 39 | { 40 | throw new NotSupportedException($"Resolve is not supported by {typeof(CountAsyncExpressionNode)}"); 41 | } 42 | 43 | /// 44 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 45 | new AnyAsyncResultOperator(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/AverageAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for AverageAsync. 14 | /// 15 | internal class AverageAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.AverageAsyncNoSelector, 23 | QueryExtensionMethods.AverageAsyncWithSelector 24 | }; 25 | 26 | /// 27 | /// Creates a new AverageAsyncExpressionNode. 28 | /// 29 | /// Method parse info. 30 | /// Optional selector for value to be summed. 31 | public AverageAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalSelector) 32 | : base(parseInfo, null, optionalSelector) 33 | { 34 | } 35 | 36 | /// 37 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 38 | ClauseGenerationContext clauseGenerationContext) 39 | { 40 | throw new NotSupportedException($"Resolve is not supported by {typeof(AverageAsyncExpressionNode)}"); 41 | } 42 | 43 | /// 44 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 45 | new AverageAsyncResultOperator(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/ConsistentWithClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using Couchbase.Query; 4 | using Remotion.Linq; 5 | using Remotion.Linq.Clauses; 6 | 7 | namespace Couchbase.Linq.Clauses 8 | { 9 | /// 10 | /// Represents a clause on the query setting index consistency using . 11 | /// If applied multiple times to the same query, the states should be merged. 12 | /// 13 | internal class ConsistentWithClause : IBodyClause 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// Mutation state for the query. 19 | /// Time to wait for index scan. 20 | public ConsistentWithClause(MutationState mutationState, TimeSpan? scanWait) 21 | { 22 | MutationState = mutationState; 23 | ScanWait = scanWait; 24 | } 25 | 26 | /// 27 | /// Mutation state for the query. 28 | /// 29 | public MutationState MutationState { get; set; } 30 | 31 | /// 32 | /// Time to wait for index scan. 33 | /// 34 | public TimeSpan? ScanWait { get; } 35 | 36 | /// 37 | public virtual void Accept(IQueryModelVisitor visitor, QueryModel queryModel, int index) 38 | { 39 | // Do nothing, the ClusterQueryExecutor will find this in QueryModel.BodyClauses to apply behavior 40 | } 41 | 42 | /// 43 | public IBodyClause Clone(CloneContext cloneContext) 44 | { 45 | return new ConsistentWithClause(MutationState, ScanWait); 46 | } 47 | 48 | /// 49 | public virtual void TransformExpressions(Func transformation) 50 | { 51 | // Do nothing 52 | } 53 | 54 | /// 55 | public override string ToString() 56 | { 57 | return $"with mutation state consistency {MutationState}"; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/CountAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for CountAsync. 14 | /// 15 | internal class CountAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.CountAsyncNoPredicate, 23 | QueryExtensionMethods.CountAsyncWithPredicate 24 | }; 25 | 26 | /// 27 | /// Creates a new CountAsyncExpressionNode. 28 | /// 29 | /// Method parse info. 30 | /// Optional predicate which filters the results. 31 | public CountAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalPredicate) 32 | : base(parseInfo, optionalPredicate, null) 33 | { 34 | } 35 | 36 | /// 37 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 38 | ClauseGenerationContext clauseGenerationContext) 39 | { 40 | throw new NotSupportedException($"Resolve is not supported by {typeof(CountAsyncExpressionNode)}"); 41 | } 42 | 43 | /// 44 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 45 | new CountAsyncResultOperator(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/ExplainAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for ExplainAsync. 14 | /// 15 | internal class ExplainAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.ExplainAsync 23 | }; 24 | 25 | /// 26 | /// Creates a new ExplainAsyncExpressionNode. 27 | /// 28 | /// Method parse info. 29 | public ExplainAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo) 30 | : base(parseInfo, null, null) 31 | { 32 | } 33 | 34 | /// 35 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 36 | ClauseGenerationContext clauseGenerationContext) 37 | { 38 | throw new NotSupportedException($"Resolve is not supported by {typeof(ExplainAsyncExpressionNode)}"); 39 | } 40 | 41 | /// 42 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 43 | new ExplainAsyncResultOperator(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/ExplainExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression parser for N1QL "Explain" method. 14 | /// 15 | internal class ExplainExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.Explain 23 | }; 24 | 25 | /// 26 | /// Creates a new ExplainExpressionNode. 27 | /// 28 | /// Method parse info. 29 | public ExplainExpressionNode(MethodCallExpressionParseInfo parseInfo) 30 | : base(parseInfo, null, null) 31 | { 32 | } 33 | 34 | /// 35 | protected override ResultOperatorBase CreateResultOperator( 36 | ClauseGenerationContext clauseGenerationContext) 37 | { 38 | return new ExplainResultOperator(); 39 | } 40 | 41 | /// 42 | public override Expression Resolve(ParameterExpression inputParameter, 43 | Expression expressionToBeResolved, 44 | ClauseGenerationContext clauseGenerationContext) 45 | { 46 | throw new NotSupportedException($"Resolve is not supported by {typeof(ExplainExpressionNode)}"); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/FirstAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for FirstAsync and FirstOrDefaultAsync. 14 | /// 15 | internal class FirstAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | public static IEnumerable GetSupportedMethods() => new[] 18 | { 19 | QueryExtensionMethods.FirstAsyncNoPredicate, 20 | QueryExtensionMethods.FirstAsyncWithPredicate, 21 | QueryExtensionMethods.FirstOrDefaultAsyncNoPredicate, 22 | QueryExtensionMethods.FirstOrDefaultAsyncWithPredicate 23 | }; 24 | 25 | public FirstAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalPredicate) 26 | : base(parseInfo, optionalPredicate, null) 27 | { 28 | } 29 | 30 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 31 | ClauseGenerationContext clauseGenerationContext) 32 | { 33 | throw new NotSupportedException($"Resolve is not supported by {typeof(FirstAsyncExpressionNode)}"); 34 | } 35 | 36 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 37 | new FirstAsyncResultOperator(ParsedExpression.Method.Name.EndsWith("OrDefaultAsync")); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/LongCountAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for LongCountAsync. 14 | /// 15 | internal class LongCountAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.LongCountAsyncNoPredicate, 23 | QueryExtensionMethods.LongCountAsyncWithPredicate 24 | }; 25 | 26 | /// 27 | /// Creates a new LongCountAsyncExpressionNode. 28 | /// 29 | /// Method parse info. 30 | /// Optional predicate which filters the results. 31 | public LongCountAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalPredicate) 32 | : base(parseInfo, optionalPredicate, null) 33 | { 34 | } 35 | 36 | /// 37 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 38 | ClauseGenerationContext clauseGenerationContext) 39 | { 40 | throw new NotSupportedException($"Resolve is not supported by {typeof(LongCountAsyncExpressionNode)}"); 41 | } 42 | 43 | /// 44 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 45 | new LongCountAsyncResultOperator(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/MaxAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for MaxAsync. 14 | /// 15 | internal class MaxAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.MaxAsyncNoSelector, 23 | QueryExtensionMethods.MaxAsyncWithSelector 24 | }; 25 | 26 | /// 27 | /// Creates a new MaxAsyncExpressionNode. 28 | /// 29 | /// Method parse info. 30 | /// Optional selector for value. 31 | public MaxAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalSelector) 32 | : base(parseInfo, null, optionalSelector) 33 | { 34 | } 35 | 36 | /// 37 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 38 | ClauseGenerationContext clauseGenerationContext) 39 | { 40 | throw new NotSupportedException($"Resolve is not supported by {typeof(MaxAsyncExpressionNode)}"); 41 | } 42 | 43 | /// 44 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 45 | new MaxAsyncResultOperator(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/MinAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for MinAsync. 14 | /// 15 | internal class MinAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.MinAsyncNoSelector, 23 | QueryExtensionMethods.MinAsyncWithSelector 24 | }; 25 | 26 | /// 27 | /// Creates a new MinAsyncExpressionNode. 28 | /// 29 | /// Method parse info. 30 | /// Optional selector for value. 31 | public MinAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalSelector) 32 | : base(parseInfo, null, optionalSelector) 33 | { 34 | } 35 | 36 | /// 37 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 38 | ClauseGenerationContext clauseGenerationContext) 39 | { 40 | throw new NotSupportedException($"Resolve is not supported by {typeof(MinAsyncExpressionNode)}"); 41 | } 42 | 43 | /// 44 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 45 | new MinAsyncResultOperator(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/ScanConsistencyClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using Couchbase.Query; 4 | using Remotion.Linq; 5 | using Remotion.Linq.Clauses; 6 | 7 | namespace Couchbase.Linq.Clauses 8 | { 9 | /// 10 | /// Represents a clause on the query setting the scan consistency. 11 | /// 12 | internal class ScanConsistencyClause : IBodyClause 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// Scan consistency for the query. 18 | /// Time to wait for index scan. 19 | public ScanConsistencyClause(QueryScanConsistency scanConsistency, TimeSpan? scanWait) 20 | { 21 | ScanConsistency = scanConsistency; 22 | ScanWait = scanWait; 23 | } 24 | 25 | /// 26 | /// Scan consistency for the query. 27 | /// 28 | public QueryScanConsistency ScanConsistency { get; set; } 29 | 30 | /// 31 | /// Time to wait for index scan. 32 | /// 33 | public TimeSpan? ScanWait { get; } 34 | 35 | /// 36 | public virtual void Accept(IQueryModelVisitor visitor, QueryModel queryModel, int index) 37 | { 38 | // Do nothing, the ClusterQueryExecutor will find this in QueryModel.BodyClauses to apply behavior 39 | } 40 | 41 | /// 42 | public IBodyClause Clone(CloneContext cloneContext) 43 | { 44 | return new ScanConsistencyClause(ScanConsistency, ScanWait); 45 | } 46 | 47 | /// 48 | public virtual void TransformExpressions(Func transformation) 49 | { 50 | // Do nothing 51 | } 52 | 53 | /// 54 | public override string ToString() 55 | { 56 | return $"with scan consistency {ScanConsistency}"; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/SingleAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for FirstAsync and FirstOrDefaultAsync. 14 | /// 15 | internal class SingleAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.SingleAsyncNoPredicate, 23 | QueryExtensionMethods.SingleAsyncWithPredicate, 24 | QueryExtensionMethods.SingleOrDefaultAsyncNoPredicate, 25 | QueryExtensionMethods.SingleOrDefaultAsyncWithPredicate 26 | }; 27 | 28 | /// 29 | /// Creates a new SingleAsyncExpressionNode. 30 | /// 31 | /// Method parse info. 32 | /// Optional predicate which filters the results. 33 | public SingleAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalPredicate) 34 | : base(parseInfo, optionalPredicate, null) 35 | { 36 | } 37 | 38 | /// 39 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 40 | ClauseGenerationContext clauseGenerationContext) 41 | { 42 | throw new NotSupportedException($"Resolve is not supported by {typeof(SingleAsyncExpressionNode)}"); 43 | } 44 | 45 | /// 46 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 47 | new SingleAsyncResultOperator(ParsedExpression.Method.Name.EndsWith("OrDefaultAsync")); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/SumAsyncExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Couchbase.Linq.Operators; 7 | using Remotion.Linq.Clauses; 8 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 9 | 10 | namespace Couchbase.Linq.Clauses 11 | { 12 | /// 13 | /// Expression node for SumAsync. 14 | /// 15 | internal class SumAsyncExpressionNode : ResultOperatorExpressionNodeBase 16 | { 17 | /// 18 | /// Methods which are supported by this type of node. 19 | /// 20 | public static IEnumerable GetSupportedMethods() => new[] 21 | { 22 | QueryExtensionMethods.SumAsyncNoSelector, 23 | QueryExtensionMethods.SumAsyncWithSelector 24 | }; 25 | 26 | /// 27 | /// Creates a new SumAsyncExpressionNode. 28 | /// 29 | /// Method parse info. 30 | /// Optional selector for value to be summed. 31 | public SumAsyncExpressionNode(MethodCallExpressionParseInfo parseInfo, LambdaExpression optionalSelector) 32 | : base(parseInfo, null, optionalSelector) 33 | { 34 | } 35 | 36 | /// 37 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 38 | ClauseGenerationContext clauseGenerationContext) 39 | { 40 | throw new NotSupportedException($"Resolve is not supported by {typeof(SumAsyncExpressionNode)}"); 41 | } 42 | 43 | /// 44 | protected override ResultOperatorBase CreateResultOperator(ClauseGenerationContext clauseGenerationContext) => 45 | new SumAsyncResultOperator(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/UseHashClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Remotion.Linq.Clauses; 4 | 5 | namespace Couchbase.Linq.Clauses 6 | { 7 | internal class UseHashClause : HintClause 8 | { 9 | public UseHashClause(HashHintType hashType) 10 | { 11 | HashHintType = hashType; 12 | } 13 | 14 | public HashHintType HashHintType { get; set; } 15 | 16 | /// 17 | /// Clones this clause. 18 | /// 19 | /// The clones of all query source clauses are registered with this . 20 | /// 21 | public override HintClause Clone(CloneContext cloneContext) 22 | { 23 | return new UseHashClause(HashHintType); 24 | } 25 | 26 | public override void AppendToStringBuilder(StringBuilder sb) 27 | { 28 | var hashTypeStr = HashHintType == HashHintType.Build ? "build" : "probe"; 29 | sb.AppendFormat("HASH ({0})", hashTypeStr); 30 | } 31 | 32 | public override string ToString() 33 | { 34 | return $"use hash ({HashHintType})"; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/UseHashExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Remotion.Linq; 7 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 8 | 9 | namespace Couchbase.Linq.Clauses 10 | { 11 | internal class UseHashExpressionNode : MethodCallExpressionNodeBase 12 | { 13 | public static readonly MethodInfo[] SupportedMethods = 14 | typeof(QueryExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) 15 | .Where(p => p.Name == "UseHash") 16 | .ToArray(); 17 | 18 | public UseHashExpressionNode(MethodCallExpressionParseInfo parseInfo, ConstantExpression hashHintType) 19 | : base(parseInfo) 20 | { 21 | if (hashHintType.Type != typeof(HashHintType)) 22 | { 23 | throw new ArgumentException($"{nameof(hashHintType)} must return a {nameof(Linq.HashHintType)}", nameof(hashHintType)); 24 | } 25 | 26 | HashHintType = hashHintType; 27 | } 28 | 29 | public ConstantExpression HashHintType { get; } 30 | 31 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 32 | ClauseGenerationContext clauseGenerationContext) 33 | { 34 | return Source.Resolve(inputParameter, expressionToBeResolved, clauseGenerationContext); 35 | } 36 | 37 | protected override void ApplyNodeSpecificSemantics(QueryModel queryModel, 38 | ClauseGenerationContext clauseGenerationContext) 39 | { 40 | queryModel.BodyClauses.Add(new UseHashClause((HashHintType) HashHintType.Value!)); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/UseIndexClause.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Couchbase.Linq.QueryGeneration; 4 | using Remotion.Linq.Clauses; 5 | 6 | namespace Couchbase.Linq.Clauses 7 | { 8 | internal class UseIndexClause : HintClause 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// Name of the index to use. 14 | /// Type of the index to use. 15 | public UseIndexClause(string indexName, N1QlIndexType indexType) 16 | { 17 | if (string.IsNullOrEmpty(indexName)) 18 | { 19 | throw new ArgumentNullException(nameof(indexName)); 20 | } 21 | 22 | IndexName = indexName; 23 | IndexType = indexType; 24 | } 25 | 26 | /// 27 | /// Name of the index to use. 28 | /// 29 | public string IndexName { get; set; } 30 | 31 | /// 32 | /// Type of the index to use. 33 | /// 34 | public N1QlIndexType IndexType { get; set; } 35 | 36 | /// 37 | /// Clones this clause. 38 | /// 39 | /// The clones of all query source clauses are registered with this . 40 | /// 41 | public override HintClause Clone(CloneContext cloneContext) 42 | { 43 | return new UseIndexClause(IndexName, IndexType); 44 | } 45 | 46 | public override void AppendToStringBuilder(StringBuilder sb) 47 | { 48 | var indexTypeStr = IndexType == N1QlIndexType.Gsi ? "GSI" : "VIEW"; 49 | sb.AppendFormat("INDEX ({0} USING {1})", N1QlHelpers.EscapeIdentifier(IndexName), indexTypeStr); 50 | } 51 | 52 | public override string ToString() 53 | { 54 | return $"use index {IndexName} using {IndexType.ToString().ToUpper()}"; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/UseIndexExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Extensions; 6 | using Remotion.Linq; 7 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 8 | 9 | namespace Couchbase.Linq.Clauses 10 | { 11 | internal class UseIndexExpressionNode : MethodCallExpressionNodeBase 12 | { 13 | public static readonly MethodInfo[] SupportedMethods = 14 | typeof(QueryExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) 15 | .Where(p => p.Name == "UseIndex") 16 | .ToArray(); 17 | 18 | public UseIndexExpressionNode(MethodCallExpressionParseInfo parseInfo, ConstantExpression indexName, ConstantExpression indexType) 19 | : base(parseInfo) 20 | { 21 | if (indexName == null) 22 | { 23 | throw new ArgumentNullException("indexName"); 24 | } 25 | if (indexName.Type != typeof(string)) 26 | { 27 | throw new ArgumentException("indexName must return a string", "indexName"); 28 | } 29 | 30 | if (indexType.Type != typeof(N1QlIndexType)) 31 | { 32 | throw new ArgumentException("indexType must return a N1QlIndexType", "indexType"); 33 | } 34 | 35 | IndexName = indexName; 36 | IndexType = indexType; 37 | } 38 | 39 | public ConstantExpression IndexName { get; private set; } 40 | public ConstantExpression IndexType { get; private set; } 41 | 42 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 43 | ClauseGenerationContext clauseGenerationContext) 44 | { 45 | return Source.Resolve(inputParameter, expressionToBeResolved, clauseGenerationContext); 46 | } 47 | 48 | protected override void ApplyNodeSpecificSemantics(QueryModel queryModel, 49 | ClauseGenerationContext clauseGenerationContext) 50 | { 51 | queryModel.BodyClauses.Add(new UseIndexClause((string) IndexName.Value!, (N1QlIndexType) IndexType.Value!)); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Clauses/UseKeysExpressionNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using Couchbase.Linq.Extensions; 5 | using Remotion.Linq; 6 | using Remotion.Linq.Parsing.Structure.IntermediateModel; 7 | 8 | namespace Couchbase.Linq.Clauses 9 | { 10 | internal class UseKeysExpressionNode : MethodCallExpressionNodeBase 11 | { 12 | public static readonly MethodInfo[] SupportedMethods = 13 | { 14 | typeof (QueryExtensions).GetMethod("UseKeys")! 15 | }; 16 | 17 | public UseKeysExpressionNode(MethodCallExpressionParseInfo parseInfo, Expression keys) 18 | : base(parseInfo) 19 | { 20 | if (keys == null) 21 | { 22 | throw new ArgumentNullException("keys"); 23 | } 24 | 25 | Keys = keys; 26 | } 27 | 28 | public Expression Keys { get; private set; } 29 | 30 | public override Expression Resolve(ParameterExpression inputParameter, Expression expressionToBeResolved, 31 | ClauseGenerationContext clauseGenerationContext) 32 | { 33 | return Source.Resolve(inputParameter, expressionToBeResolved, clauseGenerationContext); 34 | } 35 | 36 | protected override void ApplyNodeSpecificSemantics(QueryModel queryModel, 37 | ClauseGenerationContext clauseGenerationContext) 38 | { 39 | queryModel.BodyClauses.Add(new UseKeysClause(Keys)); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/CollectionQueryable.cs: -------------------------------------------------------------------------------- 1 | using Couchbase.KeyValue; 2 | using Couchbase.Linq.Execution; 3 | using Couchbase.Linq.Utils; 4 | 5 | namespace Couchbase.Linq 6 | { 7 | /// 8 | /// The main entry point and executor of the query. 9 | /// 10 | /// Document type to query. 11 | internal sealed class CollectionQueryable : CouchbaseQueryable, ICollectionQueryable 12 | { 13 | /// 14 | public string CollectionName { get; } 15 | 16 | /// 17 | public string ScopeName { get; } 18 | 19 | /// 20 | public string BucketName { get; } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The collection. 26 | /// The query provider to execute the query. 27 | public CollectionQueryable(ICouchbaseCollection collection, IAsyncQueryProvider provider) : base(provider) 28 | { 29 | ThrowHelpers.ThrowIfNull(collection); 30 | 31 | CollectionName = collection.Name; 32 | 33 | var scope = collection.Scope; 34 | ScopeName = scope.Name; 35 | BucketName = scope.Bucket.Name; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Couchbase.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/Linq2Couchbase/6b63364d429079531b3bedeb5d7adbb100b898fc/Src/Couchbase.Linq/Couchbase.snk -------------------------------------------------------------------------------- /Src/Couchbase.Linq/CouchbaseCollectionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Linq.QueryGeneration; 3 | 4 | #nullable enable 5 | 6 | namespace Couchbase.Linq 7 | { 8 | /// 9 | /// Annotates a document as belonging to a specific scope/collection. If not present, the default collection is assumed. 10 | /// 11 | /// 12 | /// This may be applied to a document class or to a property implementing on a class 13 | /// inherited from . 14 | /// 15 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] 16 | public class CouchbaseCollectionAttribute : Attribute 17 | { 18 | internal static CouchbaseCollectionAttribute Default { get; } = 19 | new(N1QlHelpers.DefaultScopeName, N1QlHelpers.DefaultCollectionName); 20 | 21 | /// 22 | /// Name of the scope. 23 | /// 24 | public string Scope { get; set; } 25 | 26 | /// 27 | /// Name of the collection. 28 | /// 29 | public string Collection { get; set; } 30 | 31 | /// 32 | /// Create a new CouchbaseCollectionAttribute. 33 | /// 34 | /// Name of the scope. 35 | /// Name of the collection. 36 | public CouchbaseCollectionAttribute(string scope, string collection) 37 | { 38 | Scope = scope; 39 | Collection = collection; 40 | } 41 | 42 | /// 43 | /// Deconstruct into variables. 44 | /// 45 | /// Name of the scope. 46 | /// Name of the collection. 47 | public void Deconstruct(out string scope, out string collection) 48 | { 49 | scope = Scope; 50 | collection = Collection; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/CouchbaseQueryable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | using System.Threading; 4 | using Couchbase.Linq.Execution; 5 | using Remotion.Linq; 6 | 7 | namespace Couchbase.Linq 8 | { 9 | /// 10 | /// The executor of the query. 11 | /// 12 | /// 13 | internal class CouchbaseQueryable : QueryableBase, IAsyncEnumerable 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// Used to build new expressions as more methods are applied to the query. 19 | /// The provider. 20 | /// The expression. 21 | public CouchbaseQueryable(IAsyncQueryProvider provider, Expression expression) 22 | : base(provider, expression) 23 | { 24 | } 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// Used by subclasses to create a root queryable. 30 | /// The provider. 31 | protected CouchbaseQueryable(IAsyncQueryProvider provider) 32 | : base(provider) 33 | { 34 | } 35 | 36 | public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => 37 | ((IAsyncQueryProvider) Provider).ExecuteAsync>(Expression) 38 | .GetAsyncEnumerator(cancellationToken); 39 | } 40 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Execution/ClusterQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using System.Threading; 6 | using Couchbase.Linq.Execution.StreamedData; 7 | using Remotion.Linq; 8 | using Remotion.Linq.Clauses.StreamedData; 9 | using Remotion.Linq.Parsing.Structure; 10 | 11 | namespace Couchbase.Linq.Execution 12 | { 13 | internal class ClusterQueryProvider : QueryProviderBase, IAsyncQueryProvider 14 | { 15 | public static readonly MethodInfo ExecuteAsyncMethod = 16 | typeof(IAsyncQueryExecutor).GetMethod(nameof(IAsyncQueryExecutor.ExecuteCollectionAsync), 17 | new[] {typeof(QueryModel), typeof(CancellationToken)}) 18 | ?? throw new MissingMemberException(); 19 | 20 | public ClusterQueryProvider(IQueryParser queryParser, IAsyncQueryExecutor executor) 21 | : base(queryParser, executor) 22 | { 23 | } 24 | 25 | public override IQueryable CreateQuery(Expression expression) => 26 | new CouchbaseQueryable(this, expression); 27 | 28 | public T ExecuteAsync(Expression expression, CancellationToken cancellationToken = default) 29 | { 30 | var queryModel = GenerateQueryModel(expression); 31 | 32 | var streamedDataInfo = queryModel.GetOutputDataInfo(); 33 | 34 | if (streamedDataInfo is StreamedSequenceInfo sequence) 35 | { 36 | var executeAsyncMethod = ExecuteAsyncMethod.MakeGenericMethod(sequence.ResultItemType); 37 | 38 | return (T) executeAsyncMethod.Invoke(Executor, new object[] {queryModel, cancellationToken})!; 39 | } 40 | else if (streamedDataInfo is AsyncStreamedValueInfo streamedValue) 41 | { 42 | return (T) streamedValue.ExecuteQueryModel(queryModel, Executor, cancellationToken).Value; 43 | } 44 | else 45 | { 46 | return (T) streamedDataInfo.ExecuteQueryModel(queryModel, Executor).Value; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Execution/IAsyncQueryExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Remotion.Linq; 5 | 6 | namespace Couchbase.Linq.Execution 7 | { 8 | /// 9 | /// Extends with routines to execute queries asynchronously. 10 | /// 11 | internal interface IAsyncQueryExecutor : IQueryExecutor 12 | { 13 | IAsyncEnumerable ExecuteCollectionAsync(QueryModel queryModel, CancellationToken cancellationToken = default); 14 | 15 | Task ExecuteSingleAsync(QueryModel queryModel, bool returnDefaultWhenEmpty, CancellationToken cancellationToken = default); 16 | 17 | Task ExecuteScalarAsync(QueryModel queryModel, CancellationToken cancellationToken = default); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Execution/IAsyncQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | using System.Threading; 4 | 5 | namespace Couchbase.Linq.Execution 6 | { 7 | internal interface IAsyncQueryProvider : IQueryProvider 8 | { 9 | T ExecuteAsync(Expression expression, CancellationToken cancellationToken = default); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Execution/ScalarResult.cs: -------------------------------------------------------------------------------- 1 | namespace Couchbase.Linq.Execution 2 | { 3 | /// 4 | /// Used to extract the result row from an Any, All, or aggregate operation. 5 | /// 6 | internal class ScalarResult 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | // Note: must be virtual to support change tracking for First/Single 10 | public virtual T result { get; set; } = default!; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Execution/StreamedData/AsyncStreamedScalarValueInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Remotion.Linq; 6 | using Remotion.Linq.Clauses.StreamedData; 7 | 8 | namespace Couchbase.Linq.Execution.StreamedData 9 | { 10 | /// 11 | /// Version of intended for scalar result objects, for example 12 | /// from CountAsync. 13 | /// 14 | internal class AsyncStreamedScalarValueInfo : AsyncStreamedValueInfo 15 | { 16 | /// 17 | /// Creates a new AsyncStreamedScalarValueInfo. 18 | /// 19 | /// Data type returned by the . Must be of type . 20 | public AsyncStreamedScalarValueInfo(Type dataType) 21 | : base(dataType) 22 | { 23 | } 24 | 25 | /// 26 | protected override AsyncStreamedValueInfo CloneWithNewDataType(Type dataType) => 27 | new AsyncStreamedScalarValueInfo(dataType); 28 | 29 | /// 30 | #pragma warning disable CS8609 // Nullability of reference types in return type doesn't match overridden member. 31 | public override Task ExecuteQueryModelAsync(QueryModel queryModel, IAsyncQueryExecutor executor, 32 | #pragma warning restore CS8609 // Nullability of reference types in return type doesn't match overridden member. 33 | CancellationToken cancellationToken = default) 34 | where T : default 35 | { 36 | if (queryModel == null) 37 | { 38 | throw new ArgumentNullException(nameof(queryModel)); 39 | } 40 | 41 | if (executor == null) 42 | { 43 | throw new ArgumentNullException(nameof(executor)); 44 | } 45 | 46 | return executor.ExecuteScalarAsync(queryModel, cancellationToken); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Execution/StreamedData/AsyncStreamedSingleValueInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Remotion.Linq; 5 | using Remotion.Linq.Clauses.StreamedData; 6 | 7 | namespace Couchbase.Linq.Execution.StreamedData 8 | { 9 | /// 10 | /// Version of intended for single result objects, for example 11 | /// from FirstAsync or SingleAsync. 12 | /// 13 | internal class AsyncStreamedSingleValueInfo : AsyncStreamedValueInfo 14 | { 15 | public bool ReturnDefaultWhenEmpty { get; } 16 | 17 | public AsyncStreamedSingleValueInfo(Type dataType, bool returnDefaultWhenEmpty) 18 | : base(dataType) 19 | { 20 | ReturnDefaultWhenEmpty = returnDefaultWhenEmpty; 21 | } 22 | 23 | protected override AsyncStreamedValueInfo CloneWithNewDataType(Type dataType) => 24 | new AsyncStreamedSingleValueInfo (dataType, ReturnDefaultWhenEmpty); 25 | 26 | public override Task ExecuteQueryModelAsync(QueryModel queryModel, IAsyncQueryExecutor executor, 27 | CancellationToken cancellationToken = default) 28 | where T : default 29 | { 30 | if (queryModel == null) 31 | { 32 | throw new ArgumentNullException(nameof(queryModel)); 33 | } 34 | 35 | if (executor == null) 36 | { 37 | throw new ArgumentNullException(nameof(executor)); 38 | } 39 | 40 | return executor.ExecuteSingleAsync(queryModel, ReturnDefaultWhenEmpty, cancellationToken); 41 | } 42 | 43 | // ReSharper disable PossibleNullReferenceException 44 | public override bool Equals(IStreamedDataInfo? obj) => 45 | base.Equals(obj) && 46 | ((AsyncStreamedSingleValueInfo) obj).ReturnDefaultWhenEmpty == ReturnDefaultWhenEmpty; 47 | // ReSharper restore PossibleNullReferenceException 48 | 49 | public override int GetHashCode() => 50 | base.GetHashCode() ^ ReturnDefaultWhenEmpty.GetHashCode(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Execution/StreamedData/AsyncStreamedValue.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Remotion.Linq.Clauses.StreamedData; 3 | 4 | namespace Couchbase.Linq.Execution.StreamedData 5 | { 6 | /// 7 | /// Implementation of intended for single value results 8 | /// being returned asynchronously. 9 | /// 10 | internal class AsyncStreamedValue : IStreamedData 11 | { 12 | public object Value { get; } 13 | 14 | public AsyncStreamedValueInfo DataInfo { get; } 15 | 16 | IStreamedDataInfo IStreamedData.DataInfo => DataInfo; 17 | 18 | public AsyncStreamedValue(Task value, AsyncStreamedValueInfo asyncStreamedValueInfo) 19 | { 20 | Value = value; 21 | DataInfo = asyncStreamedValueInfo; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Extensions/EnumerableExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Couchbase.Linq.Extensions 6 | { 7 | /// 8 | /// Static helper to store reflection method info for various extensions. 9 | /// 10 | internal static class EnumerableExtensionMethods 11 | { 12 | public static MethodInfo Nest { get; } 13 | public static MethodInfo LeftOuterNest { get; } 14 | 15 | public static MethodInfo UseKeys { get; } 16 | 17 | static EnumerableExtensionMethods() 18 | { 19 | var allMethods = typeof(EnumerableExtensions) 20 | .GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) 21 | .ToList(); 22 | 23 | Nest = allMethods.Single(p => p.Name == nameof(QueryExtensions.Nest)); 24 | LeftOuterNest = allMethods.Single(p => p.Name == nameof(QueryExtensions.LeftOuterNest)); 25 | 26 | UseKeys = allMethods.Single(p => p.Name == nameof(QueryExtensions.UseKeys)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Filters/AttributeDocumentFilterSetGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Reflection; 3 | 4 | namespace Couchbase.Linq.Filters 5 | { 6 | /// 7 | /// Generates a for a particular type, using . 8 | /// 9 | public class AttributeDocumentFilterSetGenerator : IDocumentFilterSetGenerator 10 | { 11 | /// 12 | /// Generates a for a particular type, using . 13 | /// 14 | /// Returns null if there are no filters. This is to improve efficiency. 15 | public DocumentFilterSet? GenerateDocumentFilterSet() 16 | { 17 | var filters = typeof(T).GetTypeInfo().GetCustomAttributes(true).ToArray(); 18 | 19 | return filters.Length > 0 20 | ? new DocumentFilterSet(filters.Select(p => p.CreateFilter())) 21 | : null; 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Filters/CollectionMetadataCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Reflection; 4 | 5 | namespace Couchbase.Linq.Filters 6 | { 7 | /// 8 | /// Caches reflection information regarding . 9 | /// 10 | internal class CollectionMetadataCache 11 | { 12 | public static CollectionMetadataCache Instance { get; } = new(); 13 | 14 | private readonly ConcurrentDictionary _typeCache = new(); 15 | 16 | public CouchbaseCollectionAttribute GetCollection() => 17 | GetCollection(typeof(T)); 18 | 19 | public CouchbaseCollectionAttribute GetCollection(Type type) => 20 | _typeCache.GetOrAdd(type, static key => key.GetCustomAttribute() 21 | ?? CouchbaseCollectionAttribute.Default); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Filters/DocumentFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Couchbase.Linq.Filters 4 | { 5 | /// 6 | /// Abstract base class for attribute-based implementations. 7 | /// 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 9 | public abstract class DocumentFilterAttribute : Attribute 10 | { 11 | /// 12 | /// Priority of this filter compared to other filters against the same type. Lower priorities execute first. 13 | /// 14 | public int Priority { get; set; } 15 | 16 | /// 17 | /// Returns the to be applied by this attribute. 18 | /// 19 | /// Type of the document being filtered. 20 | /// The to be applied by this attribute. 21 | public abstract IDocumentFilter CreateFilter(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Filters/DocumentTypeFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace Couchbase.Linq.Filters 6 | { 7 | /// 8 | /// When using Linq2Couchbase, automatically filter returned documents by the "type" attribute. 9 | /// 10 | public class DocumentTypeFilterAttribute : DocumentFilterAttribute 11 | { 12 | /// 13 | /// Filter the results to include documents with this string as the "type" attribute. 14 | /// 15 | public string Type { get; set; } 16 | 17 | /// 18 | /// Creates a new DocumentTypeFilterAttribute. 19 | /// 20 | /// Filter the results to include documents with this string as the "type" attribute. 21 | public DocumentTypeFilterAttribute(string type) 22 | { 23 | Type = type ?? throw new ArgumentNullException(nameof(type)); 24 | } 25 | 26 | /// 27 | public override IDocumentFilter CreateFilter() 28 | { 29 | return new WhereFilter 30 | { 31 | Priority = Priority, 32 | WhereExpression = GetExpression() 33 | }; 34 | } 35 | 36 | private Expression> GetExpression() 37 | { 38 | var parameter = Expression.Parameter(typeof (T), "p"); 39 | 40 | return Expression.Lambda>( 41 | Expression.Equal( 42 | Expression.PropertyOrField(parameter, "type"), 43 | Expression.Constant(Type)), 44 | parameter); 45 | } 46 | 47 | private class WhereFilter : IDocumentFilter 48 | { 49 | public Expression> WhereExpression { get; set; } = null!; 50 | public int Priority { get; set; } 51 | 52 | public IQueryable ApplyFilter(IQueryable source) 53 | { 54 | return source.Where(WhereExpression); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Filters/IDocumentFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace Couchbase.Linq.Filters 4 | { 5 | /// 6 | /// Filter designed to be applied to a LINQ query automatically 7 | /// 8 | public interface IDocumentFilter 9 | { 10 | /// 11 | /// Priority of this filter compared to other filters against the same type. Lower priorities execute first. 12 | /// 13 | int Priority { get; } 14 | 15 | /// 16 | /// Apply the filter to a LINQ query 17 | /// 18 | IQueryable ApplyFilter(IQueryable source); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Filters/IDocumentFilterSetGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace Couchbase.Linq.Filters 2 | { 3 | /// 4 | /// Generates a for a particular type. 5 | /// 6 | public interface IDocumentFilterSetGenerator 7 | { 8 | /// 9 | /// Generates a for a particular type. 10 | /// 11 | /// Returns null if there are no filters. This is to improve efficiency. 12 | DocumentFilterSet? GenerateDocumentFilterSet(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/HashHintType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Couchbase.Linq 4 | { 5 | /// 6 | /// Provides a hint when performing a hash join. 7 | /// 8 | public enum HashHintType 9 | { 10 | /// 11 | /// A lookup table should be built from the items on the right side of the join, and items on the left side 12 | /// should probe this lookup table to find matches. Typically used when the number of items on the right side 13 | /// of the join is smaller. 14 | /// 15 | Build = 0, 16 | 17 | /// 18 | /// A lookup table should be built from the items on the left side of the join, and items on the right side 19 | /// should probe this lookup table to find matches. Typically used when the number of items on the left side 20 | /// of the join is smaller. 21 | /// 22 | Probe = 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/IBucketContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Couchbase.Linq 5 | { 6 | /// 7 | /// Provides a single point of entry to a Couchbase collection which makes it easier to compose 8 | /// and execute queries and to group together changes which will be submitted back into the bucket. 9 | /// 10 | public interface IBucketContext 11 | { 12 | /// 13 | /// Gets the bucket the was created against. 14 | /// 15 | /// The . 16 | IBucket Bucket { get; } 17 | 18 | /// 19 | /// Query timeout, if null uses cluster default. 20 | /// 21 | TimeSpan? QueryTimeout { get; set; } 22 | 23 | /// 24 | /// Queries the current for entities of type T. This is the target of 25 | /// a LINQ query and requires that the associated JSON document have a type property that is the same as T. 26 | /// 27 | /// An entity or POCO representing the object graph of a JSON document. 28 | /// which can be used to query the bucket. 29 | IQueryable Query(); 30 | 31 | /// 32 | /// Queries the current for entities of type T. This is the target of 33 | /// a LINQ query and requires that the associated JSON document have a type property that is the same as T. 34 | /// 35 | /// An entity or POCO representing the object graph of a JSON document. 36 | /// Options to control the returned query. 37 | /// which can be used to query the bucket. 38 | IQueryable Query(BucketQueryOptions options); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/ICollectionQueryable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Couchbase.Linq 5 | { 6 | /// 7 | /// IQueryable sourced from a Couchbase collection. Used to provide the collection name to the query generator. 8 | /// 9 | internal interface ICollectionQueryable 10 | { 11 | /// 12 | /// Collection query is run against 13 | /// 14 | string CollectionName { get; } 15 | 16 | /// 17 | /// Scope query is run against 18 | /// 19 | string ScopeName { get; } 20 | 21 | /// 22 | /// Bucket query is run against 23 | /// 24 | string BucketName { get; } 25 | } 26 | 27 | /// 28 | /// IQueryable sourced from a Couchbase collection. Used to provide the collection name to the query generator. 29 | /// 30 | internal interface ICollectionQueryable : IQueryable, ICollectionQueryable, IAsyncEnumerable 31 | { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/IDocumentSet.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace Couchbase.Linq 4 | { 5 | /// 6 | /// A set of documents in a Couchbase collection. 7 | /// 8 | public interface IDocumentSet : IQueryable 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/IDocumentSet`1.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Couchbase.KeyValue; 3 | 4 | namespace Couchbase.Linq 5 | { 6 | /// 7 | /// A set of documents in a Couchbase collection. 8 | /// 9 | /// Type of the document. 10 | // ReSharper disable once TypeParameterCanBeVariant 11 | public interface IDocumentSet : IDocumentSet, IQueryable 12 | { 13 | /// 14 | /// The couchbase collection for these documents. 15 | /// 16 | ICouchbaseCollection Collection { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/LinqClusterOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Linq.Serialization; 3 | 4 | namespace Couchbase.Linq 5 | { 6 | /// 7 | /// extensions. 8 | /// 9 | public static class LinqClusterOptionsExtensions 10 | { 11 | /// 12 | /// Add Linq2Couchbase support to the cluster. 13 | /// 14 | /// Options to extend. 15 | /// The to allow chaining. 16 | public static ClusterOptions AddLinq(this ClusterOptions options) => 17 | options.AddLinq(null); 18 | 19 | /// 20 | /// Add Linq2Couchbase support to the cluster. 21 | /// 22 | /// Options to extend. 23 | /// Action to apply additional configuration to . 24 | /// The to allow chaining. 25 | public static ClusterOptions AddLinq(this ClusterOptions options, 26 | Action? setupAction) 27 | { 28 | var configuration = new CouchbaseLinqConfiguration(); 29 | 30 | setupAction?.Invoke(configuration); 31 | 32 | return options 33 | .AddClusterService(configuration.DocumentFilterManager) 34 | .AddClusterService(configuration.SerializationConverterProviderFactory); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Metadata/ContextMetadataCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace Couchbase.Linq.Metadata 5 | { 6 | /// 7 | /// Static cache of keyed by type inherited from . 8 | /// 9 | internal class ContextMetadataCache 10 | { 11 | public static ContextMetadataCache Instance { get; } = new(); 12 | 13 | private readonly ConcurrentDictionary _cache = new(); 14 | 15 | public ContextMetadata Get(Type type) 16 | { 17 | return _cache.GetOrAdd(type, static t => new ContextMetadata(t)); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Metadata/DocumentMetadata.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Couchbase.Linq.Metadata 4 | { 5 | /// 6 | /// Metadata about a document in Couchbase 7 | /// 8 | public class DocumentMetadata 9 | { 10 | /// 11 | /// "Compare and swap" value. This value changes each time the document is mutated. 12 | /// This can be used to ensure that the document has not been modified 13 | /// during a mutation, which enforces optimistic concurrency. 14 | /// 15 | [JsonProperty("cas")] 16 | public double Cas { get; set; } 17 | 18 | /// 19 | /// SDK specific flags. 20 | /// 21 | [JsonProperty("flags")] 22 | public int Flags { get; set; } 23 | 24 | /// 25 | /// Document's unique ID. Also referred to as the document key. 26 | /// 27 | [JsonProperty("id")] 28 | public string Id { get; set; } = null!; 29 | 30 | /// 31 | /// Information about the type of the document. 32 | /// 33 | [JsonProperty("type")] 34 | public string Type { get; set; } = null!; 35 | 36 | /// 37 | /// Returns the JSON string representation of this object. 38 | /// 39 | /// JSON string representation of this object. 40 | public override string ToString() 41 | { 42 | return JsonConvert.SerializeObject(this); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Metadata/DocumentSetMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Couchbase.Linq.Metadata 5 | { 6 | /// 7 | /// Metadata about a specific property returning present on 8 | /// a class inherited from . 9 | /// 10 | internal class DocumentSetMetadata 11 | { 12 | /// 13 | /// The property. 14 | /// 15 | public PropertyInfo Property { get; } 16 | 17 | /// 18 | /// The type of document. 19 | /// 20 | public Type DocumentType { get; set; } 21 | 22 | /// 23 | /// The collection where the document resides. 24 | /// 25 | public CouchbaseCollectionAttribute CollectionInfo { get; } 26 | 27 | public DocumentSetMetadata(PropertyInfo property) 28 | { 29 | Property = property; 30 | DocumentType = GetDocumentType(property.PropertyType); 31 | 32 | CollectionInfo = property.GetCustomAttribute() 33 | ?? DocumentType.GetCustomAttribute() 34 | ?? CouchbaseCollectionAttribute.Default; 35 | } 36 | 37 | private static Type GetDocumentType(Type propertyType) => 38 | propertyType.GenericTypeArguments[0]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Metadata/IDocumentMetadataProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Couchbase.Linq.Metadata 2 | { 3 | /// 4 | /// Returns metadata about the document object the interface is attached to 5 | /// 6 | /// 7 | /// Used by a unit test to fake the results of a META operation in a Linq2Couchbase query 8 | /// 9 | /// 10 | public interface IDocumentMetadataProvider 11 | { 12 | 13 | /// 14 | /// Get metadata about this document 15 | /// 16 | /// Metadata about this document 17 | DocumentMetadata? GetMetadata(); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/N1QlFunctionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Couchbase.Linq 8 | { 9 | /// 10 | /// Decorates a method that is converted to a N1QL function call. Each parameter in .Net 11 | /// is expected to correspond directly to a parameter in N1QL, in the same order. 12 | /// 13 | /// 14 | /// Should only be used to decorate static methods. 15 | /// 16 | [AttributeUsage(AttributeTargets.Method)] 17 | internal class N1QlFunctionAttribute : Attribute 18 | { 19 | /// 20 | /// Name of the function in N1QL. 21 | /// 22 | public string N1QlFunctionName { get; set; } 23 | 24 | /// 25 | /// Creates a new N1QlFunctionAttribute. 26 | /// 27 | /// Name of the function in N1QL. 28 | public N1QlFunctionAttribute(string n1QlFunctionName) 29 | { 30 | if (string.IsNullOrEmpty(n1QlFunctionName)) 31 | { 32 | throw new ArgumentNullException("n1QlFunctionName"); 33 | } 34 | 35 | N1QlFunctionName = n1QlFunctionName; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/N1QlFunctions.Core.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Couchbase.Linq 8 | { 9 | /// 10 | /// Implements static helper methods for N1QL queries 11 | /// 12 | public static partial class N1QlFunctions 13 | { 14 | /// 15 | /// Shortcut for creating an error for methods that are only supported in N1QL, not in .Net. 16 | /// 17 | private static Exception NotSupportedError() 18 | { 19 | return new NotSupportedException("This method may only be used in lambda expressions for generating N1QL queries."); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/N1QlFunctions.Metadata.cs: -------------------------------------------------------------------------------- 1 | using Couchbase.Linq.Metadata; 2 | 3 | namespace Couchbase.Linq 4 | { 5 | public static partial class N1QlFunctions 6 | { 7 | /// 8 | /// Returns metadata for a document object 9 | /// 10 | /// Document to get metadata from 11 | /// Metadata about the document 12 | /// Should only be called against a top-level document in Couchbase 13 | public static DocumentMetadata? Meta(object? document) 14 | { 15 | // Implementation will only be called when unit testing 16 | // using LINQ-to-Objects and faking a Couchbase database 17 | // Any faked document object should implement IDocumentMetadataProvider 18 | 19 | if (document is IDocumentMetadataProvider provider) 20 | { 21 | return provider.GetMetadata(); 22 | } 23 | else 24 | { 25 | return null; 26 | } 27 | } 28 | 29 | /// 30 | /// Returns the key for a document object 31 | /// 32 | /// Document to get key from 33 | /// Key of the document 34 | /// Should only be called against a top-level document in Couchbase 35 | public static string? Key(object? document) 36 | { 37 | // Implementation will only be called when unit testing 38 | // using LINQ-to-Objects and faking a Couchbase database 39 | // Any faked document object should implement IDocumentMetadataProvider 40 | 41 | if (document is IDocumentMetadataProvider provider) 42 | { 43 | var metadata = provider.GetMetadata(); 44 | 45 | return metadata?.Id; 46 | } 47 | else 48 | { 49 | return null; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/N1QlIndexType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Couchbase.Linq.Extensions; 7 | 8 | namespace Couchbase.Linq 9 | { 10 | /// 11 | /// Types of indices supported by N1QL query . 12 | /// 13 | public enum N1QlIndexType 14 | { 15 | /// 16 | /// Global secondary index 17 | /// 18 | Gsi, 19 | 20 | /// 21 | /// View index 22 | /// 23 | View 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Operators/AsyncValueFromSequenceResultOperatorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Couchbase.Linq.Execution.StreamedData; 4 | using Remotion.Linq.Clauses; 5 | using Remotion.Linq.Clauses.StreamedData; 6 | 7 | namespace Couchbase.Linq.Operators 8 | { 9 | /// 10 | /// Abstract base for result operators which return their result asynchronously. 11 | /// 12 | internal abstract class AsyncValueFromSequenceResultOperatorBase : ResultOperatorBase 13 | { 14 | private static readonly MethodInfo ExecuteMethod = typeof(AsyncValueFromSequenceResultOperatorBase) 15 | .GetMethod(nameof(ExecuteInMemory), new[] {typeof(StreamedSequence)})!; 16 | 17 | public abstract AsyncStreamedValue? ExecuteInMemory(StreamedSequence sequence); 18 | 19 | /// 20 | public sealed override IStreamedData ExecuteInMemory(IStreamedData input) 21 | { 22 | if (input == null) 23 | { 24 | throw new ArgumentNullException(nameof(input)); 25 | } 26 | if (!(input is StreamedSequence streamedSequence)) 27 | { 28 | throw new ArgumentException($"{nameof(input)} must be of type {typeof(StreamedSequence)}"); 29 | } 30 | 31 | var executeMethod = ExecuteMethod.MakeGenericMethod(streamedSequence.DataInfo.ResultItemType); 32 | return (AsyncStreamedValue) InvokeExecuteMethod(executeMethod, streamedSequence); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Operators/ChoiceAsyncResultOperatorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Couchbase.Linq.Execution.StreamedData; 4 | using Remotion.Linq.Clauses.StreamedData; 5 | 6 | namespace Couchbase.Linq.Operators 7 | { 8 | /// 9 | /// Abstract base for result operators which return a single result asynchronously, such as . 10 | /// 11 | internal abstract class ChoiceAsyncResultOperatorBase : AsyncValueFromSequenceResultOperatorBase 12 | { 13 | /// 14 | /// If true, an empty collection should return the default value. 15 | /// 16 | public bool ReturnDefaultWhenEmpty { get; set; } 17 | 18 | protected ChoiceAsyncResultOperatorBase(bool returnDefaultWhenEmpty) 19 | { 20 | ReturnDefaultWhenEmpty = returnDefaultWhenEmpty; 21 | } 22 | 23 | public sealed override IStreamedDataInfo GetOutputDataInfo(IStreamedDataInfo inputInfo) 24 | { 25 | if (inputInfo == null) 26 | { 27 | throw new ArgumentNullException(nameof(inputInfo)); 28 | } 29 | if (!(inputInfo is StreamedSequenceInfo streamedSequenceInfo)) 30 | { 31 | throw new ArgumentException($"{nameof(inputInfo)} must be of type {typeof(StreamedSequenceInfo)}"); 32 | } 33 | 34 | return GetOutputDataInfo(streamedSequenceInfo); 35 | } 36 | 37 | protected AsyncStreamedValueInfo GetOutputDataInfo(StreamedSequenceInfo streamedSequenceInfo) 38 | { 39 | if (streamedSequenceInfo == null) 40 | { 41 | throw new ArgumentNullException(nameof(streamedSequenceInfo)); 42 | } 43 | 44 | var taskType = typeof(Task<>).MakeGenericType(streamedSequenceInfo.ResultItemType); 45 | 46 | return new AsyncStreamedSingleValueInfo(taskType, ReturnDefaultWhenEmpty); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Operators/CountAsyncResultOperator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Threading.Tasks; 5 | using Couchbase.Linq.Execution.StreamedData; 6 | using Remotion.Linq.Clauses; 7 | using Remotion.Linq.Clauses.StreamedData; 8 | 9 | namespace Couchbase.Linq.Operators 10 | { 11 | /// 12 | /// Result operator for CountAsync. 13 | /// 14 | internal class CountAsyncResultOperator : AsyncValueFromSequenceResultOperatorBase 15 | { 16 | /// 17 | public override ResultOperatorBase Clone(CloneContext cloneContext) => 18 | new CountAsyncResultOperator(); 19 | 20 | /// 21 | public override AsyncStreamedValue ExecuteInMemory(StreamedSequence input) 22 | { 23 | var sequence = input.GetTypedSequence(); 24 | var result = sequence.Count(); 25 | return new AsyncStreamedValue(Task.FromResult(result), GetOutputDataInfo(input.DataInfo)); 26 | } 27 | 28 | /// 29 | public override IStreamedDataInfo GetOutputDataInfo(IStreamedDataInfo inputInfo) 30 | { 31 | if (inputInfo == null) 32 | { 33 | throw new ArgumentNullException(nameof(inputInfo)); 34 | } 35 | if (!(inputInfo is StreamedSequenceInfo streamedSequenceInfo)) 36 | { 37 | throw new ArgumentException($"{nameof(inputInfo)} must be of type {typeof(StreamedSequenceInfo)}"); 38 | } 39 | 40 | return GetOutputDataInfo(streamedSequenceInfo); 41 | } 42 | 43 | protected AsyncStreamedValueInfo GetOutputDataInfo(StreamedSequenceInfo streamedSequenceInfo) 44 | { 45 | if (streamedSequenceInfo == null) 46 | { 47 | throw new ArgumentNullException(nameof(streamedSequenceInfo)); 48 | } 49 | 50 | return new AsyncStreamedScalarValueInfo(typeof(Task)); 51 | } 52 | 53 | /// 54 | public override void TransformExpressions(Func transformation) 55 | { 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Operators/ExplainAsyncResultOperator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Threading.Tasks; 4 | using Couchbase.Linq.Execution.StreamedData; 5 | using Remotion.Linq.Clauses; 6 | using Remotion.Linq.Clauses.StreamedData; 7 | 8 | namespace Couchbase.Linq.Operators 9 | { 10 | /// 11 | /// Result operator for ExplainAsync. 12 | /// 13 | internal class ExplainAsyncResultOperator : AsyncValueFromSequenceResultOperatorBase 14 | { 15 | /// 16 | public override ResultOperatorBase Clone(CloneContext cloneContext) => 17 | new ExplainAsyncResultOperator(); 18 | 19 | /// 20 | public override AsyncStreamedValue ExecuteInMemory(StreamedSequence input) => 21 | throw new NotImplementedException("Cannot explain N1QL queries in memory"); 22 | 23 | /// 24 | public override IStreamedDataInfo GetOutputDataInfo(IStreamedDataInfo inputInfo) 25 | { 26 | if (inputInfo == null) 27 | { 28 | throw new ArgumentNullException(nameof(inputInfo)); 29 | } 30 | if (!(inputInfo is StreamedSequenceInfo streamedSequenceInfo)) 31 | { 32 | throw new ArgumentException($"{nameof(inputInfo)} must be of type {typeof(StreamedSequenceInfo)}"); 33 | } 34 | 35 | return GetOutputDataInfo(streamedSequenceInfo); 36 | } 37 | 38 | private AsyncStreamedValueInfo GetOutputDataInfo(StreamedSequenceInfo streamedSequenceInfo) 39 | { 40 | if (streamedSequenceInfo == null) 41 | { 42 | throw new ArgumentNullException(nameof(streamedSequenceInfo)); 43 | } 44 | 45 | return new AsyncStreamedScalarValueInfo(typeof(Task)); 46 | } 47 | 48 | /// 49 | public override void TransformExpressions(Func transformation) 50 | { 51 | } 52 | 53 | /// 54 | public override string ToString() 55 | { 56 | return "ExplainAsync()"; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Operators/ExplainResultOperator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using Remotion.Linq.Clauses; 4 | using Remotion.Linq.Clauses.ResultOperators; 5 | using Remotion.Linq.Clauses.StreamedData; 6 | 7 | namespace Couchbase.Linq.Operators 8 | { 9 | internal class ExplainResultOperator : ValueFromSequenceResultOperatorBase 10 | { 11 | public override StreamedValue ExecuteInMemory(StreamedSequence input) 12 | { 13 | throw new NotImplementedException("Cannot explain N1QL queries in memory"); 14 | } 15 | 16 | public override ResultOperatorBase Clone(CloneContext cloneContext) 17 | { 18 | return new ExplainResultOperator(); 19 | } 20 | 21 | public override IStreamedDataInfo GetOutputDataInfo(IStreamedDataInfo inputInfo) 22 | { 23 | if (inputInfo == null) 24 | { 25 | throw new ArgumentNullException("inputInfo"); 26 | } 27 | 28 | var sequenceInfo = inputInfo as StreamedSequenceInfo; 29 | if (sequenceInfo == null) 30 | { 31 | throw new ArgumentException(string.Format("Parameter 'inputInfo' has unexpected type '{0}'.", inputInfo.GetType())); 32 | } 33 | 34 | return GetOutputDataInfo(sequenceInfo); 35 | } 36 | 37 | private StreamedValueInfo GetOutputDataInfo(StreamedSequenceInfo sequenceInfo) 38 | { 39 | return new StreamedScalarValueInfo(typeof(object)); 40 | } 41 | 42 | public override void TransformExpressions(Func transformation) 43 | { 44 | //no parameters so just ignore this 45 | //throw new NotImplementedException(); 46 | } 47 | 48 | public override string ToString() 49 | { 50 | return "Explain()"; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Operators/FirstAsyncResultOperator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Threading.Tasks; 5 | using Couchbase.Linq.Execution.StreamedData; 6 | using Remotion.Linq.Clauses; 7 | using Remotion.Linq.Clauses.StreamedData; 8 | 9 | namespace Couchbase.Linq.Operators 10 | { 11 | /// 12 | /// Result operator for FirstAsync and FirstOrDefaultAsync. 13 | /// 14 | internal class FirstAsyncResultOperator : ChoiceAsyncResultOperatorBase 15 | { 16 | public FirstAsyncResultOperator (bool returnDefaultWhenEmpty) 17 | : base (returnDefaultWhenEmpty) 18 | { 19 | } 20 | 21 | public override ResultOperatorBase Clone(CloneContext cloneContext) => 22 | new FirstAsyncResultOperator(ReturnDefaultWhenEmpty); 23 | 24 | public override AsyncStreamedValue? ExecuteInMemory(StreamedSequence input) 25 | { 26 | var sequence = input.GetTypedSequence(); 27 | T? result = ReturnDefaultWhenEmpty ? sequence.FirstOrDefault() : sequence.First(); 28 | return new AsyncStreamedValue (Task.FromResult(result), GetOutputDataInfo (input.DataInfo)); 29 | } 30 | 31 | /// 32 | public override void TransformExpressions(Func transformation) 33 | { 34 | } 35 | 36 | public override string ToString() => 37 | ReturnDefaultWhenEmpty 38 | ? "FirstOrDefaultAsync()" 39 | : "FirstAsync()"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Operators/LongCountAsyncResultOperator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Threading.Tasks; 5 | using Couchbase.Linq.Execution.StreamedData; 6 | using Remotion.Linq.Clauses; 7 | using Remotion.Linq.Clauses.StreamedData; 8 | 9 | namespace Couchbase.Linq.Operators 10 | { 11 | /// 12 | /// Result operator for LongCountAsync. 13 | /// 14 | internal class LongCountAsyncResultOperator : AsyncValueFromSequenceResultOperatorBase 15 | { 16 | /// 17 | public override ResultOperatorBase Clone(CloneContext cloneContext) => 18 | new LongCountAsyncResultOperator(); 19 | 20 | /// 21 | public override AsyncStreamedValue ExecuteInMemory(StreamedSequence input) 22 | { 23 | var sequence = input.GetTypedSequence(); 24 | var result = sequence.LongCount(); 25 | return new AsyncStreamedValue(Task.FromResult(result), GetOutputDataInfo(input.DataInfo)); 26 | } 27 | 28 | /// 29 | public override IStreamedDataInfo GetOutputDataInfo(IStreamedDataInfo inputInfo) 30 | { 31 | if (inputInfo == null) 32 | { 33 | throw new ArgumentNullException(nameof(inputInfo)); 34 | } 35 | if (!(inputInfo is StreamedSequenceInfo streamedSequenceInfo)) 36 | { 37 | throw new ArgumentException($"{nameof(inputInfo)} must be of type {typeof(StreamedSequenceInfo)}"); 38 | } 39 | 40 | return GetOutputDataInfo(streamedSequenceInfo); 41 | } 42 | 43 | protected AsyncStreamedValueInfo GetOutputDataInfo(StreamedSequenceInfo streamedSequenceInfo) 44 | { 45 | if (streamedSequenceInfo == null) 46 | { 47 | throw new ArgumentNullException(nameof(streamedSequenceInfo)); 48 | } 49 | 50 | return new AsyncStreamedScalarValueInfo(typeof(Task)); 51 | } 52 | 53 | /// 54 | public override void TransformExpressions(Func transformation) 55 | { 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Operators/SingleAsyncResultOperator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Threading.Tasks; 5 | using Couchbase.Linq.Execution.StreamedData; 6 | using Remotion.Linq.Clauses; 7 | using Remotion.Linq.Clauses.StreamedData; 8 | 9 | namespace Couchbase.Linq.Operators 10 | { 11 | /// 12 | /// Result operator for SingleAsync and SingleOrDefaultAsync. 13 | /// 14 | internal class SingleAsyncResultOperator : ChoiceAsyncResultOperatorBase 15 | { 16 | public SingleAsyncResultOperator (bool returnDefaultWhenEmpty) 17 | : base (returnDefaultWhenEmpty) 18 | { 19 | } 20 | 21 | /// 22 | public override ResultOperatorBase Clone(CloneContext cloneContext) => 23 | new SingleAsyncResultOperator(ReturnDefaultWhenEmpty); 24 | 25 | /// 26 | public override AsyncStreamedValue? ExecuteInMemory(StreamedSequence input) 27 | { 28 | var sequence = input.GetTypedSequence(); 29 | T? result = ReturnDefaultWhenEmpty ? sequence.SingleOrDefault() : sequence.Single(); 30 | return new AsyncStreamedValue (Task.FromResult(result), GetOutputDataInfo (input.DataInfo)); 31 | } 32 | 33 | /// 34 | public override void TransformExpressions(Func transformation) 35 | { 36 | } 37 | 38 | /// 39 | public override string ToString() => 40 | ReturnDefaultWhenEmpty 41 | ? "SingleOrDefaultAsync()" 42 | : "SingleAsync()"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Runtime.InteropServices; 3 | 4 | // Setting ComVisible to false makes the types in this assembly not visible 5 | // to COM components. If you need to access a type in this assembly from 6 | // COM, set the ComVisible attribute to true on that type. 7 | 8 | [assembly: ComVisible(false)] 9 | 10 | // The following GUID is for the ID of the typelib if this project is exposed to COM 11 | 12 | [assembly: Guid("f9de7771-ec88-4eae-a039-2bdcfa8f7c3b")] 13 | 14 | #if DEBUG 15 | // For writing tests against internal classes 16 | [assembly: InternalsVisibleTo("Couchbase.Linq.UnitTests")] 17 | [assembly: InternalsVisibleTo("Couchbase.Linq.IntegrationTests")] 18 | 19 | // For using Moq against internal classes 20 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 21 | #endif -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/EnhancedPartialEvaluatingExpressionTreeProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Couchbase.Linq.Utils; 3 | using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation; 4 | using Remotion.Linq.Parsing.Structure; 5 | 6 | namespace Couchbase.Linq.QueryGeneration 7 | { 8 | internal sealed class EnhancedPartialEvaluatingExpressionTreeProcessor : IExpressionTreeProcessor 9 | { 10 | public IEvaluatableExpressionFilter Filter { get; } 11 | 12 | public EnhancedPartialEvaluatingExpressionTreeProcessor(IEvaluatableExpressionFilter filter) 13 | { 14 | ThrowHelpers.ThrowIfNull(filter); 15 | 16 | Filter = filter; 17 | } 18 | 19 | public Expression? Process(Expression expressionTree) 20 | { 21 | ThrowHelpers.ThrowIfNull(expressionTree); 22 | 23 | return EnhancedPartialEvaluatingExpressionVisitor.EvaluateIndependentSubtrees(expressionTree, Filter); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/FromParts/AnsiJoinPart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Remotion.Linq.Clauses; 4 | 5 | #nullable disable 6 | 7 | namespace Couchbase.Linq.QueryGeneration.FromParts 8 | { 9 | /// 10 | /// Represents the FROM part of a query with a full ANSI JOIN clause. 11 | /// 12 | internal class AnsiJoinPart : JoinPart 13 | { 14 | /// 15 | /// Outer key selector. 16 | /// 17 | public string OuterKey { get; set; } 18 | 19 | /// 20 | /// Inner key selector. 21 | /// 22 | public string InnerKey { get; set; } 23 | 24 | /// 25 | /// Join operator, defaults to "=" 26 | /// 27 | public string Operator { get; set; } = "="; 28 | 29 | /// 30 | /// Additional predicates to apply to the inner sequence. 31 | /// 32 | public string AdditionalInnerPredicates { get; set; } 33 | 34 | public AnsiJoinPart(IQuerySource querySource) : base(querySource) 35 | { 36 | } 37 | 38 | public override void AppendToStringBuilder(StringBuilder sb) 39 | { 40 | base.AppendToStringBuilder(sb); 41 | 42 | sb.AppendFormat(" ON ({0} {1} {2})", OuterKey, Operator, InnerKey); 43 | 44 | if (!string.IsNullOrEmpty(AdditionalInnerPredicates)) 45 | { 46 | sb.AppendFormat(" AND {0}", AdditionalInnerPredicates); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/FromParts/FromPart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Remotion.Linq.Clauses; 4 | 5 | namespace Couchbase.Linq.QueryGeneration.FromParts 6 | { 7 | /// 8 | /// Represents the FROM part of a query 9 | /// 10 | internal class FromPart : ExtentPart 11 | { 12 | public FromPart(IQuerySource querySource) : base(querySource) 13 | { 14 | } 15 | 16 | public override void AppendToStringBuilder(StringBuilder sb) 17 | { 18 | sb.Append(" FROM"); 19 | 20 | base.AppendToStringBuilder(sb); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/FromParts/JoinPart.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Remotion.Linq.Clauses; 3 | 4 | #nullable disable 5 | 6 | namespace Couchbase.Linq.QueryGeneration.FromParts 7 | { 8 | /// 9 | /// Represents the FROM part of a query with a JOIN, NEST, or UNNEST clause 10 | /// 11 | internal class JoinPart : ExtentPart 12 | { 13 | /// 14 | /// Type of join to perform 15 | /// 16 | public string JoinType { get; set; } 17 | 18 | public JoinPart(IQuerySource querySource) : base(querySource) 19 | { 20 | } 21 | 22 | public override void AppendToStringBuilder(StringBuilder sb) 23 | { 24 | sb.Append(' '); 25 | sb.Append(JoinType); 26 | 27 | base.AppendToStringBuilder(sb); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/FromParts/JoinTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Couchbase.Linq.QueryGeneration.FromParts 4 | { 5 | internal static class JoinTypes 6 | { 7 | public const string InnerJoin = "INNER JOIN"; 8 | public const string LeftJoin = "LEFT JOIN"; 9 | public const string InnerNest = "INNER NEST"; 10 | public const string LeftNest = "LEFT OUTER NEST"; 11 | public const string InnerUnnest = "INNER UNNEST"; 12 | public const string LeftUnnest = "LEFT OUTER UNNEST"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/IMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration 10 | { 11 | internal interface IMethodCallTranslator 12 | { 13 | IEnumerable SupportMethods { get; } 14 | 15 | Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/IMethodCallTranslatorProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace Couchbase.Linq.QueryGeneration 4 | { 5 | internal interface IMethodCallTranslatorProvider 6 | { 7 | IMethodCallTranslator? GetTranslator(MethodCallExpression methodCallExpression); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/IN1QlExpressionTreeVisitor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System.Text; 3 | 4 | namespace Couchbase.Linq.QueryGeneration 5 | { 6 | /// 7 | /// Expression tree visitor which builds a N1QL expression from the tree. 8 | /// 9 | public interface IN1QlExpressionTreeVisitor 10 | { 11 | /// 12 | /// N1QL query being built. 13 | /// 14 | StringBuilder Expression { get; } 15 | 16 | /// 17 | /// Visits a node in the expression tree and any child nodes, rendering 18 | /// them onto the . 19 | /// 20 | /// Node to visit. 21 | void Visit(Expression expression); 22 | } 23 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/IN1QlQueryModelVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Linq.Clauses; 3 | using Remotion.Linq; 4 | 5 | namespace Couchbase.Linq.QueryGeneration 6 | { 7 | internal interface IN1QlQueryModelVisitor : IQueryModelVisitor 8 | { 9 | void VisitNestClause(NestClause clause, QueryModel queryModel, int index); 10 | 11 | void VisitUseKeysClause(UseKeysClause clause, QueryModel queryModel, int index); 12 | 13 | void VisitHintClause(HintClause clause, QueryModel queryModel, int index); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/LinqQueryOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Linq.Execution; 3 | using Couchbase.Query; 4 | 5 | namespace Couchbase.Linq.QueryGeneration 6 | { 7 | /// 8 | /// Wraps a standard QueryOptions with some additional information used internally. 9 | /// 10 | internal class LinqQueryOptions : QueryOptions 11 | { 12 | private readonly ScalarResultBehavior _scalarResultBehavior; 13 | 14 | /// 15 | /// If true, indicates that the result of the query is wrapped in an object with a single property, named "result". 16 | /// After execution, this property should be extracted from the wrapper object. 17 | /// 18 | public ScalarResultBehavior ScalarResultBehavior 19 | { 20 | get { return _scalarResultBehavior; } 21 | } 22 | 23 | /// 24 | /// For queries returning a single result, true indicates that an empty result set should return the default value. 25 | /// For example, a call to .FirstOrDefault() would set this to true. 26 | /// 27 | public bool ReturnDefaultWhenEmpty { get; set; } 28 | 29 | /// 30 | /// Creates a new LinqQueryRequest, with the given N1QL query. 31 | /// 32 | /// Behaviors related to extracting results for scalar queries. 33 | public LinqQueryOptions(ScalarResultBehavior scalarResultBehavior) 34 | { 35 | _scalarResultBehavior = scalarResultBehavior ?? throw new ArgumentNullException("scalarResultBehavior"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MemberNameResolvers/ExtendedTypeSerializerMemberNameResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reflection; 4 | using Couchbase.Core.IO.Serializers; 5 | 6 | namespace Couchbase.Linq.QueryGeneration.MemberNameResolvers 7 | { 8 | /// 9 | /// Implementation of which uses an 10 | /// to resolve member names. 11 | /// 12 | internal class ExtendedTypeSerializerMemberNameResolver : IMemberNameResolver 13 | { 14 | private readonly IExtendedTypeSerializer _serializer; 15 | 16 | public ExtendedTypeSerializerMemberNameResolver(IExtendedTypeSerializer serializer) 17 | { 18 | _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); 19 | } 20 | 21 | public bool TryResolveMemberName(MemberInfo member, [MaybeNullWhen(false)] out string memberName) 22 | { 23 | memberName = null; 24 | 25 | if (member == null) 26 | return false; 27 | 28 | memberName = _serializer.GetMemberName(member); 29 | 30 | return memberName != null; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MemberNameResolvers/IMemberNameResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Reflection; 3 | 4 | namespace Couchbase.Linq.QueryGeneration.MemberNameResolvers 5 | { 6 | internal interface IMemberNameResolver 7 | { 8 | bool TryResolveMemberName(MemberInfo member, [MaybeNullWhen(false)] out string memberName); 9 | } 10 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MemberNameResolvers/JsonNetMemberNameResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Newtonsoft.Json.Serialization; 6 | 7 | namespace Couchbase.Linq.QueryGeneration.MemberNameResolvers 8 | { 9 | /// 10 | /// Implementation of which uses a Newtonsoft.Json 11 | /// to resolve member names. 12 | /// 13 | /// 14 | /// Used for backwards compatibility with older implementations of 15 | /// which don't have a GetMemberName implementation like . 16 | /// 17 | internal class JsonNetMemberNameResolver : IMemberNameResolver 18 | { 19 | private readonly IContractResolver _contractResolver; 20 | 21 | public JsonNetMemberNameResolver(IContractResolver contractResolver) 22 | { 23 | _contractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver)); 24 | } 25 | 26 | public bool TryResolveMemberName(MemberInfo member, [MaybeNullWhen(false)] out string memberName) 27 | { 28 | memberName = null; 29 | 30 | if (member == null) 31 | return false; 32 | 33 | var contract = _contractResolver.ResolveContract(member.DeclaringType!); 34 | 35 | if (contract.GetType() == typeof (JsonObjectContract) && 36 | ((JsonObjectContract) contract).Properties.Any(p => p.UnderlyingName == member.Name && !p.Ignored)) 37 | { 38 | memberName = 39 | ((JsonObjectContract) contract).Properties.First(p => p.UnderlyingName == member.Name).PropertyName!; 40 | return true; 41 | } 42 | 43 | return false; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/DateMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators; 7 | 8 | internal class DateMethodCallTranslator : IMethodCallTranslator 9 | { 10 | private static readonly MethodInfo[] SupportedMethodsStatic = 11 | { 12 | typeof (DateTime).GetMethod("get_Date")!, 13 | typeof (DateTimeOffset).GetMethod("get_Date")! 14 | }; 15 | 16 | public IEnumerable SupportMethods 17 | { 18 | get 19 | { 20 | return SupportedMethodsStatic; 21 | } 22 | } 23 | 24 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 25 | { 26 | if (methodCallExpression == null) 27 | { 28 | throw new ArgumentNullException("methodCallExpression"); 29 | } 30 | 31 | var expression = expressionTreeVisitor.Expression; 32 | 33 | expression.Append("DATE_TRUNC_STR("); 34 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 35 | expression.Append(@",""day"")"); 36 | 37 | return methodCallExpression; 38 | } 39 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/DictionaryContainsKeyMethodCallTranslator .cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 7 | { 8 | internal class DictionaryContainsKeyMethodCallTranslator : IMethodCallTranslator 9 | { 10 | private static readonly MethodInfo[] SupportedMethodsStatic = { 11 | typeof (IDictionary).GetMethod("Contains")!, 12 | typeof (IDictionary<,>).GetMethod("ContainsKey")! 13 | }; 14 | 15 | public IEnumerable SupportMethods => SupportedMethodsStatic; 16 | 17 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 18 | { 19 | if (methodCallExpression == null) 20 | { 21 | throw new ArgumentNullException(nameof(methodCallExpression)); 22 | } 23 | 24 | if (methodCallExpression.Arguments[0] is ConstantExpression keyExpression) 25 | { 26 | var expression = expressionTreeVisitor.Expression; 27 | 28 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 29 | expression.Append('.'); 30 | expression.Append(N1QlHelpers.EscapeIdentifier(keyExpression.Value!.ToString()!)); 31 | expression.Append(" IS NOT MISSING"); 32 | } 33 | else 34 | { 35 | throw new NotSupportedException("Dictionary keys must be constants"); 36 | } 37 | 38 | return methodCallExpression; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/DictionaryItemMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 7 | { 8 | internal class DictionaryItemMethodCallTranslator : IMethodCallTranslator 9 | { 10 | private static readonly MethodInfo[] SupportedMethodsStatic = { 11 | typeof (IDictionary).GetMethod("get_Item")!, 12 | typeof (IDictionary<,>).GetMethod("get_Item")! 13 | }; 14 | 15 | public IEnumerable SupportMethods => SupportedMethodsStatic; 16 | 17 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 18 | { 19 | if (methodCallExpression == null) 20 | { 21 | throw new ArgumentNullException("methodCallExpression"); 22 | } 23 | 24 | if (methodCallExpression.Arguments[0] is ConstantExpression keyExpression) 25 | { 26 | var expression = expressionTreeVisitor.Expression; 27 | 28 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 29 | expression.Append('.'); 30 | expression.Append(N1QlHelpers.EscapeIdentifier(keyExpression.Value!.ToString()!)); 31 | } 32 | else 33 | { 34 | throw new NotSupportedException("Dictionary keys must be constants"); 35 | } 36 | 37 | return methodCallExpression; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/DictionaryKeysMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 7 | { 8 | internal class DictionaryKeysMethodCallTranslator : IMethodCallTranslator 9 | { 10 | private static readonly MethodInfo[] SupportedMethodsStatic = { 11 | typeof (IDictionary).GetMethod("get_Keys")!, 12 | typeof (IDictionary<,>).GetMethod("get_Keys")!, 13 | typeof (Dictionary<,>).GetMethod("get_Keys")! 14 | }; 15 | 16 | public IEnumerable SupportMethods => SupportedMethodsStatic; 17 | 18 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 19 | { 20 | if (methodCallExpression == null) 21 | { 22 | throw new ArgumentNullException(nameof(methodCallExpression)); 23 | } 24 | 25 | var expression = expressionTreeVisitor.Expression; 26 | 27 | expression.Append("OBJECT_NAMES("); 28 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 29 | expression.Append(')'); 30 | 31 | return methodCallExpression; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/DictionaryValuesMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 7 | { 8 | internal class DictionaryValuesMethodCallTranslator : IMethodCallTranslator 9 | { 10 | private static readonly MethodInfo[] SupportedMethodsStatic = { 11 | typeof (IDictionary).GetMethod("get_Values")!, 12 | typeof (IDictionary<,>).GetMethod("get_Values")!, 13 | typeof (Dictionary<,>).GetMethod("get_Values")! 14 | }; 15 | 16 | public IEnumerable SupportMethods => SupportedMethodsStatic; 17 | 18 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 19 | { 20 | if (methodCallExpression == null) 21 | { 22 | throw new ArgumentNullException(nameof(methodCallExpression)); 23 | } 24 | 25 | var expression = expressionTreeVisitor.Expression; 26 | 27 | expression.Append("OBJECT_VALUES("); 28 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 29 | expression.Append(')'); 30 | 31 | return methodCallExpression; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/IsMissingMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class IsMissingMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | typeof (N1QlFunctions).GetMethods().Where(p => (p.Name == "IsMissing") || (p.Name == "IsNotMissing")).ToArray(); 15 | 16 | public IEnumerable SupportMethods 17 | { 18 | get 19 | { 20 | return SupportedMethodsStatic; 21 | } 22 | } 23 | 24 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 25 | { 26 | if (methodCallExpression == null) 27 | { 28 | throw new ArgumentNullException("methodCallExpression"); 29 | } 30 | 31 | var expression = expressionTreeVisitor.Expression; 32 | 33 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 34 | 35 | if (methodCallExpression.Arguments.Count > 1) 36 | { 37 | var constantExpression = methodCallExpression.Arguments[1] as ConstantExpression; 38 | if (constantExpression == null) 39 | { 40 | throw new NotSupportedException("IsMissing and IsNotMissing propertyName parameter must be a constant"); 41 | } 42 | 43 | expression.AppendFormat(".{0}", 44 | N1QlHelpers.EscapeIdentifier(constantExpression.Value!.ToString()!)); 45 | } 46 | 47 | expression.Append(methodCallExpression.Method.Name == "IsMissing" ? " IS MISSING" : " IS NOT MISSING"); 48 | 49 | return methodCallExpression; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/IsValuedMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class IsValuedMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | typeof (N1QlFunctions).GetMethods().Where(p => (p.Name == "IsValued") || (p.Name == "IsNotValued")).ToArray(); 15 | 16 | public IEnumerable SupportMethods 17 | { 18 | get 19 | { 20 | return SupportedMethodsStatic; 21 | } 22 | } 23 | 24 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 25 | { 26 | if (methodCallExpression == null) 27 | { 28 | throw new ArgumentNullException("methodCallExpression"); 29 | } 30 | 31 | var expression = expressionTreeVisitor.Expression; 32 | 33 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 34 | 35 | if (methodCallExpression.Arguments.Count > 1) 36 | { 37 | var constantExpression = methodCallExpression.Arguments[1] as ConstantExpression; 38 | if (constantExpression == null) 39 | { 40 | throw new NotSupportedException("IsValued and IsNotValued propertyName parameter must be a constant"); 41 | } 42 | 43 | expression.AppendFormat(".{0}", 44 | N1QlHelpers.EscapeIdentifier(constantExpression.Value!.ToString()!)); 45 | } 46 | 47 | expression.Append(methodCallExpression.Method.Name == "IsValued" ? " IS VALUED" : " IS NOT VALUED"); 48 | 49 | return methodCallExpression; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/KeyMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class KeyMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = { 14 | typeof (N1QlFunctions).GetMethod("Key")! 15 | }; 16 | 17 | public IEnumerable SupportMethods 18 | { 19 | get 20 | { 21 | return SupportedMethodsStatic; 22 | } 23 | } 24 | 25 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 26 | { 27 | if (methodCallExpression == null) 28 | { 29 | throw new ArgumentNullException("methodCallExpression"); 30 | } 31 | if (expressionTreeVisitor == null) 32 | { 33 | throw new ArgumentNullException("expressionTreeVisitor"); 34 | } 35 | 36 | var expression = expressionTreeVisitor.Expression; 37 | 38 | expression.Append("META("); 39 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 40 | expression.Append(").id"); 41 | 42 | return methodCallExpression; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/ListIndexMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 11 | { 12 | internal class ListIndexMethodCallTranslator : IMethodCallTranslator 13 | { 14 | private static readonly MethodInfo[] SupportedMethodsStatic = { 15 | typeof (IList).GetMethod("get_Item")!, 16 | typeof (IList<>).GetMethod("get_Item")! 17 | }; 18 | 19 | public IEnumerable SupportMethods 20 | { 21 | get 22 | { 23 | return SupportedMethodsStatic; 24 | } 25 | } 26 | 27 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 28 | { 29 | if (methodCallExpression == null) 30 | { 31 | throw new ArgumentNullException("methodCallExpression"); 32 | } 33 | 34 | var expression = expressionTreeVisitor.Expression; 35 | 36 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 37 | expression.Append('['); 38 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 39 | expression.Append(']'); 40 | 41 | return methodCallExpression; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/MetaMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class MetaMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = { 14 | typeof (N1QlFunctions).GetMethod("Meta")! 15 | }; 16 | 17 | public IEnumerable SupportMethods 18 | { 19 | get 20 | { 21 | return SupportedMethodsStatic; 22 | } 23 | } 24 | 25 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 26 | { 27 | if (methodCallExpression == null) 28 | { 29 | throw new ArgumentNullException("methodCallExpression"); 30 | } 31 | 32 | var expression = expressionTreeVisitor.Expression; 33 | 34 | expression.Append("META("); 35 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 36 | expression.Append(')'); 37 | 38 | return methodCallExpression; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/SerializationConverterMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Couchbase.Linq.Serialization; 6 | 7 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 8 | { 9 | internal class SerializationConverterMethodCallTranslator : IMethodCallTranslator 10 | { 11 | private static readonly MethodInfo[] SupportedMethodsStatic = 12 | { 13 | typeof (ISerializationConverter<>).GetMethod("ConvertTo")!, 14 | typeof (ISerializationConverter<>).GetMethod("ConvertFrom")! 15 | }; 16 | 17 | public IEnumerable SupportMethods => SupportedMethodsStatic; 18 | 19 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 20 | { 21 | if (methodCallExpression == null) 22 | { 23 | throw new ArgumentNullException(nameof(methodCallExpression)); 24 | } 25 | if (expressionTreeVisitor == null) 26 | { 27 | throw new ArgumentNullException(nameof(expressionTreeVisitor)); 28 | } 29 | 30 | if (methodCallExpression.Object is ConstantExpression constantExpression 31 | && constantExpression.Value is ISerializationConverter conversion) 32 | { 33 | if (methodCallExpression.Method.Name == "ConvertTo") 34 | { 35 | conversion.RenderConvertTo(methodCallExpression.Arguments[0], expressionTreeVisitor); 36 | } 37 | else 38 | { 39 | conversion.RenderConvertFrom(methodCallExpression.Arguments[0], expressionTreeVisitor); 40 | } 41 | } 42 | else 43 | { 44 | throw new InvalidOperationException( 45 | "Invalid attempt to process ISerializationConverter method call."); 46 | } 47 | 48 | return methodCallExpression; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/StringCapitalizationMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class StringCapitalizationMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | { 15 | typeof (string).GetMethod("ToUpper", Type.EmptyTypes)!, 16 | typeof (string).GetMethod("ToLower", Type.EmptyTypes)! 17 | }; 18 | 19 | public IEnumerable SupportMethods 20 | { 21 | get 22 | { 23 | return SupportedMethodsStatic; 24 | } 25 | } 26 | 27 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 28 | { 29 | if (methodCallExpression == null) 30 | { 31 | throw new ArgumentNullException("methodCallExpression"); 32 | } 33 | 34 | var expression = expressionTreeVisitor.Expression; 35 | 36 | expression.Append(methodCallExpression.Method.Name == "ToLower" ? "LOWER(" : "UPPER("); 37 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 38 | expression.Append(")"); 39 | 40 | return methodCallExpression; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/StringIndexOfMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class StringIndexOfMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | { 15 | typeof (string).GetMethod("IndexOf", new[] { typeof (char) })!, 16 | typeof (string).GetMethod("IndexOf", new[] { typeof (string) })! 17 | }; 18 | 19 | public IEnumerable SupportMethods 20 | { 21 | get 22 | { 23 | return SupportedMethodsStatic; 24 | } 25 | } 26 | 27 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 28 | { 29 | if (methodCallExpression == null) 30 | { 31 | throw new ArgumentNullException("methodCallExpression"); 32 | } 33 | 34 | var expression = expressionTreeVisitor.Expression; 35 | 36 | expression.Append("POSITION("); 37 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 38 | expression.Append(", "); 39 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 40 | expression.Append(")"); 41 | 42 | return methodCallExpression; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/StringLengthMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class StringLengthMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | { 15 | typeof (string).GetMethod("get_Length")! 16 | }; 17 | 18 | public IEnumerable SupportMethods 19 | { 20 | get 21 | { 22 | return SupportedMethodsStatic; 23 | } 24 | } 25 | 26 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 27 | { 28 | if (methodCallExpression == null) 29 | { 30 | throw new ArgumentNullException("methodCallExpression"); 31 | } 32 | 33 | var expression = expressionTreeVisitor.Expression; 34 | 35 | expression.Append("LENGTH("); 36 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 37 | expression.Append(")"); 38 | 39 | return methodCallExpression; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/StringReplaceMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class StringReplaceMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | { 15 | typeof (string).GetMethod("Replace", new[] { typeof (char), typeof(char) })!, 16 | typeof (string).GetMethod("Replace", new[] { typeof (string), typeof(string) })! 17 | }; 18 | 19 | public IEnumerable SupportMethods 20 | { 21 | get 22 | { 23 | return SupportedMethodsStatic; 24 | } 25 | } 26 | 27 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 28 | { 29 | if (methodCallExpression == null) 30 | { 31 | throw new ArgumentNullException("methodCallExpression"); 32 | } 33 | 34 | var expression = expressionTreeVisitor.Expression; 35 | 36 | expression.Append("REPLACE("); 37 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 38 | expression.Append(", "); 39 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 40 | expression.Append(", "); 41 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[1]); 42 | expression.Append(")"); 43 | 44 | return methodCallExpression; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/SubqueryMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class SubqueryMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | { 15 | typeof (Enumerable).GetMethod("ToArray")!, 16 | typeof (Enumerable).GetMethod("ToList")!, 17 | typeof (Enumerable).GetMethod("AsEnumerable")! 18 | }; 19 | 20 | public IEnumerable SupportMethods 21 | { 22 | get 23 | { 24 | return SupportedMethodsStatic; 25 | } 26 | } 27 | 28 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 29 | { 30 | if (methodCallExpression == null) 31 | { 32 | throw new ArgumentNullException("methodCallExpression"); 33 | } 34 | 35 | // Do not process ToArray, ToList, or AsEnumerable. Instead just visit the list in the first argument. 36 | 37 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 38 | 39 | return methodCallExpression; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/SubstringMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class SubstringMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | { 15 | typeof (string).GetMethod("Substring", new[] { typeof (int) })!, 16 | typeof (string).GetMethod("Substring", new[] { typeof (int), typeof(int) })!, 17 | typeof (string).GetMethod("get_Chars", new[] { typeof (int) })! 18 | }; 19 | 20 | public IEnumerable SupportMethods 21 | { 22 | get 23 | { 24 | return SupportedMethodsStatic; 25 | } 26 | } 27 | 28 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 29 | { 30 | if (methodCallExpression == null) 31 | { 32 | throw new ArgumentNullException("methodCallExpression"); 33 | } 34 | 35 | var expression = expressionTreeVisitor.Expression; 36 | 37 | expression.Append("SUBSTR("); 38 | expressionTreeVisitor.Visit(methodCallExpression.Object!); 39 | expression.Append(", "); 40 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[0]); 41 | 42 | if (methodCallExpression.Arguments.Count > 1) 43 | { 44 | expression.Append(", "); 45 | expressionTreeVisitor.Visit(methodCallExpression.Arguments[1]); 46 | } 47 | else if (methodCallExpression.Method.Name == "get_Chars") 48 | { 49 | // Called str[i], so return a single character at i 50 | expression.Append(", 1"); 51 | } 52 | 53 | expression.Append(")"); 54 | 55 | return methodCallExpression; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/MethodCallTranslators/ToStringMethodCallTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Couchbase.Linq.QueryGeneration.MethodCallTranslators 10 | { 11 | internal class ToStringMethodCallTranslator : IMethodCallTranslator 12 | { 13 | private static readonly MethodInfo[] SupportedMethodsStatic = 14 | { 15 | typeof (object).GetMethod("ToString")! 16 | }; 17 | 18 | public IEnumerable SupportMethods 19 | { 20 | get 21 | { 22 | return SupportedMethodsStatic; 23 | } 24 | } 25 | 26 | public Expression Translate(MethodCallExpression methodCallExpression, N1QlExpressionTreeVisitor expressionTreeVisitor) 27 | { 28 | if (methodCallExpression == null) 29 | { 30 | throw new ArgumentNullException("methodCallExpression"); 31 | } 32 | if (methodCallExpression.Object == null) 33 | { 34 | throw new InvalidOperationException(); 35 | } 36 | 37 | if (methodCallExpression.Object.Type == typeof (string)) 38 | { 39 | expressionTreeVisitor.Visit(methodCallExpression.Object); 40 | } 41 | else 42 | { 43 | var expression = expressionTreeVisitor.Expression; 44 | 45 | expression.Append("TOSTRING("); 46 | expressionTreeVisitor.Visit(methodCallExpression.Object); 47 | expression.Append(")"); 48 | } 49 | 50 | return methodCallExpression; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/N1QLLetQueryPart.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | namespace Couchbase.Linq.QueryGeneration 4 | { 5 | /// 6 | /// Represents an item in the LET part of a N1QL query 7 | /// 8 | internal class N1QlLetQueryPart 9 | { 10 | /// 11 | /// Name of the value being assigned 12 | /// 13 | public string ItemName { get; set; } 14 | 15 | /// 16 | /// Expression assigned to the item name 17 | /// 18 | public string Value { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/N1QLQueryType.cs: -------------------------------------------------------------------------------- 1 | namespace Couchbase.Linq.QueryGeneration 2 | { 3 | /// 4 | /// Represents the type of query or subquery being generated 5 | /// 6 | internal enum N1QlQueryType 7 | { 8 | /// 9 | /// Main SELECT statement 10 | /// 11 | Select = 0, 12 | 13 | /// 14 | /// Subquery against a bucket 15 | /// 16 | Subquery, 17 | 18 | /// 19 | /// Subquery against an array using the ARRAY keyword 20 | /// 21 | Array, 22 | 23 | /// 24 | /// Any operation performed on a nested array as a subquery 25 | /// 26 | ArrayAny, 27 | 28 | /// 29 | /// Any operation performed on a Couchbase bucket as the main query 30 | /// 31 | MainQueryAny, 32 | 33 | /// 34 | /// Any operation performed on a Couchbase bucket as a subquery 35 | /// 36 | SubqueryAny, 37 | 38 | /// 39 | /// All operation performed on a nested array as a subquery 40 | /// 41 | ArrayAll, 42 | 43 | /// 44 | /// All operation performed on a Couchbase bucket as the main query 45 | /// 46 | MainQueryAll, 47 | 48 | /// 49 | /// All operation performed on a Couchbase bucket as a subquery 50 | /// 51 | SubqueryAll, 52 | 53 | /// 54 | /// Represents a simple aggregate against a group. Query returned will be the aggregate function call only. 55 | /// 56 | Aggregate 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/NamedParameter.cs: -------------------------------------------------------------------------------- 1 | namespace Couchbase.Linq.QueryGeneration 2 | { 3 | internal sealed class NamedParameter 4 | { 5 | public string? Name { get; set; } 6 | public object? Value { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/QueryGeneration/ParameterAggregator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Couchbase.Linq.QueryGeneration 4 | { 5 | internal sealed class ParameterAggregator 6 | { 7 | private readonly List _parameters = new List(); 8 | 9 | public NamedParameter AddNamedParameter(object value) 10 | { 11 | var parameter = new NamedParameter 12 | { 13 | Value = value, 14 | Name = string.Concat("p", _parameters.Count + 1) 15 | }; 16 | _parameters.Add(parameter); 17 | return parameter; 18 | } 19 | 20 | public NamedParameter[] GetNamedParameters() 21 | { 22 | return _parameters.ToArray(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Serialization/IJsonNetSerializationConverterRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Newtonsoft.Json; 4 | 5 | namespace Couchbase.Linq.Serialization 6 | { 7 | /// 8 | /// Registry of implementations based upon s. 9 | /// 10 | public interface IJsonNetSerializationConverterRegistry 11 | { 12 | /// 13 | /// Creates an instance of an implementation for 14 | /// a specific . May return null. 15 | /// 16 | /// to acquire. 17 | /// Member the converter is applied to. 18 | /// A new , or null if no converter is found. 19 | ISerializationConverter? CreateSerializationConverter(JsonConverter jsonConverter, MemberInfo member); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Serialization/ISerializationConverterProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Couchbase.Core.IO.Serializers; 3 | 4 | namespace Couchbase.Linq.Serialization 5 | { 6 | /// 7 | /// Extension applied to implementations to provide 8 | /// information about how members are serialized. 9 | /// 10 | public interface ISerializationConverterProvider 11 | { 12 | /// 13 | /// Provides information about how a member is converted when it is serialized. 14 | /// 15 | /// Member being serialized or deserialized. 16 | /// A which can be used to affect N1QL query generation, or null if none. 17 | /// 18 | /// Should implement an internal cache for performance, as this method will be 19 | /// called repeatedly for the same member. 20 | /// 21 | ISerializationConverter? GetSerializationConverter(MemberInfo member); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Serialization/ISerializationConverterT.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Couchbase.Linq.Serialization 4 | { 5 | /// 6 | public interface ISerializationConverter : ISerializationConverter 7 | { 8 | /// 9 | /// Noop method call, used as a placeholder for a conversion replicating the 10 | /// one being performed during serialization. 11 | /// 12 | /// The value to be converted. 13 | /// The value which was converted. 14 | T ConvertTo(T value); 15 | 16 | /// 17 | /// Noop method call, used as a placeholder replicating the inverse of the conversion 18 | /// being performed during serialization. 19 | /// 20 | /// The value to be converted. 21 | /// The value which was converted. 22 | T ConvertFrom(T value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Serialization/SerializationExpressionTreeProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using Couchbase.Linq.Utils; 4 | using Remotion.Linq.Parsing.Structure; 5 | 6 | namespace Couchbase.Linq.Serialization 7 | { 8 | /// 9 | /// Process the tree to find members which have non-default serialization rules, 10 | /// and where present apply conversion expressions. Internally uses 11 | /// . 12 | /// 13 | internal class SerializationExpressionTreeProcessor : IExpressionTreeProcessor 14 | { 15 | private readonly ISerializationConverterProvider _serializationConverterProvider; 16 | 17 | public SerializationExpressionTreeProcessor(ISerializationConverterProvider serializationConverterProvider) 18 | { 19 | _serializationConverterProvider = serializationConverterProvider ?? 20 | throw new ArgumentNullException(nameof(serializationConverterProvider)); 21 | } 22 | 23 | /// 24 | /// Creates a from a . 25 | /// 26 | /// The . 27 | /// The . 28 | public static SerializationExpressionTreeProcessor FromCluster(ICluster cluster) => 29 | new SerializationExpressionTreeProcessor( 30 | cluster.ClusterServices.GetRequiredService()); 31 | 32 | /// 33 | public Expression Process(Expression expressionTree) 34 | { 35 | if (expressionTree == null) 36 | { 37 | throw new ArgumentNullException(nameof(expressionTree)); 38 | } 39 | 40 | return new SerializationExpressionTreeVisitor(_serializationConverterProvider).Visit(expressionTree)!; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Utils/ExceptionMsgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Couchbase.Linq.Utils 8 | { 9 | internal class ExceptionMsgs 10 | { 11 | public const string DocumentNotFound = "Document not found for Id: "; 12 | 13 | public const string KeyAttributeMissing = "No KeyAttribute could be found for the document. Please " + 14 | "mark a key property with a KeyAttribute"; 15 | 16 | public const string KeyNull = "KeyAttribute marks a property which returned null. Please " + 17 | "be sure the key property is non-null."; 18 | 19 | public const string QueryExecutionException = "An error occurred executing the N1QL query. See the " + 20 | "inner exception for details."; 21 | 22 | public const string QueryExecutionUnknownError = "An unknown error occurred executing the N1QL query."; 23 | 24 | public const string QueryExecutionMultipleErrors = "Multiple errors occured executing the N1QL query. " + 25 | "See the Errors property for details."; 26 | } 27 | } 28 | #region [ License information ] 29 | 30 | /* ************************************************************ 31 | * 32 | * @author Couchbase 33 | * @copyright 2015 Couchbase, Inc. 34 | * 35 | * Licensed under the Apache License, Version 2.0 (the "License"); 36 | * you may not use this file except in compliance with the License. 37 | * You may obtain a copy of the License at 38 | * 39 | * http://www.apache.org/licenses/LICENSE-2.0 40 | * 41 | * Unless required by applicable law or agreed to in writing, software 42 | * distributed under the License is distributed on an "AS IS" BASIS, 43 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 44 | * See the License for the specific language governing permissions and 45 | * limitations under the License. 46 | * 47 | * ************************************************************/ 48 | 49 | #endregion 50 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Utils/ExcludeSerializationConversionEvaluatableExpressionFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using Couchbase.Linq.Serialization; 5 | using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation; 6 | 7 | namespace Couchbase.Linq.Utils 8 | { 9 | /// 10 | /// Implementation of which prevents 11 | /// pre-evaluation of calls to methods. 12 | /// 13 | internal sealed class ExcludeSerializationConversionEvaluatableExpressionFilter : EvaluatableExpressionFilterBase 14 | { 15 | public override bool IsEvaluatableMethodCall(MethodCallExpression node) 16 | { 17 | if (node.Method.DeclaringType != null) 18 | { 19 | var typeInfo = node.Method.DeclaringType.GetTypeInfo(); 20 | if (typeInfo.IsInterface && typeInfo.IsGenericType) 21 | { 22 | return typeInfo.GetGenericTypeDefinition() != typeof(ISerializationConverter<>); 23 | } 24 | } 25 | 26 | return true; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Utils/ServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace Couchbase.Linq.Utils 5 | { 6 | internal static class ServiceProviderExtensions 7 | { 8 | public static T? GetService(this IServiceProvider serviceProvider) => 9 | (T?) serviceProvider.GetService(typeof(T)); 10 | 11 | [return: NotNull] 12 | public static T GetRequiredService(this IServiceProvider serviceProvider) => 13 | serviceProvider.GetService() ?? 14 | throw new InvalidOperationException($"Service {typeof(T).FullName} not registered."); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Utils/ThrowHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Couchbase.Linq.Utils 6 | { 7 | internal static class ThrowHelpers 8 | { 9 | public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) 10 | { 11 | #if NET6_0_OR_GREATER 12 | ArgumentNullException.ThrowIfNull(argument, paramName); 13 | #else 14 | if (argument is null) 15 | { 16 | ThrowArgumentNullException(paramName); 17 | } 18 | #endif 19 | } 20 | 21 | [DoesNotReturn] 22 | public static void ThrowArgumentNullException(string? paramName) => 23 | throw new ArgumentNullException(paramName); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/Versioning/FeatureVersions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Couchbase.Core.Version; 3 | 4 | namespace Couchbase.Linq.Versioning 5 | { 6 | /// 7 | /// Constants for the Couchbase versions where new features are implemented. 8 | /// 9 | internal static class FeatureVersions 10 | { 11 | /// 12 | /// Default version we assume if we can't get the cluster version. 13 | /// 14 | public static readonly ClusterVersion DefaultVersion = new ClusterVersion(new Version(5, 5, 0)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Src/Couchbase.Linq/couchbase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbaselabs/Linq2Couchbase/6b63364d429079531b3bedeb5d7adbb100b898fc/Src/Couchbase.Linq/couchbase.png -------------------------------------------------------------------------------- /Src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 4 | 5 | -------------------------------------------------------------------------------- /Src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestFeature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/array-filtering-projections.md: -------------------------------------------------------------------------------- 1 | # Array Filtering, Projections, and Sorting 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | Using array filtering and projections, you can alter the nature of an array located inside a document before it is returned by SQL++. 7 | 8 | ## Filtering 9 | 10 | To filter an array, simply apply a where clause to the array inside the select projection. For .Net type consistency, you should apply a ToArray() or ToList() after the subquery. 11 | 12 | ```cs 13 | var context = new BucketContext(bucket); 14 | 15 | var query = from brewery in context.Query() 16 | select new { 17 | brewery.Name, 18 | longAddresses = (from address in brewery.Address 19 | where address.Length > 30 20 | select address).ToList() 21 | }; 22 | 23 | await foreach (var doc in query.AsAsyncEnumerable()) { 24 | // do work 25 | // query will return the brewery name and a list of addresses more than 30 chars long 26 | } 27 | ``` 28 | 29 | ## Projections 30 | 31 | Arrays may also be projected to alter the contents of the array. To perform a projection, simply include it in the select clause of the subquery. 32 | 33 | ```cs 34 | var context = new BucketContext(bucket); 35 | 36 | var query = from brewery in context.Query() 37 | select new { 38 | brewery.Name, 39 | addresses = (from address in brewery.Address 40 | select address.Substring(1)).ToList() 41 | }; 42 | 43 | await foreach (var doc in query.AsAsyncEnumerable()) { 44 | // do work 45 | // query will return the brewery name and a list of the first character of each address line 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/bucket-context.md: -------------------------------------------------------------------------------- 1 | # The BucketContext 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | The public API for Linq2Couchbase is the BucketContext; this object is similar to the DbContext in Linq2SQL and the DataContext from the EntityFramework. It's primary purpose is to provide and interface for building and submitting queries to a Couchbase server Bucket. Internally, the BucketContext uses a Cluster object and CouchbaseBucket to handle communication and to send queries and updates to the server. 7 | 8 | ## Creating a BucketContext 9 | 10 | The BucketContext has a dependency on ICluster and IBucket objects; in your application you will need to have instantiated and initialized a Cluster object before you can create a BucketContext. 11 | 12 | ```cs 13 | IBucket bucket = await bucketProvider.GetBucketAsync(); 14 | 15 | var context = new BucketContext(bucket); 16 | ``` 17 | 18 | It's important to note that the ICluster and IBucket objects are a long-lived objects, so you will usually want to create a singleton per application and reuse it over the lifespan of the application. A BucketContext is slightly different; it contains no Dispose method and is more ephemeral compared to the Cluster. 19 | 20 | ## Extending BucketContext 21 | 22 | To extend BucketContext into a more powerful tool exposing strongly-typed document sets, inherit from `BucketContext` and add properties that return `IDocumentSet`. These properties will be automatically initialized with an appropriate object for running queries. 23 | 24 | ```cs 25 | public class MyContext : BucketContext 26 | { 27 | // Adding "= null!" is only necessary if nullable reference types is enabled 28 | public IDocumentSet Airlines { get; set; } = null!; 29 | public IDocumentSet Airports { get; set; } = null!; 30 | public IDocumentSet Routes { get; set; } = null!; 31 | 32 | public MyContext(IBucket bucket) : base(bucket) 33 | { 34 | } 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/custom-serializers.md: -------------------------------------------------------------------------------- 1 | # Custom JSON Serializers 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | The Couchbase SDK uses Newtonsoft's Json.Net as its default JSON serializer. If you are using a custom serializer, there are some special requirements which must be met to support Linq2Couchbase. 7 | 8 | Custom serializers are used by creating a class which implements the ITypeSerializer interface, and including it in the SDK configuration. However, to support Linq2Couchbase, instead the serializer should extend IExtendedTypeSerializer. This interface provides a key additional feature. 9 | 10 | The GetMemberName method is used to determine how a particular member property of a POCO will be written as JSON to the document in Couchbase. This is important, because the SQL++ query must reference member names in the way they appear in Couchbase, not the way they appear in your .Net POCOs. 11 | 12 | Here is [an example of how this method was implemented for Newtonsoft's Json.Net](https://github.com/couchbase/couchbase-net-client/blob/03d7957226da6f7c3e05220a21e7ebeeb0519b93/Src/Couchbase/Core/Serialization/DefaultSerializer.cs#L192). 13 | 14 | ## Non-standard conversions 15 | 16 | Some attributes may have additional decorators applied that change how they are serialized. To support this, you should also implement a customer `ISerializationConverterProvider`. This interface can return a custom `ISerializationConverter` for a particular member, altering query generation behavior when this member is used in a SQL++ query. 17 | 18 | ```cs 19 | services.AddCouchbase(options => { 20 | options.AddLinq(linqOptions => linqOptions.WithSerializationConverterProvider(new MyCustomSerializationConverterProvider())); 21 | }); 22 | ``` 23 | 24 | For more details, see [Serialization Converters](./serialization-converters.md). 25 | -------------------------------------------------------------------------------- /docs/enum.md: -------------------------------------------------------------------------------- 1 | # Working With Enumerations 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | For the most part, enumeration properties are supported transparently. LINQ queries can filter based on enumeration values just like other properties: 7 | 8 | ```cs 9 | var context = new BucketContext(bucket); 10 | 11 | var query = from beer in context.Query() 12 | where beer.Style == BeerStyle.Porter 13 | select beer; 14 | 15 | foreach (var doc in query) { 16 | // do work 17 | } 18 | 19 | // Note: this example assumes the Beer document is configured with Style as enumeration property 20 | ``` 21 | 22 | So long as you are using the standard JSON serializer, which serializes the enumeration value as a number, there are no special concerns to be addressed. 23 | 24 | ## Non-Standard JSON Converters 25 | 26 | If a non-standard JSON converter is being used, there are some additional limitations. A common example of this would be the [Json.Net StringEnumConverter](http://www.newtonsoft.com/json/help/html/t_newtonsoft_json_converters_stringenumconverter.htm). This converter serializes the enumeration as a string, rather than as an integer. 27 | 28 | In this case, there are some additional rules that must be followed. 29 | 30 | 1. Only equals (==) and not equals (!=) comparisons are supported. Greater than, less than, etc are not supported. 31 | 2. The custom serializer must be directly applied to the enumeration itself, not to the document or the property. 32 | 33 | For example, this is supported: 34 | 35 | ```cs 36 | [JsonConverter(typeof(StringEnumConverter))] 37 | public enum MyEnum 38 | { 39 | [EnumMember(Value="Value 1")] 40 | Value1, 41 | 42 | [EnumMember(Value="Value 2")] 43 | Value2 44 | } 45 | ``` 46 | 47 | However, this approach is not: 48 | 49 | ```cs 50 | public class MyDocument 51 | { 52 | // This is not supported, because we are changing how this property is serialized, 53 | // rather than changing how the enumeration is serialized 54 | [JsonConverter(typeof(StringEnumConverter))] 55 | public MyEnum EnumValue { get; set; } 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/math-functions.md: -------------------------------------------------------------------------------- 1 | # Math Functions 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | Most math functions from the .Net Math class are supported by SQL++. They may be used anywhere in the query, including where predicates, select projections, and sort ordering. 7 | 8 | ## Supported Math Functions 9 | 10 | - Math.Abs 11 | - Math.Acos 12 | - Math.Atan 13 | - Math.Atan2 14 | - Math.Asin 15 | - Math.Ceiling 16 | - Math.Cos 17 | - Math.Exp 18 | - Math.Floor 19 | - Math.Log 20 | - Math.Log10 21 | - Math.Pow 22 | - Math.Round 23 | - Math.Sign 24 | - Math.Sin 25 | - Math.Sqrt 26 | - Math.Tan 27 | - Math.Trunc 28 | 29 | ## Supported Math Operators 30 | 31 | - Addition (+) 32 | - Subtraction/Negation (-) 33 | - Multiplication (*) 34 | - Division (/) 35 | - Modulus (%) 36 | -------------------------------------------------------------------------------- /docs/poco-mapping.md: -------------------------------------------------------------------------------- 1 | # Mapping JSON fields to POCO properties 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | While not an actually responsibility of the LINQ provider, the default serialization library, Newtonsoft.Json, supports field the following serialization attributes: 7 | 8 | - [DataContractAttribute](https://msdn.microsoft.com/en-us/library/ms585243) 9 | - [DataMemberAttribute](https://msdn.microsoft.com/en-us/library/ms574795) 10 | - [NonSerializedAttribute](msdn2.microsoft.com/en-us/library/z951x24h) 11 | - All Newtonsoft.Json [attributes](http://www.newtonsoft.com/json/help/html/serializationattributes.htm). 12 | 13 | Please refer to their documentation for issues regarding serialization. 14 | 15 | Mixing of attribute types is supported by Newtonsoft.Json. Here is an example of mixing JsonPropertyAttribute and DataMemberAttribute: 16 | 17 | ```cs 18 | public class Beer 19 | { 20 | [Key] 21 | [JsonProperty("name")] 22 | public string Name { get; set; } 23 | 24 | [DataMember(Name = "abv")] 25 | public decimal Abv { get; set; } 26 | 27 | [JsonProperty("ibu")] 28 | public decimal Ibu { get; set; } 29 | 30 | // ... 31 | } 32 | ``` 33 | 34 | See [Custom JSON Serializers](./custom-serializers.md) for information on using a custom JSON serializer. 35 | -------------------------------------------------------------------------------- /docs/ryow.md: -------------------------------------------------------------------------------- 1 | # Using Read Your Own Write (RYOW) 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | When writing changes (mutating documents), Couchbase Server indexes are not updated immediately. This allows improved write performance, because it isn't necessary for mutations to wait for index updates to complete before returning. This index update delay doesn't affect CRUD operations via the SDK, but it does affect the results returned by SQL++ queries. 7 | 8 | This delay is often unnoticed. However, there can be circumstances where you need to execute a query immediately following a mutation, and need the mutation to be included in the query result. For example, if you are posting a new sales order, and then query the total sales for that customer after posting the new order. 9 | 10 | To enable Read Your Own Write when executing query, you specifically indicate which updates must be processed by the indexer before returning query results. This may add a delay to your query results, but you are guaranteed that your mutations are included in the query result. It is also more performant than AtPlus consistency, because you only need to wait for your mutations to be indexed, not all mutations. More information is available [here](http://developer.couchbase.com/documentation/server/current/developer-guide/query-consistency.html). 11 | 12 | ## Reading Your Own Writes 13 | 14 | Each `BucketContext` will automatically keep track of its own `MutationState` as mutations are applied. In order to execute a query using this state, simply use the `ConsistentWith` method when building your query. 15 | 16 | ```cs 17 | using Couchbase.Linq.Extensions; 18 | 19 | // ... 20 | 21 | var insertResult = await bucket.DefaultCollection().InsertAsync("doc-key", docValue)); 22 | 23 | var context = new BucketContext(bucket); 24 | 25 | var query = context.Query() 26 | .ConsistentWith(MutationState.From(insertResult)) 27 | .Count(); 28 | ``` 29 | 30 | > :info: **Note:** The `ConsistentWith` method should only be invoked on the main extent being queried. If you are performing joins or nests, don't include ConsistentWith on these extents. 31 | -------------------------------------------------------------------------------- /docs/simple-select.md: -------------------------------------------------------------------------------- 1 | # Controlling output with Select 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | The select clause produces the result of the query and allows you to shape the format of each element. 7 | 8 | ## Simple Select 9 | 10 | The simplest select is to return a document that maps directly to a POCO: 11 | 12 | ```cs 13 | var beers = from b in context.Query() 14 | select b; 15 | ``` 16 | 17 | This will emit query that looks something like this: 18 | 19 | ```sql 20 | SELECT RAW `beer-sample` FROM `beer-sample` WHERE type='beer' 21 | ``` 22 | 23 | Assuming the Beer class has a document filter; if not the predicate would be removed and the entire keyspace will be returned. Any returning fields from the * will be mapped to the Properties of Beer unless a match cannot be made. If a match cannot be made, the value will be ignored. 24 | 25 | ## Selecting a subset of a document's fields 26 | 27 | If you wish to constrain the output, you specify which fields you want returned: 28 | 29 | ```cs 30 | var beers = from b in context.Query() 31 | select new 32 | { 33 | name = b.Name, 34 | abv = b.Abv 35 | }; 36 | ``` 37 | 38 | This will return only the Name and Abv fields for each document in the bucket. The result will be a projection of the original type - an anonymous type. Note that the execution of the query is deferred until the query is iterated over in foreach loop. This behavior is consistent and similar to the behavior of other LINQ providers. 39 | -------------------------------------------------------------------------------- /docs/string-handling.md: -------------------------------------------------------------------------------- 1 | # String Handling 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | Many .Net string operations are supported by SQL++, and can be used directly in your LINQ queries. 7 | 8 | ## Supported String Operations 9 | 10 | - Length property 11 | - Character index (i.e. str[2]) 12 | - ToUpper() and ToLower() 13 | - String concatenation, using String.Concat(...) or the + operator 14 | - Trim(), Trim(char[]), TrimStart(), and TrimEnd() 15 | - Substring(startIndex) and Substring(startIndex, length) 16 | - IndexOf(char) and IndexOf(string) 17 | - Split() and Split(char) 18 | - Replace(oldValue, newValue) 19 | 20 | ## String Comparisons 21 | 22 | String equality comparisons can be performed using the == and != operators. 23 | 24 | ```cs 25 | var context = new BucketContext(bucket); 26 | 27 | var query = from beer in context.Query() 28 | where beer.Name == "21A IPA" 29 | select beer; 30 | 31 | await foreach (var doc in query.AsAsyncEnumerable()) { 32 | // do work 33 | // query will return beers with the name 21A IPA 34 | } 35 | ``` 36 | 37 | For other string comparison types, you may use String.Compare. The most readable method is to compare the two strings, then always compare the result to zero. 38 | 39 | ```cs 40 | var context = new BucketContext(bucket); 41 | 42 | var query = from beer in context.Query() 43 | where String.Compare(beer.Name, "21A IPA") > 0 44 | select beer; 45 | 46 | foreach (var doc in query) { 47 | // do work 48 | // query will return beers with a name greater than, but not equal to, 21A IPA 49 | } 50 | ``` 51 | 52 | If you use this format, then the comparison operator before the zero is always the same as the comparison you're using for the strings. 53 | 54 | > :info: **Note:** Any call to String.Compare within a query must be combined with a comparison to either -1, 0, or 1. Any other usage will throw an exception. 55 | -------------------------------------------------------------------------------- /docs/use-keys.md: -------------------------------------------------------------------------------- 1 | # The UseKeys Method 2 | 3 | > [NOTE] 4 | > The documetation has been updated to reflect that the product name for N1QL has been changed to SQL++, however, the source itself may still use the name N1QL. 5 | 6 | The UseKeys method is used to select documents based on a list of zero or more keys. In this way, it is similar to performing a multiple get using `IBucket.Get(IList keys)`. The UseKeys method is also required when performing subqueries. 7 | 8 | **Note:** You must import the `Couchbase.Linq.Extensions` namespace to use the UseKeys method. 9 | 10 | ## Basic Usage 11 | 12 | The call to UseKeys should be immediately after the call to `Query`. 13 | 14 | ```cs 15 | var context = new BucketContext(bucket); 16 | 17 | var keys = new[] {"alesmith_brewing-wee_heavy", "ali_i_brewing-amber_ale"}; 18 | 19 | var query = from beer in context.Query().UseKeys(keys) 20 | select beer; 21 | ``` 22 | 23 | The query in this example will return all Beer documents which match the list of keys. 24 | 25 | The advantage of this approach over using `ICouchbaseCollection.GetAsync` is that you may then expand upon the query by applying where predicates, sorting, select projections, joins to other documents, etc. However, for simple queries like the example above, using `ICouchbaseCollection.GetAsync` will be more performant because it will work directly against the data nodes instead of passing through a query node. 26 | --------------------------------------------------------------------------------